[
  {
    "path": ".github/oxid-esales/install.sh",
    "content": "#!/bin/bash\n# shellcheck disable=SC2154\n# Lower case environment variables are passed from the workflow and used here\n# We use a validation loop in init to ensure, they're set\n# shellcheck disable=SC2086\n# We want install_container_options to count as multiple arguments\nset -e\n\nfunction error() {\n    echo -e \"\\033[0;31m${1}\\033[0m\"\n    exit 1\n}\n\nfunction init() {\n    for VAR in install_container_method install_container_options install_container_name \\\n        install_config_idebug install_is_enterprise; do\n        echo -n \"Checking, if $VAR is set ...\"\n        if [ -z ${VAR+x} ]; then\n            error \"Variable '${VAR}' not set\"\n        fi\n        echo \"OK, ${VAR}='${!VAR}'\"\n    done\n    echo -n \"Locating oe-console ... \"\n    cd source || exit 1\n    if [ -f 'bin/oe-console' ]; then\n        OE_CONSOLE='bin/oe-console'\n    else\n        if [ -f 'vendor/bin/oe-console' ]; then\n        OE_CONSOLE='vendor/bin/oe-console'\n        else\n            error \"Can't find oe-console in bin or vendor/bin!\"\n        fi\n    fi\n    echo \"OK, using '${OE_CONSOLE}'\"\n    if [ -z \"${OXID_BUILD_DIRECTORY}\" ]; then\n      echo \"OXID_BUILD_DIRECTORY is not set, setting it to /var/www/var/cache/\"\n      export OXID_BUILD_DIRECTORY=\"/var/www/var/cache/\"\n    else\n      echo \"OXID_BUILD_DIRECTORY is set to '${OXID_BUILD_DIRECTORY}'\"\n    fi\n    if [ ! -d \"${OXID_BUILD_DIRECTORY/\\/var\\/www/source}\" ]; then\n      echo \"Creating '${OXID_BUILD_DIRECTORY}'\"\n      docker compose \"${install_container_method}\" -T \\\n        ${install_container_options} \\\n        \"${install_container_name}\" \\\n        mkdir -p \"${OXID_BUILD_DIRECTORY}\"\n    fi\n}\n\ninit\n# Run Install Shop\ndocker compose \"${install_container_method}\" -T \\\n    ${install_container_options} \\\n    \"${install_container_name}\" \\\n    ${OE_CONSOLE} oe:setup:shop \\\n\n# Activate iDebug\nif [ \"${install_config_idebug}\" == 'true' ]; then\n    export OXID_DEBUG_MODE=\"true\"\nfi\n\n# Activate theme\ndocker compose \"${install_container_method}\" -T \\\n    ${install_container_options} \\\n    \"${install_container_name}\" \\\n    ${OE_CONSOLE} oe:theme:activate apex\n\n# Output PHP error log\nif [ -s data/php/logs/error_log.txt ]; then\n    echo -e \"\\033[0;35mPHP error log\\033[0m\"\n    cat data/php/logs/error_log.txt\nfi\nexit 0\n"
  },
  {
    "path": ".github/oxid-esales/shop_ce.yaml",
    "content": "install:\n  add_services: 'selenium-chrome'\n  git:\n    sdk_ref: b-8.0.x\n  script: 'source/.github/oxid-esales/install.sh'\n  cache:\n    prepared_shop: true\n  composer:\n    root_url: ''\n\nrunscript:\n  matrix:\n    script: |\n      [\n        \"shop:~/unit.sh\",\n        \"shop:~/integration.sh\",\n        \"shop:~/codeception.sh\",\n      ]\n  container:\n    options:\n      -e XDEBUG_MODE=coverage\n      -e CODECEPTION_OPTIONS=\"--ext DotReporter --skip-group flow_theme\"\n      -e THEME_ID=apex\n      -e SELENIUM_SERVER_HOST=selenium\n      -e BROWSER_NAME=chrome\n      -e GITHUB_EVENT_NAME=\"{{ .Github.EventName }}\"\n      -e GITHUB_BASE_REF=\"{{ .Github.BaseRef }}\"\n      -e GITHUB_REF=\"refs/heads/{{ .Data.install.git.ref }}\"\n      -e GITHUB_REF_NAME=\"{{ .Data.install.git.ref }}\"\n\nsonarcloud:\n  matrix:\n    testplan: '[\"-\"]'\n  strip_path: '/var/www/vendor/oxid-esales/oxideshop-ce/'\n  run_cleanup: false\n  target_branch: 'b-8.0.x'\n\nfinish:\n  slack_title: 'Shop CE ({{ .Data.install.git.ref }}) by {{ .Github.Actor }}'\n"
  },
  {
    "path": ".github/workflows/dispatch-manual.yaml",
    "content": "name: Manual run\n\non:\n  workflow_dispatch:\n    inputs:\n      scenario:\n        type: choice\n        options:\n          - 8.0.x\n          - use custom testplan\n        default: '8.0.x'\n        description: 'Choose test scenario'\n      limit:\n        type: choice\n        description: 'Limit php and mysql versions'\n        options:\n          - 'no'\n          - 'PHP8.4/MySQL5.7'\n          - 'PHP8.4/MySQL8.0'\n          - 'PHP8.4/MySQL8.4'\n          - 'PHP8.5/MySQL8.0'\n          - 'PHP8.5/MySQL8.4'\n        default: 'PHP8.5/MySQL8.4'\n      custom_testplan:\n        type: string\n        description: 'Custom testplan'\n        default: '~/defaults/php8.5_mysql8.4_only.yaml,~/shop_ce.yaml'\n      runs_on:\n        type: string\n        description: 'JSON string/array describing the runner'\n        default: '\"ubuntu-latest\"'\n      use_dev_version:\n        type: choice\n        options: ['no', 'v0']\n        description: 'Use the dev version of github actions'\n        default: 'no'\n\njobs:\n  build_testplan:\n    runs-on: ${{ fromJson(inputs.runs_on) }}\n    outputs:\n      testplan: '${{ steps.build.outputs.testplan }}'\n    steps:\n      - name: 'Build testplan'\n        id: build\n        run: |\n          # Build testplan\n          # shellcheck disable=SC2088\n          PLAN=\"~/shop_ce.yaml\"\n          # shellcheck disable=SC2088\n          case \"${{ inputs.limit }}\" in\n            \"no\") LIMIT='';;\n            \"PHP8.3/MySQL5.7\") LIMIT='~/defaults/php8.3_mysql5.7_only.yaml,' ;;\n            \"PHP8.3/MySQL8.0\") LIMIT='~/defaults/php8.3_mysql8.0_only.yaml,' ;;\n            \"PHP8.3/MySQL8.4\") LIMIT='~/defaults/php8.3_mysql8.4_only.yaml,' ;;\n            \"PHP8.4/MySQL5.7\") LIMIT='~/defaults/php8.4_mysql5.7_only.yaml,' ;;\n            \"PHP8.4/MySQL8.0\") LIMIT='~/defaults/php8.4_mysql8.0_only.yaml,' ;;\n            \"PHP8.4/MySQL8.4\") LIMIT='~/defaults/php8.4_mysql8.4_only.yaml,' ;;\n            \"PHP8.5/MySQL8.0\") LIMIT='~/defaults/php8.5_mysql8.0_only.yaml,' ;;\n            \"PHP8.5/MySQL8.4\") LIMIT='~/defaults/php8.5_mysql8.4_only.yaml,' ;;\n            *) echo \"Illegal choice, fix the workflow\"\n              exit 1\n              ;;\n          esac\n          # shellcheck disable=SC2088\n          case '${{ inputs.scenario}}' in\n            \"8.0.x\") TESTPLAN=\"${LIMIT}${PLAN}\" ;;\n            \"use custom testplan\") TESTPLAN=\"${{ inputs.custom_testplan }}\" ;;\n            *)\n              echo \"Illegal choice, fix the workflow\"\n              exit 1\n              ;;\n          esac\n          echo \"testplan=${TESTPLAN}\" | tee -a \"${GITHUB_OUTPUT}\"\n\n  dispatch_stable:\n    if: ${{ inputs.use_dev_version == 'no' }}\n    needs: build_testplan\n    uses: oxid-eSales/github-actions/.github/workflows/universal_workflow_light.yaml@v5\n    with:\n      testplan: ${{ needs.build_testplan.outputs.testplan }}\n      runs_on: ${{ inputs.runs_on }}\n      defaults: 'v5'\n      plan_folder: '.github/oxid-esales'\n    secrets:\n      DOCKER_HUB_USER: ${{ secrets.DOCKER_HUB_USER }}\n      DOCKER_HUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN }}\n      CACHE_ENDPOINT: ${{ secrets.CACHE_ENDPOINT }}\n      CACHE_ACCESS_KEY: ${{ secrets.CACHE_ACCESS_KEY }}\n      CACHE_SECRET_KEY: ${{ secrets.CACHE_SECRET_KEY }}\n      enterprise_github_token: ${{ secrets.enterprise_github_token }}\n      SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}\n      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}\n\n  dispatch_v0:\n    if: ${{ inputs.use_dev_version == 'v0' }}\n    needs: build_testplan\n    uses: oxid-eSales/github-actions/.github/workflows/universal_workflow_light.yaml@v0\n    with:\n      testplan: ${{ needs.build_testplan.outputs.testplan }}\n      runs_on: ${{ inputs.runs_on }}\n      defaults: 'v0'\n      plan_folder: '.github/oxid-esales'\n    secrets:\n      DOCKER_HUB_USER: ${{ secrets.DOCKER_HUB_USER }}\n      DOCKER_HUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN }}\n      CACHE_ENDPOINT: ${{ secrets.CACHE_ENDPOINT }}\n      CACHE_ACCESS_KEY: ${{ secrets.CACHE_ACCESS_KEY }}\n      CACHE_SECRET_KEY: ${{ secrets.CACHE_SECRET_KEY }}\n      enterprise_github_token: ${{ secrets.enterprise_github_token }}\n      SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}\n      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}\n"
  },
  {
    "path": ".github/workflows/trigger.yaml",
    "content": "name: Auto trigger on push or pull requests\n\non:\n  pull_request: {}\n  push: {}\n\njobs:\n  build_testplan:\n    runs-on: 'ubuntu-latest'\n    outputs:\n      testplan: '${{ steps.build.outputs.testplan }}'\n    steps:\n      - name: 'Build testplan'\n        id: build\n        run: |\n          # Build testplan\n          if [ '${{  github.event_name }}' == 'pull_request' ]; then\n            REF_TO_CHECK='refs/heads/${{ github.base_ref }}'\n            LIMIT=\"\"\n          else\n            REF_TO_CHECK='${{ github.ref }}'\n            # shellcheck disable=SC2088\n            LIMIT=\"~/defaults/php8.5_mysql8.4_only.yaml,\"\n          fi\n          # shellcheck disable=SC2088\n          case \"${REF_TO_CHECK}\" in\n            refs/heads/b-8.0.x*) TESTPLAN=\"${LIMIT}~/shop_ce.yaml\" ;;\n            *)\n              echo \"Can't match ${REF_TO_CHECK} to a version, can't determine test plan.\"\n              echo \"Branch names should start with b-<major>.<minor>.x\"\n              exit 1\n              ;;\n          esac\n          echo \"testplan=${TESTPLAN}\" | tee -a \"${GITHUB_OUTPUT}\"\n\n  shop_ce:\n    needs: build_testplan\n    uses: oxid-eSales/github-actions/.github/workflows/universal_workflow_light.yaml@v5\n    with:\n      testplan: ${{ needs.build_testplan.outputs.testplan }}\n      runs_on: '\"ubuntu-latest\"'\n      defaults: 'v5'\n      plan_folder: '.github/oxid-esales'\n    secrets:\n      DOCKER_HUB_USER: ${{ secrets.DOCKER_HUB_USER }}\n      DOCKER_HUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN }}\n      CACHE_ENDPOINT: ${{ secrets.CACHE_ENDPOINT }}\n      CACHE_ACCESS_KEY: ${{ secrets.CACHE_ACCESS_KEY }}\n      CACHE_SECRET_KEY: ${{ secrets.CACHE_SECRET_KEY }}\n      enterprise_github_token: ${{ secrets.enterprise_github_token }}\n      SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}\n      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Log\n!/source/log/\n/source/log/*\n!/source/log/.htaccess\n\n/.phpunit.result.cache\n\n# Export\n!/source/export/\n/source/export/*\n!/source/export/.gitkeep\n\n# Composer\n/vendor/\n/composer.lock\n\n# IDE\n/.idea/\n/.phpstorm.meta.php\n/.ide-helper.php\n\n# OS\n.DS_Store\n\n# Shop config & DI for modules and packages\n/var/*\n!/var/.gitignore\n\n# DotEnv files\n/.env\n/.env.local\n/.env.local.php\n/.env.*.local\n\n# Template files\n/source/Application/views/*\n"
  },
  {
    "path": "CHANGELOG-8.0.md",
    "content": "# Change Log for OXID eShop Community Edition Core Component\n\n## v8.0.0-alpha.3 - Unreleased\n*Compilation release*\n\n### Removed\n- Remove deprecated constant `Database::MYSQL_ATTR_INIT_COMMAND`\n- PHP v8.3 support\n\n## v8.0.0-alpha.2 - 2026-02-12\n*Compilation release*\n\n### Added\n\n- ClearShopCacheEvent\n- Validation for country VAT number prefix in the admin area\n- Upgrade to Symfony version 7.3\n\n### Fixed\n\n- Creating a new country does not check the VAT-ID prefix [#0007205](https://bugs.oxid-esales.com/view.php?id=7205)\n- Use raw SQL in migrations instead of Doctrine's schema tools to keep doctrine/dbal v4.3.0 installs working\n- Admin cookie handling to avoid explode type errors on non-string values\n- Restrict shutdown logging to fatal errors\n- Read `OXID_DEBUG_MODE` from dotenv before shutdown logging\n\n### Changed\n\n- Product pictures functionality has been redesigned\n  - Unlimited product images (no longer limited to 12)\n  - Images stored in separate tables (removed from `oxarticles`)\n  - New reusable media handling infrastructure (`Internal\\Domain\\Media`)\n  - New product media services (`Internal\\Domain\\Product\\Media`)\n  - Redesigned Admin UI for image management\n- Module environment configuration file paths\n- Updated doctrine/dbal dependency to ^4.2\n- `ResultSet` constructor now strictly requires `$statement` to be a `Doctrine\\DBAL\\Statement`\n- Refactored database query logging configuration.\n- Database adapter fetcher methods now return associative results by default\n- Method `getLastInsertId` of `DatabaseInterface` Adapter will throw `DatabaseErrorException` if no insert ID is available.\n- The `DatabaseConfiguration` namespace has been renamed for consistency, and the `getScheme` method has been replaced with `getDriver`\n- Shop setup proceeds with an empty database\n- The method `prepareModulesInformation` now returns module data as arrays instead of stdClass objects\n- `SetupDbValidatorInterface` no longer throws `DatabaseNotEmptyException`; database checks are now handled by a separate validator.\n- `database_schema.sql` no longer removes existing `oxmigrations` tables during shop setup. The schema dump should only be applied to an empty database.\n\n### Deprecated\n\n- `RequestInterface` - use `Symfony\\Component\\HttpFoundation\\Request` instead\n\n### Removed\n\n- Deprecated helper class `DateFormatHelper`\n- Deprecated promotions\n- Fetch mode support from `DatabaseInterface` and `DatabaseProvider`\n- Redundant Logger classes and interfaces: `MonologConfigurationInterface`, `PsrLoggerConfigurationInterface`,\n  `LoggerConfigurationValidatorInterface`, `LoggerWrapper`, `NullLogger` and `DatabaseLoggerFactoryInterface`\n- Redundant shop state and configuration classes and services: `ShopStateServiceInterface`, `ProjectConfigurationDaoInterface`, `ProjectConfiguration`\n- A deprecated partner-related method `getBelboonParam()` of class `BaseController` [0006140](https://bugs.oxid-esales.com/view.php?id=6140)\n- `ContainerBuilderFactory` class\n- Media refactoring — replaced with MediaView-based access\n  - `Article`: `getThumbnailUrl()`, `getIconUrl()`, `getPictureUrl()`, `getMasterZoomPictureUrl()`, `getZoomPictureUrl()`, `getPictureFieldValue()`, `getMasterPicturePath()`, `getPicturesProduct()`, `getMasterZoomPicture()`, `getZoomMedia()`\n  - `ArticleDetails`: `getActPicture()`, `morePics()`, `getIcons()`, `showZoomPics()`, `getZoomPics()`\n  - `ArticleDetailsController`: `getActPictureId()`, `showZoomPics()`\n- `RequestAdapter`  - use `Symfony\\Component\\HttpFoundation\\Request` instead\n- Deprecated method `Id::fromUid()`\n\n## v8.0.0-alpha.1 - 2025-02-03\n\n### Added\n\n- Set custom product low stock label [#0004401](https://bugs.oxid-esales.com/view.php?id=4401)\n- Support PSR caching interface, related functionalities and applied them on module cache.\n- Registration of environment variables via Symfony Dotenv Component\n- Interface for storing Symfony Service Container parameters in configuration\n- Support Symfony caching interface with tags\n\n### Deprecated\n\n- `Utils` methods for managing cache will be replaced by using Symfony cache directly\n\n### Changed\n\n- Configuration parameters have been moved from `config.inc.php` to environment and container parameters\n- Admin directory is not removed from the url in `ViewConfig::getModuleUrl`\n  anymore [PR-817](https://github.com/OXID-eSales/oxideshop_ce/pull/817)\n- Reset created product \"sold\" counter during Copying of the\n  product [PR-913](https://github.com/OXID-eSales/oxideshop_ce/pull/913)\n- `ModuleConfigurationValidatorInterface` is not optional anymore in the module activation service.\n- The visibility of time-activated products has changed, products with an undefined end date appear in the shop for an\n  unlimited period of time\n- Functionality to extend Symfony DIC for environments and shops\n- Method `getAltImageUrl` of PictureHandler will not use ssl parameter anymore\n- `oe:setup:shop` command now fetches parameters from the current environment configuration.\n  All corresponding command-line parameters were removed\n- Updated list of Search Engines (formerly `aRobots` configuration)\n- Browser-based application setup was discontinued. Only console-based setup is available\n- Replace file caching in `Utils` with Symfony cache\n- Removed $includePermanentCache parameter from `oxResetFileCache` method, all cache files are now cleared without exclusions.\n\n### Removed\n\n- Remove console classes from the Internal\n  namespace: `Executor`, `ExecutorInterface`, `CommandsProvider`, `CommandsProviderInterface`\n- Cleanup deprecated Private Sales Invite functionality\n- `getContainer()` and `dispatchEvent()` methods from Core classes\n- Remove deprecated global function \\makeReadable()\n- Redundant `TemplateFileResolverInterface` functionality\n- Smarty templates support\n- `PAYMENT_INFO_OFF`\n  translation [#0006426](https://bugs.oxid-esales.com/view.php?id=6426) [PR-953](https://github.com/OXID-eSales/oxideshop_ce/pull/953)\n- Remove deprecated `TemplateCacheService` implementation\n- Remove deprecated `BasicContextInterface::getCurrentShopId` and its basic implementation in\n  `BasicContext::getCurrentShopId`\n- Remove deprecated model property `Attribute::_sTitle` [PR-914](https://github.com/OXID-eSales/oxideshop_ce/pull/914)\n- Obsolete caching related functionalities\n- Methods in deprecated `Database` and `DatabaseProvider`, related to configuration management\n- Redundant interfaces `TransactionServiceInterface`, `FinderFactoryInterface`\n- `ConnectionProviderInterface::get()` was superseded by `ConnectionFactory::create()`\n- Deprecated global functions `warningHandler(), dumpVar(), debug()`\n- Superseded and obsolete `config.inc.php` parameters\n- Obsolete `ConfigFile` class and functionality for `config.inc.php` management\n- Deprecated class `ModuleVariablesLocator`\n- Redundant `BasicContextInterface` methods\n- Related configuration parameter method `isTplBlocksDebugMode` of `ViewConfig` class\n- Deprecated `NamedArgumentsTrait`\n- Deprecated `isEnabledAdminQueryLog` method in ContextInterface. Query logging mode can be fetched directly from\n  container.\n- Deprecated `handleDatabaseException` functionality\n- Dependency on `oxideshop-facts` component\n- `FileCache` and `SubShopSpecificFileCache` classes. Use `ContextInterface::getCurrentShopId()` instead\n- Legacy file-based caching methods from `Utils` class\n- Remove deprecated Interface `CacheConnectorInterface`\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "Contributing to OXID eShop\n==========================\n\nOXID eShop is available under two different licenses, OXID Community License and a commercial license.\n\nThat's why, before contributing for the first time, you must sign the Contributor License Agreement.\nYou can find more information about it on the FAQ page OXID Contribution and Contributor Agreement FAQ:\nhttps://docs.oxid-esales.com/developer/en/latest/contributing.html#contributor-agreement\n\nIf you'd like to contribute, please read the following documents:\n\n* [Contributing guide](https://docs.oxid-esales.com/developer/en/latest/contributing.html)\n* [Reporting bugs](https://bugs.oxid-esales.com/)\n* [Submitting feature requests](https://docs.oxid-esales.com/developer/en/latest/contributing.html#new-features) \n* [Coding standards](https://docs.oxid-esales.com/developer/en/latest/development/modules_components_themes/quality.html)\n* [Running tests](https://docs.oxid-esales.com/developer/en/latest/development/testing/index.html)\n* [Security issues](https://docs.oxid-esales.com/en/security/security.html)\n"
  },
  {
    "path": "LICENSE",
    "content": "OXID eShop Community Edition Lizenz 2022\n\n1.\tPräambel\n    Mit der OXID eShop Community Edition stellen wir Ihnen die jeweils aktuelle OXID eShop Software ab dem 15.08.2022 für die Zwecke des Evaluierens, des Testens und für Proof of Concepts zur Verfügung.\n    Sie ist ausschließlich für den nichtkommerziellen Einsatz bestimmt. Wenn Sie eine kommerzielle Nutzung der Software anstreben, wenden Sie sich bitte an OXID eSales unter sales@oxid-esales.com.\n\n2.\tDefinitionen\na.\t„Mandant“: eine organisatorisch und datentechnisch abgeschlossene Einheit innerhalb des Systems mit getrennten Stammsätzen und einem eigenständigen Satz von Tabellen/Views, die über Parameter gesteuert und kontrolliert wird.\n    „View“ ist eine datenbanktechnisch eingerichtete Ansicht auf einen Teildatenbestand der Stammdaten.\nb.\t„nichtkommerzielle Zwecke“: der Einsatz der Software, der nicht direkt auf einen geldwerten Vorteil durch Anbahnen, Durchführen und Abwickeln von Kundenbestellungen („Orders“) einschließlich der Zahlungen abzielt oder dem Nutzer der Software oder einem anderen dritten einen wirtschaftlichen Vorteil verschafft, z.B. durch Stellung von Rechnungen für Leistungen, die im Zusammenhang mit der Software erbracht werden. Die rechtliche oder steuerrechtliche Qualifikation Ihres Unternehmens als „öffentlich-rechtlich“ oder „gemeinnützig“ ist unbeachtlich.\nc.\t„Software”: die (i) Softwaredateien des Produkts OXID eShop, (ii) die dazugehörige Dokumentation sowie (iii) alle Updates, Upgrades und Ergänzungen hierzu.\nd.  „Software Core“: alle Dateien mit dem Copyright-Vermerk der OXID eSales AG.\ne.\t„Verbundene Unternehmen“: verbundene Unternehmen gemäß §§ 15 ff. AktG.\n\n3.\tUrheberrecht\nDie Software einschließlich aller OXID-Softwaremodule ist urheberrechtlich geschützt und geschütztes Geschäftsgeheimnis von OXID eSales.\nOXID eSales behält sich alle Rechte vor, sofern Ihnen in dieser Lizenzvereinbarung keine ausdrücklichen Rechte an der Software eingeräumt werden.\n\n4.\tRechteeinräumung\nOXID eSales räumt Ihnen an der Software das nicht ausschließliche und nicht übertragbare Recht zur Nutzung der Software ein.\n\n5.\tNutzungsbedingungen\nSie erhalten das Recht zur Nutzung unter der Bedingung, dass Sie die Nutzungsbedingungen akzeptieren und die Nutzung per E-Mail an lizenzregistrierung@oxid-esales.com melden.\nDie Anmeldung muss wahrheitsgemäße Angaben über Ihren Namen, Adresse, Telefonnummer und E-Mail-Adresse enthalten. Diese Daten werden gemäß der Datenschutzrichtlinie unter oxid-esales.com/datenschutz erhoben und verarbeitet.\n\n6.\tEinschränkungen\na.\tDie Nutzung ist beschränkt auf\ni.\tnichtkommerzielle Zwecke\nii.\tauf einen (1) Mandanten\nb.\tDie Nutzung von mehreren Lizenzen zur Umgehung der Mandantenlimitation ist untersagt.\nc.\tDie Nutzung von unter dieser Lizenz veröffentlichten Softwareprodukten oder -modulen in Verbindung mit früheren Versionen der OXID eShop CE Software (vor Version 6.4.3) bzw. von dieser abgeleiteten Softwareprodukten (Forks) ist unter der Bedingung zulässig, dass diese nur\n    - für nichtkommerzielle Zwecke und\n    - beschränkt auf einen (1) Mandanten\n    eingesetzt werden.\nd.\tDie Nutzung dieser Software oder Teilen davon durch Module oder Software Dritter unterliegt diesen Nutzungsbedingungen.\ne.\tDie Nutzung ist auf 6 Monate beschränkt. Die Nutzungszeit beginnt mit dem erstmaligen Download. Eine Löschung der Dateien oder Deinstallation führt nicht zu einer Unterbrechung der Nutzungszeit. Für eine mögliche Verlängerung wenden Sie sich bitte an sales@oxid-esales.com.\nf.\tDie Software enthält Softwareprodukte von Fremdherstellern. Die Fremdprodukte können Sie nach der Installation der composer.lock-Datei entnehmen. Für diese Fremdprodukte gelten zusätzliche Lizenzbestimmungen, die Sie der entsprechenden Lizenzdatei des Fremdprodukts entnehmen können.\ng.\tSie dürfen den Software Core weder umarbeiten, anpassen noch übersetzen, ebenso ausgeschlossen ist Reverse Engineering und Nachbau der Software.\nh.\tJede gemäß dieser Lizenz zulässige Kopie der Software muss die Urheberrechts- und Schutzrechtsvermerke von OXID eSales tragen, die auf oder in der lizensierten Software vorhanden sind.\ni.\tSie dürfen die Software Dritten weder vermieten, verleihen, unterlizensieren oder übertragen.\nj.\tSie dürfen den Quellcode der Software Dritten nicht zugänglich machen, es sei denn,\n    - die entsprechenden Dateien sind von OXID eSales explizit als öffentlich (open source) gekennzeichnet oder\n    - der Dritte benötigt den Quellcode zur Durchführung seiner Tätigkeit für Sie und hat mit OXID eSales eine schriftliche Geheimhaltungsvereinbarung abgeschlossen.\nk.\tFür die Überprüfung der korrekten Lizensierung und die Produktoptimierung ist OXID eSales berechtigt, auf die Software und deren Systemplattform zuzugreifen bzw. lizenzrelevante Informationen über die Software über eine automatische Rückmeldung der Systemplattform zu erhalten und diese zu speichern. Die übermittelten Daten werden nicht zu anderen Zwecken verwendet und es werden dabei keine personenbezogenen Daten erfasst. Sie dürfen diese Softwarefunktion nicht abschalten.\nl.  Die Nutzung von OXID eSS-Modulen ist verpflichtend, wenn OXID ein Modul mit gleichartiger Funktionalität im Portfolio hat.\n    Falls Sie erweiterte Nutzungsbedingungen benötigen, wenden Sie sich bitte an sales@oxid-esales.com.\n\n7.\tMarketing\nOXID eSales erhält das Recht zur Nennung der mit der Software genutzten Sites und/oder Lösungen zu Werbezwecken.\n\n8.\tNutzungsuntersagung und Nutzungsentschädigung\nWenn Sie die Bestimmungen von Ziff. 5 und 6 nicht oder nicht vollständig erfüllen, ist OXID eSales ohne weiteres berechtigt, Ihnen die Nutzung der Software zu untersagen. Schadensersatzansprüche von OXID eSales bleiben vorbehalten.\nWerden die Nutzungsbedingungen nicht eingehalten und gibt es keine andere Vereinbarung, stimmt der Lizenznehmer einer Nutzungsentschädigung von 111 € zuzüglich des Inflationsausgleichs gemäß der vom statistischen Bundesamt festgestellten jährlichen Inflationsrate für jeden Monat unrechtmäßiger Nutzung zu. Diese Entschädigung entbindet nicht vom Abschluss eines der Nutzung entsprechenden Lizenzvertrags.\nFür spätere Versionen behält sich OXID eine Anpassung der Nutzungsgebühr vor.\nWerden mehrere Shops oder Teile der OXID Professional Edition oder OXID Enterprise Edition oder vergleichbare Funktionalität, die durch Dritte auf Basis von OXID Software bereitgestellt wurde, genutzt, wird eine Lizenzmiete gemäß der OXID Preisliste fällig. Es gilt die Preisliste des Monats, in dem die erste Lizenzmiete bezahlt wird.\n\n9.\tSupport und Wartung\nSupport- und Wartungsleistungen sind nicht Bestandteil dieser Lizenzvereinbarung. Wenn Sie solche Leistungen in Anspruch nehmen wollen, wenden Sie sich bitte an sales@oxid-esales.com.\n\n10.\tRechte bei Mängeln\na.\tDie vertragsgemäße Beschaffenheit der Software bestimmt sich ausschließlich nach den Spezifikationen der Dokumentation in der bei Abschluss dieses Lizenzvertrages gültigen Fassung.\nb.\tDie Verjährungsfrist beträgt 12 Monate nach Lieferung der Software (Bereitstellung zum Download).\nc.\tDie Software wird Ihnen in der Beschaffenheit überlassen, wie sie sich zum Zeitpunkt des Abschlusses dieses Lizenzvertrages befindet. OXID eSales übernimmt keine Haftung für Sach- und Rechtsmängel, insbesondere übernehmen wir keine Haftung für die vertragsgemäße Beschaffenheit oder die Eignung für den vertraglich vorausgesetzten Verwendungszweck. Die in der Dokumentation gegebene Beschreibung der Software darf in keinem Fall als Zusicherung oder Garantie verstanden werden. Unsere Haftung für vorsätzliches und/oder arglistiges Handeln bleibt davon jedoch unberührt.\n\n11.\tHaftung\na.\tOXID eSales haftet nur bei Vorsatz und grober Fahrlässigkeit, Ansprüchen nach dem deutschen Produkthaftungsgesetz sowie bei einer Verletzung des Lebens, des Körpers oder der Gesundheit. Im Übrigen haften die Parteien unbeschränkt nach den gesetzlichen Vorschriften\nb.\tAlle anderen Ansprüche sind ausgeschlossen.\n\n12.\tSonstiges\na.\tDieser Lizenzvertrag unterliegt deutschem Recht.\nb.\tSollte eine Bestimmung dieses Lizenzvertrages unwirksam oder nichtig sein oder werden, so bleibt seine Wirksamkeit im Übrigen unberührt.\nc.\tFür die Auslegung dieses Lizenzvertrages ist die deutsche Sprachfassung verbindlich.\nd.\tFür alle Streitigkeiten, die aus dieser Lizenzvereinbarung entstehen, wird die Zuständigkeit des Landgerichts Freiburg vereinbart.\n\n\n\nThis is a rough translation of the German language version for informational purposes. The German language version of this license agreement is legally binding.\n\n1. Preamble\nFrom the date of 15.08.2022, we provide you with the current OXID eShop Community Edition software for the purpose of evaluation, testing and proof of concepts.\nIt is intended exclusively for non-commercial use. If you intend to use the software commercially, please contact OXID eSales at sales@oxid-esales.com.\n\n2. Definitions\na.  \"Tenant\" means an organisationally and data-wise self-contained unit within the system with separate master records and an independent set of tables/views, which is managed and controlled by parameters. \"View\" is a view of a partial dataset of the master data set up in terms of database technology.\nb.\t\"Non-commercial Purposes\" means the use of the Software which is not directly aimed at a pecuniary advantage by initiating, carrying out and processing customer orders (\"Orders\"), including payments, or which does not provide the user of the Software or another third party with an economic advantage, e.g. by issuing invoices for services provided in connection with the Software. The legal or fiscal qualification of your company as \"public-law\" or \"non-profit\" is irrelevant.\nc.  \"Software\": the (i) software files of the OXID eShop product, (ii) the associated documentation and (iii) all updates, upgrades and supplements thereto.\nd.  \"Software Core\": all files with the copyright notice of OXID eSales AG.\ne.  \"Affiliated Companies\": affiliated companies according to §§ 15 ff. AktG (German Stock Corporation Act).\n\n3. Copyright\nThe software including all OXID software modules is copyrighted and protected trade secret of OXID eSales.\nOXID eSales reserves all rights unless you are granted explicit rights to the software in this license agreement.\n\n4. Granting of rights\nOXID eSales grants you the non-exclusive and non-transferable right to use the software.\n\n5. Terms of use\nYou are granted the right to use the software under the condition that you accept the terms of use and report the use by e-mail to lizenzregistrierung@oxid-esales.com.\nThe registration must contain truthful information about your name, address, telephone number and e-mail address. This data will be collected and processed in accordance with the privacy policy at oxid-esales.com/datenschutz.\n\n6. Restrictions\na.\tUse is restricted to\n    i. non-commercial purposes\n    ii. to one (1) Tenant\nb.\tUse of multiple licences to circumvent the Tenant limitation is prohibited.\nc.  The use of software products or modules published under this license in connection with earlier versions of the OXID eShop CE software (released prior to 15.08.2022) or software products derived from it (forks) is permitted on condition that they are used only\n    - for non-commercial purposes and\n    - limited to one (1) Tenant\n    client.\nd.  The use of this software or parts thereof by third party modules or software is subject to these Terms of Use.\ne.  The use is limited to a period of 6 months. The period of use begins with the first download. Deletion of the files or de-installation does not lead to an interruption of the period of use. For a possible extension, please contact sales@oxid-esales.com.\nf.  The software contains software products from third-party manufacturers. You can find the third-party products in the composer.lock file after installation. Additional license conditions apply to these third-party products, which you can find in the corresponding license file of the third-party product.\ng.  You may not modify, adapt or translate the Software Core, nor may you reverse engineer or decompile the Software.\nh.  Any copy of the Software permitted under this license must bear the copyright and proprietary notices of OXID eSales that are present on or in the Licensed Software.\ni.  You may not rent, loan, sublicense or transfer the Software to any third party.\nj.  You may not make the source code of the software available to third parties unless,\n    - the corresponding files are explicitly marked as public (open source) by OXID eSales or\n    - the third party requires the source code to carry out its activities for you and has concluded a written non-disclosure agreement with OXID eSales.\nk.  For the purpose of checking the correct licensing and product optimisation, OXID eSales is entitled to access the software and its system platform or to receive license-relevant information about the software via automatic feedback from the system platform and to store this information. The transmitted data will not be used for other purposes and no personal data will be collected in the process. You may not disable this software feature.\nl. The use of OXID eSS modules is mandatory if OXID has a module with similar functionality in its portfolio.\n\nIf you require extended terms of use, please contact sales@oxid-esales.com.\n\n7. Marketing\nOXID eSales receives the right to name the sites and/or solutions used with the software for advertising purposes.\n\n8. Prohibition of Use and Compensation for Use\nIf you do not or not completely fulfill the provisions of clauses 5 and 6, OXID eSales is entitled without further ado to prohibit you from using the software. OXID eSales reserves the right to claim damages.\nIf the terms of use are not complied with and there is no other agreement, the licensee agrees to compensation for use of € 111 plus inflation compensation in accordance with the annual inflation rate determined by the Federal Statistical Office of Germany for each month of unlawful use. This compensation does not release from the conclusion of a license agreement corresponding to the use.\nFor later versions OXID reserves the right to adjust the usage fee.\nIf several shops or parts of the OXID Professional Edition or OXID Enterprise Edition or comparable functionality provided by third parties based on OXID software are used, a license fee according to the OXID price list is due. The price list of the month in which the first license fee is paid applies.\n\n9 Support and Maintenance\nSupport and maintenance services are not part of this license agreement. If you wish to make use of such services, please contact sales@oxid-esales.com.\n\n10. Rights in the Event of Defects\na.\tThe contractual quality of the software is determined exclusively by the specifications of the documentation in the version valid at the time of conclusion of this license agreement.\nb.\tThe limitation period is 12 months after delivery of the software (provision for download).\nc.\tThe software is provided to you in the condition as it is at the time of the conclusion of this license agreement. OXID eSales accepts no liability for material defects or defects of title, in particular we accept no liability for the contractual condition or suitability for the contractually assumed purpose. The description of the software given in the documentation may in no case be understood as an assurance or guarantee. However, this shall not affect our liability for intentional and/or fraudulent acts.\n\n11. Liability\na.\tOXID eSales shall only be liable in the event of intent and gross negligence, claims under the German Product Liability Act and in the event of injury to life or health. In all other respects the parties shall be liable without limitation in accordance with the statutory provisions.\nb.\tAll other claims are excluded.\n\n12. Miscellaneous\na.\tThis license agreement is subject to German law.\nb.\tShould any provision of this license agreement be or become invalid or void, the validity of the remaining provisions shall remain unaffected.\nc.\tThe German language version shall be binding for the interpretation of this license agreement.\nd.\tIt is agreed that the Regional Court of Freiburg shall have jurisdiction over all disputes arising from this licence agreement."
  },
  {
    "path": "README.md",
    "content": "OXID eShop\n==========\n\nThis repository contains the sources of OXID eShop Community Edition Core Component.\n\n### About OXID eShop:\n\nOXID eShop is a flexible e-commerce software with a wide range of functionalities. \nThanks to its modular, modern and state-of-the-art architecture, it can be modified, expanded \nand customized to individual requirements with the greatest of ease. \n\nOXID eShop is just e-commerce software for agencies with deadlines :-)\n\n### Installation\n\n#### Compilation installation\n\nFor full installation instructions, please check the [OXID eShop compilation installation manual](https://docs.oxid-esales.com/developer/en/latest/getting_started/installation/eshop_installation.html).\n\n#### Installation for Contributors\n\nInformation how to install development version and make a pull request can be found in [CONTRIBUTING.md](CONTRIBUTING.md) file.\n\n### IDE code completion\n\nYou can easily enable code completion in your IDE by installing [this script](https://github.com/OXID-eSales/eshop-ide-helper) and generating it as described.\n\n### Useful links\n\n* Vendor home page - https://www.oxid-esales.com\n* Bug tracker - https://bugs.oxid-esales.com\n* SDK - https://github.com/OXID-eSales/docker-eshop-sdk\n"
  },
  {
    "path": "bin/oe-console",
    "content": "#!/usr/bin/env php\n<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\BootstrapContainerFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\ContainerBuilder;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\ProjectRootLocator;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContext;\nuse Symfony\\Component\\Console\\Application;\nuse Symfony\\Component\\Console\\Input\\ArgvInput;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\nuse Symfony\\Component\\Filesystem\\Path;\n\n$autoloadFileExist = false;\n$autoloadFiles = [\n    __DIR__ . '/../vendor/autoload.php',\n    __DIR__ . '/../../vendor/autoload.php',\n    __DIR__ . '/../../../vendor/autoload.php',\n    __DIR__ . '/../../../../vendor/autoload.php',\n];\n\nforeach ($autoloadFiles as $autoloadFile) {\n    if (file_exists($autoloadFile)) {\n        require_once $autoloadFile;\n        $autoloadFileExist = true;\n        break;\n    }\n}\n\nif (!$autoloadFileExist) {\n    exit('autoload.php file was not found!');\n}\n\n$shopId =  (int) (new ArgvInput())->getParameterOption(\n    '--shop-id',\n    1\n);\n\n$shopConfigurations = BootstrapContainerFactory::getBootstrapContainer()\n    ->get(ShopConfigurationDaoInterface::class)\n    ->getAll();\nif (!isset($shopConfigurations[$shopId])) {\n    echo \"Error: Invalid --shop-id value. Configuration for shop '$shopId' can not be found!\\n\";\n    exit(1);\n}\n\n$_POST['shp'] = $shopId;\n\n$bootstrapFilePath = Path::join((new ProjectRootLocator())->getProjectRoot(), 'source', 'bootstrap.php');\nrequire_once $bootstrapFilePath;\n\n$container = (new ContainerBuilder(new BasicContext(), $shopId))->getContainer();\n$container->compile(true);\n\n$commandLoader = $container->get('console.command_loader');\n\n$idCommands = [];\nforeach ($container->getParameter('console.command.ids') as $id) {\n    $idCommands[] = $container->get($id);\n}\n\n$application = new Application();\n$application->setCommandLoader($commandLoader);\n$application->setDispatcher($container->get(EventDispatcherInterface::class));\n$application->addCommands(\n    $idCommands\n);\n\n$application->run();\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"oxid-esales/oxideshop-ce\",\n    \"description\": \"This package contains OXID eShop CE source code.\",\n    \"license\": \"proprietary\",\n    \"type\": \"oxideshop\",\n    \"keywords\": [\n        \"oxid\",\n        \"modules\",\n        \"eShop\"\n    ],\n    \"homepage\": \"https://www.oxid-esales.com/en\",\n    \"require\": {\n        \"php\": \"^8.4\",\n        \"ext-dom\": \"*\",\n        \"ext-gd\": \"*\",\n        \"ext-iconv\": \"*\",\n        \"ext-json\": \"*\",\n        \"ext-pdo\": \"*\",\n        \"ext-pdo_mysql\": \"*\",\n        \"ext-tokenizer\": \"*\",\n        \"doctrine/collections\": \"^1.4.0\",\n        \"doctrine/dbal\": \"^4.2.5\",\n        \"monolog/monolog\": \"^1.23.0\",\n        \"oxid-esales/oxideshop-composer-plugin\": \"dev-b-8.0.x\",\n        \"oxid-esales/oxideshop-db-views-generator\": \"dev-b-8.0.x\",\n        \"oxid-esales/oxideshop-demodata-installer\": \"dev-b-8.0.x\",\n        \"oxid-esales/oxideshop-doctrine-migration-wrapper\": \"dev-b-8.0.x\",\n        \"oxid-esales/oxideshop-unified-namespace-generator\": \"dev-b-8.0.x\",\n        \"phpmailer/phpmailer\": \"^6.5.0\",\n        \"psr/container\": \"^1.1.1\",\n        \"symfony/config\": \"^7.3\",\n        \"symfony/console\": \"^7.3\",\n        \"symfony/dependency-injection\": \"^7.3\",\n        \"symfony/dotenv\": \"^7.3\",\n        \"symfony/event-dispatcher\": \"^7.3\",\n        \"symfony/expression-language\": \"^7.3\",\n        \"symfony/filesystem\": \"^7.3\",\n        \"symfony/finder\": \"^7.3\",\n        \"symfony/http-foundation\": \"^7.3\",\n        \"symfony/html-sanitizer\": \"^7.3\",\n        \"symfony/lock\": \"^7.3\",\n        \"symfony/mailer\": \"^7.3\",\n        \"symfony/mime\": \"^7.3\",\n        \"symfony/string\": \"^7.3\",\n        \"symfony/yaml\": \"^7.3\",\n        \"symfony/http-kernel\": \"^7.3\",\n        \"symfony/routing\": \"^7.3\",\n        \"symfony/error-handler\": \"^7.3\",\n        \"symfony/rate-limiter\": \"^7.3\"\n    },\n    \"require-dev\": {\n        \"codeception/codeception\": \"^5.0\",\n        \"codeception/module-asserts\": \"^3.0\",\n        \"codeception/module-db\": \"^3.0\",\n        \"codeception/module-filesystem\": \"^3.0\",\n        \"codeception/module-webdriver\": \"^4.0\",\n        \"composer/composer\": \"^2.0\",\n        \"incenteev/composer-parameter-handler\": \"^2.1.4\",\n        \"mikey179/vfsstream\": \"~1.6.8\",\n        \"oxid-esales/codeception-modules\": \"dev-b-8.0.x\",\n        \"oxid-esales/codeception-page-objects\": \"dev-b-8.0.x\",\n        \"oxid-esales/developer-tools\": \"dev-b-8.0.x\",\n        \"oxid-esales/oxideshop-ide-helper\": \"dev-b-8.0.x\",\n        \"phpspec/prophecy-phpunit\": \"^2.0.1\",\n        \"phpunit/phpunit\": \"^12.5\",\n        \"squizlabs/php_codesniffer\": \"^3.5.4\",\n        \"symfony/browser-kit\": \"^7.3\",\n        \"symfony/http-client\": \"^7.3\"\n    },\n    \"minimum-stability\": \"dev\",\n    \"prefer-stable\": true,\n    \"autoload\": {\n        \"psr-4\": {\n            \"OxidEsales\\\\EshopCommunity\\\\\": \"./source\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"OxidEsales\\\\EshopCommunity\\\\Tests\\\\\": \"./tests\"\n        }\n    },\n    \"bin\": [\n        \"bin/oe-console\"\n    ],\n    \"config\": {\n        \"allow-plugins\": {\n            \"oxid-esales/oxideshop-composer-plugin\": true,\n            \"oxid-esales/oxideshop-unified-namespace-generator\": true\n        }\n    },\n    \"extra\": {\n        \"oxideshop\": {\n            \"blacklist-filter\": [\n                \"Application/Component/**/*\",\n                \"Application/Controller/**/*\",\n                \"Application/Model/**/*\",\n                \"Core/**/*\",\n                \"Internal/**/*\"\n            ]\n        }\n    },\n    \"scripts\": {\n        \"post-install-cmd\": [\n            \"@oe:ide-helper:generate\"\n        ],\n        \"post-update-cmd\": [\n            \"@oe:ide-helper:generate\"\n        ],\n        \"oe:ide-helper:generate\": [\n            \"if [ -f ./vendor/bin/oe-eshop-ide_helper ]; then oe-eshop-ide_helper; fi\"\n        ]\n    }\n}\n"
  },
  {
    "path": "phpcs.xml.dist",
    "content": "<?xml version=\"1.0\"?>\n<ruleset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" name=\"PHP_CodeSniffer\" xsi:noNamespaceSchemaLocation=\"phpcs.xsd\">\n    <description>The coding standard for PHP_CodeSniffer itself.</description>\n    <file>source</file>\n    <exclude-pattern>*source/Application/views/.*$</exclude-pattern>\n    <arg name=\"colors\"/>\n    <arg value=\"p\"/>\n    <arg value=\"n\"/>\n    <arg name=\"parallel\" value=\"75\"/>\n    <arg name=\"extensions\" value=\"php\"/>\n    <rule ref=\"PSR12\"/>\n    <rule ref=\"PSR2.Methods.MethodDeclaration.Underscore\">\n        <exclude-pattern>*tests/Codeception/.*$</exclude-pattern>\n    </rule>\n</ruleset>\n"
  },
  {
    "path": "phpunit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit\n        xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n        xsi:noNamespaceSchemaLocation=\"https://schema.phpunit.de/12.5/phpunit.xsd\"\n        bootstrap=\"./tests/bootstrap.php\"\n        cacheDirectory=\"./var/.phpunit.cache\"\n        processIsolation=\"true\"\n        backupGlobals=\"true\"\n        backupStaticProperties=\"true\"\n        beStrictAboutChangesToGlobalState=\"true\"\n        beStrictAboutCoverageMetadata=\"true\"\n        displayDetailsOnTestsThatTriggerNotices=\"true\"\n        displayDetailsOnTestsThatTriggerDeprecations=\"true\"\n        displayDetailsOnTestsThatTriggerWarnings=\"true\"\n        displayDetailsOnTestsThatTriggerErrors=\"true\"\n>\n    <testsuites>\n        <testsuite name=\"Unit\">\n            <directory>tests/Unit</directory>\n        </testsuite>\n        <testsuite name=\"Integration\">\n            <directory>tests/Integration</directory>\n        </testsuite>\n    </testsuites>\n    <source>\n        <include>\n            <directory>source</directory>\n        </include>\n    </source>\n    <php>\n        <env name=\"OXID_CONTAINER_PROVIDER\" value=\"OxidEsales\\EshopCommunity\\Tests\\TestContainerFactory\" force=\"true\"/>\n    </php>\n</phpunit>\n"
  },
  {
    "path": "source/.htaccess",
    "content": "<IfModule mod_rewrite.c>\n    Options +FollowSymLinks\n    RewriteEngine On\n    RewriteBase /\n\n    RewriteCond %{HTTP:Authorization} .\n    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]\n\n    RewriteRule ^graphql/?$    widget.php?cl=graphql&skipSession=1   [QSA,NC,L]\n\n    RewriteRule ^api/(.*)$ api.php/$1 [QSA,NC,L]\n\n    RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK)\n    RewriteRule .* - [F]\n\n    RewriteCond %{REQUEST_URI} oxseo\\.php$\n    RewriteCond %{QUERY_STRING} mod_rewrite_module_is=off\n    RewriteRule oxseo\\.php$ oxseo.php?mod_rewrite_module_is=on [L]\n\n    RewriteCond %{REQUEST_URI} !^(\\/admin\\/|\\/Core\\/|\\/Application\\/|\\/export\\/|\\/modules\\/|\\/out\\/|\\/views\\/)\n    RewriteCond %{REQUEST_FILENAME} !-f\n    RewriteCond %{REQUEST_FILENAME} !-d\n    RewriteRule !(\\.html|\\/|\\.jpe?g|\\.css|\\.pdf|\\.doc|\\.gif|\\.png|\\.webp|\\.js|\\.htc|\\.svg)$ %{REQUEST_URI}/ [NC,R=301,L]\n\n    RewriteCond %{REQUEST_URI} !^(\\/admin\\/|\\/Core\\/|\\/Application\\/|\\/export\\/|\\/modules\\/|\\/out\\/|\\/views\\/)\n    RewriteCond %{REQUEST_FILENAME} !-f\n    RewriteCond %{REQUEST_FILENAME} !-d\n    RewriteRule (\\.html|\\/)$ oxseo.php\n\n\n    RewriteCond %{REQUEST_URI} (\\/out\\/pictures\\/generated\\/)\n    RewriteCond %{REQUEST_FILENAME} !-f\n    RewriteCond %{REQUEST_FILENAME} !-d\n    RewriteRule (\\.jpe?g|\\.gif|\\.png|\\.webp|\\.svg)$ getimg.php [NC]\n\n    RewriteRule ^(vendor/) - [F,L,NC]\n    RewriteRule ^migration - [R=403,L]\n</IfModule>\n\n# disabling log file access from outside\n<FilesMatch \"(EXCEPTION_LOG\\.txt|\\.log|\\.tpl|pkg\\.rev|\\.ini|pkg\\.info|\\.pem|composer\\.json|composer\\.lock|test_config\\.yml)$\">\n   <IfModule mod_authz_core.c>\n       Require all denied\n   </IfModule>\n   <IfModule !mod_authz_core.c>\n       Order allow,deny\n       Deny from all\n   </IfModule>\n</FilesMatch>\n\n# Prevent .ht* files from being sent to outside requests\n<Files ~ \"^\\.ht\">\n    <IfModule mod_authz_core.c>\n        Require all denied\n    </IfModule>\n    <IfModule !mod_authz_core.c>\n        Order allow,deny\n        Deny from all\n    </IfModule>\n</Files>\n\nOptions -Indexes\nDirectoryIndex index.php\n"
  },
  {
    "path": "source/Application/Component/BasketComponent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component;\n\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Exception\\ArticleInputException;\nuse OxidEsales\\Eshop\\Core\\Exception\\NoArticleException;\nuse OxidEsales\\Eshop\\Core\\Exception\\OutOfStockException;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents\\BasketChangedEvent;\nuse Psr\\Log\\LoggerInterface;\nuse stdClass;\n\nuse function oxNew;\n\n/**\n * Main shopping basket manager. Arranges shopping basket\n * contents, updates amounts, prices, taxes etc.\n *\n * @subpackage oxcmp\n */\nclass BasketComponent extends \\OxidEsales\\Eshop\\Core\\Controller\\BaseController\n{\n    /**\n     * Marking object as component\n     *\n     * @var bool\n     */\n    protected $_blIsComponent = true;\n\n    /**\n     * Last call function name\n     *\n     * @var string\n     */\n    protected $_sLastCallFnc = null;\n\n    protected bool $isBasketCalculated = false;\n\n    /**\n     * Parameters which are kept when redirecting after user\n     * puts something to basket\n     *\n     * @var array\n     */\n    public $aRedirectParams = [\n        'cnid', // category id\n        'mnid', // manufacturer id\n        'anid', // active article id\n        'tpl', // spec. template\n        'listtype', // list type\n        'searchcnid', // search category\n        'searchvendor', // search vendor\n        'searchmanufacturer', // search manufacturer\n        // @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n        'searchrecomm', // search recomendation\n        'recommid' // recomm. list id\n        // END deprecated\n    ];\n\n    public function init()\n    {\n        if (Registry::getConfig()->getConfigParam('blPsBasketReservationEnabled')) {\n            $basketReservations = Registry::getSession()->getBasketReservations();\n            if ($basketReservations) {\n                if (!$basketReservations->getTimeLeft()) {\n                    $basket = Registry::getSession()->getBasket();\n                    if ($basket && $basket->getProductsCount()) {\n                        $this->emptyBasket($basket);\n                    }\n                }\n                $basketReservations->discardUnusedReservations(\n                    ContainerFacade::getParameter('oxid_esales.basket_reservation_cleanup_rate')\n                );\n            }\n        }\n\n        parent::init();\n\n        // Basket exclude\n        if (Registry::getConfig()->getConfigParam('blBasketExcludeEnabled')) {\n            $basket = Registry::getSession()->getBasket();\n            if ($basket) {\n                $this->getParent()->setRootCatChanged($this->isRootCatChanged() && $basket->getContents());\n            }\n        }\n    }\n\n    /**\n     * Loads basket ($oBasket = $mySession->getBasket()), calls oBasket->calculateBasket,\n     * executes parent::render() and returns basket object.\n     *\n     * @return object   $oBasket    basket object\n     */\n    public function render()\n    {\n        $session = Registry::getSession();\n\n        if ($basket = $session->getBasket()) {\n            if (!$this->isBasketCalculated) {\n                $basket->calculateBasket(true);\n                $this->isBasketCalculated = true;\n            }\n        }\n\n        parent::render();\n\n        return $basket;\n    }\n\n    /**\n     * Basket content update controller.\n     * Before adding article - check if client is not a search engine. If\n     * yes - exits method by returning false. If no - executes\n     * oxcmp_basket::_addItems() and puts article to basket.\n     * Returns position where to redirect user browser.\n     *\n     * @param string $sProductId Product ID (default null)\n     * @param double $dAmount    Product amount (default null)\n     * @param array  $aSel       (default null)\n     * @param array  $aPersParam (default null)\n     * @param bool   $blOverride If true amount in basket is replaced by $dAmount otherwise amount is increased by\n     *                           $dAmount (default false)\n     *\n     * @return mixed\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\DatabaseConnectionException\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\DatabaseErrorException\n     */\n    public function toBasket($sProductId = null, $dAmount = null, $aSel = null, $aPersParam = null, $blOverride = false)\n    {\n        if (\n            Registry::getSession()->getId() &&\n            Registry::getSession()->isActualSidInCookie() &&\n            !Registry::getSession()->checkSessionChallenge()\n        ) {\n            ContainerFacade::get(LoggerInterface::class)\n                ->warning('EXCEPTION_NON_MATCHING_CSRF_TOKEN');\n            Registry::getUtilsView()->addErrorToDisplay('ERROR_MESSAGE_NON_MATCHING_CSRF_TOKEN');\n            return;\n        }\n\n        // adding to basket is not allowed ?\n        $myConfig = Registry::getConfig();\n        if (Registry::getUtils()->isSearchEngine()) {\n            return;\n        }\n\n        // adding articles\n        if ($aProducts = $this->getItems($sProductId, $dAmount, $aSel, $aPersParam, $blOverride)) {\n            $this->setLastCallFnc('tobasket');\n\n            $database = DatabaseProvider::getDb();\n            $database->startTransaction();\n            try {\n                $oBasketItem = $this->addItems($aProducts);\n                //reserve active basket\n                if (Registry::getConfig()->getConfigParam('blPsBasketReservationEnabled')) {\n                    $basket = Registry::getSession()->getBasket();\n                    Registry::getSession()->getBasketReservations()->reserveBasket($basket);\n                }\n            } catch (\\Exception $exception) {\n                $database->rollbackTransaction();\n                unset($oBasketItem);\n                throw $exception;\n            }\n            $database->commitTransaction();\n\n            // new basket item marker\n            if ($oBasketItem && $myConfig->getConfigParam('iNewBasketItemMessage') != 0) {\n                $oNewItem = new stdClass();\n                $oNewItem->sTitle = $oBasketItem->getTitle();\n                $oNewItem->sId = $oBasketItem->getProductId();\n                $oNewItem->dAmount = $oBasketItem->getAmount();\n                $oNewItem->dBundledAmount = $oBasketItem->getdBundledAmount();\n\n                // passing article\n                Registry::getSession()->setVariable('_newitem', $oNewItem);\n            }\n\n            // redirect to basket\n            $redirectUrl = $this->getRedirectUrl();\n            ContainerFacade::dispatch(new BasketChangedEvent($this));\n\n            return $redirectUrl;\n        }\n    }\n\n    /**\n     * Similar to tobasket, except that as product id \"bindex\" parameter is (can be) taken\n     *\n     * @param string $sProductId Product ID (default null)\n     * @param double $dAmount    Product amount (default null)\n     * @param array  $aSel       (default null)\n     * @param array  $aPersParam (default null)\n     * @param bool   $blOverride If true means increase amount of chosen article (default false)\n     *\n     * @return mixed\n     */\n    public function changeBasket(\n        $productId = null,\n        $amount = null,\n        $sel = null,\n        $persParam = null,\n        $override = true\n    ) {\n        if (!Registry::getSession()->checkSessionChallenge()) {\n            return;\n        }\n\n        if (Registry::getUtils()->isSearchEngine()) {\n            return;\n        }\n\n        $session = Registry::getSession();\n\n        if (!$productId) {\n            $basketItemId = Registry::getRequest()->getRequestEscapedParameter('bindex');\n\n            if ($basketItemId) {\n                $basket = $session->getBasket();\n                $basketContents = $basket->getContents();\n                $item = $basketContents[$basketItemId];\n\n                $productId = isset($item) ? $item->getProductId() : null;\n            } else {\n                $productId = Registry::getRequest()->getRequestEscapedParameter('aid');\n            }\n        }\n\n        $amount = $amount ?? Registry::getRequest()->getRequestEscapedParameter('am');\n        $sel = $sel ?? Registry::getRequest()->getRequestEscapedParameter('sel');\n        $persParam = $persParam ?: Registry::getRequest()->getRequestEscapedParameter('persparam');\n\n        if ($products = $this->getItems($productId, $amount, $sel, $persParam, $override)) {\n            $basket = $session->getBasket();\n            $basket->onUpdate();\n            $this->setLastCallFnc('changebasket');\n\n            $database = DatabaseProvider::getDb();\n            $database->startTransaction();\n            try {\n                $basketItem = $this->addItems($products);\n                if (Registry::getConfig()->getConfigParam('blPsBasketReservationEnabled')) {\n                    Registry::getSession()->getBasketReservations()->reserveBasket($basket);\n                }\n            } catch (NoArticleException $exception) {\n                return $this->getRedirectUrl();\n            } catch (\\Exception $exception) {\n                $database->rollbackTransaction();\n                unset($basketItem);\n                throw $exception;\n            }\n            $database->commitTransaction();\n        }\n    }\n\n    /**\n     * Formats and returns redirect URL where shop must be redirected after\n     * storing something to basket\n     *\n     * @return string   $sClass.$sPosition  redirection URL\n     */\n    protected function getRedirectUrl()\n    {\n\n        // active controller id\n        $controllerId = Registry::getConfig()->getRequestControllerId();\n        $controllerId = $controllerId ? $controllerId . '?' : 'start?';\n        $sPosition = '';\n\n        // setting redirect parameters\n        foreach ($this->aRedirectParams as $sParamName) {\n            $sParamVal = Registry::getRequest()->getRequestEscapedParameter($sParamName);\n            $sPosition .= $sParamVal ? $sParamName . '=' . $sParamVal . '&' : '';\n        }\n\n        // special treatment\n        // search param\n        if ($sParam = Registry::getRequest()->getRequestParameter('searchparam')) {\n            $sPosition .= 'searchparam=' . rawurlencode($sParam) . '&';\n        }\n\n        // current page number\n        $iPageNr = (int) Registry::getRequest()->getRequestEscapedParameter('pgNr');\n        $sPosition .= ($iPageNr > 0) ? 'pgNr=' . $iPageNr . '&' : '';\n\n        // reload and backbutton blocker\n        if (Registry::getConfig()->getConfigParam('iNewBasketItemMessage') == 3) {\n            // saving return to shop link to session\n            Registry::getSession()->setVariable('_backtoshop', $controllerId . $sPosition);\n\n            // redirecting to basket\n            $controllerId = 'basket?';\n        }\n\n        return $controllerId . $sPosition;\n    }\n\n    /**\n     * Cleans and returns persisted parameters.\n     *\n     * @param array $persistedParameters key-value parameters (optional). If not passed - takes parameters from request.\n     *\n     * @return array|null cleaned up parameters or null, if there are no non-empty parameters\n     */\n    protected function getPersistedParameters($persistedParameters = null)\n    {\n        $persistedParameters = ($persistedParameters ?: Registry::getRequest()->getRequestEscapedParameter('persparam'));\n        if (!is_array($persistedParameters)) {\n            return null;\n        }\n        return array_filter($persistedParameters) ?: null;\n    }\n\n    /**\n     * Collects and returns array of items to add to basket. Product info is taken not only from\n     * given parameters, but additionally from request 'aproducts' parameter\n     *\n     * @param string $sProductId product ID\n     * @param double $dAmount    product amount\n     * @param array  $aSel       product select lists\n     * @param array  $aPersParam product persistent parameters\n     * @param bool   $blOverride amount override status\n     *\n     * @return mixed\n     */\n    protected function getItems(\n        $sProductId = null,\n        $dAmount = null,\n        $aSel = null,\n        $aPersParam = null,\n        $blOverride = false\n    ) {\n        // collecting items to add\n        $aProducts = Registry::getRequest()->getRequestEscapedParameter('aproducts');\n\n        // collecting specified item\n        $sProductId = $sProductId ? $sProductId : Registry::getRequest()->getRequestEscapedParameter('aid');\n        if ($sProductId) {\n            // additionally fetching current product info\n            $dAmount = isset($dAmount) ? $dAmount : Registry::getRequest()->getRequestEscapedParameter('am');\n\n            // select lists\n            $aSel = isset($aSel) ? $aSel : Registry::getRequest()->getRequestEscapedParameter('sel');\n\n            // persistent parameters\n            if (empty($aPersParam)) {\n                $aPersParam = $this->getPersistedParameters();\n            }\n\n            $sBasketItemId = Registry::getRequest()->getRequestEscapedParameter('bindex');\n\n            $aProducts[$sProductId] = [\n                'am'           => $dAmount,\n                'sel'          => $aSel,\n                'persparam'    => $aPersParam,\n                'override'     => $blOverride,\n                'basketitemid' => $sBasketItemId\n            ];\n        }\n\n        if (is_array($aProducts) && count($aProducts)) {\n            if (Registry::getRequest()->getRequestEscapedParameter('removeBtn') !== null) {\n                //setting amount to 0 if removing article from basket\n                foreach ($aProducts as $sProductId => $aProduct) {\n                    if (isset($aProduct['remove']) && $aProduct['remove']) {\n                        $aProducts[$sProductId]['am'] = 0;\n                    } else {\n                        unset($aProducts[$sProductId]);\n                    }\n                }\n            }\n\n            return $aProducts;\n        }\n\n        return false;\n    }\n\n    /**\n     * Adds all articles user wants to add to basket. Returns\n     * last added to basket item.\n     *\n     * @param array $products products to add array\n     *\n     * @return  object  $oBasketItem    last added basket item\n     */\n    protected function addItems($products)\n    {\n        $activeView = Registry::getConfig()->getActiveView();\n        $errorDestination = $activeView->getErrorDestination();\n        $session = Registry::getSession();\n\n        $basket = $session->getBasket();\n        $basketInfo = $basket->getBasketSummary();\n\n        $basketItemAmounts = [];\n\n        foreach ($products as $addProductId => $productInfo) {\n            $data = $this->prepareProductInformation($addProductId, $productInfo);\n            $productAmount = 0;\n            if (isset($basketInfo->aArticles[$data['id']])) {\n                $productAmount = $basketInfo->aArticles[$data['id']];\n            }\n            $products[$addProductId]['oldam'] = $productAmount;\n\n            //If we already changed articles so they now exactly match existing ones,\n            //we need to make sure we get the amounts correct\n            if ($data['oldBasketItemId'] !== null && isset($basketItemAmounts[$data['oldBasketItemId']])) {\n                $data['amount'] = $data['amount'] + $basketItemAmounts[$data['oldBasketItemId']];\n            }\n\n            $basketItem = $this->addItemToBasket($basket, $data, $errorDestination);\n\n            if (($basketItem instanceof \\OxidEsales\\Eshop\\Application\\Model\\BasketItem)) {\n                $basketItemKey = $basketItem->getBasketItemKey();\n                if ($basketItemKey) {\n                    if (! isset($basketItemAmounts[$basketItemKey])) {\n                        $basketItemAmounts[$basketItemKey] = 0;\n                    }\n                    $basketItemAmounts[$basketItemKey] += $data['amount'];\n                }\n            }\n\n            if (!$basketItem) {\n                $info = $basket->getBasketSummary();\n                $products[$addProductId]['am'] = $info->aArticles[$data['id']] ?? 0;\n            }\n        }\n\n        //if basket empty remove possible gift card\n        if ($basket->getProductsCount() == 0) {\n            $basket->setCardId(null);\n        }\n\n        // information that last call was tobasket\n        $this->setLastCall($this->getLastCallFnc(), $products, $basketInfo);\n\n        return $basketItem;\n    }\n\n    /**\n     * Setting last call data to session (data used by econda)\n     *\n     * @param string $sCallName    name of action ('tobasket', 'changebasket')\n     * @param array  $aProductInfo data which comes from request when you press button \"to basket\"\n     * @param array  $aBasketInfo  array returned by \\OxidEsales\\Eshop\\Application\\Model\\Basket::getBasketSummary()\n     */\n    protected function setLastCall($sCallName, $aProductInfo, $aBasketInfo)\n    {\n        Registry::getSession()->setVariable('aLastcall', [$sCallName => $aProductInfo]);\n    }\n\n    /**\n     * Setting last call function name (data used by econda)\n     *\n     * @param string $sCallName name of action ('tobasket', 'changebasket')\n     */\n    protected function setLastCallFnc($sCallName)\n    {\n        $this->_sLastCallFnc = $sCallName;\n    }\n\n    /**\n     * Getting last call function name (data used by econda)\n     *\n     * @return string\n     */\n    protected function getLastCallFnc()\n    {\n        return $this->_sLastCallFnc;\n    }\n\n    /**\n     * Returns true if active root category was changed\n     *\n     * @return bool\n     */\n    public function isRootCatChanged()\n    {\n        // in Basket\n        $session = Registry::getSession();\n        $oBasket = $session->getBasket();\n        if ($oBasket->showCatChangeWarning()) {\n            $oBasket->setCatChangeWarningState(false);\n\n            return true;\n        }\n\n        // in Category, only then category is empty ant not equal to default category\n        $sDefCat = Registry::getConfig()->getActiveShop()->oxshops__oxdefcat->value;\n        $sActCat = Registry::getRequest()->getRequestEscapedParameter('cnid');\n        $oActCat = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n        if ($sActCat && $sActCat != $sDefCat && $oActCat->load($sActCat)) {\n            $sActRoot = $oActCat->oxcategories__oxrootid->value;\n            if ($oBasket->getBasketRootCatId() && $sActRoot != $oBasket->getBasketRootCatId()) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Executes user choice:\n     *\n     * - if user clicked on \"Proceed to checkout\" - redirects to basket,\n     * - if clicked \"Continue shopping\" - clear basket\n     *\n     * @return mixed\n     */\n    public function executeUserChoice()\n    {\n        ContainerFacade::dispatch(new BasketChangedEvent($this));\n\n        // redirect to basket\n        if (Registry::getRequest()->getRequestEscapedParameter(\"tobasket\")) {\n            return \"basket\";\n        } else {\n            // clear basket\n            $session = Registry::getSession();\n            $session->getBasket()->deleteBasket();\n            $this->getParent()->setRootCatChanged(false);\n        }\n    }\n\n    /**\n     * Deletes user basket object from session and saved one from DB if needed.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket\n     */\n    protected function emptyBasket($oBasket)\n    {\n        $oBasket->deleteBasket();\n    }\n\n    /**\n     * Prepare information for adding product to basket.\n     *\n     * @param string $addProductId\n     * @param array  $productInfo\n     *\n     * @return array\n     */\n    protected function prepareProductInformation($addProductId, $productInfo)\n    {\n        $return = [];\n\n        $return['id'] = isset($productInfo['aid']) ? $productInfo['aid'] : $addProductId;\n        $return['amount'] = isset($productInfo['am']) ? $productInfo['am'] : 0;\n        $return['selectList'] = isset($productInfo['sel']) ? $productInfo['sel'] : null;\n\n        $return['persistentParameters'] = $this->getPersistedParameters($productInfo['persparam'] ?? null);\n        $return['override'] = isset($productInfo['override']) ? $productInfo['override'] : null;\n        $return['bundle'] = isset($productInfo['bundle']) ? true : false;\n        $return['oldBasketItemId'] = isset($productInfo['basketitemid']) ? $productInfo['basketitemid'] : null;\n\n        return $return;\n    }\n\n    /**\n     * Add one item to basket. Handle eventual errors.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $basket\n     * @param array                                      $itemData\n     * @param string                                     $errorDestination\n     *\n     * @return null|\\OxidEsales\\Eshop\\Application\\Model\\BasketItem\n     */\n    protected function addItemToBasket($basket, $itemData, $errorDestination)\n    {\n        $basketItem = null;\n\n        try {\n            $basketItem = $basket->addToBasket(\n                $itemData['id'],\n                $itemData['amount'],\n                $itemData['selectList'],\n                $itemData['persistentParameters'],\n                $itemData['override'],\n                $itemData['bundle'],\n                $itemData['oldBasketItemId']\n            );\n        } catch (OutOfStockException $exception) {\n            $exception->setDestination($errorDestination);\n            // #950 Change error destination to basket popup\n            if (!$errorDestination && Registry::getConfig()->getConfigParam('iNewBasketItemMessage') == 2) {\n                $errorDestination = 'popup';\n            }\n            Registry::getUtilsView()->addErrorToDisplay($exception, false, (bool) $errorDestination, $errorDestination);\n        } catch (ArticleInputException $exception) {\n            //add to display at specific position\n            $exception->setDestination($errorDestination);\n            Registry::getUtilsView()->addErrorToDisplay($exception, false, (bool) $errorDestination, $errorDestination);\n        } catch (NoArticleException $exception) {\n            //ignored, best solution F ?\n        }\n\n        return $basketItem;\n    }\n}\n"
  },
  {
    "path": "source/Application/Component/CategoriesComponent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Str;\n\n/**\n * Transparent category manager class (executed automatically).\n *\n * @subpackage oxcmp\n */\nclass CategoriesComponent extends \\OxidEsales\\Eshop\\Core\\Controller\\BaseController\n{\n    /**\n     * More category object.\n     *\n     * @var object\n     */\n    protected $_oMoreCat = null;\n\n    /**\n     * Marking object as component\n     *\n     * @var bool\n     */\n    protected $_blIsComponent = true;\n\n    /**\n     * Marking object as component\n     *\n     * @var bool\n     */\n    protected $_oCategoryTree = null;\n\n    /**\n     * Marking object as component\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\ManufacturerList\n     */\n    protected $_oManufacturerTree = null;\n\n    /**\n     * Executes parent::init(), searches for active category in URL,\n     * session, post variables (\"cnid\", \"cdefnid\"), active article\n     * (\"anid\", usually article details), then loads article and\n     * category if any of them available. Generates category/navigation\n     * list.\n     *\n     * @return null\n     */\n    public function init()\n    {\n        parent::init();\n\n        // Performance\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        if (\n            $myConfig->getConfigParam('blDisableNavBars') &&\n            $myConfig->getTopActiveView()->getIsOrderStep()\n        ) {\n            return;\n        }\n\n        $sActCat = $this->getActCat();\n\n        if ($myConfig->getConfigParam('bl_perfLoadManufacturerTree')) {\n            // building Manufacturer tree\n            $sActManufacturer = Registry::getRequest()->getRequestEscapedParameter('mnid');\n            $this->loadManufacturerTree($sActManufacturer);\n        }\n\n        // building category tree for all purposes (nav, search and simple category trees)\n        $this->loadCategoryTree($sActCat);\n    }\n\n    /**\n     * get active article\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    public function getProduct()\n    {\n        if (($sActProduct = Registry::getRequest()->getRequestEscapedParameter('anid'))) {\n            $oParentView = $this->getParent();\n            if (($oProduct = $oParentView->getViewProduct())) {\n                return $oProduct;\n            } else {\n                $oProduct = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n                if ($oProduct->load($sActProduct)) {\n                    // storing for reuse\n                    $oParentView->setViewProduct($oProduct);\n\n                    return $oProduct;\n                }\n            }\n        }\n    }\n\n    /**\n     * get active category id\n     *\n     * @return string\n     */\n    protected function getActCat()\n    {\n        $sActManufacturer = Registry::getRequest()->getRequestEscapedParameter('mnid');\n\n        $sActCat = $sActManufacturer ? null : Registry::getRequest()->getRequestEscapedParameter('cnid');\n\n        // loaded article - then checking additional parameters\n        $oProduct = $this->getProduct();\n        if ($oProduct) {\n            $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n            $sActManufacturer = $myConfig->getConfigParam('bl_perfLoadManufacturerTree') ? $sActManufacturer : null;\n\n            $sActVendor = (Str::getStr()->preg_match('/^v_.?/i', $sActCat)) ? $sActCat : null;\n\n            $sActCat = $this->addAdditionalParams($oProduct, $sActCat, $sActManufacturer, $sActVendor);\n        }\n\n        // Checking for the default category\n        if ($sActCat === null && !$oProduct && !$sActManufacturer) {\n            // set remote cat\n            $sActCat = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActiveShop()->oxshops__oxdefcat->value;\n            if ($sActCat == 'oxrootid') {\n                // means none selected\n                $sActCat = null;\n            }\n        }\n\n        return $sActCat;\n    }\n\n    /**\n     * Category tree loader\n     *\n     * @param string $sActCat active category id\n     */\n    protected function loadCategoryTree($sActCat)\n    {\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\CategoryList $oCategoryTree */\n        $oCategoryTree = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\CategoryList::class);\n        $oCategoryTree->buildTree($sActCat);\n\n        $oParentView = $this->getParent();\n\n        // setting active category tree\n        $oParentView->setCategoryTree($oCategoryTree);\n        $this->setCategoryTree($oCategoryTree);\n\n        // setting active category\n        $oParentView->setActiveCategory($oCategoryTree->getClickCat());\n    }\n\n    /**\n     * Manufacturer tree loader\n     *\n     * @param string $sActManufacturer active Manufacturer id\n     */\n    protected function loadManufacturerTree($sActManufacturer)\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        if ($myConfig->getConfigParam('bl_perfLoadManufacturerTree')) {\n            $oManufacturerTree = $this->getManufacturerList();\n            $shopHomeURL = $myConfig->getShopHomeUrl();\n            $oManufacturerTree->buildManufacturerTree('manufacturerlist', $sActManufacturer, $shopHomeURL);\n\n            $oParentView = $this->getParent();\n\n            // setting active Manufacturer list\n            $oParentView->setManufacturerTree($oManufacturerTree);\n            $this->setManufacturerTree($oManufacturerTree);\n\n            // setting active Manufacturer\n            if (($oManufacturer = $oManufacturerTree->getClickManufacturer())) {\n                $oParentView->setActManufacturer($oManufacturer);\n            }\n        }\n    }\n\n    /**\n     * Executes parent::render(), loads expanded/clicked category object,\n     * adds parameters template engine and returns list of category tree.\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\CategoryList\n     */\n    public function render()\n    {\n        parent::render();\n\n        // Performance\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $oParentView = $this->getParent();\n\n        if ($myConfig->getConfigParam('bl_perfLoadManufacturerTree') && $this->_oManufacturerTree) {\n            $oParentView->setManufacturerlist($this->_oManufacturerTree);\n            $oParentView->setRootManufacturer($this->_oManufacturerTree->getRootCat());\n        }\n\n        if ($this->_oCategoryTree) {\n            return $this->_oCategoryTree;\n        }\n    }\n\n    /**\n     * Adds additional parameters: active category, list type and category id\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oProduct         loaded product\n     * @param string                                      $sActCat          active category id\n     * @param string                                      $sActManufacturer active manufacturer id\n     * @param string                                      $sActVendor       active vendor\n     *\n     * @return string $sActCat\n     */\n    protected function addAdditionalParams($oProduct, $sActCat, $sActManufacturer, $sActVendor)\n    {\n        $sSearchPar = Registry::getRequest()->getRequestEscapedParameter('searchparam');\n        $sSearchCat = Registry::getRequest()->getRequestEscapedParameter('searchcnid');\n        $sSearchVnd = Registry::getRequest()->getRequestEscapedParameter('searchvendor');\n        $sSearchMan = Registry::getRequest()->getRequestEscapedParameter('searchmanufacturer');\n        $sListType = Registry::getRequest()->getRequestEscapedParameter('listtype');\n\n        // search ?\n        if ((!$sListType || $sListType == 'search') && ($sSearchPar || $sSearchCat || $sSearchVnd || $sSearchMan)) {\n            // setting list type directly\n            $sListType = 'search';\n        } else {\n            // such Manufacturer is available ?\n            if ($sActManufacturer && ($sActManufacturer == $oProduct->getManufacturerId())) {\n                // setting list type directly\n                $sListType = 'manufacturer';\n                $sActCat = $sActManufacturer;\n            } elseif ($sActVendor && (substr($sActVendor, 2) == $oProduct->getVendorId())) {\n                // such vendor is available ?\n                $sListType = 'vendor';\n                $sActCat = $sActVendor;\n            } elseif ($sActCat && $oProduct->isAssignedToCategory($sActCat)) {\n                // category ?\n            } else {\n                list($sListType, $sActCat) = $this->getDefaultParams($oProduct);\n            }\n        }\n\n        $oParentView = $this->getParent();\n        //set list type and category id\n        $oParentView->setListType($sListType);\n        $oParentView->setCategoryId($sActCat);\n\n        return $sActCat;\n    }\n\n    /**\n     * Returns array containing default list type and category (or manufacturer ir vendor) id\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oProduct current product object\n     *\n     * @return array\n     */\n    protected function getDefaultParams($oProduct)\n    {\n        $sListType = null;\n        $aArticleCats = $oProduct->getCategoryIds(true);\n        if (is_array($aArticleCats) && count($aArticleCats)) {\n            $sActCat = reset($aArticleCats);\n        } elseif (($sActCat = $oProduct->getManufacturerId())) {\n            // not assigned to any category ? maybe it is assigned to Manufacturer ?\n            $sListType = 'manufacturer';\n        } elseif (($sActCat = $oProduct->getVendorId())) {\n            // not assigned to any category ? maybe it is assigned to vendor ?\n            $sListType = 'vendor';\n        } else {\n            $sActCat = null;\n        }\n\n        return [$sListType, $sActCat];\n    }\n\n    /**\n     * Setter of category tree\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\CategoryList $oCategoryTree category list\n     */\n    public function setCategoryTree($oCategoryTree)\n    {\n        $this->_oCategoryTree = $oCategoryTree;\n    }\n\n    /**\n     * Setter of manufacturer tree\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\ManufacturerList $oManufacturerTree manufacturer list\n     */\n    public function setManufacturerTree($oManufacturerTree)\n    {\n        $this->_oManufacturerTree = $oManufacturerTree;\n    }\n\n    /**\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\ManufacturerList\n     */\n    protected function getManufacturerList()\n    {\n        return oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ManufacturerList::class);\n    }\n}\n"
  },
  {
    "path": "source/Application/Component/CurrencyComponent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Currency manager class.\n *\n * @subpackage oxcmp\n */\nclass CurrencyComponent extends \\OxidEsales\\Eshop\\Core\\Controller\\BaseController\n{\n    /**\n     * Array of available currencies.\n     *\n     * @var array\n     */\n    public $aCurrencies = null;\n\n    /**\n     * Active currency object.\n     *\n     * @var object\n     */\n    protected $_oActCur = null;\n\n    /**\n     * Marking object as component\n     *\n     * @var bool\n     */\n    protected $_blIsComponent = true;\n\n    /**\n     * Checks for currency parameter set in URL, session or post\n     * variables. If such were found - loads all currencies possible\n     * in shop, searches if passed is available (if no - default\n     * currency is set the first defined in admin). Then sets currency\n     * parameter so session ($myConfig->setActShopCurrency($iCur)),\n     * loads basket and forces ir to recalculate (oBasket->blCalcNeeded\n     * = true). Finally executes parent::init().\n     *\n     * @return null\n     */\n    public function init()\n    {\n        // Performance\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        if (!$myConfig->getConfigParam('bl_perfLoadCurrency')) {\n            //#861C -  show first currency\n            $aCurrencies = $myConfig->getCurrencyArray();\n            $this->_oActCur = current($aCurrencies);\n\n            return;\n        }\n\n        $iCur = Registry::getRequest()->getRequestEscapedParameter('cur');\n        $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n\n        if (isset($iCur)) {\n            $aCurrencies = $myConfig->getCurrencyArray();\n            if (!isset($aCurrencies[$iCur])) {\n                $iCur = 0;\n            }\n\n            // set new currency\n            $myConfig->setActShopCurrency($iCur);\n\n            // recalc basket\n            $oBasket = $session->getBasket();\n            $oBasket->onUpdate();\n        }\n\n        $iActCur = $myConfig->getShopCurrency();\n        $this->aCurrencies = $myConfig->getCurrencyArray($iActCur);\n\n        $this->_oActCur = $this->aCurrencies[$iActCur];\n\n        //setting basket currency (M:825)\n        if (!isset($oBasket)) {\n            $oBasket = $session->getBasket();\n        }\n        $oBasket->setBasketCurrency($this->_oActCur);\n        parent::init();\n    }\n\n    /**\n     * Executes parent::render(), passes currency object to template\n     * engine and returns currencies array.\n     *\n     * Template variables:\n     * <b>currency</b>\n     *\n     * @return array\n     */\n    public function render()\n    {\n        parent::render();\n        $oParentView = $this->getParent();\n        $oParentView->setActCurrency($this->_oActCur);\n\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('bl_perfLoadCurrency')) {\n            $oUrlUtils = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsUrl();\n            $sUrl = $oUrlUtils->cleanUrl(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getTopActiveView()->getLink(), [\"cur\"]);\n\n            reset($this->aCurrencies);\n            foreach ($this->aCurrencies as $oItem) {\n                $oItem->link = $oUrlUtils->processUrl($sUrl, true, [\"cur\" => $oItem->id]);\n            }\n        }\n\n        return $this->aCurrencies;\n    }\n}\n"
  },
  {
    "path": "source/Application/Component/LanguageComponent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component;\n\nuse oxRegistry;\n\n/**\n * Shop language manager.\n * Performs language manager function: changes template settings, modifies URL's.\n *\n * @subpackage oxcmp\n */\nclass LanguageComponent extends \\OxidEsales\\Eshop\\Core\\Controller\\BaseController\n{\n    /**\n     * Marking object as component\n     *\n     * @var bool\n     */\n    protected $_blIsComponent = true;\n\n    /**\n     * Executes parent::render() and returns array with languages.\n     *\n     * @return array $this->aLanguages languages\n     */\n    public function render()\n    {\n        parent::render();\n\n        // Performance\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('bl_perfLoadLanguages')) {\n            $aLanguages = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getLanguageArray(null, true, true);\n            reset($aLanguages);\n            foreach ($aLanguages as $oVal) {\n                $oVal->link = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getTopActiveView()->getLink($oVal->id);\n            }\n\n            return $aLanguages;\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Component/Locator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component;\n\nuse OxidEsales\\Eshop\\Application\\Controller\\FrontendController;\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Application\\Model\\SeoEncoderManufacturer;\nuse OxidEsales\\Eshop\\Application\\Model\\SeoEncoderVendor;\nuse OxidEsales\\Eshop\\Core\\Model\\ListModel;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\nuse function getLogger;\n\n/**\n * Locator controller for: category, vendor, manufacturers and search lists.\n */\nclass Locator extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Locator type\n     */\n    protected $_sType = \"list\";\n\n    /**\n     * Next product to currently loaded\n     */\n    protected $_oNextProduct = null;\n\n    /**\n     * Previous product to currently loaded\n     */\n    protected $_oBackProduct = null;\n\n    /**\n     * search handle\n     */\n    protected $_sSearchHandle = null;\n\n    /**\n     * error message\n     */\n    protected $_sErrorMessage = null;\n\n    /**\n     * Class constructor - sets locator type and parameters posted or loaded\n     * from GET/Session\n     *\n     * @param string $sType locator type\n     */\n    public function __construct($sType = null)\n    {\n        // setting locator type\n        if ($sType) {\n            $this->_sType = trim($sType);\n        }\n    }\n\n    /**\n     * Executes locator method according locator type\n     *\n     * @param Article            $oCurrArticle   current article\n     * @param FrontendController $oLocatorTarget FrontendController object\n     */\n    public function setLocatorData($oCurrArticle, $oLocatorTarget)\n    {\n        $sLocfnc = \"set{$this->_sType}LocatorData\";\n\n        try {\n            call_user_func([$this, $sLocfnc], $oLocatorTarget, $oCurrArticle);\n        } catch (\\Exception $e) {\n            getLogger()->warning(\"Locator Type is wrong $this->_sType\");\n            $this->_sType = '';\n        }\n\n        // passing list type to view\n        $oLocatorTarget->setListType($this->_sType);\n    }\n\n    /**\n     * Sets details locator data for articles that came from regular list.\n     *\n     * @param FrontendController $oLocatorTarget view object\n     * @param Article            $oCurrArticle   current article\n     */\n    protected function setListLocatorData($oLocatorTarget, $oCurrArticle)\n    {\n        // if no active category is loaded - lets check for category passed by post/get\n        if (($oCategory = $oLocatorTarget->getActiveCategory())) {\n            $sOrderBy = $oLocatorTarget->getSortingSql($oLocatorTarget->getSortIdent());\n            $oIdList = $this->loadIdsInList($oCategory, $oCurrArticle, $sOrderBy);\n\n            //page number\n            $iPage = $this->findActPageNumber($oLocatorTarget->getActPage(), $oIdList, $oCurrArticle);\n\n            // setting product position in list, amount of articles etc\n            $oCategory->iCntOfProd = $oIdList->count();\n            $oCategory->iProductPos = $this->getProductPos($oCurrArticle, $oIdList, $oLocatorTarget);\n\n            if (Registry::getUtils()->seoIsActive() && $iPage) {\n                /** @var \\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderCategory $oSeoEncoderCategory */\n                $oSeoEncoderCategory = Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderCategory::class);\n                $oCategory->toListLink = $oSeoEncoderCategory->getCategoryPageUrl($oCategory, $iPage);\n            } else {\n                $oCategory->toListLink = $this->makeLink($oCategory->getLink(), $this->getPageNumber($iPage));\n            }\n\n            $oNextProduct = $this->_oNextProduct;\n            $oBackProduct = $this->_oBackProduct;\n            $oCategory->nextProductLink = $oNextProduct ? $this->makeLink($oNextProduct->getLink(), '') : null;\n            $oCategory->prevProductLink = $oBackProduct ? $this->makeLink($oBackProduct->getLink(), '') : null;\n\n            // active category\n            $oLocatorTarget->setActiveCategory($oCategory);\n\n            // category path\n            if (($oCatTree = $oLocatorTarget->getCategoryTree())) {\n                $oLocatorTarget->setCatTreePath($oCatTree->getPath());\n            }\n        }\n    }\n\n    /**\n     * Sets details locator data for articles that came from vendor list.\n     *\n     * @param FrontendController $oLocatorTarget FrontendController object\n     * @param Article            $oCurrArticle   current article\n     */\n    protected function setVendorLocatorData($oLocatorTarget, $oCurrArticle)\n    {\n        if (($oVendor = $oLocatorTarget->getActVendor())) {\n            $sVendorId = $oVendor->getId();\n            $myUtils = Registry::getUtils();\n\n            $blSeo = $myUtils->seoIsActive();\n\n            // loading data for article navigation\n            $oIdList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n            $oIdList->setCustomSorting($oLocatorTarget->getSortingSql($oLocatorTarget->getSortIdent()));\n            $oIdList->loadVendorIds($sVendorId);\n\n            //page number\n            $iPage = $this->findActPageNumber($oLocatorTarget->getActPage(), $oIdList, $oCurrArticle);\n\n            $sAdd = null;\n            if (!$blSeo) {\n                $sAdd = 'listtype=vendor&amp;cnid=v_' . $sVendorId;\n            }\n\n            // setting product position in list, amount of articles etc\n            $oVendor->iCntOfProd = $oIdList->count();\n            $oVendor->iProductPos = $this->getProductPos($oCurrArticle, $oIdList, $oLocatorTarget);\n\n            if ($blSeo && $iPage) {\n                $oVendor->toListLink = Registry::get(SeoEncoderVendor::class)->getVendorPageUrl($oVendor, $iPage);\n            } else {\n                $oVendor->toListLink = $this->makeLink($oVendor->getLink(), $this->getPageNumber($iPage));\n            }\n\n            $oNextProduct = $this->_oNextProduct;\n            $oBackProduct = $this->_oBackProduct;\n            $oVendor->nextProductLink = $oNextProduct ? $this->makeLink($oNextProduct->getLink(), $sAdd) : null;\n            $oVendor->prevProductLink = $oBackProduct ? $this->makeLink($oBackProduct->getLink(), $sAdd) : null;\n        }\n    }\n\n    /**\n     * Sets details locator data for articles that came from Manufacturer list.\n     *\n     * @param FrontendController $oLocatorTarget FrontendController object\n     * @param Article            $oCurrArticle   current article\n     */\n    protected function setManufacturerLocatorData($oLocatorTarget, $oCurrArticle)\n    {\n        if (($oManufacturer = $oLocatorTarget->getActManufacturer())) {\n            $sManufacturerId = $oManufacturer->getId();\n            $myUtils = Registry::getUtils();\n\n            $blSeo = $myUtils->seoIsActive();\n\n            // loading data for article navigation\n            $oIdList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n            $oIdList->setCustomSorting($oLocatorTarget->getSortingSql($oLocatorTarget->getSortIdent()));\n            $oIdList->loadManufacturerIds($sManufacturerId);\n\n            //page number\n            $iPage = $this->findActPageNumber($oLocatorTarget->getActPage(), $oIdList, $oCurrArticle);\n\n            $sAdd = null;\n            if (!$blSeo) {\n                $sAdd = 'listtype=manufacturer&amp;mnid=' . $sManufacturerId;\n            }\n\n            // setting product position in list, amount of articles etc\n            $oManufacturer->iCntOfProd = $oIdList->count();\n            $oManufacturer->iProductPos = $this->getProductPos($oCurrArticle, $oIdList, $oLocatorTarget);\n\n            if ($blSeo && $iPage) {\n                /** @var SeoEncoderManufacturer $oSeoEncoderManufacturer */\n                $oSeoEncoderManufacturer = Registry::get(SeoEncoderManufacturer::class);\n                $oManufacturer->toListLink = $oSeoEncoderManufacturer->getManufacturerPageUrl($oManufacturer, $iPage);\n            } else {\n                $oManufacturer->toListLink = $this->makeLink($oManufacturer->getLink(), $this->getPageNumber($iPage));\n            }\n\n            $oNextProduct = $this->_oNextProduct;\n            $oBackProduct = $this->_oBackProduct;\n            $oManufacturer->nextProductLink = $oNextProduct ? $this->makeLink($oNextProduct->getLink(), $sAdd) : null;\n            $oManufacturer->prevProductLink = $oBackProduct ? $this->makeLink($oBackProduct->getLink(), $sAdd) : null;\n\n            // active Manufacturer\n            $oLocatorTarget->setActiveCategory($oManufacturer);\n\n            // Manufacturer path\n            if (($oManufacturerTree = $oLocatorTarget->getManufacturerTree())) {\n                $oLocatorTarget->setCatTreePath($oManufacturerTree->getPath());\n            }\n        }\n    }\n\n    /**\n     * Sets details locator data for articles that came from search list.\n     *\n     * @param FrontendController $oLocatorTarget FrontendController object\n     * @param Article            $oCurrArticle   current article\n     */\n    protected function setSearchLocatorData($oLocatorTarget, $oCurrArticle)\n    {\n        if (($oSearchCat = $oLocatorTarget->getActSearch())) {\n            // #1834/1184M - specialchar search\n            $sSearchParam = Registry::getRequest()->getRequestParameter('searchparam');\n            $sSearchFormParam = Registry::getRequest()->getRequestEscapedParameter('searchparam');\n            $sSearchLinkParam = rawurlencode($sSearchParam);\n\n            $sSearchCat = Registry::getRequest()->getRequestEscapedParameter('searchcnid');\n            $sSearchCat = $sSearchCat ? rawurldecode($sSearchCat) : $sSearchCat;\n\n            $sSearchVendor = Registry::getRequest()->getRequestEscapedParameter('searchvendor');\n            $sSearchVendor = $sSearchVendor ? rawurldecode($sSearchVendor) : $sSearchVendor;\n\n            $sSearchManufacturer = Registry::getRequest()->getRequestEscapedParameter('searchmanufacturer');\n            $sSearchManufacturer = $sSearchManufacturer ? rawurldecode($sSearchManufacturer) : $sSearchManufacturer;\n\n            // loading data for article navigation\n            $oIdList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n            $oIdList->setCustomSorting($oLocatorTarget->getSortingSql($oLocatorTarget->getSortIdent()));\n            $oIdList->loadSearchIds($sSearchParam, $sSearchCat, $sSearchVendor, $sSearchManufacturer);\n\n            //page number\n            $iPage = $this->findActPageNumber($oLocatorTarget->getActPage(), $oIdList, $oCurrArticle);\n\n            $sAddSearch = \"searchparam={$sSearchLinkParam}\";\n            $sAddSearch .= '&amp;listtype=search';\n\n            if ($sSearchCat !== null) {\n                $sAddSearch .= \"&amp;searchcnid={$sSearchCat}\";\n            }\n\n            if ($sSearchVendor !== null) {\n                $sAddSearch .= \"&amp;searchvendor={$sSearchVendor}\";\n            }\n\n            if ($sSearchManufacturer !== null) {\n                $sAddSearch .= \"&amp;searchmanufacturer={$sSearchManufacturer}\";\n            }\n\n            // setting product position in list, amount of articles etc\n            $oSearchCat->iCntOfProd = $oIdList->count();\n            $oSearchCat->iProductPos = $this->getProductPos($oCurrArticle, $oIdList, $oLocatorTarget);\n\n            $sPageNr = $this->getPageNumber($iPage);\n            $sParams = $sPageNr . ($sPageNr ? '&amp;' : '') . $sAddSearch;\n            $oSearchCat->toListLink = $this->makeLink($oSearchCat->link, $sParams);\n            $oNextProd = $this->_oNextProduct;\n            $oBackProd = $this->_oBackProduct;\n            $oSearchCat->nextProductLink = $oNextProd ? $this->makeLink($oNextProd->getLink(), $sAddSearch) : null;\n            $oSearchCat->prevProductLink = $oBackProd ? $this->makeLink($oBackProd->getLink(), $sAddSearch) : null;\n\n            $sFormat = Registry::getLang()->translateString('SEARCH_RESULT');\n            $oLocatorTarget->setSearchTitle(sprintf($sFormat, $sSearchFormParam));\n            $oLocatorTarget->setActiveCategory($oSearchCat);\n        }\n    }\n\n    /**\n     * Sets details locator data for articles that came from recommlist.\n     *\n     * Template variables:\n     * <b>sSearchTitle</b>, <b>searchparamforhtml</b>\n     *\n     * @param FrontendController $oLocatorTarget FrontendController object\n     * @param Article            $oCurrArticle   current article\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     */\n    protected function _setRecommlistLocatorData($oLocatorTarget, $oCurrArticle) // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore\n    {\n        if (($oRecommList = $oLocatorTarget->getActiveRecommList())) {\n            // loading data for article navigation\n            $oIdList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n            $oIdList->loadRecommArticleIds($oRecommList->getId(), null);\n\n            //page number\n            $iPage = $this->findActPageNumber($oLocatorTarget->getActPage(), $oIdList, $oCurrArticle);\n\n            $sSearchRecomm = Registry::getRequest()->getRequestParameter('searchrecomm');\n\n            if ($sSearchRecomm !== null) {\n                $sSearchFormRecomm = Registry::getRequest()->getRequestEscapedParameter('searchrecomm');\n                $sSearchLinkRecomm = rawurlencode($sSearchRecomm);\n                $sAddSearch = 'searchrecomm=' . $sSearchLinkRecomm;\n            }\n\n            // setting product position in list, amount of articles etc\n            $oRecommList->iCntOfProd = $oIdList->count();\n            $oRecommList->iProductPos = $this->getProductPos($oCurrArticle, $oIdList, $oLocatorTarget);\n            $blSeo = Registry::getUtils()->seoIsActive();\n\n            if ($blSeo && $iPage) {\n                /** @var \\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderRecomm $oSeoEncoderRecomm */\n                $oSeoEncoderRecomm = Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderRecomm::class);\n                $oRecommList->toListLink = $oSeoEncoderRecomm->getRecommPageUrl($oRecommList, $iPage);\n            } else {\n                $oRecommList->toListLink = $this->makeLink($oRecommList->getLink(), $this->getPageNumber($iPage));\n            }\n            $oRecommList->toListLink = $this->makeLink($oRecommList->toListLink, $sAddSearch);\n\n            $sAdd = '';\n            if (!$blSeo) {\n                $sAdd = 'recommid=' . $oRecommList->getId() . '&amp;listtype=recommlist' . ($sAddSearch ? '&amp;' : '');\n            }\n            $sAdd .= $sAddSearch;\n            $oNextProduct = $this->_oNextProduct;\n            $oBackProduct = $this->_oBackProduct;\n            $oRecommList->nextProductLink = $oNextProduct ? $this->makeLink($oNextProduct->getLink(), $sAdd) : null;\n            $oRecommList->prevProductLink = $oBackProduct ? $this->makeLink($oBackProduct->getLink(), $sAdd) : null;\n\n            $oLang = Registry::getLang();\n            $sTitle = $oLang->translateString('RECOMMLIST');\n            if ($sSearchRecomm !== null) {\n                $sTitle .= \" / \" . $oLang->translateString('RECOMMLIST_SEARCH') . ' \"' . $sSearchFormRecomm . '\"';\n            }\n            $oLocatorTarget->setSearchTitle($sTitle);\n            $oLocatorTarget->setActiveCategory($oRecommList);\n        }\n    }\n\n    /**\n     * Setting product position in list, amount of articles etc\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Category $oCategory    active category id\n     * @param object                                       $oCurrArticle current article\n     * @param string                                       $sOrderBy     order by fields\n     *\n     * @return object\n     */\n    protected function loadIdsInList($oCategory, $oCurrArticle, $sOrderBy = null)\n    {\n        $oIdList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n        $oIdList->setCustomSorting($sOrderBy);\n\n        // additionally check if this category is loaded and is price category ?\n        if ($oCategory->isPriceCategory()) {\n            $oIdList->loadPriceIds(\n                $oCategory->oxcategories__oxpricefrom->value,\n                $oCategory->oxcategories__oxpriceto->value\n            );\n        } else {\n            $sActCat = $oCategory->getId();\n            $oIdList->loadCategoryIDs($sActCat, Registry::getSession()->getVariable('session_attrfilter'));\n            // if not found - reloading with empty filter\n            if (!isset($oIdList[$oCurrArticle->getId()])) {\n                $oIdList->loadCategoryIDs($sActCat, null);\n            }\n        }\n\n        return $oIdList;\n    }\n\n    /**\n     * Appends urs with currently passed parameters\n     *\n     * @param string $sLink   url to add parameters\n     * @param string $sParams parameters to add to url\n     *\n     * @return string\n     */\n    protected function makeLink($sLink, $sParams)\n    {\n        if ($sParams) {\n            $sLink .= ((strpos($sLink, '?') !== false) ? '&amp;' : '?') . $sParams;\n        }\n\n        return $sLink;\n    }\n\n    /**\n     * If page number is not passed trying to fetch it from list of ids. To search\n     * for position in list, article ids list and current article id must be passed\n     *\n     * @param int       $iPageNr  current page number (user defined or passed by request)\n     * @param ListModel $oIdList  list of article ids (optional)\n     * @param Article   $oArticle active article id (optional)\n     *\n     * @return int\n     */\n    protected function findActPageNumber($iPageNr, $oIdList = null, $oArticle = null)\n    {\n        //page number\n        $iPageNr = (int) $iPageNr;\n\n        // maybe there is no page number passed, but we still can find the position in id's list\n        if (!$iPageNr && $oIdList && $oArticle) {\n            $iNrofCatArticles = (int) \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iNrofCatArticles');\n            $iNrofCatArticles = $iNrofCatArticles ? $iNrofCatArticles : 1;\n            $sParentIdField = 'oxarticles__oxparentid';\n            $sArticleId = $oArticle->$sParentIdField->value ? $oArticle->$sParentIdField->value : $oArticle->getId();\n            $iPos = Registry::getUtils()->arrayStringSearch($sArticleId, $oIdList->arrayKeys());\n            $iPageNr = floor($iPos / $iNrofCatArticles);\n        }\n\n        return $iPageNr;\n    }\n\n    /**\n     * Gets current page number.\n     *\n     * @param int $iPageNr page number\n     *\n     * @return string $sPageNum\n     */\n    protected function getPageNumber($iPageNr)\n    {\n        //page number\n        $iPageNr = (int) $iPageNr;\n\n        return (($iPageNr > 0) ? \"pgNr=$iPageNr\" : '');\n    }\n\n    /**\n     * Searches for current article in article list and sets previous/next product ids\n     *\n     * @param Article            $oArticle       current Article\n     * @param object             $oIdList        articles list containing only fake article objects !!!\n     * @param FrontendController $oLocatorTarget FrontendController object\n     *\n     * @return integer\n     */\n    protected function getProductPos($oArticle, $oIdList, $oLocatorTarget)\n    {\n        // variant handling\n        $sOxid = $oArticle->oxarticles__oxparentid->value\n            ? $oArticle->oxarticles__oxparentid->value\n            : $oArticle->getId();\n        if ($oIdList->count() && isset($oIdList[$sOxid])) {\n            $aIds = $oIdList->arrayKeys();\n            $iPos = Registry::getUtils()->arrayStringSearch($sOxid, $aIds);\n\n            if (array_key_exists($iPos - 1, $aIds)) {\n                $oBackProduct = oxNew(Article::class);\n                $oBackProduct->modifyCacheKey('_locator');\n                $oBackProduct->setNoVariantLoading(true);\n                if ($oBackProduct->load($aIds[$iPos - 1])) {\n                    $oBackProduct->setLinkType($oLocatorTarget->getLinkType());\n                    $this->_oBackProduct = $oBackProduct;\n                }\n            }\n\n            if (array_key_exists($iPos + 1, $aIds)) {\n                $oNextProduct = oxNew(Article::class);\n                $oNextProduct->modifyCacheKey('_locator');\n                $oNextProduct->setNoVariantLoading(true);\n                if ($oNextProduct->load($aIds[$iPos + 1])) {\n                    $oNextProduct->setLinkType($oLocatorTarget->getLinkType());\n                    $this->_oNextProduct = $oNextProduct;\n                }\n            }\n\n            return $iPos + 1;\n        }\n\n        return 0;\n    }\n\n    /**\n     * Template variable getter. Returns error message\n     *\n     * @return string\n     */\n    public function getErrorMessage()\n    {\n        return $this->_sErrorMessage;\n    }\n}\n"
  },
  {
    "path": "source/Application/Component/ShopComponent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse oxRegistry;\n\n/**\n * Translarent shop manager (executed automatically), sets\n * registration information and current shop object.\n *\n * @subpackage oxcmp\n */\nclass ShopComponent extends \\OxidEsales\\Eshop\\Core\\Controller\\BaseController\n{\n    /**\n     * Marking object as component\n     *\n     * @var bool\n     */\n    protected $_blIsComponent = true;\n\n    /**\n     * Executes parent::render() and returns active shop object.\n     *\n     * @return  object  $this->oActShop active shop object\n     */\n    public function render()\n    {\n        parent::render();\n\n        $myConfig = Registry::getConfig();\n\n        // is shop active?\n        $oShop = $myConfig->getActiveShop();\n        $sActiveField = 'oxshops__oxactive';\n        $sClassName = $myConfig->getActiveView()->getClassKey();\n\n        if (!$oShop->$sActiveField->value && 'oxstart' != $sClassName && !$this->isAdmin()) {\n            // redirect to offline if there is no active shop\n            Registry::getUtils()->showOfflinePage();\n        }\n\n        return $oShop;\n    }\n}\n"
  },
  {
    "path": "source/Application/Component/UserComponent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component;\n\nuse Exception;\nuse OxidEsales\\Eshop\\Application\\Model\\Address;\nuse OxidEsales\\Eshop\\Application\\Model\\User;\nuse OxidEsales\\Eshop\\Application\\Model\\User\\UserShippingAddressUpdatableFields;\nuse OxidEsales\\Eshop\\Application\\Model\\User\\UserUpdatableFields;\nuse OxidEsales\\Eshop\\Core\\Contract\\AbstractUpdatableFields;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Exception\\ConnectionException;\nuse OxidEsales\\Eshop\\Core\\Exception\\DatabaseConnectionException;\nuse OxidEsales\\Eshop\\Core\\Exception\\InputException;\nuse OxidEsales\\Eshop\\Core\\Exception\\UserException;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Form\\FormFields;\nuse OxidEsales\\Eshop\\Core\\Form\\FormFieldsTrimmer;\nuse OxidEsales\\Eshop\\Core\\Form\\UpdatableFieldsConstructor;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\PasswordServiceBridgeInterface;\n\nuse function array_key_exists;\nuse function is_array;\n\n// defining login/logout states\ndefine('USER_LOGIN_SUCCESS', 1);\ndefine('USER_LOGIN_FAIL', 2);\ndefine('USER_LOGOUT', 3);\n\n/**\n * User object manager.\n * Sets user details data, switches, logouts, logins user etc.\n *\n * @subpackage oxcmp\n */\nclass UserComponent extends \\OxidEsales\\Eshop\\Core\\Controller\\BaseController\n{\n    /**\n     * Boolean - if user is new or not.\n     *\n     * @var bool\n     */\n    protected $_blIsNewUser = false;\n\n    /**\n     * Marking object as component\n     *\n     * @var bool\n     */\n    protected $_blIsComponent = true;\n\n    /**\n     * Newsletter subscription status\n     *\n     * @var bool\n     */\n    protected $_blNewsSubscriptionStatus = null;\n\n    /**\n     * User login state marker:\n     *  - USER_LOGIN_SUCCESS - user successfully logged in;\n     *  - USER_LOGIN_FAIL - login failed;\n     *  - USER_LOGOUT - user logged out.\n     *\n     * @var int\n     */\n    protected $_iLoginStatus = null;\n\n    /**\n     * Terms/conditions version number\n     *\n     * @var string\n     */\n    protected $_sTermsVer = null;\n\n    /**\n     * View classes accessible for not logged in customers\n     *\n     * @var array\n     */\n    protected $_aAllowedClasses = [\n        'register',\n        'forgotpwd',\n        'content',\n        'account',\n        'clearcookies',\n        'oxwservicemenu',\n        'oxwminibasket',\n    ];\n\n    /**\n     * Sets oxcmp_oxuser::blIsComponent = true, fetches user error\n     * code and sets it to default - 0. Executes parent::init().\n     *\n     * Session variable:\n     * <b>usr_err</b>\n     */\n    public function init()\n    {\n        $this->saveDeliveryAddressState();\n        $this->loadSessionUser();\n        $this->saveInvitor();\n\n        parent::init();\n    }\n\n    /**\n     * Executes parent::render(), oxcmp_user::_loadSessionUser(), loads user delivery\n     * info. Returns user object oxcmp_user::oUser.\n     *\n     * @return  object  user object\n     */\n    public function render()\n    {\n        // checks if private sales allows further tasks\n        $this->checkPsState();\n\n        parent::render();\n\n        return $this->getUser();\n    }\n\n    /**\n     * If private sales enabled, checks:\n     *  (1) if no session user and view can be accessed;\n     *  (2) session user is available and accepted terms version matches actual version.\n     * In case any condition is not satisfied redirects user to:\n     *  (1) login page;\n     *  (2) terms agreement page;\n     */\n    protected function checkPsState()\n    {\n        $oConfig = Registry::getConfig();\n        if ($this->getParent()->isEnabledPrivateSales()) {\n            // load session user\n            $oUser = $this->getUser();\n            $sClass = $this->getParent()->getClassKey();\n\n            // no session user\n            if (!$oUser && !in_array($sClass, $this->_aAllowedClasses)) {\n                Registry::getUtils()->redirect($oConfig->getShopHomeUrl() . 'cl=account', false, 302);\n            }\n\n            if ($oUser && !$oUser->isTermsAccepted() && !in_array($sClass, $this->_aAllowedClasses)) {\n                Registry::getUtils()->redirect($oConfig->getShopHomeUrl() . 'cl=account&term=1', false, 302);\n            }\n        }\n    }\n\n    /**\n     * Tries to load user ID from session.\n     *\n     * @return null\n     */\n    protected function loadSessionUser()\n    {\n        $myConfig = Registry::getConfig();\n        $session = Registry::getSession();\n\n        $oUser = $this->getUser();\n\n        // no session user\n        if (!$oUser) {\n            return;\n        }\n\n        // this user is blocked, deny him\n        if ($oUser->inGroup('oxidblocked')) {\n            $sUrl = $myConfig->getShopHomeUrl() . 'cl=content&tpl=user_blocked';\n            Registry::getUtils()->redirect($sUrl, true, 302);\n        }\n\n        // TODO: move this to a proper place\n        if ($oUser->isLoadedFromCookie() && !$myConfig->getConfigParam('blPerfNoBasketSaving')) {\n            if ($oBasket = $session->getBasket()) {\n                $oBasket->load();\n                $oBasket->onUpdate();\n            }\n        }\n    }\n\n    /**\n     * Collects posted user information from posted variables (\"lgn_usr\",\n     * \"lgn_pwd\", \"lgn_cook\"), executes User::login() and checks if\n     * such user exists.\n     *\n     * Session variables:\n     * <b>usr</b>, <b>usr_err</b>\n     *\n     * Template variables:\n     * <b>usr_err</b>\n     *\n     * @return  string  redirection string\n     */\n    public function login()\n    {\n        $sUser = Registry::getRequest()->getRequestEscapedParameter('lgn_usr');\n        $sPassword = Registry::getRequest()->getRequestParameter('lgn_pwd');\n        $sCookie = Registry::getRequest()->getRequestEscapedParameter('lgn_cook');\n\n        $this->setLoginStatus(USER_LOGIN_FAIL);\n\n        // trying to login user\n        try {\n            /** @var User $oUser */\n            $oUser = oxNew(User::class);\n            $oUser->login($sUser, $sPassword, $sCookie);\n            $this->setLoginStatus(USER_LOGIN_SUCCESS);\n        } catch (UserException $oEx) {\n            // for login component send exception text to a custom component (if defined)\n            Registry::getUtilsView()->addErrorToDisplay($oEx, false, true, '', false);\n\n            return 'user';\n        } catch (\\OxidEsales\\Eshop\\Core\\Exception\\CookieException $oEx) {\n            Registry::getUtilsView()->addErrorToDisplay($oEx);\n\n            return 'user';\n        }\n\n        // finalizing ..\n        return $this->afterLogin($oUser);\n    }\n\n    /**\n     * Special functionality which is performed after user logs in (or user is created without pass).\n     * Performes additional checking if user is not BLOCKED\n     * (User::InGroup(\"oxidblocked\")) - if yes - redirects to blocked user\n     * page (\"cl=content&tpl=user_blocked\").\n     * Stores cookie info if user confirmed in login screen.\n     * Then loads delivery info and forces basket to recalculate\n     * (\\OxidEsales\\Eshop\\Core\\Session::getBasket() + oBasket::blCalcNeeded = true). Returns\n     * \"payment\" to redirect to payment screen. If problems occured loading\n     * user - sets error code according problem, and returns \"user\" to redirect\n     * to user info screen.\n     *\n     * @param User $oUser user object\n     *\n     * @return string\n     */\n    protected function afterLogin($oUser)\n    {\n        $session = Registry::getSession();\n        if ($session->isSessionStarted()) {\n            $session->regenerateSessionId();\n        }\n\n        // this user is blocked, deny him\n        if ($oUser->inGroup('oxidblocked')) {\n            $sUrl = Registry::getConfig()->getShopHomeUrl() . 'cl=content&tpl=user_blocked';\n            Registry::getUtils()->redirect($sUrl, true, 302);\n        }\n\n        // recalc basket\n        if ($oBasket = $session->getBasket()) {\n            $oBasket->onUpdate();\n        }\n\n        return 'payment';\n    }\n\n    /**\n     * Executes oxcmp_user::login() method. After loggin user will not be\n     * redirected to user or payment screens.\n     */\n    public function login_noredirect() //phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps\n    {\n        $blAgb = Registry::getRequest()->getRequestEscapedParameter('ord_agb');\n\n        if ($this->getParent()->isEnabledPrivateSales() && $blAgb !== null && ($oUser = $this->getUser())) {\n            if ($blAgb) {\n                $oUser->acceptTerms();\n            }\n        } else {\n            $this->login();\n\n            if (!$this->isAdmin() && !Registry::getConfig()->getConfigParam('blPerfNoBasketSaving')) {\n                //load basket from the database\n                try {\n                    $session = Registry::getSession();\n                    if ($oBasket = $session->getBasket()) {\n                        $oBasket->load();\n                    }\n                } catch (Exception $oE) {\n                    //just ignore it\n                }\n            }\n        }\n    }\n\n    /**\n     * Special utility function which is executed right after\n     * oxcmp_user::logout is called. Currently it unsets such\n     * session parameters as user chosen payment id, delivery\n     * address id, active delivery set.\n     */\n    protected function afterLogout()\n    {\n        $session = Registry::getSession();\n\n        $session->deleteVariable('paymentid');\n        $session->deleteVariable('sShipSet');\n        $session->deleteVariable('deladrid');\n        $session->deleteVariable('dynvalue');\n\n        // resetting & recalc basket\n        if (($oBasket = $session->getBasket())) {\n            $oBasket->resetUserInfo();\n            $oBasket->onUpdate();\n\n            // resetting voucher reservations\n            if ($vouchers = $oBasket->getVouchers()) {\n                foreach ($vouchers as $voucherId => $voucher) {\n                    $oBasket->removeVoucher($voucherId);\n                }\n            }\n        }\n\n        $session->delBasket();\n    }\n\n    /**\n     * @return string|void\n     */\n    public function logout()\n    {\n        if (oxNew(User::class)->logout()) {\n            $this->setLoginStatus(USER_LOGOUT);\n            $this->afterLogout();\n            $this->resetPermissions();\n            if ($this->getParent()->isEnabledPrivateSales()) {\n                return 'account';\n            }\n            if (\n                ContainerFacade::getParameter('oxid_esales.shop_url') &&\n                Registry::getRequest()->getRequestEscapedParameter('redirect')\n            ) {\n                Registry::getUtils()->redirect($this->getLogoutLink());\n            }\n        }\n    }\n\n    /**\n     * Any additional permission reset actions required on logout or changeuser actions\n     */\n    protected function resetPermissions()\n    {\n    }\n\n    /**\n     * Executes blUserRegistered = oxcmp_user::changeUserWithoutRedirect().\n     * if this returns true - returns \"payment\" (this redirects to\n     * payment page), else returns blUserRegistered value.\n     *\n     * @see oxcmp_user::changeUserWithoutRedirect()\n     *\n     * @return  mixed    redirection string or true if user is registered, false otherwise\n     */\n    public function changeUser()\n    {\n        return ($this->changeUserWithoutRedirect() === true) ? 'payment' : false;\n    }\n\n    /**\n     * Executes oxcmp_user::changeUserWithoutRedirect().\n     * returns \"account_user\" (this redirects to billing and shipping settings page) on success\n     *\n     * @return null\n     */\n    public function changeuser_testvalues() //phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps\n    {\n        // skip updating user info if this is just form reload\n        // on selecting delivery address\n        // We do redirect only on success not to loose errors.\n\n        if ($this->changeUserWithoutRedirect()) {\n            return 'account_user';\n        }\n    }\n\n    /**\n     * First test if all required fields were filled, then performed\n     * additional checking oxcmp_user::CheckValues(). If no errors\n     * occured - trying to create new user (User::CreateUser()),\n     * logging him to shop (User::Login() if user has entered password).\n     * If User::CreateUser() returns false - this means user is\n     * already created - we only logging him to shop (oxcmp_user::Login()).\n     * If there is any error with missing data - function will return\n     * false and set error code (oxcmp_user::iError). If user was\n     * created successfully - will return \"payment\" to redirect to\n     * payment interface.\n     *\n     * Template variables:\n     * <b>usr_err</b>\n     *\n     * Session variables:\n     * <b>usr_err</b>, <b>usr</b>\n     *\n     * @return  mixed    redirection string or true if successful, false otherwise\n     */\n    public function createUser()\n    {\n        if (!Registry::getSession()->checkSessionChallenge()) {\n            Registry::getUtilsView()->addErrorToDisplay('ERROR_MESSAGE_NON_MATCHING_CSRF_TOKEN');\n\n            return false;\n        }\n\n        $isPrivateSales = $this->getParent()->isEnabledPrivateSales();\n\n        if (\n            $isPrivateSales\n            && !Registry::getRequest()->getRequestEscapedParameter('ord_agb')\n            && Registry::getConfig()->getConfigParam('blConfirmAGB')\n        ) {\n            Registry::getUtilsView()->addErrorToDisplay('READ_AND_CONFIRM_TERMS', false, true);\n\n            return false;\n        }\n\n        $username = Registry::getRequest()->getRequestEscapedParameter('lgn_usr');\n        $password = Registry::getRequest()->getRequestParameter('lgn_pwd');\n        $passwordConfirmation = Registry::getRequest()->getRequestParameter('lgn_pwd2');\n\n        $billingAddress = $this->getBillingAddress();\n        $shippingAddress = $this->getShippingAddress();\n        try {\n            $user = oxNew(User::class);\n            $user->checkValues($username, $password, $passwordConfirmation, $billingAddress, $shippingAddress);\n\n            $user->oxuser__oxusername = new Field($username, Field::T_RAW);\n            $user->setPassword($password);\n            $user->oxuser__oxactive = new Field(\n                $isPrivateSales ? 0 : 1,\n                Field::T_RAW\n            );\n\n            $userSubscriptionStatus = $user->getNewsSubscription()->getOptInStatus();\n\n            $database = DatabaseProvider::getDb();\n            $database->startTransaction();\n            try {\n                $user->createUser();\n                $user = $this->configureUserBeforeCreation($user);\n                $user->load($user->getId());\n                $user->changeUserData(\n                    $user->oxuser__oxusername->value,\n                    $password,\n                    $password,\n                    $billingAddress,\n                    $shippingAddress\n                );\n\n                if ($isPrivateSales) {\n                    $user->acceptTerms();\n                }\n\n                $database->commitTransaction();\n            } catch (Exception $exception) {\n                $database->rollbackTransaction();\n\n                throw $exception;\n            }\n\n            $invitationSenderUserId = Registry::getSession()->getVariable(\"su\");\n            $invitationRecipientEmail = Registry::getSession()->getVariable(\"re\");\n            if (\n                $invitationSenderUserId\n                && $invitationRecipientEmail\n                && Registry::getConfig()->getConfigParam('blInvitationsEnabled')\n            ) {\n                $user->setCreditPointsForRegistrant($invitationSenderUserId, $invitationRecipientEmail);\n            }\n\n            $isSubscriptionRequested = Registry::getRequest()->getRequestEscapedParameter('blnewssubscribed');\n            if ($isSubscriptionRequested && $userSubscriptionStatus == 1) {\n                // if user was assigned to newsletter\n                // and is creating account with newsletter checked,\n                // don't require confirm\n                $user->getNewsSubscription()->setOptInStatus(1);\n                $user->addToGroup('oxidnewsletter');\n                $this->_blNewsSubscriptionStatus = 1;\n            } else {\n                $isSubscriptionEmailRequested = Registry::getConfig()->getConfigParam('blOrderOptInEmail');\n                $this->_blNewsSubscriptionStatus = $user->setNewsSubscription(\n                    $isSubscriptionRequested,\n                    $isSubscriptionEmailRequested\n                );\n            }\n\n            $user->addToGroup('oxidnotyetordered');\n            $user->logout();\n        } catch (UserException | ConnectionException | InputException | DatabaseConnectionException $exception) {\n            Registry::getUtilsView()->addErrorToDisplay($exception, false, true);\n\n            return false;\n        }\n\n        if (!$isPrivateSales) {\n            Registry::getSession()->setVariable('usr', $user->getId());\n            $this->setSessionLoginToken((string)$user->getFieldData('oxpassword'));\n            $this->afterLogin($user);\n\n            // order remark\n            //V #427: order remark for new users\n            $orderRemark = Registry::getRequest()->getRequestParameter('order_remark');\n            if ($orderRemark) {\n                Registry::getSession()->setVariable('ordrem', $orderRemark);\n            }\n        }\n\n        if ((int)Registry::getRequest()->getRequestEscapedParameter('option') === 3) {\n            $user->sendRegistrationEmail($isPrivateSales);\n        }\n\n        // new registered\n        $this->_blIsNewUser = true;\n\n        return $this->_blNewsSubscriptionStatus !== null && !$this->_blNewsSubscriptionStatus\n            ? 'payment?new_user=1&success=1&newslettererror=4'\n            : 'payment?new_user=1&success=1';\n    }\n\n    /**\n     * If any additional configurations required right before user creation\n     *\n     * @param User $user\n     *\n     * @return User The user we gave in.\n     */\n    protected function configureUserBeforeCreation($user)\n    {\n        return $user;\n    }\n\n    /**\n     * Creates new oxid user\n     *\n     * @return string partial parameter string or null\n     */\n    public function registerUser()\n    {\n        // registered new user ?\n        if ($this->createUser() != false && $this->_blIsNewUser) {\n            if ($this->_blNewsSubscriptionStatus === null || $this->_blNewsSubscriptionStatus) {\n                return 'register?success=1';\n            } else {\n                return 'register?success=1&newslettererror=4';\n            }\n        } else {\n            // problems with registration ...\n            $this->logout();\n        }\n    }\n\n    /**\n     * Deletes user shipping address.\n     */\n    public function deleteShippingAddress()\n    {\n        $session = Registry::getSession();\n\n        $addressId = Registry::getRequest()->getRequestEscapedParameter('oxaddressid');\n\n        $address = oxNew(Address::class);\n        $address->load($addressId);\n        if ($this->canUserDeleteShippingAddress($address) && $session->checkSessionChallenge()) {\n            $address->delete($addressId);\n        }\n    }\n\n    /**\n     * Checks if shipping address is assigned to user.\n     *\n     * @param Address $address\n     * @return bool\n     */\n    private function canUserDeleteShippingAddress($address)\n    {\n        $canDelete = false;\n        $user = $this->getUser();\n        if ($address->oxaddress__oxuserid->value === $user->getId()) {\n            $canDelete = true;\n        }\n\n        return $canDelete;\n    }\n\n    /**\n     * Saves invitor ID\n     */\n    protected function saveInvitor()\n    {\n        if (Registry::getConfig()->getConfigParam('blInvitationsEnabled')) {\n            $this->getInvitor();\n            $this->setRecipient();\n        }\n    }\n\n    /**\n     * Saving show/hide delivery address state\n     */\n    protected function saveDeliveryAddressState()\n    {\n        $oSession = Registry::getSession();\n\n        $blShow = Registry::getRequest()->getRequestEscapedParameter('blshowshipaddress');\n        if (!isset($blShow)) {\n            $blShow = $oSession->getVariable('blshowshipaddress');\n        }\n\n        $oSession->setVariable('blshowshipaddress', $blShow);\n    }\n\n    /**\n     * Mostly used for customer profile editing screen (OXID eShop ->\n     * MY ACCOUNT). Checks if oUser is set (oxcmp_user::oUser) - if\n     * not - executes oxcmp_user::_loadSessionUser(). If user unchecked newsletter\n     * subscription option - removes him from this group. There is an\n     * additional MUST FILL fields checking. Function returns true or false\n     * according to user data submission status.\n     *\n     * Session variables:\n     * <b>ordrem</b>\n     *\n     * @return  bool|void true on success, false otherwise\n     */\n    protected function changeUserWithoutRedirect()\n    {\n        $session = Registry::getSession();\n\n        if (!$session->checkSessionChallenge()) {\n            return;\n        }\n\n        $user = $this->getUser();\n        if (!$user) {\n            return;\n        }\n\n        $currentEmail = $user->getFieldData('oxusername');\n        $password = $user->getFieldData('oxpassword');\n        $shippingAddress = $this->getShippingAddress();\n        $billingAddress = $this->getBillingAddress();\n        $newEmail = $billingAddress['oxuser__oxusername'] ?? '';\n\n        try {\n            $isUsernameUpdated = $this->isUserNameUpdated($currentEmail ?? '', $newEmail);\n            if ($isUsernameUpdated && $this->isGuestUser($user)) {\n                $this->deleteExistingGuestUser($newEmail);\n            }\n            if (\n                !$this->isGuestUser($user)\n                && $isUsernameUpdated\n                && $user->isEmailInUse($newEmail)\n            ) {\n                Registry::getUtilsView()->addErrorToDisplay('ERROR_MESSAGE_USER_USEREXISTS');\n                return false;\n            }\n            $user->changeUserData($currentEmail, $password, $password, $billingAddress, $shippingAddress);\n\n            $isSubscriptionRequested = Registry::getRequest()->getRequestEscapedParameter('blnewssubscribed');\n            $userSubscriptionStatus = $isSubscriptionRequested ?? $user->getNewsSubscription()->getOptInStatus();\n            $isSubscriptionEmailRequested = Registry::getConfig()->getConfigParam('blOrderOptInEmail');\n\n            $this->_blNewsSubscriptionStatus = $user->setNewsSubscription(\n                $userSubscriptionStatus,\n                $isSubscriptionEmailRequested,\n                $isUsernameUpdated\n            );\n\n            $this->resetPermissions();\n\n            $orderRemark = Registry::getRequest()->getRequestParameter('order_remark');\n            if ($orderRemark) {\n                $session->setVariable('ordrem', $orderRemark);\n            } else {\n                $session->deleteVariable('ordrem');\n            }\n\n            if ($basket = $session->getBasket()) {\n                $basket->setBasketUser(null);\n                $basket->onUpdate();\n            }\n\n            return true;\n        } catch (UserException | ConnectionException | InputException $exception) {\n            Registry::getUtilsView()->addErrorToDisplay($exception, false, true);\n            return;\n        } catch (\\Throwable) {\n            Registry::getUtilsView()->addErrorToDisplay('ERROR_MESSAGE_USER_UPDATE_FAILED', false, true);\n            return false;\n        }\n    }\n\n    /**\n     * Returns delivery address from request. Before returning array is checked if\n     * all needed data is there\n     *\n     * @return array\n     */\n    protected function getDelAddressData()\n    {\n        // if user company name, user name and additional info has special chars\n        $blShowShipAddressParameter = Registry::getRequest()->getRequestEscapedParameter('blshowshipaddress');\n        $blShowShipAddressVariable = Registry::getSession()->getVariable('blshowshipaddress');\n        $sDeliveryAddressParameter = Registry::getRequest()->getRequestParameter('deladr');\n        $aDeladr = ($blShowShipAddressParameter || $blShowShipAddressVariable) ? $sDeliveryAddressParameter : [];\n        $aDelAdress = $aDeladr;\n\n        if (is_array($aDeladr)) {\n            // checking if data is filled\n            if (isset($aDeladr['oxaddress__oxsal'])) {\n                unset($aDeladr['oxaddress__oxsal']);\n            }\n            if (!count($aDeladr) || implode('', $aDeladr) == '') {\n                // resetting to avoid empty records\n                $aDelAdress = [];\n            }\n        }\n\n        return $aDelAdress;\n    }\n\n    /**\n     * Returns logout link with additional params\n     *\n     * @return string $sLogoutLink\n     */\n    protected function getLogoutLink()\n    {\n        $oConfig = Registry::getConfig();\n\n        $sLogoutLink = $oConfig->isSsl() ? $oConfig->getShopSecureHomeUrl() : $oConfig->getShopHomeUrl();\n        $sLogoutLink .= 'cl=' . $oConfig->getRequestControllerId() . $this->getParent()->getDynUrlParams();\n        if ($sParam = Registry::getRequest()->getRequestEscapedParameter('anid')) {\n            $sLogoutLink .= '&amp;anid=' . $sParam;\n        }\n        if ($sParam = Registry::getRequest()->getRequestEscapedParameter('cnid')) {\n            $sLogoutLink .= '&amp;cnid=' . $sParam;\n        }\n        if ($sParam = Registry::getRequest()->getRequestEscapedParameter('mnid')) {\n            $sLogoutLink .= '&amp;mnid=' . $sParam;\n        }\n        if ($sParam = basename(Registry::getRequest()->getRequestEscapedParameter('tpl'))) {\n            $sLogoutLink .= '&amp;tpl=' . $sParam;\n        }\n        if ($sParam = Registry::getRequest()->getRequestEscapedParameter('oxloadid')) {\n            $sLogoutLink .= '&amp;oxloadid=' . $sParam;\n        }\n        // @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n        if ($sParam = Registry::getRequest()->getRequestEscapedParameter('recommid')) {\n            $sLogoutLink .= '&amp;recommid=' . $sParam;\n        }\n        // END deprecated\n\n        return $sLogoutLink . '&amp;fnc=logout';\n    }\n\n    /**\n     * Sets user login state\n     *\n     * @param int $iStatus login state (USER_LOGIN_SUCCESS/USER_LOGIN_FAIL/USER_LOGOUT)\n     */\n    public function setLoginStatus($iStatus)\n    {\n        $this->_iLoginStatus = $iStatus;\n    }\n\n    /**\n     * Returns user login state marker:\n     *  - USER_LOGIN_SUCCESS - user successfully logged in;\n     *  - USER_LOGIN_FAIL - login failed;\n     *  - USER_LOGOUT - user logged out.\n     *\n     * @return int\n     */\n    public function getLoginStatus()\n    {\n        return $this->_iLoginStatus;\n    }\n\n    /**\n     * Sets invitor id to session from URL\n     */\n    public function getInvitor()\n    {\n        $sSu = Registry::getSession()->getVariable('su');\n\n        if (!$sSu && ($sSuNew = Registry::getRequest()->getRequestEscapedParameter('su'))) {\n            Registry::getSession()->setVariable('su', $sSuNew);\n        }\n    }\n\n    /**\n     * sets from URL invitor id\n     */\n    public function setRecipient()\n    {\n        $sRe = Registry::getSession()->getVariable('re');\n        if (!$sRe && ($sReNew = Registry::getRequest()->getRequestEscapedParameter('re'))) {\n            Registry::getSession()->setVariable('re', $sReNew);\n        }\n    }\n\n    /**\n     * @param array                   $address\n     * @param AbstractUpdatableFields $updatableFields\n     *\n     * @return array\n     */\n    private function cleanAddress($address, $updatableFields)\n    {\n        if (is_array($address)) {\n            /** @var UpdatableFieldsConstructor $updatableFieldsConstructor */\n            $updatableFieldsConstructor = oxNew(UpdatableFieldsConstructor::class);\n            $cleaner = $updatableFieldsConstructor->getAllowedFieldsCleaner($updatableFields);\n            return $cleaner->filterByUpdatableFields($address);\n        }\n\n        return $address;\n    }\n\n    /**\n     * Returns trimmed address.\n     *\n     * @param array $address\n     *\n     * @return array\n     */\n    private function trimAddress($address)\n    {\n        if (is_array($address)) {\n            $fields  = oxNew(FormFields::class, $address);\n            $trimmer = oxNew(FormFieldsTrimmer::class);\n\n            $address = (array)$trimmer->trim($fields);\n        }\n\n        return $address;\n    }\n\n    private function isGuestUser(User $user): bool\n    {\n        return empty($user->oxuser__oxpassword->value);\n    }\n\n    private function isUserNameUpdated(string $currentName, string $newName): bool\n    {\n        return $currentName && $newName && $currentName !== $newName;\n    }\n\n    /**\n     * @throws Exception\n     */\n    private function deleteExistingGuestUser(string $newName): void\n    {\n        $existingUser = oxNew(User::class);\n        $existingUser->load($existingUser->getIdByUserName($newName));\n        if ($existingUser && $this->isGuestUser($existingUser)) {\n            $existingUser->delete();\n        }\n    }\n\n    private function getShippingAddress(): ?array\n    {\n        $shippingAddress = $this->getDelAddressData();\n        $shippingAddress = $this->cleanAddress($shippingAddress, oxNew(UserShippingAddressUpdatableFields::class));\n        return $this->trimAddress($shippingAddress);\n    }\n\n    private function getBillingAddress(): ?array\n    {\n        $billingAddress = Registry::getRequest()->getRequestParameter('invadr');\n        $billingAddress = $this->cleanAddress($billingAddress, oxNew(UserUpdatableFields::class));\n        if ($billingAddress && is_array($billingAddress)) {\n            $billingAddress = $this->removeNonAddressFields($billingAddress);\n        }\n        return $this->trimAddress($billingAddress);\n    }\n\n    private function removeNonAddressFields(array $addressFormData): array\n    {\n        $nonAddressFields = [\n            'oxuser__oxactive',\n            'oxuser__oxshopid',\n            'oxuser__oxpassword',\n            'oxuser__oxpasssalt',\n            'oxuser__oxupdatekey',\n            'oxuser__oxupdateexp',\n        ];\n        foreach ($nonAddressFields as $field) {\n            if ($addressFormData && array_key_exists($field, $addressFormData)) {\n                unset($addressFormData[$field]);\n            }\n        }\n\n        return $addressFormData;\n    }\n\n    private function setSessionLoginToken(string $passwordHash): void\n    {\n        Registry::getSession()\n            ->setVariable(\n                'login-token',\n                ContainerFacade::get(PasswordServiceBridgeInterface::class)->hash($passwordHash)\n            );\n    }\n}\n"
  },
  {
    "path": "source/Application/Component/UtilsComponent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Transparent shop utilities class.\n * Some specific utilities, such as fetching article info, etc. (Class may be used\n * for overriding).\n *\n * @subpackage oxcmp\n */\nclass UtilsComponent extends \\OxidEsales\\Eshop\\Core\\Controller\\BaseController\n{\n    /**\n     * Marking object as component\n     *\n     * @var bool\n     */\n    protected $_blIsComponent = true;\n\n    /**\n     * Adds/removes chosen article to/from article comparison list\n     *\n     * @param object $sProductId product id\n     * @param double $dAmount    amount\n     * @param array  $aSel       (default null)\n     * @param bool   $blOverride allow override\n     * @param bool   $blBundle   bundled\n     */\n    public function toCompareList(\n        $sProductId = null,\n        $dAmount = null,\n        $aSel = null,\n        $blOverride = false,\n        $blBundle = false\n    ) {\n        // only if enabled and not search engine..\n        if ($this->getViewConfig()->getShowCompareList() && !Registry::getUtils()->isSearchEngine()) {\n            // #657 special treatment if we want to put on comparelist\n            $blAddCompare = Registry::getRequest()->getRequestEscapedParameter('addcompare');\n            $blRemoveCompare = Registry::getRequest()->getRequestEscapedParameter('removecompare');\n            $sProductId = $sProductId ? $sProductId : Registry::getRequest()->getRequestEscapedParameter('aid');\n            if (($blAddCompare || $blRemoveCompare) && $sProductId) {\n                // toggle state in session array\n                $aItems = Registry::getSession()->getVariable('aFiltcompproducts');\n                if ($blAddCompare && !isset($aItems[$sProductId])) {\n                    $aItems[$sProductId] = true;\n                }\n\n                if ($blRemoveCompare) {\n                    unset($aItems[$sProductId]);\n                }\n\n                Registry::getSession()->setVariable('aFiltcompproducts', $aItems);\n                $oParentView = $this->getParent();\n\n                // #843C there was problem then field \"blIsOnComparisonList\" was not set to article object\n                if (($oProduct = $oParentView->getViewProduct())) {\n                    if (isset($aItems[$oProduct->getId()])) {\n                        $oProduct->setOnComparisonList(true);\n                    } else {\n                        $oProduct->setOnComparisonList(false);\n                    }\n                }\n\n                $aViewProds = $oParentView->getViewProductList();\n                if (is_array($aViewProds) && count($aViewProds)) {\n                    foreach ($aViewProds as $oProduct) {\n                        if (isset($aItems[$oProduct->getId()])) {\n                            $oProduct->setOnComparisonList(true);\n                        } else {\n                            $oProduct->setOnComparisonList(false);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * If session user is set loads user noticelist (\\OxidEsales\\Eshop\\Application\\Model\\User::GetBasket())\n     * and adds article to it.\n     *\n     * @param string $sProductId Product/article ID (default null)\n     * @param double $dAmount    amount of good (default null)\n     * @param array  $aSel       product selection list (default null)\n     */\n    public function toNoticeList($sProductId = null, $dAmount = null, $aSel = null)\n    {\n        if (!Registry::getSession()->checkSessionChallenge()) {\n            return;\n        }\n\n        $this->toList('noticelist', $sProductId, $dAmount, $aSel);\n    }\n\n    /**\n     * If session user is set loads user wishlist (\\OxidEsales\\Eshop\\Application\\Model\\User::GetBasket()) and\n     * adds article to it.\n     *\n     * @param string $sProductId Product/article ID (default null)\n     * @param double $dAmount    amount of good (default null)\n     * @param array  $aSel       product selection list (default null)\n     */\n    public function toWishList($sProductId = null, $dAmount = null, $aSel = null)\n    {\n        if (!Registry::getSession()->checkSessionChallenge()) {\n            return;\n        }\n\n        // only if enabled\n        if ($this->getViewConfig()->getShowWishlist()) {\n            $this->toList('wishlist', $sProductId, $dAmount, $aSel);\n        }\n    }\n\n    /**\n     * Adds chosen product to defined user list. if amount is 0, item is removed from the list\n     *\n     * @param string $sListType  user product list type\n     * @param string $sProductId product id\n     * @param double $dAmount    product amount\n     * @param array  $aSel       product selection list\n     */\n    protected function toList($sListType, $sProductId, $dAmount, $aSel)\n    {\n        // only if user is logged in\n        if ($oUser = $this->getUser()) {\n            $sProductId = ($sProductId) ? $sProductId : Registry::getRequest()->getRequestEscapedParameter('itmid');\n            $sProductId = ($sProductId) ? $sProductId : Registry::getRequest()->getRequestEscapedParameter('aid');\n            $dAmount = isset($dAmount) ? $dAmount : Registry::getRequest()->getRequestEscapedParameter('am');\n            $aSel = $aSel ? $aSel : Registry::getRequest()->getRequestEscapedParameter('sel');\n\n            // processing amounts\n            $dAmount = str_replace(',', '.', $dAmount);\n            if (!\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blAllowUnevenAmounts')) {\n                $dAmount = round((string) $dAmount);\n            }\n\n            $oBasket = $oUser->getBasket($sListType);\n            $oBasket->addItemToBasket($sProductId, abs($dAmount), $aSel, ($dAmount == 0));\n\n            // recalculate basket count\n            $oBasket->getItemCount(true);\n        }\n    }\n\n    /**\n     *  Set view data, call parent::render\n     *\n     * @return null\n     */\n    public function render()\n    {\n        parent::render();\n\n        $oParentView = $this->getParent();\n\n        // add content for main menu\n        $oContentList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ContentList::class);\n        $oContentList->loadMainMenulist();\n        $oParentView->setMenueList($oContentList);\n\n        return;\n    }\n}\n"
  },
  {
    "path": "source/Application/Component/Widget/Actions.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component\\Widget;\n\n/**\n * Actions widget.\n * Access actions in tpl.\n */\nclass Actions extends \\OxidEsales\\Eshop\\Application\\Component\\Widget\\WidgetController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'widget/product/action';\n\n    /**\n     * Are actions on\n     *\n     * @var bool\n     */\n    protected $_blLoadActions = null;\n\n    /**\n     * Returns article list with action articles\n     *\n     * @return object\n     */\n    public function getAction()\n    {\n        $actionId = $this->getViewParameter('action');\n        if ($actionId && $this->getLoadActionsParam()) {\n            $artList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n            $artList->loadActionArticles($actionId);\n            if ($artList->count()) {\n                return $artList;\n            }\n        }\n    }\n\n    /**\n     * Returns if actions are ON\n     *\n     * @return string\n     */\n    protected function getLoadActionsParam()\n    {\n        $this->_blLoadActions = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('bl_perfLoadAktion');\n\n        return $this->_blLoadActions;\n    }\n\n    /**\n     * Returns action name\n     *\n     * @return string\n     */\n    public function getActionName()\n    {\n        $actionId = $this->getViewParameter('action');\n        $action   = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Actions::class);\n        if ($action->load($actionId)) {\n            return $action->oxactions__oxtitle->value;\n        }\n    }\n\n    /**\n     * Returns products list type\n     *\n     * @return string\n     */\n    public function getListType()\n    {\n        return $this->getViewParameter('listtype');\n    }\n}\n"
  },
  {
    "path": "source/Application/Component/Widget/ArticleBox.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component\\Widget;\n\nuse oxRegistry;\nuse oxArticle;\n\n/**\n * Article box widget\n */\nclass ArticleBox extends \\OxidEsales\\Eshop\\Application\\Component\\Widget\\WidgetController\n{\n    /**\n     * Names of components (classes) that are initiated and executed\n     * before any other regular operation.\n     * User component used in template.\n     *\n     * @var array\n     */\n    protected $_aComponentNames = ['oxcmp_user' => 1, 'oxcmp_basket' => 1, 'oxcmp_cur' => 1];\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sTemplate = 'widget/product/boxproduct';\n\n    /**\n     * Current article\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Article|null\n     */\n    protected $_oArticle = null;\n\n    /**\n     * Returns active category\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Category|null\n     */\n    public function getActiveCategory()\n    {\n        $oCategory = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getTopActiveView()->getActiveCategory();\n        if ($oCategory) {\n            $this->setActiveCategory($oCategory);\n        }\n\n        return $this->_oActCategory;\n    }\n\n    /**\n     * Renders template based on widget type or just use directly passed path of template\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        $sWidgetType = $this->getViewParameter('sWidgetType');\n        $sListType = $this->getViewParameter('sListType');\n\n        if ($sWidgetType && $sListType) {\n            $this->_sTemplate = \"widget/\" . $sWidgetType . \"/\" . $sListType;\n        }\n\n        $sForceTemplate = $this->getViewParameter('oxwtemplate');\n        if ($sForceTemplate) {\n            $this->_sTemplate = $sForceTemplate;\n        }\n\n        return $this->_sTemplate;\n    }\n\n    /**\n     * Sets box product\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle Box product\n     */\n    public function setProduct($oArticle)\n    {\n        $this->_oArticle = $oArticle;\n    }\n\n    /**\n     * Get product article\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    public function getProduct()\n    {\n        if (is_null($this->_oArticle)) {\n            if ($this->getViewParameter('_object')) {\n                $oArticle = $this->getViewParameter('_object');\n            } else {\n                $sAddDynParams = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getTopActiveView()->getAddUrlParams();\n\n                $sAddDynParams = $this->updateDynamicParameters($sAddDynParams);\n\n                $oArticle = $this->getArticleById($this->getViewParameter('anid'));\n                $this->addDynParamsToLink($sAddDynParams, $oArticle);\n            }\n\n            $this->setProduct($oArticle);\n        }\n\n        return $this->_oArticle;\n    }\n\n    /**\n     * get link of current top view\n     *\n     * @param int $iLang requested language\n     *\n     * @return string\n     */\n    public function getLink($iLang = null)\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getTopActiveView()->getLink($iLang);\n    }\n\n    /**\n     * Returns if VAT is included in price\n     *\n     * @return bool\n     */\n    public function isVatIncluded()\n    {\n        return (bool) $this->getViewParameter(\"isVatIncluded\");\n    }\n\n    /**\n     * Returns wish list id\n     *\n     * @return string\n     */\n    public function getWishId()\n    {\n        return $this->getViewParameter('owishid');\n    }\n\n    /**\n     * Returns remove function\n     *\n     * @return string\n     */\n    public function getRemoveFunction()\n    {\n        return $this->getViewParameter('removeFunction');\n    }\n\n    /**\n     * Returns toBasket function\n     *\n     * @return string\n     */\n    public function getToBasketFunction()\n    {\n        return $this->getViewParameter('toBasketFunction');\n    }\n\n    /**\n     * Returns if toCart must be disabled\n     *\n     * @return bool\n     */\n    public function getDisableToCart()\n    {\n        return (bool) $this->getViewParameter('blDisableToCart');\n    }\n\n    /**\n     * Returns list item id with identifier\n     *\n     * @return string\n     */\n    public function getIndex()\n    {\n        return $this->getViewParameter('iIndex');\n    }\n\n    /**\n     * Returns recommendation id\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @return string\n     */\n    public function getRecommId()\n    {\n        return $this->getViewParameter('recommid');\n    }\n\n    /**\n     * Returns iteration number\n     *\n     * @return string\n     */\n    public function getIteration()\n    {\n        return $this->getViewParameter('iIteration');\n    }\n\n    /**\n     * Returns the answer if main link must be showed\n     *\n     * @return bool\n     */\n    public function getShowMainLink()\n    {\n        return (bool) $this->getViewParameter('showMainLink');\n    }\n\n    /**\n     * Returns if alternate product exists\n     *\n     * @return bool\n     */\n    public function getAltProduct()\n    {\n        return (bool) $this->getViewParameter('altproduct');\n    }\n\n    /**\n     * Appends dyn params to url.\n     *\n     * @param string                                      $sAddDynParams Dyn params\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle      Article\n     *\n     * @return bool\n     */\n    protected function addDynParamsToLink($sAddDynParams, $oArticle)\n    {\n        $blAddedParams = false;\n        if ($sAddDynParams) {\n            $blSeo = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->seoIsActive();\n            if (!$blSeo) {\n                // only if seo is off..\n                $oArticle->appendStdLink($sAddDynParams);\n            }\n            $oArticle->appendLink($sAddDynParams);\n            $blAddedParams = true;\n        }\n\n        return $blAddedParams;\n    }\n\n    /**\n     * Returns prepared article by id.\n     *\n     * @param string $sArticleId Article id\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected function getArticleById($sArticleId)\n    {\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle */\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $oArticle->load($sArticleId);\n        $iLinkType = $this->getViewParameter('iLinkType');\n\n        if ($this->getViewParameter('inlist')) {\n            $oArticle->setInList();\n        }\n        if ($iLinkType) {\n            $oArticle->setLinkType($iLinkType);\n        }\n        // @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n        if ($oRecommList = $this->getActiveRecommList()) {\n            $oArticle->text = $oRecommList->getArtDescription($oArticle->getId());\n        }\n        // END deprecated\n\n        return $oArticle;\n    }\n\n    /**\n     * @param string $dynamicParameters\n     *\n     * @return string\n     */\n    protected function updateDynamicParameters($dynamicParameters)\n    {\n        return $dynamicParameters;\n    }\n}\n"
  },
  {
    "path": "source/Application/Component/Widget/ArticleDetails.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component\\Widget;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Application\\Model\\ArticleList;\nuse OxidEsales\\Eshop\\Application\\Model\\Manufacturer;\nuse OxidEsales\\Eshop\\Application\\Model\\SimpleVariantList;\nuse OxidEsales\\Eshop\\Application\\Model\\Vendor;\nuse OxidEsales\\Eshop\\Core\\Config;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Utils;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Core\\SortingValidator;\nuse stdClass;\n\n/**\n * Article detailed information widget.\n */\nclass ArticleDetails extends \\OxidEsales\\Eshop\\Application\\Component\\Widget\\WidgetController\n{\n    /**\n     * List of article variants.\n     *\n     * @var array\n     */\n    protected $_aVariantList = null;\n    /**\n     * Names of components (classes) that are initiated and executed\n     * before any other regular operation.\n     *\n     * @var array\n     */\n    protected $_aComponentNames = ['oxcmp_cur' => 1, 'oxcmp_shop' => 1, 'oxcmp_basket' => 1, 'oxcmp_user' => 1];\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'widget/product/details';\n\n    /**\n     * Current product parent article object.\n     *\n     * @var Article\n     */\n    protected $_oParentProd = null;\n\n    /**\n     * Marker if user can rate current product.\n     *\n     * @var bool\n     */\n    protected $_blCanRate = null;\n\n    /**\n     * Media files.\n     *\n     * @var array\n     */\n    protected $_aMediaFiles = null;\n\n    /**\n     * History (last seen) products.\n     *\n     * @var array\n     */\n    protected $_aLastProducts = null;\n\n    /**\n     * Current product's vendor.\n     *\n     * @var Vendor\n     */\n    protected $_oVendor = null;\n\n    /**\n     * Current product's manufacturer.\n     *\n     * @var Manufacturer\n     */\n    protected $_oManufacturer = null;\n\n    /**\n     * Current product's category.\n     *\n     * @var object\n     */\n    protected $_oCategory = null;\n\n    /**\n     * Current product's attributes.\n     *\n     * @var array\n     */\n    protected $_aAttributes = null;\n\n    /**\n     * Picture gallery.\n     *\n     * @var array\n     */\n    protected $_aPicGallery = null;\n\n    /**\n     * Reviews of current article.\n     *\n     * @var array\n     */\n    protected $_aReviews = null;\n\n    /**\n     * CrossSelling article list\n     *\n     * @var object\n     */\n    protected $_oCrossSelling = null;\n\n    /**\n     * Similar products article list.\n     *\n     * @var object\n     */\n    protected $_oSimilarProducts = null;\n\n    /**\n     * Accessories of current article.\n     *\n     * @var object\n     */\n    protected $_oAccessoires = null;\n\n    /**\n     * List of customer also bought these products.\n     *\n     * @var object\n     */\n    protected $_aAlsoBoughtArts = null;\n\n    /**\n     * Search title.\n     *\n     * @var string\n     */\n    protected $_sSearchTitle = null;\n\n    /**\n     * Marker if active product was fully initialized before returning it.\n     * (see details::getProduct())\n     *\n     * @var bool\n     */\n    protected $_blIsInitialized = false;\n\n    /**\n     * Current view link type.\n     *\n     * @var int\n     */\n    protected $_iLinkType = null;\n\n    /**\n     * Is multi dimension variant view.\n     *\n     * @var bool\n     */\n    protected $_blMdView = null;\n\n    /**\n     * Rating value.\n     *\n     * @var double\n     */\n    protected $_dRatingValue = null;\n\n    /**\n     * Rating count.\n     *\n     * @var integer\n     */\n    protected $_iRatingCnt = null;\n\n    /**\n     * Bid price.\n     *\n     * @var string\n     */\n    protected $_sBidPrice = null;\n\n    /**\n     * Marked which defines if current view is sortable or not.\n     *\n     * @var bool\n     */\n    protected $_blShowSorting = true;\n\n    /**\n     * Array of id to form recommendation list.\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @var array\n     */\n    protected $_aSimilarRecommListIds = null;\n\n    /**\n     * Template variable getter. Returns active zoom picture id.\n     *\n     * @return int\n     */\n    public function getActZoomPic()\n    {\n        return 1;\n    }\n\n    /**\n     * Returns current product parent article object if it is available.\n     *\n     * @param string $sParentId parent product id\n     *\n     * @return Article\n     */\n    protected function getParentProduct($sParentId)\n    {\n        if ($sParentId && $this->_oParentProd === null) {\n            $this->_oParentProd = false;\n            $oProduct = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            if (($oProduct->load($sParentId))) {\n                $this->processProduct($oProduct);\n                $this->_oParentProd = $oProduct;\n            }\n        }\n\n        return $this->_oParentProd;\n    }\n\n    /**\n     * In case list type is \"search\" returns search parameters which will be added to product details link.\n     *\n     * @return string|null\n     */\n    protected function getAddDynUrlParams()\n    {\n        if ($this->getListType() == \"search\") {\n            return $this->getDynUrlParams();\n        }\n    }\n\n    /**\n     * Processes product by setting link type and in case list type is search adds search parameters to details link.\n     *\n     * @param object $oProduct Product to process.\n     */\n    protected function processProduct($oProduct)\n    {\n        $oProduct->setLinkType($this->getLinkType());\n        if ($sAddParams = $this->getAddDynUrlParams()) {\n            $oProduct->appendLink($sAddParams);\n        }\n    }\n\n    /**\n     * Checks if rating functionality is active.\n     *\n     * @return bool\n     */\n    public function ratingIsActive()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('bl_perfLoadReviews');\n    }\n\n    /**\n     * Checks if rating functionality is on and allowed to user.\n     *\n     * @return bool\n     */\n    public function canRate()\n    {\n        if ($this->_blCanRate === null) {\n            $this->_blCanRate = false;\n\n            if ($this->ratingIsActive() && $oUser = $this->getUser()) {\n                $oRating = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Rating::class);\n                $this->_blCanRate = $oRating->allowRating($oUser->getId(), 'oxarticle', $this->getProduct()->getId());\n            }\n        }\n\n        return $this->_blCanRate;\n    }\n\n    /**\n     * Loading full list of attributes.\n     *\n     * @return array\n     */\n    public function getAttributes()\n    {\n        if ($this->_aAttributes === null) {\n            // all attributes this article has\n            $aArtAttributes = $this->getProduct()->getAttributes();\n\n            //making a new array for backward compatibility\n            $this->_aAttributes = false;\n\n            if (count($aArtAttributes)) {\n                foreach ($aArtAttributes as $sKey => $oAttribute) {\n                    $this->_aAttributes[$sKey] = new stdClass();\n                    $this->_aAttributes[$sKey]->title = $oAttribute->oxattribute__oxtitle->value;\n                    $this->_aAttributes[$sKey]->value = $oAttribute->oxattribute__oxvalue->value;\n                }\n            }\n        }\n\n        return $this->_aAttributes;\n    }\n\n    /**\n     * Returns current view link type.\n     *\n     * @return int\n     */\n    public function getLinkType()\n    {\n        if ($this->_iLinkType === null) {\n            $sListType = Registry::getRequest()->getRequestEscapedParameter('listtype');\n            if ('vendor' == $sListType) {\n                $this->_iLinkType = OXARTICLE_LINKTYPE_VENDOR;\n            } elseif ('manufacturer' == $sListType) {\n                $this->_iLinkType = OXARTICLE_LINKTYPE_MANUFACTURER;\n                // @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n            } elseif ('recommlist' == $sListType) {\n                $this->_iLinkType = OXARTICLE_LINKTYPE_RECOMM;\n                // END deprecated\n            } else {\n                $this->_iLinkType = OXARTICLE_LINKTYPE_CATEGORY;\n\n                // price category has own type..\n                if (($oCat = $this->getActiveCategory()) && $oCat->isPriceCategory()) {\n                    $this->_iLinkType = OXARTICLE_LINKTYPE_PRICECATEGORY;\n                }\n            }\n        }\n\n        return $this->_iLinkType;\n    }\n\n    /**\n     * Returns variant lists of current product\n     * excludes currently viewed product.\n     *\n     * @return array|SimpleVariantList|ArticleList\n     */\n    public function getVariantListExceptCurrent()\n    {\n        $oList = $this->getVariantList();\n        if (is_object($oList)) {\n            $oList = clone $oList;\n        }\n\n        $sOxId = $this->getProduct()->getId();\n        if (isset($oList[$sOxId])) {\n            unset($oList[$sOxId]);\n        }\n\n        return $oList;\n    }\n\n    /**\n     * Loading full list of variants,\n     * if we are child and do not have any variants then let's load all parent variants as ours.\n     *\n     * @return array|SimpleVariantList|ArticleList\n     */\n    public function loadVariantInformation()\n    {\n        if ($this->_aVariantList === null) {\n            $oProduct = $this->getProduct();\n\n            //if we are child and do not have any variants then let's load all parent variants as ours\n            if ($oParent = $oProduct->getParentArticle()) {\n                $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n                $oParent->setNoVariantLoading(false);\n                $this->_aVariantList = $oParent->getFullVariants(false);\n\n                //lets additionally add parent article if it is sellable\n                if (count($this->_aVariantList) && $myConfig->getConfigParam('blVariantParentBuyable')) {\n                    //#1104S if parent is buyable load select lists too\n                    $oParent->enablePriceLoad();\n                    $oParent->aSelectlist = $oParent->getSelectLists();\n                    $this->_aVariantList = array_merge([$oParent], $this->_aVariantList->getArray());\n                }\n            } else {\n                //loading full list of variants\n                $this->_aVariantList = $oProduct->getFullVariants(false);\n            }\n\n            // setting link type for variants ..\n            foreach ($this->_aVariantList as $oVariant) {\n                $this->processProduct($oVariant);\n            }\n        }\n\n        return $this->_aVariantList;\n    }\n\n    /**\n     * Returns variant lists of current product.\n     *\n     * @return array|SimpleVariantList|ArticleList\n     */\n    public function getVariantList()\n    {\n        return $this->loadVariantInformation();\n    }\n\n    /**\n     * Template variable getter. Returns media files of current product.\n     *\n     * @return array\n     */\n    public function getMediaFiles()\n    {\n        if ($this->_aMediaFiles === null) {\n            $aMediaFiles = $this->getProduct()->getMediaUrls();\n            $this->_aMediaFiles = count($aMediaFiles) ? $aMediaFiles : false;\n        }\n\n        return $this->_aMediaFiles;\n    }\n\n    /**\n     * Template variable getter. Returns last seen products.\n     *\n     * @param int $iCnt product count\n     *\n     * @return array\n     */\n    public function getLastProducts($iCnt = 4)\n    {\n        if ($this->_aLastProducts === null) {\n            //last seen products for #768CA\n            $oProduct = $this->getProduct();\n            $sParentIdField = 'oxarticles__oxparentid';\n            $sArtId = $oProduct->$sParentIdField->value ? $oProduct->$sParentIdField->value : $oProduct->getId();\n\n            $oHistoryArtList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n            $oHistoryArtList->loadHistoryArticles($sArtId, $iCnt);\n            $this->_aLastProducts = $oHistoryArtList;\n        }\n\n        return $this->_aLastProducts;\n    }\n\n    /**\n     * Template variable getter. Returns product's vendor.\n     *\n     * @return object\n     */\n    public function getManufacturer()\n    {\n        if ($this->_oManufacturer === null) {\n            $this->_oManufacturer = $this->getProduct()->getManufacturer(false);\n        }\n\n        return $this->_oManufacturer;\n    }\n\n    /**\n     * Template variable getter. Returns product's vendor.\n     *\n     * @return object\n     */\n    public function getVendor()\n    {\n        if ($this->_oVendor === null) {\n            $this->_oVendor = $this->getProduct()->getVendor(false);\n        }\n\n        return $this->_oVendor;\n    }\n\n    /**\n     * Template variable getter. Returns product's root category.\n     *\n     * @return object\n     */\n    public function getCategory()\n    {\n        if ($this->_oCategory === null) {\n            $this->_oCategory = $this->getProduct()->getCategory();\n        }\n\n        return $this->_oCategory;\n    }\n\n    /**\n     * Template variable getter. Returns picture gallery of current article.\n     *\n     * @return array\n     */\n    public function getPictureGallery()\n    {\n        if ($this->_aPicGallery === null) {\n            //get picture gallery\n            $this->_aPicGallery = $this->getPicturesProduct()->getPictureGallery();\n        }\n\n        return $this->_aPicGallery;\n    }\n\n    public function hasMultipleImages(): bool\n    {\n        return $this->getPictureGallery()['hasMultipleImages'];\n    }\n\n    public function getMediaItems(): array\n    {\n        return $this->getPictureGallery()['mediaItems'];\n    }\n\n    /**\n     * Template variable getter. Returns reviews of current article.\n     *\n     * @return array\n     */\n    public function getReviews()\n    {\n        if ($this->_aReviews === null) {\n            $this->_aReviews = false;\n            if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('bl_perfLoadReviews')) {\n                $this->_aReviews = $this->getProduct()->getReviews();\n            }\n        }\n\n        return $this->_aReviews;\n    }\n\n    /**\n     * Template variable getter. Returns cross selling.\n     *\n     * @return object\n     */\n    public function getCrossSelling()\n    {\n        if ($this->_oCrossSelling === null) {\n            $this->_oCrossSelling = false;\n            if ($oProduct = $this->getProduct()) {\n                $this->_oCrossSelling = $oProduct->getCrossSelling();\n            }\n        }\n\n        return $this->_oCrossSelling;\n    }\n\n    /**\n     * Template variable getter. Returns similar article list.\n     *\n     * @return object\n     */\n    public function getSimilarProducts()\n    {\n        if ($this->_oSimilarProducts === null) {\n            $this->_oSimilarProducts = false;\n            if ($oProduct = $this->getProduct()) {\n                $this->_oSimilarProducts = $oProduct->getSimilarProducts();\n            }\n        }\n\n        return $this->_oSimilarProducts;\n    }\n\n    /**\n     * Return array of id to form recommend list.\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @return array\n     */\n    public function getSimilarRecommListIds()\n    {\n        if ($this->_aSimilarRecommListIds === null) {\n            $this->_aSimilarRecommListIds = false;\n\n            if ($oProduct = $this->getProduct()) {\n                $this->_aSimilarRecommListIds = [$oProduct->getId()];\n            }\n        }\n\n        return $this->_aSimilarRecommListIds;\n    }\n\n    /**\n     * Template variable getter. Returns accessories of article.\n     *\n     * @return object\n     */\n    public function getAccessoires()\n    {\n        if ($this->_oAccessoires === null) {\n            $this->_oAccessoires = false;\n            if ($oProduct = $this->getProduct()) {\n                $this->_oAccessoires = $oProduct->getAccessoires();\n            }\n        }\n\n        return $this->_oAccessoires;\n    }\n\n    /**\n     * Template variable getter. Returns list of customer also bought these products.\n     *\n     * @return object\n     */\n    public function getAlsoBoughtTheseProducts()\n    {\n        if ($this->_aAlsoBoughtArts === null) {\n            $this->_aAlsoBoughtArts = false;\n            if ($oProduct = $this->getProduct()) {\n                $this->_aAlsoBoughtArts = $oProduct->getCustomerAlsoBoughtThisProducts();\n            }\n        }\n\n        return $this->_aAlsoBoughtArts;\n    }\n\n    /**\n     * Template variable getter. Returns if price alarm is enabled.\n     *\n     * @return bool\n     */\n    public function isPriceAlarm()\n    {\n        return $this->getProduct()->isPriceAlarm();\n    }\n\n    /**\n     * returns object, associated with current view.\n     * (the object that is shown in frontend)\n     *\n     * @param int $iLang language id\n     *\n     * @return object\n     */\n    protected function getSubject($iLang)\n    {\n        return $this->getProduct();\n    }\n\n    /**\n     * Returns search title. It will be set in Locator.\n     *\n     * @return string\n     */\n    public function getSearchTitle()\n    {\n        return $this->_sSearchTitle;\n    }\n\n    /**\n     * Returns search title setter.\n     *\n     * @param string $sTitle search title\n     */\n    public function setSearchTitle($sTitle)\n    {\n        $this->_sSearchTitle = $sTitle;\n    }\n\n    /**\n     * active category path setter\n     *\n     * @param string $sActCatPath category tree path.\n     */\n    public function setCatTreePath($sActCatPath)\n    {\n        $this->_sCatTreePath = $sActCatPath;\n    }\n\n    /**\n     * Checks should persistent parameter input field be displayed.\n     *\n     * @return bool\n     */\n    public function isPersParam()\n    {\n        $oProduct = $this->getProduct();\n\n        return $oProduct->oxarticles__oxisconfigurable->value;\n    }\n\n    /**\n     * Template variable getter. Returns rating value.\n     *\n     * @return double\n     */\n    public function getRatingValue()\n    {\n        if ($this->_dRatingValue === null) {\n            $this->_dRatingValue = 0.0;\n            if ($this->isReviewActive() && ($oDetailsProduct = $this->getProduct())) {\n                $blShowVariantsReviews = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blShowVariantReviews');\n                $this->_dRatingValue = round($oDetailsProduct->getArticleRatingAverage($blShowVariantsReviews), 1);\n            }\n        }\n\n        return (float) $this->_dRatingValue;\n    }\n\n    /**\n     * Template variable getter. Returns if review module is on.\n     *\n     * @return bool\n     */\n    public function isReviewActive()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('bl_perfLoadReviews');\n    }\n\n    /**\n     * Template variable getter. Returns rating count.\n     *\n     * @return integer\n     */\n    public function getRatingCount()\n    {\n        if ($this->_iRatingCnt === null) {\n            $this->_iRatingCnt = false;\n            if ($this->isReviewActive() && ($oDetailsProduct = $this->getProduct())) {\n                $blShowVariantsReviews = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blShowVariantReviews');\n                $this->_iRatingCnt = $oDetailsProduct->getArticleRatingCount($blShowVariantsReviews);\n            }\n        }\n\n        return $this->_iRatingCnt;\n    }\n\n    /**\n     * Return price alarm status (if it was send).\n     *\n     * @return integer\n     */\n    public function getPriceAlarmStatus()\n    {\n        return $this->getViewParameter('iPriceAlarmStatus');\n    }\n\n    /**\n     * Template variable getter. Returns bid price.\n     *\n     * @return string\n     */\n    public function getBidPrice()\n    {\n        if ($this->_sBidPrice === null) {\n            $this->_sBidPrice = false;\n\n            $aParams = Registry::getRequest()->getRequestEscapedParameter('pa');\n            $oCur = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActShopCurrencyObject();\n            $iPrice = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->currency2Float($aParams['price']);\n            $this->_sBidPrice = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->formatCurrency($iPrice, $oCur);\n        }\n\n        return $this->_sBidPrice;\n    }\n\n    /**\n     * Returns variant selection.\n     *\n     * @return array\n     */\n    public function getVariantSelections()\n    {\n        // finding parent\n        $oProduct = $this->getProduct();\n        $sParentIdField = 'oxarticles__oxparentid';\n        if (($oParent = $this->getParentProduct($oProduct->$sParentIdField->value))) {\n            $sVarSelId = Registry::getRequest()->getRequestEscapedParameter(\"varselid\");\n\n            return $oParent->getVariantSelections($sVarSelId, $oProduct->getId());\n        }\n\n        return $oProduct->getVariantSelections(Registry::getRequest()->getRequestEscapedParameter(\"varselid\"));\n    }\n\n    /**\n     * Returns pictures product object.\n     *\n     * @return Article\n     */\n    public function getPicturesProduct()\n    {\n        $aVariantSelections = $this->getVariantSelections();\n        if ($aVariantSelections && $aVariantSelections['oActiveVariant'] && !$aVariantSelections['blPerfectFit']) {\n            return $aVariantSelections['oActiveVariant'];\n        }\n\n        return $this->getProduct();\n    }\n\n    /**\n     * Get product.\n     *\n     * @return Article\n     */\n    public function getProduct()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $myUtils = \\OxidEsales\\Eshop\\Core\\Registry::getUtils();\n\n        if ($this->_oProduct === null) {\n            if ($this->getViewParameter('_object')) {\n                $this->_oProduct = $this->getViewParameter('_object');\n            } else {\n                //this option is only for lists and we must reset value\n                //as blLoadVariants = false affect \"ab price\" functionality\n                $myConfig->setConfigParam('blLoadVariants', true);\n\n                $sOxid = Registry::getRequest()->getRequestEscapedParameter('anid');\n\n                // object is not yet loaded\n                $this->_oProduct = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n\n                if (!$this->_oProduct->load($sOxid)) {\n                    $myUtils->redirect($myConfig->getShopHomeUrl());\n                    $myUtils->showMessageAndExit('');\n                }\n\n                $sVarSelId = Registry::getRequest()->getRequestEscapedParameter(\"varselid\");\n                $aVarSelections = $this->_oProduct->getVariantSelections($sVarSelId);\n                if ($aVarSelections && $aVarSelections['oActiveVariant'] && $aVarSelections['blPerfectFit']) {\n                    $this->_oProduct = $aVarSelections['oActiveVariant'];\n                }\n            }\n        }\n        if (!$this->_blIsInitialized) {\n            $this->additionalChecksForArticle($myUtils, $myConfig);\n        }\n\n        return $this->_oProduct;\n    }\n\n    /**\n     * Set item sorting for widget based of retrieved parameters.\n     */\n    protected function setSortingParameters()\n    {\n        $sSortingParameters = $this->getViewParameter('sorting');\n        if ($sSortingParameters) {\n            list($sortBy, $sortOrder) = explode('|', $sSortingParameters);\n            if ((new SortingValidator())->isValid($sortBy, $sortOrder)) {\n                $this->setItemSorting($this->getSortIdent(), $sortBy, $sortOrder);\n            }\n        }\n    }\n\n    /**\n     * Executes parent::render().\n     * Returns name of template file to render.\n     *\n     * @return string $this->_sThisTemplate current template file name\n     */\n    public function render()\n    {\n        $oProduct = $this->getProduct();\n\n        parent::render();\n\n        $oCategory = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n\n        // if category parameter is not found, use category from product\n        $sCatId = $this->getViewParameter(\"cnid\");\n\n        if (!$sCatId && $oProduct->getCategory()) {\n            $oCategory = $oProduct->getCategory();\n        } else {\n            $oCategory->load($sCatId);\n        }\n        $this->setSortingParameters();\n\n        $this->setActiveCategory($oCategory);\n\n        $oLocator = oxNew(\\OxidEsales\\Eshop\\Application\\Component\\Locator::class, $this->getListType());\n        $oLocator->setLocatorData($oProduct, $this);\n\n        $this->_aViewData[\"config\"] = Registry::getConfig();\n\n        $config = Registry::getConfig();\n        $this->_aViewData['preview'] = Registry::getRequest()->getRequestEscapedParameter('preview');\n        $this->_aViewData['altImageUrl'] = ContainerFacade::getParameter('oxid_esales.alternative_image_url');\n        $this->_aViewData['SSLAltImageUrl'] = ContainerFacade::getParameter('oxid_esales.alternative_image_url');\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Should we show MD variant selection? - Not for 1 dimension variants.\n     *\n     * @return bool\n     */\n    public function isMdVariantView()\n    {\n        if ($this->_blMdView === null) {\n            $this->_blMdView = false;\n            if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blUseMultidimensionVariants')) {\n                $iMaxMdDepth = $this->getProduct()->getMdVariants()->getMaxDepth();\n                $this->_blMdView = ($iMaxMdDepth > 1);\n            }\n        }\n\n        return $this->_blMdView;\n    }\n\n    /**\n     * Runs additional checks for article.\n     *\n     * @param Utils  $myUtils  General utils.\n     * @param Config $myConfig Main shop configuration.\n     */\n    protected function additionalChecksForArticle($myUtils, $myConfig)\n    {\n        $blContinue = true;\n        if (!$this->_oProduct->isVisible()) {\n            $blContinue = false;\n        } elseif ($this->_oProduct->oxarticles__oxparentid->value) {\n            $oParent = $this->getParentProduct($this->_oProduct->oxarticles__oxparentid->value);\n            if (!$oParent || !$oParent->isVisible()) {\n                $blContinue = false;\n            }\n        }\n\n        if (!$blContinue) {\n            $myUtils->redirect($myConfig->getShopHomeUrl());\n            $myUtils->showMessageAndExit('');\n        }\n\n        $this->processProduct($this->_oProduct);\n        $this->_blIsInitialized = true;\n    }\n\n    /**\n     * Returns default category sorting for selected category.\n     *\n     * @return array\n     */\n    public function getDefaultSorting()\n    {\n        $aSorting = parent::getDefaultSorting();\n\n        $oCategory = $this->getActiveCategory();\n\n        if ($this->getListType() != 'search' && $oCategory && $oCategory instanceof \\OxidEsales\\Eshop\\Application\\Model\\Category) {\n            if ($sSortBy = $oCategory->getDefaultSorting()) {\n                $sSortDir = ($oCategory->getDefaultSortingMode()) ? \"desc\" : \"asc\";\n                $aSorting = ['sortby' => $sSortBy, 'sortdir' => $sSortDir];\n            }\n        }\n\n        return $aSorting;\n    }\n}\n"
  },
  {
    "path": "source/Application/Component/Widget/CategoryTree.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace  OxidEsales\\EshopCommunity\\Application\\Component\\Widget;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererBridgeInterface;\n\nuse function basename;\n\nclass CategoryTree extends \\OxidEsales\\Eshop\\Application\\Component\\Widget\\WidgetController\n{\n    /**\n     * Names of components (classes) that are initiated and executed\n     * before any other regular operation.\n     * Cartegory component used in template.\n     *\n     * @var array\n     */\n    protected $_aComponentNames = ['oxcmp_categories' => 1];\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'widget/sidebar/categorytree';\n\n    /**\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        $widgetType = $this->getViewParameter('sWidgetType');\n        if (!$widgetType) {\n            return $this->_sThisTemplate;\n        }\n        $template = sprintf(\n            'widget/%s/categorylist',\n            basename($widgetType)\n        );\n        $templateExists = ContainerFacade::get(TemplateRendererBridgeInterface::class)\n            ->getTemplateRenderer()\n            ->exists($template);\n        if ($templateExists) {\n            $this->_sThisTemplate = $template;\n        }\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Returns the deep level of category tree\n     *\n     * @return null\n     */\n    public function getDeepLevel()\n    {\n        return $this->getViewParameter(\"deepLevel\");\n    }\n\n    /**\n     * Content category getter.\n     *\n     * @return bool|string\n     */\n    public function getContentCategory()\n    {\n        return Registry::getRequest()->getRequestParameter('oxcid', false);\n    }\n}\n"
  },
  {
    "path": "source/Application/Component/Widget/CookieNote.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component\\Widget;\n\n/**\n * Cookie note widget\n */\nclass CookieNote extends \\OxidEsales\\Eshop\\Application\\Component\\Widget\\WidgetController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'widget/header/cookienote';\n\n    /**\n     * Executes parent::render(). Check if need to hide cookie note.\n     * Returns name of template file to render.\n     *\n     * @return  string  current template file name\n     */\n    public function render()\n    {\n        parent::render();\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Return if cookie notification is enabled by config.\n     *\n     * @return boolean\n     */\n    public function isEnabled()\n    {\n        return (bool) \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blShowCookiesNotification');\n    }\n}\n"
  },
  {
    "path": "source/Application/Component/Widget/CurrencyList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component\\Widget;\n\n/**\n * Currency selection list widget\n */\nclass CurrencyList extends \\OxidEsales\\Eshop\\Application\\Component\\Widget\\WidgetController\n{\n    /**\n     * Names of components (classes) that are initiated and executed\n     * before any other regular operation.\n     * User component used in template.\n     *\n     * @var array\n     */\n    protected $_aComponentNames = ['oxcmp_cur' => 1];\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'widget/header/currencies';\n}\n"
  },
  {
    "path": "source/Application/Component/Widget/Information.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component\\Widget;\n\n/**\n * List of additional shop information links widget.\n * Forms info link list.\n */\nclass Information extends \\OxidEsales\\Eshop\\Application\\Component\\Widget\\WidgetController\n{\n    /**\n     * Current class template name\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'widget/footer/info';\n\n    /**\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\ContentList\n     */\n    protected $_oContentList;\n\n    /**\n     * Returns service keys.\n     *\n     * @return array\n     */\n    public function getServicesKeys()\n    {\n        $oContentList = $this->getContentList();\n\n        return $oContentList->getServiceKeys();\n    }\n\n    /**\n     * Get services content list\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\ContentList\n     */\n    public function getServicesList()\n    {\n        $oContentList = $this->getContentList();\n        $oContentList->loadServices();\n\n        return $oContentList;\n    }\n\n    /**\n     * Returns content list object.\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\ContentList\n     */\n    protected function getContentList()\n    {\n        if (!$this->_oContentList) {\n            $this->_oContentList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ContentList::class);\n        }\n\n        return $this->_oContentList;\n    }\n}\n"
  },
  {
    "path": "source/Application/Component/Widget/LanguageList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component\\Widget;\n\n/**\n * Language selection list widget\n */\nclass LanguageList extends \\OxidEsales\\Eshop\\Application\\Component\\Widget\\WidgetController\n{\n    /**\n     * Names of components (classes) that are initiated and executed\n     * before any other regular operation.\n     * User component used in template.\n     *\n     * @var array\n     */\n    protected $_aComponentNames = ['oxcmp_lang' => 0];\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'widget/header/languages';\n}\n"
  },
  {
    "path": "source/Application/Component/Widget/ManufacturerList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component\\Widget;\n\n/**\n * Manufacturer list widget.\n * Forms Manufacturer list.\n */\nclass ManufacturerList extends \\OxidEsales\\Eshop\\Application\\Component\\Widget\\WidgetController\n{\n    /**\n     * Names of components (classes) that are initiated and executed\n     * before any other regular operation.\n     * Cartegory component used in template.\n     *\n     * @var array\n     */\n    protected $_aComponentNames = ['oxcmp_categories' => 1];\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'widget/footer/manufacturerlist';\n}\n"
  },
  {
    "path": "source/Application/Component/Widget/MiniBasket.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component\\Widget;\n\n/**\n * Mini basket widget\n */\nclass MiniBasket extends \\OxidEsales\\Eshop\\Application\\Component\\Widget\\WidgetController\n{\n    /**\n     * Names of components (classes) that are initiated and executed\n     * before any other regular operation.\n     * User component used in template.\n     *\n     * @var array\n     */\n    protected $_aComponentNames = ['oxcmp_cur' => 1, 'oxcmp_basket' => 1, 'oxcmp_user' => 1];\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'widget/header/minibasket';\n}\n"
  },
  {
    "path": "source/Application/Component/Widget/Rating.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component\\Widget;\n\n/**\n * Product Ratings widget.\n * Forms product ratings.\n */\nclass Rating extends \\OxidEsales\\Eshop\\Application\\Component\\Widget\\WidgetController\n{\n    /**\n     * Names of components (classes) that are initiated and executed\n     * before any other regular operation.\n     * User component used in template.\n     *\n     * @var array\n     */\n    protected $_aComponentNames = ['oxcmp_user' => 1];\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'widget/reviews/rating';\n\n    /**\n     * Rating value\n     *\n     * @var double\n     */\n    protected $_dRatingValue = null;\n\n    /**\n     * Rating count\n     *\n     * @var integer\n     */\n    protected $_iRatingCnt = null;\n\n    /**\n     * Executes parent::render().\n     * Returns name of template file to render.\n     *\n     * @return string current template file name\n     */\n    public function render()\n    {\n        parent::render();\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Template variable getter. Returns rating value\n     *\n     * @return double\n     */\n    public function getRatingValue()\n    {\n        if ($this->_dRatingValue === null) {\n            $this->_dRatingValue = 0.0;\n            $dValue = $this->getViewParameter(\"dRatingValue\");\n            if ($dValue) {\n                $this->_dRatingValue = round($dValue, 1);\n            }\n        }\n\n        return (float) $this->_dRatingValue;\n    }\n\n    /**\n     * Template variable getter. Returns rating count\n     *\n     * @return integer\n     */\n    public function getRatingCount()\n    {\n        return $dCount = $this->getViewParameter(\"dRatingCount\");\n    }\n\n    /**\n     * Template variable getter. Returns rating url\n     *\n     * @return string\n     */\n    public function getRateUrl()\n    {\n        return $this->getViewParameter(\"sRateUrl\");\n    }\n\n    /**\n     * Template variable getter. Returns rating count\n     *\n     * @return integer\n     */\n    public function canRate()\n    {\n        return $this->getViewParameter(\"blCanRate\");\n    }\n\n    /**\n     * Template variable getter. Returns article nid\n     *\n     * @return string\n     */\n    public function getArticleNId()\n    {\n        return $this->getViewParameter('anid');\n    }\n}\n"
  },
  {
    "path": "source/Application/Component/Widget/Recommendation.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component\\Widget;\n\n/**\n * Recomendation list.\n * Forms recomendation list.\n *\n * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n */\nclass Recommendation extends \\OxidEsales\\Eshop\\Application\\Component\\Widget\\WidgetController\n{\n    /**\n     * Names of components (classes) that are initiated and executed\n     * before any other regular operation.\n     * User component used in template.\n     *\n     * @var array\n     */\n    protected $_aComponentNames = ['oxcmp_cur' => 1];\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'widget/sidebar/recommendation';\n\n    /**\n     * Returns similar recommendation list.\n     *\n     * @return array\n     */\n    public function getSimilarRecommLists()\n    {\n        $aArticleIds = $this->getViewParameter(\"aArticleIds\");\n\n        $oRecommList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\RecommendationList::class);\n\n        return $oRecommList->getRecommListsByIds($aArticleIds);\n    }\n\n    /**\n     * Return recomm list object.\n     *\n     * @return object\n     */\n    public function getRecommList()\n    {\n        return oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\RecommListController::class);\n    }\n}\n"
  },
  {
    "path": "source/Application/Component/Widget/Review.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component\\Widget;\n\n/**\n * Product reviews widget\n */\nclass Review extends \\OxidEsales\\Eshop\\Application\\Component\\Widget\\WidgetController\n{\n    /**\n     * Names of components (classes) that are initiated and executed\n     * before any other regular operation.\n     * User component used in template.\n     *\n     * @var array\n     */\n    protected $_aComponentNames = ['oxcmp_user' => 1];\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'widget/reviews/reviews';\n\n    /**\n     * Executes parent::render().\n     * Returns name of template file to render.\n     *\n     * @return  string  current template file name\n     */\n    public function render()\n    {\n        parent::render();\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Template variable getter. Returns review type\n     *\n     * @return string\n     */\n    public function getReviewType()\n    {\n        return strtolower($this->getViewParameter('type'));\n    }\n\n    /**\n     * Template variable getter. Returns article id\n     *\n     * @return string\n     */\n    public function getArticleId()\n    {\n        return $this->getViewParameter('aid');\n    }\n\n    /**\n     * Template variable getter. Returns article nid\n     *\n     * @return string\n     */\n    public function getArticleNId()\n    {\n        return $this->getViewParameter('anid');\n    }\n\n    /**\n     * Template variable getter. Returns recommlist id\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @return string\n     */\n    public function getRecommListId()\n    {\n        return $this->getViewParameter('recommid');\n    }\n\n    /**\n     * Template variable getter. Returns whether user can rate\n     *\n     * @return string\n     */\n    public function canRate()\n    {\n        return $this->getViewParameter('canrate');\n    }\n\n    /**\n     * Template variable getter. Returns review user id\n     *\n     * @return string\n     */\n    public function getReviewUserHash()\n    {\n        return $this->getViewParameter('reviewuserhash');\n    }\n\n    /**\n     * Template variable getter. Returns active object's reviews from parent class\n     *\n     * @return array\n     */\n    public function getReviews()\n    {\n        $oReview = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getTopActiveView();\n\n        return $oReview->getReviews();\n    }\n}\n"
  },
  {
    "path": "source/Application/Component/Widget/ServiceList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component\\Widget;\n\n/**\n * List of shop services widget.\n * Forms service list.\n */\nclass ServiceList extends \\OxidEsales\\Eshop\\Application\\Component\\Widget\\WidgetController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'widget/footer/services';\n}\n"
  },
  {
    "path": "source/Application/Component/Widget/ServiceMenu.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component\\Widget;\n\n/**\n * Recomendation list.\n * Forms recomendation list.\n */\nclass ServiceMenu extends \\OxidEsales\\Eshop\\Application\\Component\\Widget\\WidgetController\n{\n    /**\n     * Names of components (classes) that are initiated and executed\n     * before any other regular operation.\n     * User component used in template.\n     *\n     * @var array\n     */\n    protected $_aComponentNames = ['oxcmp_user' => 1];\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'widget/header/servicemenu';\n\n    /**\n     * Template variable getter. Returns comparison article list.\n     *\n     * @param bool $blJson return json encoded array\n     *\n     * @return array\n     */\n    public function getCompareItems($blJson = false)\n    {\n        $oCompare = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\CompareController::class);\n        $aCompareItems = $oCompare->getCompareItems();\n\n        if ($blJson) {\n            $aCompareItems = json_encode($aCompareItems);\n        }\n\n        return $aCompareItems;\n    }\n}\n"
  },
  {
    "path": "source/Application/Component/Widget/VendorList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component\\Widget;\n\n/**\n * Vendor list widget.\n * Forms vendor list.\n */\nclass VendorList extends \\OxidEsales\\Eshop\\Application\\Component\\Widget\\WidgetController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'widget/footer/vendorlist';\n\n    /**\n     * Template variable getter. Returns vendorlist for search\n     *\n     * @return array\n     */\n    public function getVendorlist()\n    {\n        if ($this->_aVendorlist === null) {\n            $oVendorTree = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\VendorList::class);\n            $oVendorTree->buildVendorTree('vendorlist', null, \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopHomeUrl());\n            $this->_aVendorlist = $oVendorTree;\n        }\n\n        return $this->_aVendorlist;\n    }\n}\n"
  },
  {
    "path": "source/Application/Component/Widget/WidgetController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Component\\Widget;\n\n/**\n * Widget parent.\n * Gather functionality needed for all widgets but not for other views.\n */\nclass WidgetController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Names of components (classes) that are initiated and executed\n     * before any other regular operation.\n     * Widget should rewrite and use only those which  it needs.\n     *\n     * @var array\n     */\n    protected $_aComponentNames = [];\n\n    /**\n     * If active load components\n     * Widgets loads active view components\n     *\n     * @var array\n     */\n    protected $_blLoadComponents = false;\n\n    /**\n     * Sets self::$_aCollectedComponentNames to null, as views and widgets\n     * controllers loads different components and calls parent::init()\n     */\n    public function init()\n    {\n        self::$_aCollectedComponentNames = null;\n\n        if (!empty($this->_aComponentNames)) {\n            foreach ($this->_aComponentNames as $sComponentName => $sCompCache) {\n                $oActTopView = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getTopActiveView();\n                if ($oActTopView) {\n                    $this->_oaComponents[$sComponentName] = $oActTopView->getComponent($sComponentName);\n                    if (!isset($this->_oaComponents[$sComponentName])) {\n                        $this->_blLoadComponents = true;\n                        break;\n                    } else {\n                        $this->_oaComponents[$sComponentName]->setParent($this);\n                    }\n                }\n            }\n        }\n\n        parent::init();\n    }\n\n    /**\n     * In widgets we do not need to parse seo and do any work related to that\n     * Shop main control is responsible for that, and that has to be done once\n     */\n    protected function processRequest()\n    {\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/AccountController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Current user \"My account\" window.\n * When user is logged in arranges \"My account\" window, by creating\n * links to user details, order review, notice list, wish list. There\n * is a link for logging out. Template includes Topoffer , bargain\n * boxes. OXID eShop -> MY ACCOUNT.\n */\nclass AccountController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Number of user's orders.\n     *\n     * @var integer\n     */\n    protected $_iOrderCnt = null;\n\n    /**\n     * Current article id.\n     *\n     * @var string\n     */\n    protected $_sArticleId = null;\n\n    /**\n     * Search parameter for Html\n     *\n     * @var string\n     */\n    protected $_sSearchParamForHtml = null;\n\n    /**\n     * Search parameter\n     *\n     * @var string\n     */\n    protected $_sSearchParam = null;\n\n    /**\n     * List type\n     *\n     * @var string\n     */\n    protected $_sListType = null;\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/account/dashboard';\n\n    /**\n     * Current class login template name.\n     *\n     * @var string\n     */\n    protected $_sThisLoginTemplate = 'page/account/login';\n\n    /**\n     * Alternative login template name.\n     *\n     * @var string\n     */\n    protected $_sThisAltLoginTemplate = 'page/privatesales/login';\n\n    /**\n     * Current view search engine indexing state\n     *\n     * @var int\n     */\n    protected $_iViewIndexState = VIEW_INDEXSTATE_NOINDEXNOFOLLOW;\n\n    /**\n     * Start page meta description CMS ident\n     *\n     * @var string\n     */\n    protected $_sMetaDescriptionIdent = 'oxstartmetadescription';\n\n    /**\n     * Start page meta keywords CMS ident\n     *\n     * @var string\n     */\n    protected $_sMetaKeywordsIdent = 'oxstartmetakeywords';\n\n    /**\n     * Sign if to load and show bargain action\n     *\n     * @var bool\n     */\n    protected $_blBargainAction = true;\n\n    /**\n     * Status of the account deletion\n     *\n     * @var bool\n     */\n    private $accountDeletionStatus;\n\n    /**\n     * Loads action articles. If user is logged and returns name of\n     * template to render account::_sThisTemplate\n     *\n     * @return  string  $_sThisTemplate current template file name\n     */\n    public function render()\n    {\n        parent::render();\n\n        // performing redirect if needed\n        $this->redirectAfterLogin();\n\n        // is logged in ?\n        $user = $this->getUser();\n        $passwordField = 'oxuser__oxpassword';\n        if (\n            !$user || ($user && !$user->$passwordField->value) ||\n            ($this->isEnabledPrivateSales() && $user && (!$user->isTermsAccepted() || $this->confirmTerms()))\n        ) {\n            $this->_sThisTemplate = $this->getLoginTemplate();\n        }\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Returns login template name:\n     *  - if \"login\" feature is on returns $this->_sThisAltLoginTemplate\n     *  - else returns $this->_sThisLoginTemplate\n     *\n     * @return string\n     */\n    protected function getLoginTemplate()\n    {\n        return $this->isEnabledPrivateSales() ? $this->_sThisAltLoginTemplate : $this->_sThisLoginTemplate;\n    }\n\n    /**\n     * Confirms term agreement. Returns value of confirmed term\n     *\n     * @return string|bool\n     */\n    public function confirmTerms()\n    {\n        $termsConfirmation = Registry::getRequest()->getRequestEscapedParameter(\"term\");\n        if (!$termsConfirmation && $this->isEnabledPrivateSales()) {\n            $user = $this->getUser();\n            if ($user && !$user->isTermsAccepted()) {\n                $termsConfirmation = true;\n            }\n        }\n\n        return $termsConfirmation;\n    }\n\n    /**\n     * Returns array from parent::getNavigationParams(). If current request\n     * contains \"sourcecl\" and \"anid\" parameters - appends array with this\n     * data. Array is used to fill forms and append shop urls with actual\n     * state parameters\n     *\n     * @return array\n     */\n    public function getNavigationParams()\n    {\n        $parameters = parent::getNavigationParams();\n\n        if ($sourceClass = Registry::getRequest()->getRequestEscapedParameter(\"sourcecl\")) {\n            $parameters['sourcecl'] = $sourceClass;\n        }\n\n        if ($articleId = Registry::getRequest()->getRequestEscapedParameter(\"anid\")) {\n            $parameters['anid'] = $articleId;\n        }\n\n        return $parameters;\n    }\n\n    /**\n     * For some user actions (like writing product\n     * review) user must be logged in. So e.g. in product details page\n     * there is a link leading to current view. Link contains parameter\n     * \"sourcecl\", which tells where to redirect after successfull login.\n     * If this parameter is defined and oxcmp_user::getLoginStatus() ==\n     * USER_LOGIN_SUCCESS (means user has just logged in) then user is\n     * redirected back to source view.\n     *\n     * @return null\n     */\n    public function redirectAfterLogin()\n    {\n        // in case source class is provided - redirecting back to it with all default parameters\n        if (\n            ($sourceClass = Registry::getRequest()->getRequestEscapedParameter(\"sourcecl\")) &&\n            $this->_oaComponents['oxcmp_user']->getLoginStatus() === USER_LOGIN_SUCCESS\n        ) {\n            $redirectUrl = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopUrl() . 'index.php?cl=' . rawurlencode($sourceClass);\n\n            // building redirect link\n            foreach ($this->getNavigationParams() as $key => $value) {\n                if ($value && $key != \"sourcecl\") {\n                    $redirectUrl .= '&' . rawurlencode($key) . \"=\" . rawurlencode($value);\n                }\n            }\n\n            /** @var \\OxidEsales\\Eshop\\Core\\UtilsUrl $utilsUrl */\n            $utilsUrl = Registry::getUtilsUrl();\n            return Registry::getUtils()->redirect($utilsUrl->processUrl($redirectUrl), true, 302);\n        }\n    }\n\n    /**\n     * changes default template for compare in popup\n     *\n     * @return null\n     */\n    public function getOrderCnt()\n    {\n        if ($this->_iOrderCnt === null) {\n            $this->_iOrderCnt = 0;\n            if ($user = $this->getUser()) {\n                $this->_iOrderCnt = $user->getOrderCount();\n            }\n        }\n\n        return $this->_iOrderCnt;\n    }\n\n    /**\n     * Return the active article id\n     *\n     * @return string|bool\n     */\n    public function getArticleId()\n    {\n        if ($this->_sArticleId === null) {\n            // passing wishlist information\n            if ($articleId = Registry::getRequest()->getRequestEscapedParameter('aid')) {\n                $this->_sArticleId = $articleId;\n            }\n        }\n\n        return $this->_sArticleId;\n    }\n\n    /**\n     * Template variable getter. Returns search parameter for Html\n     *\n     * @return string\n     */\n    public function getSearchParamForHtml()\n    {\n        if ($this->_sSearchParamForHtml === null) {\n            $this->_sSearchParamForHtml = false;\n            if ($this->getArticleId()) {\n                $this->_sSearchParamForHtml = Registry::getRequest()->getRequestEscapedParameter('searchparam');\n            }\n        }\n\n        return $this->_sSearchParamForHtml;\n    }\n\n    /**\n     * Template variable getter. Returns search parameter\n     *\n     * @return string\n     */\n    public function getSearchParam()\n    {\n        if ($this->_sSearchParam === null) {\n            $this->_sSearchParam = false;\n            if ($this->getArticleId()) {\n                $this->_sSearchParam = rawurlencode(Registry::getRequest()->getRequestParameter('searchparam'));\n            }\n        }\n\n        return $this->_sSearchParam;\n    }\n\n    /**\n     * Template variable getter. Returns list type\n     *\n     * @return string\n     */\n    public function getListType()\n    {\n        if ($this->_sListType === null) {\n            $this->_sListType = false;\n            if ($this->getArticleId()) {\n                // searching in vendor #671\n                $this->_sListType = Registry::getRequest()->getRequestEscapedParameter('listtype');\n            }\n        }\n\n        return $this->_sListType;\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $paths = [];\n        $pathData = [];\n        $language = Registry::getLang();\n        $baseLanguageId = $language->getBaseLanguage();\n        if ($user = $this->getUser()) {\n            $username = $user->oxuser__oxusername->value;\n            $pathData['title'] = $language->translateString('MY_ACCOUNT', $baseLanguageId, false) . \" - \" . $username;\n        } else {\n            $pathData['title'] = $language->translateString('LOGIN', $baseLanguageId, false);\n        }\n        $pathData['link'] = $this->getLink();\n        $paths[] = $pathData;\n\n        return $paths;\n    }\n\n    /**\n     * Template variable getter. Returns article list count in comparison\n     *\n     * @return integer\n     */\n    public function getCompareItemsCnt()\n    {\n        $compare = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\CompareController::class);\n\n        return $compare->getCompareItemsCnt();\n    }\n\n    /**\n     * Page Title\n     *\n     * @return string\n     */\n    public function getTitle()\n    {\n        $title = parent::getTitle();\n\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActiveView()->getClassKey() == 'account') {\n            $baseLanguageId = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage();\n            $title = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('PAGE_TITLE_ACCOUNT', $baseLanguageId, false);\n            if ($user = $this->getUser()) {\n                $username = $user->oxuser__oxusername->value;\n                $title .= ' - \"' . $username . '\"';\n            }\n        }\n\n        return $title;\n    }\n\n    /**\n     * Deletes User account.\n     */\n    public function deleteAccount()\n    {\n        $this->accountDeletionStatus = false;\n        $user = $this->getUser();\n\n        /**\n         * Setting derived to false allows mall users to delete their account being in a different shop as the shop\n         * the account was originally created in.\n         */\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blMallUsers')) {\n            $user->setIsDerived(false);\n        }\n\n        if ($this->canUserAccountBeDeleted() && $user->delete()) {\n            $this->accountDeletionStatus = true;\n            $user->logout();\n            $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n            $session->destroy();\n        }\n    }\n\n    /**\n     * Returns true if User is allowed to delete own account.\n     *\n     * @return bool\n     */\n    public function isUserAllowedToDeleteOwnAccount()\n    {\n        $allowUsersToDeleteTheirAccount = Registry::getConfig()->getConfigParam('blAllowUsersToDeleteTheirAccount');\n\n        $user = $this->getUser();\n\n        return $allowUsersToDeleteTheirAccount && $user && !$user->isMallAdmin();\n    }\n\n    /**\n     * Template variable getter. Returns true, if a user account has been sucessfully deleted, else false.\n     *\n     * @return bool\n     */\n    public function getAccountDeletionStatus()\n    {\n        return $this->accountDeletionStatus;\n    }\n\n    /**\n     * Checks if possible to delete user.\n     *\n     * @return bool\n     */\n    private function canUserAccountBeDeleted()\n    {\n        $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n        return $session->checkSessionChallenge() && $this->isUserAllowedToDeleteOwnAccount();\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/AccountDownloadsController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse oxArticleList;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Account article file download page.\n */\nclass AccountDownloadsController extends \\OxidEsales\\Eshop\\Application\\Controller\\AccountController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/account/downloads';\n\n    /**\n     * Current view search engine indexing state\n     *\n     * @var int\n     */\n    protected $_iViewIndexState = VIEW_INDEXSTATE_NOINDEXNOFOLLOW;\n\n    /**\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\OrderFileList\n     */\n    protected $_oOrderFilesList = null;\n\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n\n        $iBaseLanguage = Registry::getLang()->getBaseLanguage();\n        /** @var \\OxidEsales\\Eshop\\Core\\SeoEncoder $oSeoEncoder */\n        $oSeoEncoder = Registry::getSeoEncoder();\n        $aPath['title'] = Registry::getLang()->translateString('MY_ACCOUNT', $iBaseLanguage, false);\n        $aPath['link'] = $oSeoEncoder->getStaticUrl($this->getViewConfig()->getSelfLink() . \"cl=account\");\n        $aPaths[] = $aPath;\n\n        $aPath['title'] = Registry::getLang()->translateString('MY_DOWNLOADS', $iBaseLanguage, false);\n        $aPath['link'] = $this->getLink();\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n\n    /**\n     * Returns article list which was ordered and has downloadable files\n     *\n     * @return null|oxArticleList\n     */\n    public function getOrderFilesList()\n    {\n        if ($this->_oOrderFilesList !== null) {\n            return $this->_oOrderFilesList;\n        }\n\n        $oOrderFileList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\OrderFileList::class);\n        $oOrderFileList->loadUserFiles($this->getUser()->getId());\n\n        $this->_oOrderFilesList = $this->prepareForTemplate($oOrderFileList);\n\n        return $this->_oOrderFilesList;\n    }\n\n    /**\n     * Returns prepared orders files list\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\OrderFileList $oOrderFileList - list or orderfiles\n     *\n     * @return array\n     */\n    protected function prepareForTemplate($oOrderFileList)\n    {\n        $oOrderArticles = [];\n\n        foreach ($oOrderFileList as $oOrderFile) {\n            $sOrderArticleIdField = 'oxorderfiles__oxorderarticleid';\n            $sOrderNumberField = 'oxorderfiles__oxordernr';\n            $sOrderDateField = 'oxorderfiles__oxorderdate';\n            $sOrderTitleField = 'oxorderfiles__oxarticletitle';\n            $sOrderArticleId = $oOrderFile->$sOrderArticleIdField->value;\n            $oOrderArticles[$sOrderArticleId]['oxordernr'] = $oOrderFile->$sOrderNumberField->value;\n            $oOrderArticles[$sOrderArticleId]['oxorderdate'] = substr($oOrderFile->$sOrderDateField->value, 0, 16);\n            $oOrderArticles[$sOrderArticleId]['oxarticletitle'] = $oOrderFile->$sOrderTitleField->value;\n            $oOrderArticles[$sOrderArticleId]['oxorderfiles'][] = $oOrderFile;\n        }\n\n        return $oOrderArticles;\n    }\n\n    /**\n     * Returns error code.\n     *\n     * @return int\n     */\n    public function getDownloadError()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('download_error');\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/AccountNewsletterController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Current user newsletter manager.\n * When user is logged in in this manager window he can modify\n * his newletter subscription status - simply register or\n * unregister from newsletter. OXID eShop -> MY ACCOUNT -> Newsletter.\n */\nclass AccountNewsletterController extends \\OxidEsales\\Eshop\\Application\\Controller\\AccountController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/account/newsletter';\n\n    /**\n     * Whether the newsletter option had been changed give some affirmation.\n     *\n     * @var integer\n     */\n    protected $_iSubscriptionStatus = 0;\n\n    /**\n     * If user is not logged in - returns name of template AccountNewsletterController::_sThisLoginTemplate, or if user\n     * is already logged in - returns name of template AccountNewsletterController::_sThisTemplate\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        // is logged in ?\n        $oUser = $this->getUser();\n        if (!$oUser) {\n            return $this->_sThisTemplate = $this->_sThisLoginTemplate;\n        }\n\n        return $this->_sThisTemplate;\n    }\n\n\n    /**\n     * Template variable getter. Returns 0 when newsletter had been changed.\n     *\n     * @return int\n     */\n    public function isNewsletter()\n    {\n        $oUser = $this->getUser();\n        if (!$oUser) {\n            return false;\n        }\n\n        return $oUser->getNewsSubscription()->getOptInStatus();\n    }\n\n    /**\n     * Removes or adds user to newsletter group according to\n     * current subscription status. Returns true on success.\n     *\n     * @return bool\n     */\n    public function subscribe()\n    {\n        if (!Registry::getSession()->checkSessionChallenge()) {\n            return false;\n        }\n\n        // is logged in ?\n        $oUser = $this->getUser();\n        if (!$oUser) {\n            return false;\n        }\n\n        $iStatus = Registry::getRequest()->getRequestEscapedParameter('status');\n        if ($oUser->setNewsSubscription($iStatus, Registry::getConfig()->getConfigParam('blOrderOptInEmail'))) {\n            $this->_iSubscriptionStatus = ($iStatus == 0 && $iStatus !== null) ? -1 : 1;\n        }\n    }\n\n    /**\n     * Template variable getter. Returns 1 when newsletter had been changed to \"yes\"\n     * else return -1 if had been changed to \"no\".\n     *\n     * @return integer\n     */\n    public function getSubscriptionStatus()\n    {\n        return $this->_iSubscriptionStatus;\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n        $oUtils = Registry::getUtilsUrl();\n        $iBaseLanguage = Registry::getLang()->getBaseLanguage();\n        $sSelfLink = $this->getViewConfig()->getSelfLink();\n\n        $aPath['title'] = Registry::getLang()->translateString('MY_ACCOUNT', $iBaseLanguage, false);\n        $aPath['link'] = Registry::getSeoEncoder()->getStaticUrl($sSelfLink . 'cl=account');\n        $aPaths[] = $aPath;\n\n        $aPath['title'] = Registry::getLang()->translateString('NEWSLETTER_SETTINGS', $iBaseLanguage, false);\n        $aPath['link'] = $oUtils->cleanUrl($this->getLink(), ['fnc']);\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/AccountNoticeListController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse oxRegistry;\n\n/**\n * Current user notice list manager.\n * When user is logged in in this manager window he can modify\n * his notice list status - remove articles from notice list or\n * store them to shopping basket, view detail information.\n * OXID eShop -> MY ACCOUNT -> Newsletter.\n */\nclass AccountNoticeListController extends \\OxidEsales\\Eshop\\Application\\Controller\\AccountController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/account/noticelist';\n\n    /**\n     * Check if there is an product in the noticelist.\n     *\n     * @var array\n     */\n    protected $_aNoticeProductList = null;\n\n    /**\n     * return the similar prodcuts from the notice list.\n     *\n     * @var array\n     */\n    protected $_aSimilarProductList = null;\n\n    /**\n     * return the recommlist\n     *\n     * @var array\n     */\n    protected $_aRecommList = null;\n\n    /**\n     * Current view search engine indexing state\n     *\n     * @var int\n     */\n    protected $_iViewIndexState = VIEW_INDEXSTATE_NOINDEXNOFOLLOW;\n\n    /**\n     * Array of id to form recommendation list.\n     *\n     * @var array\n     */\n    protected $_aSimilarRecommListIds = null;\n\n    /**\n     * If user is not logged in - returns name of template\n     * \\OxidEsales\\Eshop\\Application\\Controller\\AccountNoticeListController::_sThisLoginTemplate, or if user is already\n     * logged in - loads notice list articles (articles may be accessed\n     * by \\OxidEsales\\Eshop\\Application\\Model\\User::getBasket()), loads similar articles (if available) for\n     * the last article in list \\OxidEsales\\Eshop\\Application\\Model\\Article::GetSimilarProducts() and returns name of\n     * template to render AccountNoticeListController::_sThisTemplate\n     *\n     * @return string current template file name\n     */\n    public function render()\n    {\n        parent::render();\n\n        // is logged in ?\n        $oUser = $this->getUser();\n        if (!$oUser) {\n            return $this->_sThisTemplate = $this->_sThisLoginTemplate;\n        }\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Template variable getter. Returns an array if there is something in the list\n     *\n     * @return array\n     */\n    public function getNoticeProductList()\n    {\n        if ($this->_aNoticeProductList === null) {\n            if ($oUser = $this->getUser()) {\n                $this->_aNoticeProductList = $oUser->getBasket('noticelist')->getArticles();\n            }\n        }\n\n        return $this->_aNoticeProductList;\n    }\n\n    /**\n     * Template variable getter. Returns the products which are in the noticelist\n     *\n     * @return array\n     */\n    public function getSimilarProducts()\n    {\n        // similar products list\n        if ($this->_aSimilarProductList === null && count($this->getNoticeProductList())) {\n            // just ensuring that next call will skip this check\n            $this->_aSimilarProductList = false;\n\n            // loading similar products\n            if ($oSimilarProd = current($this->getNoticeProductList())) {\n                $this->_aSimilarProductList = $oSimilarProd->getSimilarProducts();\n            }\n        }\n\n        return $this->_aSimilarProductList;\n    }\n\n    /**\n     * Return array of id to form recommend list.\n     *\n     * @return array\n     */\n    public function getSimilarRecommListIds()\n    {\n        if ($this->_aSimilarRecommListIds === null) {\n            $this->_aSimilarRecommListIds = false;\n\n            $aNoticeProdList = $this->getNoticeProductList();\n            if (is_array($aNoticeProdList) && count($aNoticeProdList)) {\n                $this->_aSimilarRecommListIds = array_keys($aNoticeProdList);\n            }\n        }\n\n        return $this->_aSimilarRecommListIds;\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n\n        $oLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n        $sSelfLink = $this->getViewConfig()->getSelfLink();\n\n        $iBaseLanguage = $oLang->getBaseLanguage();\n        $aPath['title'] = $oLang->translateString('MY_ACCOUNT', $iBaseLanguage, false);\n        $aPath['link'] = \\OxidEsales\\Eshop\\Core\\Registry::getSeoEncoder()->getStaticUrl($sSelfLink . \"cl=account\");\n        $aPaths[] = $aPath;\n\n        $aPath['title'] = $oLang->translateString('MY_WISH_LIST', $iBaseLanguage, false);\n        $aPath['link'] = $this->getLink();\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/AccountOrderController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse oxRegistry;\n\n/**\n * Current user order history review.\n * When user is logged in order review fulfils history about user\n * submitted orders. There is some details information, such as\n * ordering date, number, recipient, order status, some base\n * ordered articles information, button to add article to basket.\n * OXID eShop -> MY ACCOUNT -> Newsletter.\n */\nclass AccountOrderController extends \\OxidEsales\\Eshop\\Application\\Controller\\AccountController\n{\n    /**\n     * Count of all articles in list.\n     *\n     * @var integer\n     */\n    protected $_iAllArtCnt = 0;\n\n    /**\n     * Number of possible pages.\n     *\n     * @var integer\n     */\n    protected $_iCntPages = null;\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/account/order';\n\n    /**\n     * collecting orders\n     *\n     * @var array\n     */\n    protected $_aOrderList = null;\n\n    /**\n     * collecting article which ordered\n     *\n     * @var array\n     */\n    protected $_aArticlesList = null;\n\n    /**\n     * If user is not logged in - returns name of template AccountOrderController::_sThisLoginTemplate, or if user is\n     * already logged in - returns name of template AccountOrderController::_sThisTemplate\n     *\n     * @return string $_sThisTemplate current template file name\n     */\n    public function render()\n    {\n        parent::render();\n\n        // is logged in ?\n        $oUser = $this->getUser();\n        if (!$oUser) {\n            return $this->_sThisTemplate = $this->_sThisLoginTemplate;\n        }\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Template variable getter. Returns orders\n     *\n     * @return array\n     */\n    public function getOrderList()\n    {\n        if ($this->_aOrderList === null) {\n            $this->_aOrderList = [];\n\n            // Load user Orderlist\n            if ($oUser = $this->getUser()) {\n                $iNrofCatArticles = (int) \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iNrofCatArticles');\n                $iNrofCatArticles = $iNrofCatArticles ? $iNrofCatArticles : 1;\n                $this->_iAllArtCnt = $oUser->getOrderCount();\n                if ($this->_iAllArtCnt && $this->_iAllArtCnt > 0) {\n                    $this->_aOrderList = $oUser->getOrders($iNrofCatArticles, $this->getActPage());\n                    $this->_iCntPages = ceil($this->_iAllArtCnt / $iNrofCatArticles);\n                }\n            }\n        }\n\n        return $this->_aOrderList;\n    }\n\n    /**\n     * Template variable getter. Returns ordered articles\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\ArticleList|false\n     */\n    public function getOrderArticleList()\n    {\n        if ($this->_aArticlesList === null) {\n            // marking as set\n            $this->_aArticlesList = false;\n            $oOrdersList = $this->getOrderList();\n            if ($oOrdersList && $oOrdersList->count()) {\n                $this->_aArticlesList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n                $this->_aArticlesList->loadOrderArticles($oOrdersList);\n            }\n        }\n\n        return $this->_aArticlesList;\n    }\n\n    /**\n     * Template variable getter. Returns page navigation\n     *\n     * @return object\n     */\n    public function getPageNavigation()\n    {\n        if ($this->_oPageNavigation === null) {\n            $this->_oPageNavigation = $this->generatePageNavigation();\n        }\n\n        return $this->_oPageNavigation;\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n        $iBaseLanguage = Registry::getLang()->getBaseLanguage();\n        $sSelfLink = $this->getViewConfig()->getSelfLink();\n\n        $aPath['title'] = Registry::getLang()->translateString('MY_ACCOUNT', $iBaseLanguage, false);\n        $aPath['link'] = Registry::getSeoEncoder()->getStaticUrl($sSelfLink . 'cl=account');\n        $aPaths[] = $aPath;\n\n        $aPath['title'] = Registry::getLang()->translateString('ORDER_HISTORY', $iBaseLanguage, false);\n        $aPath['link'] = $this->getLink();\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/AccountPasswordController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Current user password change form.\n * When user is logged in he may change his Billing and Shipping\n * information (this is important for ordering purposes).\n * Information as email, password, greeting, name, company, address,\n * etc. Some fields must be entered. OXID eShop -> MY ACCOUNT\n * -> Update your billing and delivery settings.\n */\nclass AccountPasswordController extends \\OxidEsales\\Eshop\\Application\\Controller\\AccountController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/account/password';\n\n    /**\n     * Whether the password had been changed.\n     *\n     * @var bool\n     */\n    protected $_blPasswordChanged = false;\n\n    /**\n     * If user is not logged in - returns name of template AccountUserController::_sThisLoginTemplate, or if user is\n     * already logged in additionally loads user delivery address info and forms country list. Returns name of template\n     * AccountUserController::_sThisTemplate\n     *\n     * @return string $_sThisTemplate current template file name\n     */\n    public function render()\n    {\n        parent::render();\n\n        // is logged in ?\n        $oUser = $this->getUser();\n        if (!$oUser) {\n            return $this->_sThisTemplate = $this->_sThisLoginTemplate;\n        }\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * changes current user password\n     *\n     * @return null\n     */\n    public function changePassword()\n    {\n        if (!\\OxidEsales\\Eshop\\Core\\Registry::getSession()->checkSessionChallenge()) {\n            return;\n        }\n\n        $oUser = $this->getUser();\n        if (!$oUser) {\n            return;\n        }\n\n        $sOldPass = Registry::getRequest()->getRequestParameter('password_old');\n        $sNewPass = Registry::getRequest()->getRequestParameter('password_new');\n        $sConfPass = Registry::getRequest()->getRequestParameter('password_new_confirm');\n\n        /** @var \\OxidEsales\\Eshop\\Core\\InputValidator $oInputValidator */\n        $oInputValidator = \\OxidEsales\\Eshop\\Core\\Registry::getInputValidator();\n        if (($oExcp = $oInputValidator->checkPassword($oUser, $sNewPass, $sConfPass, true))) {\n            switch ($oExcp->getMessage()) {\n                case \\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('ERROR_MESSAGE_INPUT_EMPTYPASS'):\n                case \\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('ERROR_MESSAGE_PASSWORD_TOO_SHORT'):\n                    return \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay(\n                        'ERROR_MESSAGE_PASSWORD_TOO_SHORT',\n                        false,\n                        true\n                    );\n                default:\n                    return \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay(\n                        'ERROR_MESSAGE_PASSWORD_DO_NOT_MATCH',\n                        false,\n                        true\n                    );\n            }\n        }\n\n        if (!$sOldPass || !$oUser->isSamePassword($sOldPass)) {\n            /** @var \\OxidEsales\\Eshop\\Core\\UtilsView $oUtilsView */\n            $oUtilsView = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView();\n\n            return $oUtilsView->addErrorToDisplay('ERROR_MESSAGE_CURRENT_PASSWORD_INVALID', false, true);\n        }\n\n        // testing passed - changing password\n        $oUser->setPassword($sNewPass);\n        if ($oUser->save()) {\n            $this->_blPasswordChanged = true;\n            // deleting user autologin cookies.\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtilsServer()->deleteUserCookie(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId());\n        }\n    }\n\n    /**\n     * Template variable getter. Returns true when password had been changed.\n     *\n     * @return bool\n     */\n    public function isPasswordChanged()\n    {\n        return $this->_blPasswordChanged;\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n\n        /** @var \\OxidEsales\\Eshop\\Core\\SeoEncoder $oSeoEncoder */\n        $oSeoEncoder = \\OxidEsales\\Eshop\\Core\\Registry::getSeoEncoder();\n        $oLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n        $iBaseLanguage = $oLang->getBaseLanguage();\n        $aPath['title'] = $oLang->translateString('MY_ACCOUNT', $iBaseLanguage, false);\n        $aPath['link'] = $oSeoEncoder->getStaticUrl($this->getViewConfig()->getSelfLink() . 'cl=account');\n        $aPaths[] = $aPath;\n\n        $aPath['title'] = $oLang->translateString('CHANGE_PASSWORD', $iBaseLanguage, false);\n        $aPath['link'] = $this->getLink();\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/AccountRecommlistController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Current user recommlist manager.\n * When user is logged in in this manager window he can modify his\n * own recommlists status - remove articles from list or store\n * them to shopping basket, view detail information.\n *\n * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n */\nclass AccountRecommlistController extends \\OxidEsales\\Eshop\\Application\\Controller\\AccountController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/account/recommendationlist';\n\n    /**\n     * Is recomendation list entry was saved this marker gets value TRUE. Default is FALSE\n     *\n     * @var bool\n     */\n    protected $_blSavedEntry = false;\n\n    /**\n     * returns the recomm list articles\n     *\n     * @var object\n     */\n    protected $_oActRecommListArticles = null;\n\n    /**\n     * returns the recomm list article. Whether the variable is empty, it list nothing\n     *\n     * @var array\n     */\n    protected $_aUserRecommLists = null;\n\n    /**\n     * returns the recomm list articles\n     *\n     * @var object\n     */\n    protected $_oActRecommList = null;\n\n    /**\n     * List items count\n     *\n     * @var int\n     */\n    protected $_iAllArtCnt = 0;\n\n    /**\n     * Page navigation\n     *\n     * @var object\n     */\n    protected $_oPageNavigation = null;\n\n    /**\n     * If user is logged in loads his wishlist articles (articles may be accessed by\n     * \\OxidEsales\\Eshop\\Application\\Model\\User::GetBasket()), loads similar articles (is available) for the last\n     * article in list loaded by \\OxidEsales\\Eshop\\Application\\Model\\Article::GetSimilarProducts() and returns name of\n     * template to render \\OxidEsales\\Eshop\\Application\\Controller\\AccountWishlistController::_sThisTemplate\n     *\n     * @return  string  $_sThisTemplate current template file name\n     */\n    public function render()\n    {\n        parent::render();\n\n        // is logged in ?\n        if (!($oUser = $this->getUser())) {\n            return $this->_sThisTemplate = $this->_sThisLoginTemplate;\n        }\n\n        $oLists = $this->getRecommLists();\n        $oActList = $this->getActiveRecommList();\n\n        // list of found oxrecommlists\n        if (!$oActList && $oLists->count()) {\n            $this->_iAllArtCnt = $oUser->getRecommListsCount();\n            $iNrofCatArticles = (int) \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iNrofCatArticles');\n            $iNrofCatArticles = $iNrofCatArticles ? $iNrofCatArticles : 10;\n            $this->_iCntPages = ceil($this->_iAllArtCnt / $iNrofCatArticles);\n        }\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Returns array of params => values which are used in hidden forms and as additional url params\n     *\n     * @return array\n     */\n    public function getNavigationParams()\n    {\n        $aParams = parent::getNavigationParams();\n\n        // adding recommendation list id to list product urls\n        if (($oList = $this->getActiveRecommList())) {\n            $aParams['recommid'] = $oList->getId();\n        }\n\n        return $aParams;\n    }\n\n    /**\n     * return recomm list from the user\n     *\n     * @return array\n     */\n    public function getRecommLists()\n    {\n        if ($this->_aUserRecommLists === null) {\n            $this->_aUserRecommLists = false;\n            if (($oUser = $this->getUser())) {\n                // recommendation list\n                $this->_aUserRecommLists = $oUser->getUserRecommLists();\n            }\n        }\n\n        return $this->_aUserRecommLists;\n    }\n\n    /**\n     * return all articles in the recomm list\n     *\n     * @return null\n     */\n    public function getArticleList()\n    {\n        if ($this->_oActRecommListArticles === null) {\n            $this->_oActRecommListArticles = false;\n\n            if (($oRecommList = $this->getActiveRecommList())) {\n                $oItemList = $oRecommList->getArticles();\n\n                if ($oItemList->count()) {\n                    foreach ($oItemList as $key => $oItem) {\n                        if (!$oItem->isVisible()) {\n                            $oRecommList->removeArticle($oItem->getId());\n                            $oItemList->offsetUnset($key);\n                            continue;\n                        }\n\n                        $oItem->text = $oRecommList->getArtDescription($oItem->getId());\n                    }\n                    $this->_oActRecommListArticles = $oItemList;\n                }\n            }\n        }\n\n        return $this->_oActRecommListArticles;\n    }\n\n    /**\n     * return the active entrys\n     *\n     * @return null\n     */\n    public function getActiveRecommList()\n    {\n        if (!$this->getViewConfig()->getShowListmania()) {\n            return false;\n        }\n\n        if ($this->_oActRecommList === null) {\n            $this->_oActRecommList = false;\n\n            if (\n                ($oUser = $this->getUser()) &&\n                ($sRecommId = Registry::getRequest()->getRequestEscapedParameter('recommid'))\n            ) {\n                $oRecommList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\RecommendationList::class);\n                $sUserIdField = 'oxrecommlists__oxuserid';\n                if (($oRecommList->load($sRecommId)) && $oUser->getId() === $oRecommList->$sUserIdField->value) {\n                    $this->_oActRecommList = $oRecommList;\n                }\n            }\n        }\n\n        return $this->_oActRecommList;\n    }\n\n    /**\n     * Set active recommlist\n     *\n     * @param object $oRecommList Recommendation list\n     */\n    public function setActiveRecommList($oRecommList)\n    {\n        $this->_oActRecommList = $oRecommList;\n    }\n\n    /**\n     * add new recommlist\n     *\n     * @return null\n     */\n    public function saveRecommList()\n    {\n        if (!Registry::getSession()->checkSessionChallenge()) {\n            return;\n        }\n\n        if (!$this->getViewConfig()->getShowListmania()) {\n            return;\n        }\n\n        if (($oUser = $this->getUser())) {\n            if (!($oRecommList = $this->getActiveRecommList())) {\n                $oRecommList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\RecommendationList::class);\n                $oRecommList->oxrecommlists__oxuserid = new \\OxidEsales\\Eshop\\Core\\Field($oUser->getId());\n                $oRecommList->oxrecommlists__oxshopid = new \\OxidEsales\\Eshop\\Core\\Field(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId());\n            } else {\n                $this->_sThisTemplate = 'page/account/recommendationedit';\n            }\n\n            $sTitle = trim((string) Registry::getRequest()->getRequestParameter('recomm_title'));\n            $sAuthor = trim((string) Registry::getRequest()->getRequestParameter('recomm_author'));\n            $sText = trim((string) Registry::getRequest()->getRequestParameter('recomm_desc'));\n\n            $oRecommList->oxrecommlists__oxtitle = new Field($sTitle);\n            $oRecommList->oxrecommlists__oxauthor = new Field($sAuthor);\n            $oRecommList->oxrecommlists__oxdesc = new Field($sText);\n\n            try {\n                // marking entry as saved\n                $this->_blSavedEntry = (bool) $oRecommList->save();\n                $this->setActiveRecommList($this->_blSavedEntry ? $oRecommList : false);\n            } catch (\\OxidEsales\\Eshop\\Core\\Exception\\ObjectException $oEx) {\n                //add to display at specific position\n                Registry::getUtilsView()->addErrorToDisplay($oEx, false, true, 'user');\n            }\n        }\n    }\n\n    /**\n     * List entry saving status getter. Saving status is\n     *\n     * @return bool\n     */\n    public function isSavedList()\n    {\n        return $this->_blSavedEntry;\n    }\n\n    /**\n     * Delete recommlist\n     *\n     * @return null\n     */\n    public function editList()\n    {\n        if (!Registry::getSession()->checkSessionChallenge()) {\n            return;\n        }\n\n        if (!$this->getViewConfig()->getShowListmania()) {\n            return;\n        }\n\n        // deleting on demand\n        if (\n            ($sAction = Registry::getRequest()->getRequestEscapedParameter('deleteList')) &&\n            ($oRecommList = $this->getActiveRecommList())\n        ) {\n            $oRecommList->delete();\n            $this->setActiveRecommList(false);\n        } else {\n            $this->_sThisTemplate = 'page/account/recommendationedit';\n        }\n    }\n\n    /**\n     * Delete recommlist\n     *\n     * @return null\n     */\n    public function removeArticle()\n    {\n        if (!Registry::getSession()->checkSessionChallenge()) {\n            return;\n        }\n\n        if (!$this->getViewConfig()->getShowListmania()) {\n            return;\n        }\n\n        if (\n            ($sArtId = Registry::getRequest()->getRequestEscapedParameter('aid')) &&\n            ($oRecommList = $this->getActiveRecommList())\n        ) {\n            $oRecommList->removeArticle($sArtId);\n        }\n        $this->_sThisTemplate = 'page/account/recommendationedit';\n    }\n\n    /**\n     * Template variable getter. Returns page navigation\n     *\n     * @return object\n     */\n    public function getPageNavigation()\n    {\n        if ($this->_oPageNavigation === null) {\n            $this->_oPageNavigation = false;\n            if (!$this->getActiveRecommlist()) {\n                $this->_oPageNavigation = $this->generatePageNavigation();\n            }\n        }\n\n        return $this->_oPageNavigation;\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n\n        $iBaseLanguage = Registry::getLang()->getBaseLanguage();\n        $sSelfLink = $this->getViewConfig()->getSelfLink();\n        $aPath['title'] = Registry::getLang()->translateString('MY_ACCOUNT', $iBaseLanguage, false);\n        $aPath['link'] = Registry::getSeoEncoder()->getStaticUrl($sSelfLink . 'cl=account');\n        $aPaths[] = $aPath;\n\n        $aPath['title'] = Registry::getLang()->translateString('LISTMANIA', $iBaseLanguage, false);\n        $aPath['link'] = $this->getLink();\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n\n    /**\n     * Article count getter\n     *\n     * @return int\n     */\n    public function getArticleCount()\n    {\n        return $this->_iAllArtCnt;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/AccountReviewController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Review;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Request;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge\\UserRatingBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge\\UserReviewBridgeInterface;\n\n/**\n * Class AccountReviewController\n *\n * @package OxidEsales\\EshopCommunity\\Application\\Controller\n */\nclass AccountReviewController extends \\OxidEsales\\Eshop\\Application\\Controller\\AccountController\n{\n    protected $itemsPerPage = 10;\n\n    protected $_sThisTemplate = 'page/account/reviews';\n\n    /**\n     * Redirect to My Account, if validation does not pass.\n     */\n    public function init()\n    {\n        if (!$this->isUserAllowedToManageOwnReviews() || !$this->getUser()) {\n            $this->redirectToAccountDashboard();\n        }\n\n        parent::init();\n    }\n\n    /**\n     * Returns Review List\n     *\n     * @return array\n     */\n    public function getReviewList()\n    {\n        $currentPage = $this->getActPage();\n        $itemsPerPage = $this->getItemsPerPage();\n        $offset = $currentPage * $itemsPerPage;\n\n        $userId = $this->getUser()->getId();\n\n        $reviewModel = oxNew(Review::class);\n        $reviewAndRatingList = $reviewModel->getReviewAndRatingListByUserId($userId);\n\n        return $this->getPaginatedReviewAndRatingList(\n            $reviewAndRatingList,\n            $itemsPerPage,\n            $offset\n        );\n    }\n\n    /**\n     * Delete review and rating, which belongs to the active user.\n     */\n    public function deleteReviewAndRating()\n    {\n        $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n\n        if ($session->checkSessionChallenge()) {\n            try {\n                $this->deleteReview();\n                $this->deleteRating();\n            } catch (EntryDoesNotExistDaoException $exception) {\n                //if user reloads the page after deletion\n            }\n        }\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        return [\n            [\n                'title' => $this->getTranslatedString('MY_ACCOUNT'),\n                'link'  => $this->getMyAccountPageUrl(),\n            ],\n            [\n                'title' => $this->getTranslatedString('MY_REVIEWS'),\n                'link'  => $this->getLink(),\n            ],\n        ];\n    }\n\n    /**\n     * Generates the pagination.\n     *\n     * @return \\stdClass\n     */\n    public function getPageNavigation()\n    {\n        $this->_iCntPages = $this->getPagesCount();\n        $this->_oPageNavigation = $this->generatePageNavigation();\n\n        return $this->_oPageNavigation;\n    }\n\n    /**\n     * Return how many items will be displayed per page.\n     *\n     * @return int\n     */\n    public function getItemsPerPage()\n    {\n        return $this->itemsPerPage;\n    }\n\n    /**\n     * Get actual page number.\n     *\n     * @return int\n     */\n    public function getActPage()\n    {\n        $lastPage = $this->getPagesCount();\n        $currentPage = parent::getActPage();\n\n        if ($currentPage >= $lastPage) {\n            $currentPage = $lastPage - 1;\n        }\n\n        return $currentPage;\n    }\n\n    /**\n     * Deletes Review.\n     */\n    private function deleteReview()\n    {\n        $userId = $this->getUser()->getId();\n        $reviewId = $this->getReviewIdFromRequest();\n\n        if ($reviewId) {\n            ContainerFacade::get(UserReviewBridgeInterface::class)\n                ->deleteReview($userId, $reviewId);\n        }\n    }\n\n    /**\n     * Deletes Rating.\n     */\n    private function deleteRating()\n    {\n        $userId = $this->getUser()->getId();\n        $ratingId = $this->getRatingIdFromRequest();\n\n        if ($ratingId) {\n            ContainerFacade::get(UserRatingBridgeInterface::class)\n                ->deleteRating($userId, $ratingId);\n        }\n    }\n\n    /**\n     * Retrieve the Review id from the request\n     *\n     * @return string\n     */\n    private function getReviewIdFromRequest()\n    {\n        $request = oxNew(Request::class);\n\n        return $request->getRequestEscapedParameter('reviewId');\n    }\n\n    /**\n     * Retrieve the Rating id from the request\n     *\n     * @return string\n     */\n    private function getRatingIdFromRequest()\n    {\n        $request = oxNew(Request::class);\n\n        return $request->getRequestEscapedParameter('ratingId');\n    }\n\n    /**\n     * Redirect to My Account dashboard\n     */\n    private function redirectToAccountDashboard()\n    {\n        Registry::getUtils()->redirect(\n            $this->getMyAccountPageUrl(),\n            true,\n            302\n        );\n    }\n\n    /**\n     * Returns pages count.\n     *\n     * @return int\n     */\n    private function getPagesCount()\n    {\n        return ceil($this->getReviewAndRatingItemsCount() / $this->getItemsPerPage());\n    }\n\n    /**\n     * Returns My Account page url.\n     *\n     * @return string\n     */\n    private function getMyAccountPageUrl()\n    {\n        $selfLink = $this->getViewConfig()->getSelfLink();\n\n        return Registry::getSeoEncoder()->getStaticUrl($selfLink . 'cl=account');\n    }\n\n    /**\n     * Returns translated string.\n     *\n     * @param string $string\n     *\n     * @return string\n     */\n    private function getTranslatedString($string)\n    {\n        $languageId = Registry::getLang()->getBaseLanguage();\n\n        return Registry::getLang()->translateString(\n            $string,\n            $languageId,\n            false\n        );\n    }\n\n    /**\n     * Paginate ReviewAndRating list.\n     *\n     * @param array $reviewAndRatingList\n     * @param int   $itemsCount\n     * @param int   $offset\n     *\n     * @return array\n     */\n    private function getPaginatedReviewAndRatingList(\n        $reviewAndRatingList,\n        $itemsCount,\n        $offset\n    ) {\n        return array_slice(\n            $reviewAndRatingList,\n            $offset,\n            $itemsCount,\n            true\n        );\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/AccountUserController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse oxRegistry;\n\n/**\n * Current user Data Maintenance form.\n * When user is logged in he may change his Billing and Shipping\n * information (this is important for ordering purposes).\n * Information as email, password, greeting, name, company, address\n * etc. Some fields must be entered. OXID eShop -> MY ACCOUNT\n * -> Update your billing and delivery settings.\n */\nclass AccountUserController extends \\OxidEsales\\Eshop\\Application\\Controller\\AccountController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/account/user';\n\n    /**\n     * If user is not logged in - returns name of template\n     * \\OxidEsales\\Eshop\\Application\\Controller\\AccountUserController::_sThisLoginTemplate, or if user is already\n     * logged in additionally loads user delivery address info and forms country list. Returns name of template\n     * \\OxidEsales\\Eshop\\Application\\Controller\\AccountUserController::_sThisTemplate\n     *\n     * @return  string  $_sThisTemplate current template file name\n     */\n    public function render()\n    {\n        parent::render();\n\n        // is logged in ?\n        if (!($this->getUser())) {\n            return $this->_sThisTemplate = $this->_sThisLoginTemplate;\n        }\n\n        $this->_aViewData[\"deladr\"] = Registry::getRequest()->getRequestEscapedParameter('deladr');\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Template variable getter. Checks to show or not shipping address entry form\n     *\n     * @return bool\n     */\n    public function showShipAddress()\n    {\n        return Registry::getSession()->getVariable('blshowshipaddress');\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n\n        $iBaseLanguage = Registry::getLang()->getBaseLanguage();\n        $sSelfLink = $this->getViewConfig()->getSelfLink();\n\n        $aPath['title'] = Registry::getLang()->translateString('MY_ACCOUNT', $iBaseLanguage, false);\n        $aPath['link'] = Registry::getSeoEncoder()->getStaticUrl($sSelfLink . 'cl=account');\n        $aPaths[] = $aPath;\n\n        $aPath['title'] = Registry::getLang()->translateString('BILLING_SHIPPING_SETTINGS', $iBaseLanguage, false);\n        $aPath['link'] = $this->getLink();\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/AccountWishlistController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Current user wishlist manager.\n * When user is logged in in this manager window he can modify his\n * own wishlist status - remove articles from wishlist or store\n * them to shopping basket, view detail information. Additionally\n * user can view wishlist of some other user by entering users\n * login name in special field. OXID eShop -> MY ACCOUNT\n *  -> Newsletter.\n */\n#[\\AllowDynamicProperties]\nclass AccountWishlistController extends \\OxidEsales\\Eshop\\Application\\Controller\\AccountController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/account/wishlist';\n\n    /**\n     * If true, list will be shown, if false - will not\n     *\n     * @var bool\n     */\n    protected $_blShowSuggest = null;\n\n    /**\n     * Wheter the var is false the wishlist will be shown\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\UserBasket|bool|null\n     */\n    protected $_oWishList = null;\n\n    /**\n     * list the wishlist items\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\UserBasket|bool|null\n     */\n    protected $_aRecommList = null;\n\n    /**\n     * Wheter the var is false the productlist will not be list\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\UserBasket|bool|null\n     */\n    protected $_oEditval = null;\n\n    /**\n     * If sending failed give false back\n     *\n     * @var integer / bool\n     */\n    protected $_iSendWishList = null;\n\n    /**\n     * Wishlist search param\n     *\n     * @var string\n     */\n    protected $_sSearchParam = null;\n\n    /**\n     * List of users which were found according to search condition\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    protected $_oWishListUsers = false;\n\n    /**\n     * Wishlist email sending status\n     *\n     * @var bool\n     */\n    protected $_blEmailSent = false;\n\n    /**\n     * User entered values for sending email\n     *\n     * @var array\n     */\n    protected $_aEditValues = false;\n\n    /**\n     * Array of id to form recommendation list.\n     *\n     * @var array\n     */\n    protected $_aSimilarRecommListIds = null;\n\n    /**\n     * Current view search engine indexing state\n     *\n     * @var int\n     */\n    protected $_iViewIndexState = VIEW_INDEXSTATE_NOINDEXNOFOLLOW;\n\n    /**\n     * If user is logged in loads his wishlist articles (articles may be accessed by\n     * \\OxidEsales\\Eshop\\Application\\Model\\User::GetBasket()), loads similar articles (is available) for\n     * the last article in list loaded by \\OxidEsales\\Eshop\\Application\\Model\\Article::GetSimilarProducts() and returns\n     * name of template to render \\OxidEsales\\Eshop\\Application\\Controller\\AccountWishlistController::_sThisTemplate\n     *\n     * @return  string  $_sThisTemplate current template file name\n     */\n    public function render()\n    {\n        parent::render();\n\n        // is logged in ?\n        $oUser = $this->getUser();\n        if (!$oUser) {\n            return $this->_sThisTemplate = $this->_sThisLoginTemplate;\n        }\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * check if the wishlist is allowed\n     *\n     * @return bool\n     */\n    public function showSuggest()\n    {\n        if ($this->_blShowSuggest === null) {\n            $this->_blShowSuggest = (bool) Registry::getRequest()->getRequestEscapedParameter('blshowsuggest');\n        }\n\n        return $this->_blShowSuggest;\n    }\n\n    /**\n     * Show the Wishlist\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\UserBasket|bool\n     */\n    public function getWishList()\n    {\n        if ($this->_oWishList === null) {\n            $this->_oWishList = false;\n            if ($oUser = $this->getUser()) {\n                $this->_oWishList = $oUser->getBasket('wishlist');\n                if ($this->_oWishList->isEmpty()) {\n                    $this->_oWishList = false;\n                }\n            }\n        }\n\n        return $this->_oWishList;\n    }\n\n    /**\n     * Returns array of producst assigned to user wish list\n     *\n     * @return array|bool\n     */\n    public function getWishProductList()\n    {\n        if (!isset($this->_aWishProductList)) {\n            $this->_aWishProductList = false;\n            if ($oWishList = $this->getWishList()) {\n                $this->_aWishProductList = $oWishList->getArticles();\n            }\n        }\n\n        return $this->_aWishProductList ?? null;\n    }\n\n    /**\n     * Return array of id to form recommend list.\n     *\n     * @return array\n     */\n    public function getSimilarRecommListIds()\n    {\n        if ($this->_aSimilarRecommListIds === null) {\n            $this->_aSimilarRecommListIds = false;\n\n            $aWishProdList = $this->getWishProductList();\n            if (is_array($aWishProdList) && ($oSimilarProd = current($aWishProdList))) {\n                $this->_aSimilarRecommListIds = [$oSimilarProd->getId()];\n            }\n        }\n\n        return $this->_aSimilarRecommListIds;\n    }\n\n    /**\n     * Sends wishlist mail to recipient. On errors returns false.\n     *\n     * @return bool\n     */\n    public function sendWishList()\n    {\n        if (!Registry::getSession()->checkSessionChallenge()) {\n            return false;\n        }\n\n        $aParams = Registry::getRequest()->getRequestParameter('editval');\n        if (is_array($aParams)) {\n            $oUtilsView = Registry::getUtilsView();\n            $oParams = (object) $aParams;\n            $this->setEnteredData((object) Registry::getRequest()->getRequestEscapedParameter('editval'));\n\n            if (\n                !isset($aParams['rec_name']) || !isset($aParams['rec_email']) ||\n                !$aParams['rec_name'] || !$aParams['rec_email']\n            ) {\n                return $oUtilsView->addErrorToDisplay('ERROR_MESSAGE_COMPLETE_FIELDS_CORRECTLY', false, true);\n            } else {\n                if ($oUser = $this->getUser()) {\n                    $sFirstName = 'oxuser__oxfname';\n                    $sLastName = 'oxuser__oxlname';\n                    $sSendEmail = 'send_email';\n                    $sUserNameField = 'oxuser__oxusername';\n                    $sSendName = 'send_name';\n                    $sSendId = 'send_id';\n\n                    $oParams->$sSendEmail = $oUser->$sUserNameField->value;\n                    $oParams->$sSendName = $oUser->$sFirstName->getRawValue() . ' ' . $oUser->$sLastName->getRawValue();\n                    $oParams->$sSendId = $oUser->getId();\n\n                    $this->_blEmailSent = oxNew(\\OxidEsales\\Eshop\\Core\\Email::class)->sendWishlistMail($oParams);\n                    if (!$this->_blEmailSent) {\n                        return $oUtilsView->addErrorToDisplay('ERROR_MESSAGE_CHECK_EMAIL', false, true);\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * If email was sent.\n     *\n     * @return bool\n     */\n    public function isWishListEmailSent()\n    {\n        return $this->_blEmailSent;\n    }\n\n    /**\n     * Wishlist data setter\n     *\n     * @param object $oData suggest data object\n     */\n    public function setEnteredData($oData)\n    {\n        $this->_aEditValues = $oData;\n    }\n\n    /**\n     * Terurns user entered values for sending email.\n     *\n     * @return array\n     */\n    public function getEnteredData()\n    {\n        return $this->_aEditValues;\n    }\n\n    /**\n     * Changes wishlist status - public/non public. Returns false on\n     * error (if user is not logged in).\n     *\n     * @return bool\n     */\n    public function togglePublic()\n    {\n        if (!Registry::getSession()->checkSessionChallenge()) {\n            return false;\n        }\n\n        if ($oUser = $this->getUser()) {\n            $blPublic = (int) Registry::getRequest()->getRequestEscapedParameter('blpublic');\n            $oBasket = $oUser->getBasket('wishlist');\n            $oBasket->oxuserbaskets__oxpublic = new \\OxidEsales\\Eshop\\Core\\Field(($blPublic == 1) ? $blPublic : 0);\n            $oBasket->save();\n        }\n    }\n\n    /**\n     * Searches for wishlist of another user. Returns false if no\n     * searching conditions set (no login name defined).\n     */\n    public function searchForWishList()\n    {\n        if ($sSearch = Registry::getRequest()->getRequestEscapedParameter('search')) {\n            // search for baskets\n            $oUserList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\UserList::class);\n            $oUserList->loadWishlistUsers($sSearch);\n            if ($oUserList->count()) {\n                $this->_oWishListUsers = $oUserList;\n            }\n\n            $this->_sSearchParam = $sSearch;\n        }\n    }\n\n    /**\n     * Returns a list of users which were found according to search condition.\n     * If no users were found - false is returned\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\ListModel|bool\n     */\n    public function getWishListUsers()\n    {\n        return $this->_oWishListUsers;\n    }\n\n    /**\n     * Returns wish list search parameter\n     *\n     * @return string\n     */\n    public function getWishListSearchParam()\n    {\n        return $this->_sSearchParam;\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n\n        $iBaseLanguage = Registry::getLang()->getBaseLanguage();\n        $sSelfLink = $this->getViewConfig()->getSelfLink();\n\n        $aPath['title'] = Registry::getLang()->translateString('MY_ACCOUNT', $iBaseLanguage, false);\n        $aPath['link'] = Registry::getSeoEncoder()->getStaticUrl($sSelfLink . 'cl=account');\n        $aPaths[] = $aPath;\n\n        $aPath['title'] = Registry::getLang()->translateString('MY_GIFT_REGISTRY', $iBaseLanguage, false);\n        $aPath['link'] = $this->getLink();\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ActionsArticleAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class controls article assignment to attributes\n */\nclass ActionsArticleAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * If true extended column selection will be build\n     *\n     * @var bool\n     */\n    protected $_blAllowExtColumns = true;\n\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,         visible, multilanguage, ident\n        ['oxartnum', 'oxarticles', 1, 0, 0],\n        ['oxtitle', 'oxarticles', 1, 1, 0],\n        ['oxean', 'oxarticles', 1, 0, 0],\n        ['oxmpn', 'oxarticles', 0, 0, 0],\n        ['oxprice', 'oxarticles', 0, 0, 0],\n        ['oxstock', 'oxarticles', 0, 0, 0],\n        ['oxid', 'oxarticles', 0, 0, 1]\n    ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $myConfig = Registry::getConfig();\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sArticleTable = $this->getViewName('oxarticles');\n        $sViewName = $this->getViewName('oxobject2category');\n\n        $sSelId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchSelId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sSelId) {\n            $sQAdd = \" from $sArticleTable where 1 \";\n            $sQAdd .= $myConfig->getConfigParam('blVariantsSelection') ? '' : \" and $sArticleTable.oxparentid = '' \";\n        } else {\n            // selected category ?\n            if ($sSynchSelId) {\n                $blVariantsSelectionParameter = $myConfig->getConfigParam('blVariantsSelection');\n                $sSqlIfTrue = \" ({$sArticleTable}.oxid=oxobject2category.oxobjectid \" .\n                              \"or {$sArticleTable}.oxparentid=oxobject2category.oxobjectid)\";\n                $sSqlIfFalse = \" {$sArticleTable}.oxid=oxobject2category.oxobjectid \";\n                $sVariantSelection = $blVariantsSelectionParameter ? $sSqlIfTrue : $sSqlIfFalse;\n                $sQAdd = \" from {$sViewName} as oxobject2category left join {$sArticleTable} on \" . $sVariantSelection .\n                         \" where oxobject2category.oxcatnid = \" . $oDb->quote($sSelId) . \" \";\n            }\n        }\n        // #1513C/#1826C - skip references, to not existing articles\n        $sQAdd .= \" and $sArticleTable.oxid IS NOT NULL \";\n\n        // skipping self from list\n        $sQAdd .= \" and $sArticleTable.oxid != \" . $oDb->quote($sSynchSelId) . \" \";\n\n        return $sQAdd;\n    }\n\n    /**\n     * Adds filter SQL to current query\n     *\n     * @param string $sQ query to add filter condition\n     *\n     * @return string\n     */\n    protected function addFilter($sQ)\n    {\n        $sArtTable = $this->getViewName('oxarticles');\n        $sQ = parent::addFilter($sQ);\n\n        // display variants or not ?\n        $sQ .= Registry::getConfig()->getConfigParam('blVariantsSelection')\n            ? ' group by ' . $sArtTable . '.oxid '\n            : '';\n\n        return $sQ;\n    }\n\n    /**\n     * Removing article assignment\n     */\n    public function removeActionArticle()\n    {\n        $sActionId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        //$sActionId = $this->getConfig()->getConfigParam( 'oxid' );\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $oDb->Execute(\n            'delete from oxobject2action '\n            . 'where oxactionid = :oxactionid'\n            . ' and oxclass = \"oxarticle\"',\n            ['oxactionid' => $sActionId]\n        );\n    }\n\n    /**\n     * Set article assignment\n     */\n    public function setActionArticle()\n    {\n        $sArticleId = Registry::getRequest()->getRequestEscapedParameter('oxarticleid');\n        $sActionId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $oDb->Execute(\n            'delete from oxobject2action '\n            . 'where oxactionid = :oxactionid'\n            . ' and oxclass = \"oxarticle\"',\n            ['oxactionid' => $sActionId]\n        );\n\n        $oObject2Promotion = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n        $oObject2Promotion->init('oxobject2action');\n        $oObject2Promotion->oxobject2action__oxactionid = new \\OxidEsales\\Eshop\\Core\\Field($sActionId);\n        $oObject2Promotion->oxobject2action__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sArticleId);\n        $oObject2Promotion->oxobject2action__oxclass = new \\OxidEsales\\Eshop\\Core\\Field(\"oxarticle\");\n        $oObject2Promotion->save();\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ActionsController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Sets view template, that arranges two other templates (\"actions_list\"\n * and \"actions_main\") to frame.\n * Admin Menu: Manage Products -> Actions.\n */\nclass ActionsController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'actions';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ActionsGroupsAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages promotion groups\n */\nclass ActionsGroupsAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = [\n        // field , table,  visible, multilanguage, ident\n        'container1' => [\n            ['oxtitle', 'oxgroups', 1, 0, 0],\n            ['oxid', 'oxgroups', 0, 0, 0],\n            ['oxid', 'oxgroups', 0, 0, 1],\n        ],\n         'container2' => [\n             ['oxtitle', 'oxgroups', 1, 0, 0],\n             ['oxid', 'oxgroups', 0, 0, 0],\n             ['oxid', 'oxobject2action', 0, 0, 1],\n         ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        // active AJAX component\n        $sGroupTable = $this->getViewName('oxgroups');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $sId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sId) {\n            $sQAdd = \" from {$sGroupTable} where 1 \";\n        } else {\n            $sQAdd = \" from oxobject2action, {$sGroupTable} where {$sGroupTable}.oxid=oxobject2action.oxobjectid \" .\n                      \" and oxobject2action.oxactionid = \" . $oDb->quote($sId) .\n                      \" and oxobject2action.oxclass = 'oxgroups' \";\n        }\n\n        if ($sSynchId && $sSynchId != $sId) {\n            $sQAdd .= \" and {$sGroupTable}.oxid not in ( select {$sGroupTable}.oxid \" .\n                      \"from oxobject2action, {$sGroupTable} where $sGroupTable.oxid=oxobject2action.oxobjectid \" .\n                      \" and oxobject2action.oxactionid = \" . $oDb->quote($sSynchId) .\n                      \" and oxobject2action.oxclass = 'oxgroups' ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes user group from promotion\n     */\n    public function removePromotionGroup()\n    {\n        $aRemoveGroups = $this->getActionIds('oxobject2action.oxid');\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxobject2action.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif ($aRemoveGroups && is_array($aRemoveGroups)) {\n            $sRemoveGroups = implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aRemoveGroups));\n            $sQ = \"delete from oxobject2action where oxobject2action.oxid in (\" . $sRemoveGroups . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adds user group to promotion\n     *\n     * @return bool Whether at least one promotion was added.\n     */\n    public function addPromotionGroup()\n    {\n        $aChosenGroup = $this->getActionIds('oxgroups.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sGroupTable = $this->getViewName('oxgroups');\n            $aChosenGroup = $this->getAll($this->addFilter(\"select $sGroupTable.oxid \" . $this->getQuery()));\n        }\n\n        $promotionAdded = false;\n        if ($soxId && $soxId != \"-1\" && is_array($aChosenGroup)) {\n            foreach ($aChosenGroup as $sChosenGroup) {\n                $oObject2Promotion = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oObject2Promotion->init('oxobject2action');\n                $oObject2Promotion->oxobject2action__oxactionid = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                $oObject2Promotion->oxobject2action__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sChosenGroup);\n                $oObject2Promotion->oxobject2action__oxclass = new \\OxidEsales\\Eshop\\Core\\Field(\"oxgroups\");\n                $oObject2Promotion->save();\n            }\n\n            $promotionAdded = true;\n        }\n\n        return $promotionAdded;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ActionsList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Admin actionss manager.\n * Sets list template, list object class ('oxactions') and default sorting\n * field ('oxactions.oxtitle').\n * Admin Menu: Manage Products -> Actions.\n */\nclass ActionsList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'actions_list';\n\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxactions';\n\n    /**\n     * Default SQL sorting parameter (default null).\n     *\n     * @var string\n     */\n    protected $_sDefSortField = 'oxtitle';\n\n    /**\n     * Calls parent::render() and returns name of template to render\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        // passing display type back to view\n        $this->_aViewData[\"displaytype\"] = Registry::getRequest()->getRequestEscapedParameter(\"displaytype\");\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Adds active promotion check\n     *\n     * @param array  $aWhere  SQL condition array\n     * @param string $sqlFull SQL query string\n     *\n     * @return $sQ\n     */\n    protected function prepareWhereQuery($aWhere, $sqlFull)\n    {\n        $sQ = parent::prepareWhereQuery($aWhere, $sqlFull);\n        $sDisplayType = (int) Registry::getRequest()->getRequestEscapedParameter('displaytype');\n        $tableViewNameGenerator = new TableViewNameGenerator();\n        $sTable = $tableViewNameGenerator->getViewName(\"oxactions\");\n\n        // searching for empty oxfolder fields\n        if ($sDisplayType) {\n            $sNow = date('Y-m-d H:i:s', \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime());\n\n            switch ($sDisplayType) {\n                case 1: // active\n                    $sQ .= \" and {$sTable}.oxactivefrom < '{$sNow}' and {$sTable}.oxactiveto > '{$sNow}' \";\n                    break;\n                case 2: // upcoming\n                    $sQ .= \" and {$sTable}.oxactivefrom > '{$sNow}' \";\n                    break;\n                case 3: // expired\n                    $sQ .= \" and {$sTable}.oxactiveto < '{$sNow}' and {$sTable}.oxactiveto != '0000-00-00 00:00:00' \";\n                    break;\n            }\n        }\n\n        return $sQ;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ActionsMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse stdClass;\nuse OxidEsales\\Eshop\\Application\\Model\\Actions;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Request;\n\n/**\n * Admin article main actions manager.\n * There is possibility to change actions description, assign articles to\n * this actions, etc.\n * Admin Menu: Manage Products -> actions -> Main.\n */\nclass ActionsMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n\n        if ($this->isNewEditObject() !== true) {\n            $oAction = oxNew(Actions::class);\n            $oAction->loadInLang($this->_iEditLang, $soxId);\n\n            $oOtherLang = $oAction->getAvailableInLangs();\n            if (!isset($oOtherLang[$this->_iEditLang])) {\n                $oAction->loadInLang(key($oOtherLang), $soxId);\n            }\n\n            $this->_aViewData[\"edit\"] = $oAction;\n\n            // remove already created languages\n            $aLang = array_diff(Registry::getLang()->getLanguageNames(), $oOtherLang);\n\n            if (count($aLang)) {\n                $this->_aViewData[\"posslang\"] = $aLang;\n            }\n\n            foreach ($oOtherLang as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $oLang;\n            }\n        }\n\n        if ($this->getViewConfig()->isAltImageServerConfigured()) {\n            $this->_aViewData[\"imageUrl\"] = ContainerFacade::getParameter('oxid_esales.alternative_image_url');\n        }\n\n        if (Registry::getRequest()->getRequestEscapedParameter(\"aoc\")) {\n            // generating category tree for select list\n            $this->createCategoryTree(\"artcattree\", $soxId);\n\n            $oActionsMainAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ActionsMainAjax::class);\n            $this->_aViewData['oxajax'] = $oActionsMainAjax->getColumns();\n\n            return \"popups/actions_main\";\n        }\n\n\n        if (($oPromotion = $this->getViewDataElement(\"edit\"))) {\n            if (($oPromotion->oxactions__oxtype->value == 2) || ($oPromotion->oxactions__oxtype->value == 3)) {\n                if ($iAoc = Registry::getRequest()->getRequestEscapedParameter(\"oxpromotionaoc\")) {\n                    $sPopup = false;\n                    switch ($iAoc) {\n                        case 'article':\n                            // generating category tree for select list\n                            $this->createCategoryTree(\"artcattree\", $soxId);\n\n                            if ($oArticle = $oPromotion->getBannerArticle()) {\n                                $this->_aViewData['actionarticle_artnum'] = $oArticle->oxarticles__oxartnum->value;\n                                $this->_aViewData['actionarticle_title'] = $oArticle->oxarticles__oxtitle->value;\n                            }\n\n                            $sPopup = 'actions_article';\n                            break;\n                        case 'groups':\n                            $sPopup = 'actions_groups';\n                            break;\n                    }\n\n                    if ($sPopup) {\n                        $oActionsArticleAjax = oxNew($sPopup . '_ajax');\n                        $this->_aViewData['oxajax'] = $oActionsArticleAjax->getColumns();\n\n                        return \"popups/{$sPopup}\";\n                    }\n                } else {\n                    if ($oPromotion->oxactions__oxtype->value == 2) {\n                        $this->_aViewData[\"editor\"] = $this->generateTextEditor(\n                            \"100%\",\n                            300,\n                            $oPromotion,\n                            \"oxactions__oxlongdesc\",\n                            \"details.css\"\n                        );\n                    }\n                }\n            }\n        }\n\n        return \"actions_main\";\n    }\n\n    /**\n     * Saves Promotions\n     */\n    public function save()\n    {\n        parent::save();\n\n        $action = oxNew(Actions::class);\n\n        if ($this->isNewEditObject() !== true) {\n            $action->load($this->getEditObjectId());\n        }\n\n        if ($this->checkAccessToEditAction($action) === true) {\n            $action->assign($this->getActionFormData());\n            $action->setLanguage($this->_iEditLang);\n            $action = Registry::getUtilsFile()->processFiles($action);\n            $action->save();\n\n            $this->setEditObjectId($action->getId());\n        }\n    }\n\n    /**\n     * Saves changed selected action parameters in different language.\n     */\n    public function saveinnlang()\n    {\n        $this->save();\n    }\n\n    /**\n     * Checks access to edit Action.\n     *\n     * @param Actions $action\n     *\n     * @return bool\n     */\n    protected function checkAccessToEditAction(Actions $action)\n    {\n        return true;\n    }\n\n    /**\n     * Returns form data for Action.\n     *\n     * @return array\n     */\n    private function getActionFormData()\n    {\n        $request    = oxNew(Request::class);\n        $formData   = $request->getRequestEscapedParameter(\"editval\");\n        $formData   = $this->normalizeActionFormData($formData);\n\n        return $formData;\n    }\n\n    /**\n     * Normalizes form data for Action.\n     *\n     * @param   array $formData\n     *\n     * @return  array\n     */\n    private function normalizeActionFormData($formData)\n    {\n        if ($this->isNewEditObject() === true) {\n            $formData['oxactions__oxid'] = null;\n        }\n\n        if (!isset($formData['oxactions__oxactive']) || !$formData['oxactions__oxactive']) {\n            $formData['oxactions__oxactive'] = 0;\n        }\n\n        return $formData;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ActionsMainAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse Exception;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class controls article assignment to action\n */\nclass ActionsMainAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * If true extended column selection will be build\n     *\n     * @var bool\n     */\n    protected $_blAllowExtColumns = true;\n\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,         visible, multilanguage, ident\n        ['oxartnum', 'oxarticles', 1, 0, 0],\n        ['oxtitle', 'oxarticles', 1, 1, 0],\n        ['oxean', 'oxarticles', 1, 0, 0],\n        ['oxmpn', 'oxarticles', 0, 0, 0],\n        ['oxprice', 'oxarticles', 0, 0, 0],\n        ['oxstock', 'oxarticles', 0, 0, 0],\n        ['oxid', 'oxarticles', 0, 0, 1]\n    ],\n                                 'container2' => [\n                                     ['oxartnum', 'oxarticles', 1, 0, 0],\n                                     ['oxsort', 'oxactions2article', 1, 0, 0],\n                                     ['oxtitle', 'oxarticles', 1, 1, 0],\n                                     ['oxean', 'oxarticles', 1, 0, 0],\n                                     ['oxmpn', 'oxarticles', 0, 0, 0],\n                                     ['oxprice', 'oxarticles', 0, 0, 0],\n                                     ['oxstock', 'oxarticles', 0, 0, 0],\n                                     ['oxid', 'oxactions2article', 0, 0, 1]\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        // looking for table/view\n        $sArtTable = $this->getViewName('oxarticles');\n        $sView = $this->getViewName('oxobject2category');\n\n        $sSelId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchSelId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sSelId) {\n            //performance\n            $sQAdd = \" from $sArtTable where 1 \";\n            $sQAdd .= $myConfig->getConfigParam('blVariantsSelection') ? '' : \" and $sArtTable.oxparentid = '' \";\n        } else {\n            // selected category ?\n            if ($sSynchSelId && $sSelId != $sSynchSelId) {\n                $sQAdd = \" from {$sView} left join $sArtTable on \";\n                $blVariantsSelectionParameter = $myConfig->getConfigParam('blVariantsSelection');\n                $sSqlIfTrue = \" ( $sArtTable.oxid={$sView}.oxobjectid or $sArtTable.oxparentid={$sView}.oxobjectid) \";\n                $sSqlIfFalse = \" $sArtTable.oxid={$sView}.oxobjectid \";\n                $sQAdd .= $blVariantsSelectionParameter ? $sSqlIfTrue : $sSqlIfFalse;\n                $sQAdd .= \" where {$sView}.oxcatnid = \" . $oDb->quote($sSelId);\n            } else {\n                $sQAdd = \" from {$sArtTable} left join oxactions2article \" .\n                         \"on {$sArtTable}.oxid=oxactions2article.oxartid \" .\n                         \" where oxactions2article.oxactionid = \" . $oDb->quote($sSelId) .\n                         \" and oxactions2article.oxshopid = '\" . $myConfig->getShopID() . \"' \";\n            }\n        }\n\n        if ($sSynchSelId && $sSynchSelId != $sSelId) {\n            $sQAdd .= \" and {$sArtTable}.oxid not in ( select oxactions2article.oxartid from oxactions2article \" .\n                      \" where oxactions2article.oxactionid = \" . $oDb->quote($sSynchSelId) .\n                      \" and oxactions2article.oxshopid = '\" . $myConfig->getShopID() . \"' ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Adds filter SQL to current query\n     *\n     * @param string $sQ query to add filter condition\n     *\n     * @return string\n     */\n    protected function addFilter($sQ)\n    {\n        $sQ = parent::addFilter($sQ);\n\n        // display variants or not ?\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blVariantsSelection')) {\n            $sQ .= ' group by ' . $this->getViewName('oxarticles') . '.oxid ';\n\n            $oStr = Str::getStr();\n            if ($oStr->strpos($sQ, \"select count( * ) \") === 0) {\n                $sQ = \"select count( * ) from ( {$sQ} ) as _cnttable\";\n            }\n        }\n\n        return $sQ;\n    }\n\n    /**\n     * Returns SQL query addon for sorting\n     *\n     * @return string\n     */\n    protected function getSorting()\n    {\n        $sOxIdParameter = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchOxidParameter = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n        if ($sOxIdParameter && !$sSynchOxidParameter) {\n            return 'order by oxactions2article.oxsort ';\n        }\n\n        return parent::getSorting();\n    }\n\n    /**\n     * Removes article from Promotions list\n     */\n    public function removeArtFromAct()\n    {\n        $aChosenArt = $this->getActionIds('oxactions2article.oxid');\n        $sOxid = Registry::getRequest()->getRequestEscapedParameter('oxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = parent::addFilter(\"delete oxactions2article.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif (is_array($aChosenArt)) {\n            $sChosenArticles = implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aChosenArt));\n            $sQ = \"delete from oxactions2article where oxactions2article.oxid in (\" . $sChosenArticles . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adds article to Promotions list\n     *\n     * @return bool Whether any article was added to action.\n     *\n     * @throws Exception\n     */\n    public function addArtToAct()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $aArticles = $this->getActionIds('oxarticles.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sArtTable = $this->getViewName('oxarticles');\n            $aArticles = $this->getAll($this->addFilter(\"select $sArtTable.oxid \" . $this->getQuery()));\n        }\n\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804 and ESDEV-3822).\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster();\n        $sArtTable = $this->getViewName('oxarticles');\n        $sQ = \"select max(oxactions2article.oxsort) from oxactions2article join {$sArtTable} \" .\n              \"on {$sArtTable}.oxid=oxactions2article.oxartid \" .\n              \"where oxactions2article.oxactionid = :oxactionid \" .\n              \"and oxactions2article.oxshopid = :oxshopid \" .\n              \"and $sArtTable.oxid is not null\";\n\n        $parameters = [\n            'oxactionid' => $soxId,\n            'oxshopid' => $myConfig->getShopId()\n        ];\n\n        $iSort = ((int) $database->getOne($sQ, $parameters)) + 1;\n\n        $articleAdded = false;\n        if ($soxId && $soxId != \"-1\" && is_array($aArticles)) {\n            $sShopId = $myConfig->getShopId();\n            foreach ($aArticles as $sAdd) {\n                $oNewGroup = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oNewGroup->init('oxactions2article');\n                $oNewGroup->oxactions2article__oxshopid = new \\OxidEsales\\Eshop\\Core\\Field($sShopId);\n                $oNewGroup->oxactions2article__oxactionid = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                $oNewGroup->oxactions2article__oxartid = new \\OxidEsales\\Eshop\\Core\\Field($sAdd);\n                $oNewGroup->oxactions2article__oxsort = new \\OxidEsales\\Eshop\\Core\\Field($iSort++);\n                $oNewGroup->save();\n            }\n            $articleAdded = true;\n        }\n\n        return $articleAdded;\n    }\n\n    /**\n     * Sets sorting position for current action article\n     */\n    public function setSorting()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $sArtTable = $this->getViewName('oxarticles');\n        $sSelId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSelect = \"select * from $sArtTable left join oxactions2article on $sArtTable.oxid=oxactions2article.oxartid \";\n        $sSelect .= \"where oxactions2article.oxactionid = :oxactionid \" .\n                    \"and oxactions2article.oxshopid = :oxshopid \" . $this->getSorting();\n\n        $oList = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n        $oList->init(\"oxbase\", \"oxactions2article\");\n        $oList->selectString($sSelect, [\n            'oxactionid' => $sSelId,\n            'oxshopid' => $myConfig->getShopID()\n        ]);\n\n        // fixing indexes\n        $iSelCnt = 0;\n        $aIdx2Id = [];\n        foreach ($oList as $sKey => $oSel) {\n            if ($oSel->oxactions2article__oxsort->value != $iSelCnt) {\n                $oSel->oxactions2article__oxsort->setValue($iSelCnt);\n\n                // saving new index\n                $oSel->save();\n            }\n            $aIdx2Id[$iSelCnt] = $sKey;\n            $iSelCnt++;\n        }\n\n        //\n        if (($iKey = array_search(Registry::getRequest()->getRequestEscapedParameter('sortoxid'), $aIdx2Id)) !== false) {\n            $iDir = (Registry::getRequest()->getRequestEscapedParameter('direction') == 'up') ? ($iKey - 1) : ($iKey + 1);\n            if (isset($aIdx2Id[$iDir])) {\n                // exchanging indexes\n                $oDir1 = $oList->offsetGet($aIdx2Id[$iDir]);\n                $oDir2 = $oList->offsetGet($aIdx2Id[$iKey]);\n\n                $iCopy = $oDir1->oxactions2article__oxsort->value;\n                $oDir1->oxactions2article__oxsort->setValue($oDir2->oxactions2article__oxsort->value);\n                $oDir2->oxactions2article__oxsort->setValue($iCopy);\n\n                $oDir1->save();\n                $oDir2->save();\n            }\n        }\n\n        $sQAdd = $this->getQuery();\n\n        $sQ = 'select ' . $this->getQueryCols() . $sQAdd;\n        $sCountQ = 'select count( * ) ' . $sQAdd;\n\n        $this->outputResponse($this->getData($sCountQ, $sQ));\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ActionsOrderAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages article select lists sorting\n */\nclass ActionsOrderAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [\n        ['oxtitle', 'oxselectlist', 1, 1, 0],\n        ['oxsort', 'oxobject2selectlist', 1, 0, 0],\n        ['oxident', 'oxselectlist', 0, 0, 0],\n        ['oxvaldesc', 'oxselectlist', 0, 0, 0],\n        ['oxid', 'oxobject2selectlist', 0, 0, 1]\n    ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $sSelTable = $this->getViewName('oxselectlist');\n        $sArtId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n\n        return \" from $sSelTable left join oxobject2selectlist on oxobject2selectlist.oxselnid = $sSelTable.oxid \" .\n                 \"where oxobjectid = \" . \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quote($sArtId) . \"  \";\n    }\n\n    /**\n     * Returns SQL query addon for sorting\n     *\n     * @return string\n     */\n    protected function getSorting()\n    {\n        return 'order by oxobject2selectlist.oxsort ';\n    }\n\n    /**\n     * Applies sorting for selection lists\n     */\n    public function setSorting()\n    {\n        $sSelId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSelect = \"select * from oxobject2selectlist where oxobjectid = :oxobjectid order by oxsort\";\n\n        $oList = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n        $oList->init(\"oxbase\", \"oxobject2selectlist\");\n        $oList->selectString($sSelect, [\n            'oxobjectid' => $sSelId\n        ]);\n\n        // fixing indexes\n        $iSelCnt = 0;\n        $aIdx2Id = [];\n        foreach ($oList as $sKey => $oSel) {\n            if ($oSel->oxobject2selectlist__oxsort->value != $iSelCnt) {\n                $oSel->oxobject2selectlist__oxsort->setValue($iSelCnt);\n\n                // saving new index\n                $oSel->save();\n            }\n            $aIdx2Id[$iSelCnt] = $sKey;\n            $iSelCnt++;\n        }\n\n        //\n        if (($iKey = array_search(Registry::getRequest()->getRequestEscapedParameter('sortoxid'), $aIdx2Id)) !== false) {\n            $iDir = (Registry::getRequest()->getRequestEscapedParameter('direction') == 'up') ? ($iKey - 1) : ($iKey + 1);\n            if (isset($aIdx2Id[$iDir])) {\n                // exchanging indexes\n                $oDir1 = $oList->offsetGet($aIdx2Id[$iDir]);\n                $oDir2 = $oList->offsetGet($aIdx2Id[$iKey]);\n\n                $iCopy = $oDir1->oxobject2selectlist__oxsort->value;\n                $oDir1->oxobject2selectlist__oxsort->setValue($oDir2->oxobject2selectlist__oxsort->value);\n                $oDir2->oxobject2selectlist__oxsort->setValue($iCopy);\n\n                $oDir1->save();\n                $oDir2->save();\n            }\n        }\n\n        $sQAdd = $this->getQuery();\n\n        $sQ = 'select ' . $this->getQueryCols() . $sQAdd;\n        $sCountQ = 'select count( * ) ' . $sQAdd;\n\n        $this->outputResponse($this->getData($sCountQ, $sQ));\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AdminContent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Sets template, that arranges two other templates (\"content_list\"\n * and \"content_main\") to frame.\n * Admin Menu: Customerinformations -> Content.\n */\nclass AdminContent extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'content';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AdminController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\Validator\\FileValidatorBridgeInterface;\n\n/**\n * Main Controller class for admin area.\n */\nclass AdminController extends \\OxidEsales\\Eshop\\Core\\Controller\\BaseController\n{\n    /**\n     * Fixed types - enums in database.\n     *\n     * @var array\n     */\n    protected $_aSumType = [\n        0 => 'abs',\n        1 => '%',\n        2 => 'itm'\n    ];\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = null;\n\n    /**\n     * Override this in list class to show other tab from beginning\n     * (default 0 - the first tab).\n     *\n     * @var int\n     */\n    protected $_iDefEdit = 0;\n\n    /**\n     * Navigation tree object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\NavigationTree\n     */\n    protected static $_oNaviTree = null;\n\n    /**\n     * Objects editing language (default 0).\n     *\n     * @var integer\n     */\n    protected $_iEditLang = 0;\n\n    /**\n     * Active shop title\n     *\n     * @var string\n     */\n    protected $_sShopTitle = \" - \";\n\n    /**\n     * Session user rights\n     *\n     * @var string\n     */\n    protected static $_sAuthUserRights = null;\n\n    /**\n     * Active shop object\n     *\n     * @return\n     */\n    protected $_oEditShop = null;\n\n    /**\n     * Editable object id\n     *\n     * @var string\n     */\n    protected $_sEditObjectId = null;\n\n    /**\n     * Optional view id.\n     *\n     * @var string\n     */\n    protected $viewId = null;\n\n    /**\n     * Creates oxshop object and loads shop data, sets title of shop\n     */\n    public function __construct()\n    {\n        $myConfig = Registry::getConfig();\n        $myConfig->setConfigParam('blAdmin', true);\n        $this->setAdminMode(true);\n\n        if ($oShop = $this->getEditShop($myConfig->getShopId())) {\n            // passing shop info\n            $this->_sShopTitle = $oShop->oxshops__oxname->getRawValue();\n        }\n    }\n\n    /**\n     * Returns (cached) shop object\n     *\n     * @param object $sShopId shop id\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Shop\n     */\n    protected function getEditShop($sShopId)\n    {\n        if (!$this->_oEditShop) {\n            $this->_oEditShop = Registry::getConfig()->getActiveShop();\n            if ($this->_oEditShop->getId() != $sShopId) {\n                $oEditShop = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Shop::class);\n                if ($oEditShop->load($sShopId)) {\n                    $this->_oEditShop = $oEditShop;\n                }\n            }\n        }\n\n        return $this->_oEditShop;\n    }\n\n    /**\n     * Sets some shop configuration parameters (such as language),\n     * creates some list object (depends on subclass) and executes\n     * parent method parent::Init().\n     */\n    public function init()\n    {\n        // authorization check\n        if (!$this->authorize()) {\n            Registry::getUtils()->redirect('index.php?cl=login', true, 302);\n            exit('Authorization error occurred!');\n        }\n\n        $oLang = Registry::getLang();\n\n        // language handling\n        $this->_iEditLang = $oLang->getEditLanguage();\n        $oLang->setBaseLanguage();\n\n        parent::init();\n\n        $this->_aViewData['malladmin'] = Registry::getSession()->getVariable('malladmin');\n    }\n\n    /**\n     * Sets global parameters (such as self link, etc.) and returns modified shop object.\n     *\n     * @param object $oShop Object to modify some parameters\n     *\n     * @return object\n     */\n    public function addGlobalParams($oShop = null)\n    {\n        $myConfig = Registry::getConfig();\n        $oLang = Registry::getLang();\n\n        $oShop = parent::addGlobalParams($oShop);\n\n        if (ContainerFacade::getParameter('oxid_esales.shop_admin_url')) {\n            $url = ContainerFacade::getParameter('oxid_esales.shop_admin_url');\n        } else {\n            $url = ContainerFacade::getParameter('oxid_esales.shop_url') .\n                $myConfig->getConfigParam('sAdminDir') .\n                \"/\";\n        }\n\n        $oViewConf = $this->getViewConfig();\n        $oViewConf->setViewConfigParam(\n            'selflink',\n            Registry::getUtilsUrl()->processUrl(\n                $url .\n                'index.php?editlanguage=' .\n                $this->_iEditLang,\n                false\n            )\n        );\n        $oViewConf->setViewConfigParam(\n            'ajaxlink',\n            str_replace(\n                '&amp;',\n                '&',\n                Registry::getUtilsUrl()->processUrl(\n                    $url .\n                    'oxajax.php?editlanguage=' .\n                    $this->_iEditLang,\n                    false\n                )\n            )\n        );\n        // set language of admin backend\n        $this->_aViewData['adminlang'] = $oLang->getTplLanguage();\n        $this->_aViewData['charset'] = $this->getCharSet();\n\n        //setting active currency object\n        $this->_aViewData[\"oActCur\"] = $myConfig->getActShopCurrencyObject();\n\n        return $oShop;\n    }\n\n    /**\n     * Returns service url protocol: \"https\" is admin works in ssl mode, \"http\" if no ssl\n     *\n     * @return string\n     */\n    protected function getServiceProtocol()\n    {\n        return Registry::getConfig()->isSsl() ? 'https' : 'http';\n    }\n\n    /**\n     * Sets-up navigation parameters\n     *\n     * @param string $sNode active view id\n     */\n    protected function setupNavigation($sNode)\n    {\n        // navigation according to class\n        if ($sNode) {\n            $myAdminNavig = $this->getNavigation();\n\n            // active tab\n            $iActTab = Registry::getRequest()->getRequestEscapedParameter('actedit');\n            $iActTab = $iActTab ? $iActTab : $this->_iDefEdit;\n\n            $sActTab = $iActTab ? \"&actedit=$iActTab\" : '';\n\n            // store navigation history\n            $this->addNavigationHistory($sNode);\n\n            // list url\n            $this->_aViewData['listurl'] = $myAdminNavig->getListUrl($sNode) . $sActTab;\n\n            // edit url\n            $this->_aViewData['editurl'] = $myAdminNavig->getEditUrl($sNode, $iActTab) . $sActTab;\n        }\n    }\n\n    protected function addNavigationHistory(string $nodeId): void\n    {\n        $utilsServer = Registry::getUtilsServer();\n\n        $historyCookie = $utilsServer->getOxCookie('oxidadminhistory');\n        $history = is_string($historyCookie) ? explode('|', $historyCookie) : [];\n\n        if (!in_array($nodeId, $history, true)) {\n            $history[] = $nodeId;\n        }\n\n        $utilsServer->setOxCookie('oxidadminhistory', implode('|', $history));\n    }\n\n    /** @inheritdoc */\n    public function render()\n    {\n        $sReturn = parent::render();\n\n        $myConfig = Registry::getConfig();\n        $oLang = Registry::getLang();\n\n        // sets up navigation data\n        $this->setupNavigation(Registry::getConfig()->getRequestControllerId());\n\n        // active object id\n        $sOxId = $this->getEditObjectId();\n        $this->_aViewData['oxid'] = (!$sOxId) ? -1 : $sOxId;\n        // add Sumtype to all templates\n        $this->_aViewData['sumtype'] = $this->_aSumType;\n\n        // active shop title\n        $this->_aViewData['actshop'] = $this->_sShopTitle;\n        $this->_aViewData[\"shopid\"] = $myConfig->getShopId();\n\n        // loading active shop\n        if ($sActShopId = Registry::getSession()->getVariable('actshop')) {\n            // load object\n            $this->_aViewData['actshopobj'] = $this->getEditShop($sActShopId);\n        }\n\n        // add language data to all templates\n        $this->_aViewData['actlang'] = $iLanguage = $oLang->getBaseLanguage();\n        $this->_aViewData['editlanguage'] = $this->_iEditLang;\n        $this->_aViewData['languages'] = $oLang->getLanguageArray($iLanguage);\n\n        // setting maximum upload size\n        list($this->_aViewData['iMaxUploadFileSize'], $this->_aViewData['sMaxFormattedFileSize']) = $this->getMaxUploadFileInfo(@ini_get(\"upload_max_filesize\"));\n\n        // \"save-on-tab\"\n        if (!isset($this->_aViewData['updatelist'])) {\n            $this->_aViewData['updatelist'] = Registry::getRequest()->getRequestEscapedParameter('updatelist');\n        }\n\n        return $sReturn;\n    }\n\n    /**\n     * Returns maximum allowed size of upload file and formatted size equivalent\n     *\n     * @param string $maxFileSize recommended maximum size of file (normalu value is taken from php ini, otherwise sets 2MB)\n     * @param bool $isFormatted Return formated\n     *\n     * @return array\n     */\n    protected function getMaxUploadFileInfo(\n        $maxFileSize,\n        $isFormatted = false\n    ) {\n        $maxFileSize = $maxFileSize ? trim($maxFileSize) : '2M';\n\n        // processing config\n        $intMaxFileSize = (int)$maxFileSize;\n        $sParam = strtolower($maxFileSize[strlen($maxFileSize) - 1]);\n        switch ($sParam) {\n            case 'g':\n                $intMaxFileSize *= 1024;\n            // no break\n            case 'm':\n                $intMaxFileSize *= 1024;\n            // no break\n            case 'k':\n                $intMaxFileSize *= 1024;\n        }\n\n        // formatting\n        $markers = ['KB', 'MB', 'GB'];\n        $sFormattedMaxSize = '';\n\n        $size = floor($intMaxFileSize / 1024);\n        while ($size && current($markers)) {\n            $sFormattedMaxSize = $size . \" \" . current($markers);\n            $size = floor($size / 1024);\n            next($markers);\n        }\n\n        return [$intMaxFileSize, $sFormattedMaxSize];\n    }\n\n    /**\n     * Clears cache\n     */\n    public function save()\n    {\n        $this->resetContentCache();\n    }\n\n    /**\n     * Reset output cache\n     *\n     * @param bool $blForceReset if true, forces reset\n     */\n    public function resetContentCache($blForceReset = null)\n    {\n        $blDeleteCacheOnLogout = Registry::getConfig()->getConfigParam('blClearCacheOnLogout');\n        if (!$blDeleteCacheOnLogout || $blForceReset) {\n            Registry::getUtils()->oxResetFileCache();\n        }\n    }\n\n    /**\n     * Resets counters values from cache. Resets price category articles, category articles,\n     * vendor articles, manufacturer articles count.\n     *\n     * @param string $sCounterType counter type\n     * @param string $sValue       reset value\n     */\n    public function resetCounter($sCounterType, $sValue = null)\n    {\n        $blDeleteCacheOnLogout = Registry::getConfig()->getConfigParam('blClearCacheOnLogout');\n        $myUtilsCount = Registry::getUtilsCount();\n\n        if (!$blDeleteCacheOnLogout) {\n            switch ($sCounterType) {\n                case 'priceCatArticle':\n                    $myUtilsCount->resetPriceCatArticleCount($sValue);\n                    break;\n                case 'catArticle':\n                    $myUtilsCount->resetCatArticleCount($sValue);\n                    break;\n                case 'vendorArticle':\n                    $myUtilsCount->resetVendorArticleCount($sValue);\n                    break;\n                case 'manufacturerArticle':\n                    $myUtilsCount->resetManufacturerArticleCount($sValue);\n                    break;\n            }\n            $this->resetContentCacheAfterResetCounter();\n        }\n    }\n\n    /**\n     * Resets cache.\n     */\n    protected function resetContentCacheAfterResetCounter()\n    {\n    }\n\n    /**\n     * Checks if current $sUserId user is not an admin and checks if user is able to be edited by logged in user.\n     * This method does not perform full rights check.\n     *\n     * @param string $sUserId user id\n     *\n     * @return bool\n     */\n    protected function allowAdminEdit($sUserId)\n    {\n        return true;\n    }\n\n    /**\n     * Get english country name by country iso alpha 2 code\n     *\n     * @param string $sCountryCode Country code\n     *\n     * @return boolean\n     */\n    protected function getCountryByCode($sCountryCode)\n    {\n        //default country\n        $sCountry = 'international';\n\n        if (!empty($sCountryCode)) {\n            $aLangIds = Registry::getLang()->getLanguageIds();\n            $iEnglishId = array_search(\"en\", $aLangIds);\n            if (false !== $iEnglishId) {\n                $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n                $sViewName = $tableViewNameGenerator->getViewName(\"oxcountry\", $iEnglishId);\n                $sQ = \"select oxtitle from {$sViewName} where oxisoalpha2 = :oxisoalpha2\";\n                // Value does not change that often, reading from slave is ok here (see ESDEV-3804 and ESDEV-3822).\n                $sCountryName = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->getOne($sQ, [\n                    'oxisoalpha2' => $sCountryCode\n                ]);\n                if ($sCountryName) {\n                    $sCountry = $sCountryName;\n                }\n            } else {\n                // handling when english language is deleted\n                switch ($sCountryCode) {\n                    case 'de':\n                        return 'germany';\n                    default:\n                        return 'international';\n                }\n            }\n        }\n\n        return strtolower($sCountry);\n    }\n\n    /**\n     * performs authorization of admin user\n     *\n     * @return boolean\n     */\n    protected function authorize()\n    {\n        $session = Registry::getSession();\n        return (bool) (\n            $session->checkSessionChallenge()\n            && count(Registry::getUtilsServer()->getOxCookie())\n            && Registry::getUtils()->checkAccessRights()\n        );\n    }\n\n    /**\n     * Returns navigation object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\NavigationTree\n     */\n    public function getNavigation()\n    {\n        if (self::$_oNaviTree == null) {\n            self::$_oNaviTree = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\NavigationTree::class);\n        }\n\n        return self::$_oNaviTree;\n    }\n\n    /**\n     * Current view ID getter helps to identify navigation position\n     *\n     * @return string\n     */\n    public function getViewId()\n    {\n        $viewId = is_null($this->viewId) ? strtolower($this->getControllerKey()) : $this->viewId;\n        return $this->getNavigation()->getClassId($viewId);\n    }\n\n    /**\n     * Changing active shop\n     */\n    public function chshp()\n    {\n        $sActShop = Registry::getRequest()->getRequestEscapedParameter('shp');\n        Registry::getSession()->setVariable(\"shp\", $sActShop);\n        Registry::getSession()->setVariable('currentadminshop', $sActShop);\n    }\n\n    /**\n     * Marks seo entires as expired.\n     *\n     * @param string $sShopId Shop id\n     */\n    public function resetSeoData($sShopId)\n    {\n        $aTypes = ['oxarticle', 'oxcategory', 'oxvendor', 'oxcontent', 'dynamic', 'oxmanufacturer'];\n        $oEncoder = Registry::getSeoEncoder();\n        foreach ($aTypes as $sType) {\n            $oEncoder->markAsExpired(null, $sShopId, 1, null, \"oxtype = '{$sType}'\");\n        }\n    }\n\n    /**\n     * Returns id which is used for product preview in shop during administration\n     *\n     * @return string\n     */\n    public function getPreviewId()\n    {\n        return Registry::getUtils()->getPreviewId();\n    }\n\n    /**\n     * Returns active/editable object id\n     *\n     * @return string\n     */\n    public function getEditObjectId()\n    {\n        if (null === ($sId = $this->_sEditObjectId)) {\n            if (null === ($sId = Registry::getRequest()->getRequestEscapedParameter(\"oxid\"))) {\n                $sId = Registry::getSession()->getVariable(\"saved_oxid\");\n            }\n        }\n\n        return $sId;\n    }\n\n    /**\n     * Sets editable object id\n     *\n     * @param string $sId object id\n     */\n    public function setEditObjectId($sId)\n    {\n        $this->_sEditObjectId = $sId;\n        $this->_aViewData[\"updatelist\"] = 1;\n    }\n\n    /**\n     * Returns true if editable object is new.\n     *\n     * @return bool\n     */\n    protected function isNewEditObject()\n    {\n        return '-1' === (string) $this->getEditObjectId();\n    }\n\n    /**\n     * Get controller key also for chain extended class.\n     *\n     * @return null|string\n     */\n    protected function getControllerKey()\n    {\n        $actualClass = get_class($this);\n        $controllerKey = Registry::getControllerClassNameResolver()->getIdByClassName($actualClass);\n        if (is_null($controllerKey)) {\n            //we might not have found a class key because class is a module chain extended class\n            $controllerKey = Registry::getControllerClassNameResolver()->getIdByClassName($this->getShopParentClass());\n        }\n        return $controllerKey;\n    }\n\n    /**\n     * Method to figure out \\OxidEsales\\Eshop class.\n     *\n     * @return string\n     */\n    protected function getShopParentClass()\n    {\n        $className = get_class($this); //actual class, might be shop class chain extended by module\n        while ($className && !\\OxidEsales\\Eshop\\Core\\NamespaceInformationProvider::classBelongsToShopUnifiedNamespace($className)) {\n            $className = get_parent_class($className);\n        }\n        return $className;\n    }\n\n    //The current method would be better suited in a form validator.\n    protected function validateRequestImages(): bool\n    {\n        if (empty($_FILES['myfile'])) {\n            return true;\n        }\n\n        $fileValidator = ContainerFacade::get(FileValidatorBridgeInterface::class);\n        foreach ($_FILES['myfile']['tmp_name'] as $file) {\n            if (!$fileValidator->validateImage($file)) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AdminDetailsController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Controller\\TextEditorHandler;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\ShopVersion;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass AdminDetailsController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        $sReturn = parent::render();\n\n        // generate help link\n        $myConfig = Registry::getConfig();\n        $sDir = Path::join(\n            ContainerFacade::getParameter('oxid_esales.shop_source_directory'),\n            'documentation',\n            'admin'\n        );\n        if (is_dir($sDir)) {\n            $sDir = ContainerFacade::getParameter('oxid_esales.shop_url') . 'documentation/admin';\n        } else {\n            $languageId = $this->getDocumentationLanguageId();\n            $shopVersion = oxNew(ShopVersion::class)->getVersion();\n            $sDir = \"http://docu.oxid-esales.com/PE/{$shopVersion}/\" . $languageId . '/admin';\n        }\n\n        $this->_aViewData['sHelpURL'] = $sDir;\n\n        return $sReturn;\n    }\n\n    /**\n     * Get language id for documentation by current language id.\n     *\n     * @return int\n     */\n    protected function getDocumentationLanguageId()\n    {\n        $language = Registry::getLang();\n        $languageAbbr = $language->getLanguageAbbr($language->getTplLanguage());\n\n        return $languageAbbr === \"de\" ? 0 : 1;\n    }\n\n    /**\n     * @param \\OxidEsales\\Eshop\\Core\\Model\\BaseModel $object\n     * @param string $fieldName\n     *\n     * @return string\n     * @deprecated method will be removed in v7.0\n     */\n    protected function getEditValue($object, $fieldName)\n    {\n        if (!$object || !$fieldName || !isset($object->$fieldName)) {\n            return '';\n        }\n        if (!$object->$fieldName instanceof Field) {\n            $object->$fieldName = new Field($object->$fieldName->value, Field::T_RAW);\n        }\n\n        return $object->$fieldName->getRawValue();\n    }\n\n    /**\n     * Generates Text editor html code.\n     *\n     * @param int                                    $width      editor width\n     * @param int                                    $height     editor height\n     * @param \\OxidEsales\\Eshop\\Core\\Model\\BaseModel $object     object passed to editor\n     * @param string                                 $field      object field which content is passed to editor\n     * @param string                                 $stylesheet stylesheet to use in editor\n     *\n     * @return string Editor output\n     */\n    protected function generateTextEditor($width, $height, $object, $field, $stylesheet = null)\n    {\n        $objectValue = $this->getEditValue($object, $field);\n\n        $textEditorHandler = $this->createTextEditorHandler();\n        $this->configureTextEditorHandler($textEditorHandler, $object, $field, $stylesheet);\n\n        return $textEditorHandler->renderTextEditor($width, $height, $objectValue, $field);\n    }\n\n    /**\n     * Resets number of articles in current shop categories.\n     */\n    public function resetNrOfCatArticles()\n    {\n        // resetting categories article count cache\n        $this->resetContentCache();\n    }\n\n    /**\n     * Resets number of articles in current shop vendors.\n     */\n    public function resetNrOfVendorArticles()\n    {\n        // resetting vendors cache\n        $this->resetContentCache();\n    }\n\n    /**\n     * Resets number of articles in current shop manufacturers.\n     */\n    public function resetNrOfManufacturerArticles()\n    {\n        // resetting manufacturers cache\n        $this->resetContentCache();\n    }\n\n    /**\n     * Function creates category tree for select list used in \"Category main\", \"Article extend\" etc.\n     *\n     * @param string $sTplVarName     name of template variable where is stored category tree\n     * @param string $sEditCatId      ID of category witch we are editing\n     * @param bool   $blForceNonCache Set to true to disable caching\n     * @param int    $iTreeShopId     tree shop id\n     *\n     * @return string\n     */\n    protected function createCategoryTree($sTplVarName, $sEditCatId = '', $blForceNonCache = false, $iTreeShopId = null)\n    {\n        // caching category tree, to load it once, not many times\n        if (!isset($this->oCatTree) || $blForceNonCache) {\n            $this->oCatTree = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\CategoryList::class);\n            $this->oCatTree->setShopID($iTreeShopId);\n\n            // setting language\n            $oBase = $this->oCatTree->getBaseObject();\n            $oBase->setLanguage($this->_iEditLang);\n\n            $this->oCatTree->loadList();\n        }\n\n        // copying tree\n        $oCatTree = $this->oCatTree;\n        //removing current category\n        if ($sEditCatId && isset($oCatTree[$sEditCatId])) {\n            unset($oCatTree[$sEditCatId]);\n        }\n\n        // add first fake category for not assigned articles\n        $oRoot = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n        $oRoot->oxcategories__oxtitle = new Field('--');\n\n        $oCatTree->assign(array_merge(['' => $oRoot], $oCatTree->getArray()));\n\n        // passing to view\n        $this->_aViewData[$sTplVarName] = $oCatTree;\n\n        return $oCatTree;\n    }\n\n    /**\n     * Function creates category tree for select list used in \"Category main\", \"Article extend\" etc.\n     * Returns ID of selected category if available.\n     *\n     * @param string $sTplVarName     name of template variable where is stored category tree\n     * @param string $sSelectedCatId  ID of category witch was selected in select list\n     * @param string $sEditCatId      ID of category witch we are editing\n     * @param bool   $blForceNonCache Set to true to disable caching\n     * @param int    $iTreeShopId     tree shop id\n     *\n     * @return string\n     */\n    protected function getCategoryTree(\n        $sTplVarName,\n        $sSelectedCatId,\n        $sEditCatId = '',\n        $blForceNonCache = false,\n        $iTreeShopId = null\n    ) {\n        $oCatTree = $this->createCategoryTree($sTplVarName, $sEditCatId, $blForceNonCache, $iTreeShopId);\n\n        // mark selected\n        if ($sSelectedCatId) {\n            // fixed parent category in select list\n            foreach ($oCatTree as $oCategory) {\n                if (strcmp($oCategory->getId(), $sSelectedCatId) == 0) {\n                    $oCategory->selected = 1;\n                    break;\n                }\n            }\n        } else {\n            // no category selected - opening first available\n            $oCatTree->rewind();\n            if ($oCat = $oCatTree->current()) {\n                $oCat->selected = 1;\n                $sSelectedCatId = $oCat->getId();\n            }\n        }\n\n        // passing to view\n        $this->_aViewData[$sTplVarName] = $oCatTree;\n\n        return $sSelectedCatId;\n    }\n\n    /**\n     * Updates object folder parameters.\n     */\n    public function changeFolder()\n    {\n        $sFolder = Registry::getRequest()->getRequestEscapedParameter('setfolder');\n        $sFolderClass = Registry::getRequest()->getRequestEscapedParameter('folderclass');\n\n        if ($sFolderClass == 'oxcontent' && $sFolder == 'CMSFOLDER_NONE') {\n            $sFolder = '';\n        }\n\n        $oObject = oxNew($sFolderClass);\n        if ($oObject->load($this->getEditObjectId())) {\n            $oObject->{$oObject->getCoreTableName() . '__oxfolder'} = new Field($sFolder);\n            $oObject->save();\n        }\n    }\n\n    /**\n     * Sets-up navigation parameters.\n     *\n     * @param string $sNode active view id\n     */\n    protected function setupNavigation($sNode)\n    {\n        // navigation according to class\n        if ($sNode) {\n            $myAdminNavig = $this->getNavigation();\n\n            // default tab\n            $this->_aViewData['default_edit'] = $myAdminNavig->getActiveTab($sNode, $this->_iDefEdit);\n\n            // buttons\n            $this->_aViewData['bottom_buttons'] = $myAdminNavig->getBtn($sNode);\n        }\n    }\n\n    /**\n     * Resets count of vendor/manufacturer category items.\n     *\n     * @param array $aIds to reset type => id\n     */\n    protected function resetCounts($aIds)\n    {\n        foreach ($aIds as $sType => $aResetInfo) {\n            foreach ($aResetInfo as $sResetId => $iPos) {\n                switch ($sType) {\n                    case 'vendor':\n                        $this->resetCounter(\"vendorArticle\", $sResetId);\n                        break;\n                    case 'manufacturer':\n                        $this->resetCounter(\"manufacturerArticle\", $sResetId);\n                        break;\n                }\n            }\n        }\n    }\n\n    /**\n     * Create the handler for the text editor.\n     *\n     * Note: the parameters editedObject and field are not used here but in the enterprise edition.\n     *\n     * @param TextEditorHandler $textEditorHandler\n     * @param mixed             $editedObject      The object we want to edit, either type of\n     *                                             \\OxidEsales\\Eshop\\Core\\BaseModel if you want to persist or anything\n     *                                             else\n     * @param string            $field             The input field we want to edit\n     * @param string            $stylesheet        The name of the CSS file\n     */\n    protected function configureTextEditorHandler(\n        TextEditorHandler $textEditorHandler,\n        $editedObject,\n        $field,\n        $stylesheet\n    ) {\n        $textEditorHandler->setStyleSheet($stylesheet);\n    }\n\n    /**\n     * Create the handler for the text editor.\n     *\n     * @return TextEditorHandler The text editor handler\n     */\n    protected function createTextEditorHandler()\n    {\n        $textEditorHandler = oxNew(TextEditorHandler::class);\n\n        return $textEditorHandler;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AdminLinks.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admins links manager.\n * Sets template, that arranges two other templates (\"adminlinks_lis\"\n * and \"adminlinks_main\") to frame.\n * Admin Menu: Customer Info -> Links.\n */\nclass AdminLinks extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'admin_links';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AdminListController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse stdClass;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Admin selectlist list manager.\n */\nclass AdminListController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = null;\n\n    /**\n     * Type of list.\n     *\n     * @var string\n     */\n    protected $_sListType = 'oxlist';\n\n    /**\n     * List of objects (default null).\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    protected $_oList = null;\n\n    /**\n     * Position in list of objects (default 0).\n     *\n     * @var int\n     */\n    protected $_iCurrListPos = 0;\n\n    /**\n     * Size of object list (default 0).\n     *\n     * @var int\n     */\n    protected $_iListSize = 0;\n\n    /**\n     * Array of SQL query conditions (default null).\n     *\n     * @var array\n     */\n    protected $_aWhere = null;\n\n    /**\n     * Enable/disable sorting by DESC (SQL) (default false - disable).\n     *\n     * @var bool\n     */\n    protected $_blDesc = false;\n\n    /**\n     * Set to true to enable multi language\n     *\n     * @var bool\n     */\n    protected $_blEmployMultilanguage = null;\n\n    /**\n     * (default null).\n     *\n     * @var int\n     */\n    protected $_iOverPos = null;\n\n    /**\n     * Viewable list size\n     *\n     * @var int\n     */\n    protected $_iViewListSize = 0;\n\n    /**\n     * Viewable default list size (used in list_*.php views)\n     *\n     * @var int\n     */\n    protected $_iDefViewListSize = 50;\n\n    /**\n     * List sorting array\n     *\n     * @var array\n     */\n    protected $_aCurrSorting = null;\n\n    /**\n     * Default sorting field\n     *\n     * @var string\n     */\n    protected $_sDefSortField = null;\n\n    /**\n     * List filter array\n     *\n     * @var array\n     */\n    protected $_aListFilter = null;\n\n    /**\n     * Returns sorting fields array\n     *\n     * @return array\n     */\n    public function getListSorting()\n    {\n        if ($this->_aCurrSorting === null) {\n            $this->_aCurrSorting = Registry::getRequest()->getRequestEscapedParameter('sort');\n\n            if (!$this->_aCurrSorting && $this->_sDefSortField && ($baseObject = $this->getItemListBaseObject())) {\n                $this->_aCurrSorting[$baseObject->getCoreTableName()] = [$this->_sDefSortField => \"asc\"];\n            }\n        }\n\n        return $this->_aCurrSorting;\n    }\n\n    /**\n     * Returns list filter array\n     *\n     * @return array\n     */\n    public function getListFilter()\n    {\n        if ($this->_aListFilter === null) {\n            $request = \\OxidEsales\\Eshop\\Core\\Registry::getRequest();\n            $filter = $request->getRequestParameter(\"where\");\n            $request->checkParamSpecialChars($filter);\n\n            $this->_aListFilter = $filter;\n        }\n\n        return $this->_aListFilter;\n    }\n\n    /**\n     * Viewable list size getter\n     *\n     * @return int\n     */\n    public function getViewListSize()\n    {\n        if (!$this->_iViewListSize) {\n            $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n            if ($profile = \\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable('profile')) {\n                if (isset($profile[1])) {\n                    $config->setConfigParam('iAdminListSize', (int)$profile[1]);\n                }\n            }\n\n            $this->_iViewListSize = (int)$config->getConfigParam('iAdminListSize');\n            if (!$this->_iViewListSize) {\n                $this->_iViewListSize = 10;\n                $config->setConfigParam('iAdminListSize', $this->_iViewListSize);\n            }\n        }\n\n        return $this->_iViewListSize;\n    }\n\n    /**\n     * Viewable list size getter (used in list_*.php views)\n     *\n     * @return int\n     */\n    protected function getUserDefListSize()\n    {\n        if (!$this->_iViewListSize) {\n            if (!($viewListSize = (int)Registry::getRequest()->getRequestEscapedParameter('viewListSize'))) {\n                $viewListSize = $this->_iDefViewListSize;\n            }\n            $this->_iViewListSize = $viewListSize;\n        }\n\n        return $this->_iViewListSize;\n    }\n\n    /** @inheritdoc */\n    public function render()\n    {\n        $return = parent::render();\n\n        // assign our list\n        $this->_aViewData['mylist'] = $this->getItemList();\n\n        // set navigation parameters\n        $this->setListNavigationParams();\n\n        return $return;\n    }\n\n    /**\n     * Deletes this entry from the database\n     *\n     * @return null\n     */\n    public function deleteEntry()\n    {\n        $delete = oxNew($this->_sListClass);\n\n        //disabling deletion for derived items\n        if ($delete->isDerived()) {\n            return;\n        }\n\n        $blDelete = $delete->delete($this->getEditObjectId());\n\n        // #A - we must reset object ID\n        if ($blDelete && isset($_POST['oxid'])) {\n            $_POST['oxid'] = -1;\n        }\n\n        $this->resetContentCache();\n\n        $this->init();\n    }\n\n    /**\n     * Calculates list items count\n     *\n     * @param string $sql SQL query used co select list items\n     */\n    protected function calcListItemsCount($sql)\n    {\n        $stringModifier = Str::getStr();\n\n        // count SQL\n        $sql = $stringModifier->preg_replace('/select .* from/i', 'select count(*) from ', $sql);\n\n        // removing order by\n        $sql = $stringModifier->preg_replace('/order by .*$/i', '', $sql);\n\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        // con of list items which fits current search conditions\n        $this->_iListSize = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster()->getOne($sql);\n\n        // set it into session that other frames know about size of DB\n        \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable('iArtCnt', $this->_iListSize);\n    }\n\n    /**\n     * Set current list position\n     *\n     * @param string $page jump page string\n     */\n    protected function setCurrentListPosition($page = null)\n    {\n        $adminListSize = $this->getViewListSize();\n\n        $jumpToPage = $page ? ((int)$page) : ((int)((int)Registry::getRequest()->getRequestEscapedParameter('lstrt')) / $adminListSize);\n        $jumpToPage = ($page && $jumpToPage) ? ($jumpToPage - 1) : $jumpToPage;\n\n        $jumpToPage = $jumpToPage * $adminListSize;\n        if ($jumpToPage < 1) {\n            $jumpToPage = 0;\n        } elseif ($jumpToPage >= $this->_iListSize) {\n            $jumpToPage = floor($this->_iListSize / $adminListSize - 1) * $adminListSize;\n        }\n\n        $this->_iCurrListPos = $this->_iOverPos = (int)$jumpToPage;\n    }\n\n    /**\n     * Adds order by to SQL query string.\n     *\n     * @param string $query sql string\n     *\n     * @return string\n     */\n    protected function prepareOrderByQuery($query = null)\n    {\n        // sorting\n        $sortFields = $this->getListSorting();\n\n        if (is_array($sortFields) && count($sortFields)) {\n            // only add order by at full sql not for count(*)\n            $query .= ' order by ';\n            $addSeparator = false;\n\n            $listItem = $this->getItemListBaseObject();\n            $languageId = $listItem->isMultilang() ? $listItem->getLanguage() : \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage();\n\n            $descending = Registry::getRequest()->getRequestEscapedParameter('adminorder');\n            $descending = $descending !== null ? (bool)$descending : $this->_blDesc;\n\n            foreach ($sortFields as $table => $fieldData) {\n                $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n                $table = $table ? ($tableViewNameGenerator->getViewName($table, $languageId) . '.') : '';\n                foreach ($fieldData as $column => $sortDirectory) {\n                    $field = $table . $column;\n\n                    //add table name to column name if no table name found attached to column name\n                    $query .= ((($addSeparator) ? ', ' : '')) . \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteIdentifier($field);\n\n                    //V oxActive field search always DESC\n                    if ($descending || $column == \"oxactive\" || strcasecmp($sortDirectory, 'desc') == 0) {\n                        $query .= ' desc ';\n                    }\n\n                    $addSeparator = true;\n                }\n            }\n        }\n\n        return $query;\n    }\n\n    /**\n     * Builds and returns SQL query string.\n     *\n     * @param object $listObject list main object\n     *\n     * @return string\n     */\n    protected function buildSelectString($listObject = null)\n    {\n        return $listObject !== null ? $listObject->buildSelectString(null) : \"\";\n    }\n\n\n    /**\n     * Prepares SQL where query according SQL condition array and attaches it to SQL end.\n     * For each search value if german umlauts exist, adds them\n     * and replaced by spec. char to query\n     *\n     * @param string $fieldValue Filters\n     *\n     * @return string\n     */\n    protected function processFilter($fieldValue)\n    {\n        $stringModifier = Str::getStr();\n\n        //removing % symbols\n        $fieldValue = $stringModifier->preg_replace(\"/^%|%$/\", \"\", trim($fieldValue));\n\n        return $stringModifier->preg_replace(\"/\\s+/\", \" \", $fieldValue);\n    }\n\n    /**\n     * Builds part of SQL query\n     *\n     * @param string $value         filter value\n     * @param bool   $isSearchValue filter value type, true means surrount search key with '%'\n     *\n     * @return string\n     */\n    protected function buildFilter($value, $isSearchValue)\n    {\n        if ($isSearchValue) {\n            //is search string, using LIKE\n            $query = \" like \" . \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quote('%' . $value . '%') . \" \";\n        } else {\n            //not search string, values must be equal\n            $query = \" = \" . \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quote($value) . \" \";\n        }\n\n        return $query;\n    }\n\n    /**\n     * Checks if filter contains wildcards like %\n     *\n     * @param string $fieldValue filter value\n     *\n     * @return bool\n     */\n    protected function isSearchValue($fieldValue)\n    {\n        return (Str::getStr()->preg_match('/^%/', $fieldValue) && Str::getStr()->preg_match('/%$/', $fieldValue));\n    }\n\n    /**\n     * Prepares SQL where query according SQL condition array and attaches it to SQL end.\n     * For each search value if german umlauts exist, adds them\n     * and replaced by spec. char to query\n     *\n     * @param array  $whereQuery SQL condition array\n     * @param string $fullQuery  SQL query string\n     *\n     * @return string\n     */\n    protected function prepareWhereQuery($whereQuery, $fullQuery)\n    {\n        if (is_array($whereQuery) && count($whereQuery)) {\n            $myUtilsString = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsString();\n            foreach ($whereQuery as $identifierName => $fieldValue) {\n                $fieldValue = trim($fieldValue);\n\n                //check if this is search string (contains % sign at beginning and end of string)\n                $isSearchValue = $this->isSearchValue($fieldValue);\n\n                //removing % symbols\n                $fieldValue = $this->processFilter($fieldValue);\n\n                if (strlen($fieldValue)) {\n                    $values = explode(' ', $fieldValue);\n\n                    //for each search field using AND action\n                    $queryBoolAction = ' and (';\n\n                    foreach ($values as $value) {\n                        // trying to search spec chars in search value\n                        // if found, add cleaned search value to search sql\n                        $uml = $myUtilsString->prepareStrForSearch($value);\n                        if ($uml) {\n                            $queryBoolAction .= '(';\n                        }\n\n                        $quotedIdentifierName = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteIdentifier($identifierName);\n                        $fullQuery .= \" {$queryBoolAction} {$quotedIdentifierName} \";\n\n                        //for search in same field for different values using AND\n                        $queryBoolAction = ' and ';\n\n                        $fullQuery .= $this->buildFilter($value, $isSearchValue);\n\n                        if ($uml) {\n                            $fullQuery .= \" or {$quotedIdentifierName} \";\n\n                            $fullQuery .= $this->buildFilter($uml, $isSearchValue);\n                            $fullQuery .= ')'; // end of OR section\n                        }\n                    }\n\n                    // end for AND action\n                    $fullQuery .= ' ) ';\n                }\n            }\n        }\n\n        return $fullQuery;\n    }\n\n    /**\n     * Override this for individual search in admin.\n     *\n     * @param string $query SQL select to change\n     *\n     * @return string\n     */\n    protected function changeselect($query)\n    {\n        return $query;\n    }\n\n    /**\n     * Builds and returns array of SQL WHERE conditions.\n     *\n     * @return array\n     */\n    public function buildWhere()\n    {\n        if ($this->_aWhere === null && ($list = $this->getItemList())) {\n            $this->_aWhere = [];\n            $filter = $this->getListFilter();\n            if (is_array($filter)) {\n                $listItem = $this->getItemListBaseObject();\n                $languageId = $listItem->isMultilang() ? $listItem->getLanguage() : \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage();\n                $localDateFormat = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('sLocalDateFormat');\n\n                foreach ($filter as $table => $filterData) {\n                    foreach ($filterData as $name => $value) {\n                        if ($value || '0' === (string)$value) {\n                            $field = \"{$table}__{$name}\";\n\n                            // if no table name attached to field name, add it\n                            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n                            $name = $table ? $tableViewNameGenerator->getViewName($table, $languageId) . \".{$name}\" : $name;\n\n                            // #M1260: if field is date\n                            if ($localDateFormat && $localDateFormat != 'ISO' && isset($listItem->$field)) {\n                                $fieldType = $listItem->{$field}->fldtype;\n                                if (\"datetime\" == $fieldType || \"date\" == $fieldType) {\n                                    $value = $this->convertToDBDate($value, $fieldType);\n                                }\n                            }\n\n                            $this->_aWhere[$name] = \"%{$value}%\";\n                        }\n                    }\n                }\n            }\n        }\n\n        return $this->_aWhere;\n    }\n\n    /**\n     * Converts date/datetime values to DB scheme (#M1260)\n     *\n     * @param string $value     Field value\n     * @param string $fieldType Field type\n     *\n     * @return string\n     */\n    protected function convertToDBDate($value, $fieldType)\n    {\n        $convertedObject = new \\OxidEsales\\Eshop\\Core\\Field();\n        $convertedObject->setValue($value);\n        if ($fieldType == \"datetime\") {\n            if (strlen($value) == 10 || strlen($value) == 22 || (strlen($value) == 19 && !stripos($value, \"m\"))) {\n                \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->convertDBDateTime($convertedObject, true);\n            } else {\n                if (strlen($value) > 10) {\n                    return $this->convertTime($value);\n                } else {\n                    return $this->convertDate($value);\n                }\n            }\n        } elseif ($fieldType == \"date\") {\n            if (strlen($value) == 10) {\n                \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->convertDBDate($convertedObject, true);\n            } else {\n                return $this->convertDate($value);\n            }\n        }\n\n        return $convertedObject->value;\n    }\n\n    /**\n     * Converter for date field search. If not full date will be searched.\n     *\n     * @param string $date searched date\n     *\n     * @return string\n     */\n    protected function convertDate($date)\n    {\n        // regexps to validate input\n        $datePatterns = [\n            \"/^([0-9]{2})\\.([0-9]{4})/\" => \"EUR2\", // MM.YYYY\n            \"/^([0-9]{2})\\.([0-9]{2})/\" => \"EUR1\", // DD.MM\n            \"/^([0-9]{2})\\/([0-9]{4})/\" => \"USA2\", // MM.YYYY\n            \"/^([0-9]{2})\\/([0-9]{2})/\" => \"USA1\" // DD.MM\n        ];\n\n        // date/time formatting rules\n        $dateFormats = [\n            \"EUR1\" => [2, 1],\n            \"EUR2\" => [2, 1],\n            \"USA1\" => [1, 2],\n            \"USA2\" => [2, 1]\n        ];\n\n        // looking for date field\n        $dateMatches = [];\n        $stringModifier = Str::getStr();\n        foreach ($datePatterns as $pattern => $type) {\n            if ($stringModifier->preg_match($pattern, $date, $dateMatches)) {\n                $date = $dateMatches[$dateFormats[$type][0]] . \"-\" . $dateMatches[$dateFormats[$type][1]];\n                break;\n            }\n        }\n\n        return $date;\n    }\n\n    /**\n     * Converter for datetime field search. If not full time will be searched.\n     *\n     * @param string $fullDate searched date\n     *\n     * @return string\n     */\n    protected function convertTime($fullDate)\n    {\n        $date = substr($fullDate, 0, 10);\n        $convertedObject = new \\OxidEsales\\Eshop\\Core\\Field();\n        $convertedObject->setValue($date);\n        \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->convertDBDate($convertedObject, true);\n        $stringModifier = Str::getStr();\n\n        // looking for time field\n        $time = substr($fullDate, 11);\n        if ($stringModifier->preg_match(\"/([0-9]{2}):([0-9]{2}) ([AP]{1}[M]{1})$/\", $time, $timeMatches)) {\n            if ($timeMatches[3] == \"PM\") {\n                $intVal = (int)$timeMatches[1];\n                if ($intVal < 13) {\n                    $time = ($intVal + 12) . \":\" . $timeMatches[2];\n                }\n            } else {\n                $time = $timeMatches[1] . \":\" . $timeMatches[2];\n            }\n        } elseif ($stringModifier->preg_match(\"/([0-9]{2}) ([AP]{1}[M]{1})$/\", $time, $timeMatches)) {\n            if ($timeMatches[2] == \"PM\") {\n                $intVal = (int)$timeMatches[1];\n                if ($intVal < 13) {\n                    $time = ($intVal + 12);\n                }\n            } else {\n                $time = $timeMatches[1];\n            }\n        } else {\n            $time = str_replace(\".\", \":\", $time);\n        }\n\n        return $convertedObject->value . \" \" . $time;\n    }\n\n    /**\n     * Set parameters needed for list navigation\n     */\n    protected function setListNavigationParams()\n    {\n        // list navigation\n        $showNavigation = false;\n        $adminListSize = $this->getViewListSize();\n        if ($this->_iListSize > $adminListSize) {\n            // yes, we need to build the navigation object\n            $pageNavigation = new stdClass();\n            $pageNavigation->pages = round((($this->_iListSize - 1) / $adminListSize) + 0.5, 0);\n            $pageNavigation->actpage = round(\n                ($this->_iCurrListPos / $adminListSize) + 0.5,\n                0\n            );\n            $pageNavigation->lastlink = ($pageNavigation->pages - 1) * $adminListSize;\n            $pageNavigation->nextlink = null;\n            $pageNavigation->backlink = null;\n\n            $position = $this->_iCurrListPos + $adminListSize;\n            if ($position < $this->_iListSize) {\n                $pageNavigation->nextlink = $position = $this->_iCurrListPos + $adminListSize;\n            }\n\n            if (($this->_iCurrListPos - $adminListSize) >= 0) {\n                $pageNavigation->backlink = $position = $this->_iCurrListPos - $adminListSize;\n            }\n\n            // calculating list start position\n            $start = $pageNavigation->actpage - 5;\n            $start = ($start <= 0) ? 1 : $start;\n\n            // calculating list end position\n            $end = $pageNavigation->actpage + 5;\n            $end = ($end < $start + 10) ? $start + 10 : $end;\n            $end = ($end > $pageNavigation->pages) ? $pageNavigation->pages : $end;\n\n            // once again adjusting start pos ..\n            $start = ($end - 10 > 0) ? $end - 10 : $start;\n            $start = ($pageNavigation->pages <= 11) ? 1 : $start;\n\n            // navigation urls\n            for ($i = $start; $i <= $end; $i++) {\n                $page = new stdclass();\n                $page->selected = 0;\n                if ($i == $pageNavigation->actpage) {\n                    $page->selected = 1;\n                }\n                $pageNavigation->changePage[$i] = $page;\n            }\n\n            $this->_aViewData['pagenavi'] = $pageNavigation;\n\n            if (isset($this->_iOverPos)) {\n                $position = $this->_iOverPos;\n                $this->_iOverPos = null;\n            } else {\n                $position = Registry::getRequest()->getRequestEscapedParameter('lstrt');\n            }\n\n            if (!$position) {\n                $position = 0;\n            }\n\n            $this->_aViewData['lstrt'] = $position;\n            $this->_aViewData['listsize'] = $this->_iListSize;\n            $showNavigation = true;\n        }\n\n        // determine not used space in List\n        $listSizeToShow = $this->_iListSize - $this->_iCurrListPos;\n        $adminListSize = $this->getViewListSize();\n        $notUsed = $adminListSize - min($listSizeToShow, $adminListSize);\n        $space = $notUsed * 15;\n\n        if (!$showNavigation) {\n            $space += 20;\n        }\n\n        $this->_aViewData['iListFillsize'] = $space;\n    }\n\n    /**\n     * Sets-up navigation parameters\n     *\n     * @param string $node active view id\n     */\n    protected function setupNavigation($node)\n    {\n        // navigation according to class\n        if ($node) {\n            $adminNavigation = $this->getNavigation();\n\n            $objectId = $this->getEditObjectId();\n\n            if ($objectId == -1) {\n                //on first call or when pressed creating new item button, resetting active tab\n                $activeTab = $this->_iDefEdit;\n            } else {\n                // active tab\n                $activeTab = Registry::getRequest()->getRequestEscapedParameter('actedit');\n                $activeTab = $activeTab ? $activeTab : $this->_iDefEdit;\n            }\n\n            // tabs\n            $this->_aViewData['editnavi'] = $adminNavigation->getTabs($node, $activeTab);\n\n            // active tab\n            $this->_aViewData['actlocation'] = $adminNavigation->getActiveTab($node, $activeTab);\n\n            // default tab\n            $this->_aViewData['default_edit'] = $adminNavigation->getActiveTab($node, $this->_iDefEdit);\n\n            // assign active tab number\n            $this->_aViewData['actedit'] = $activeTab;\n        }\n    }\n\n    /**\n     * Returns items list\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    public function getItemList()\n    {\n        if ($this->_oList === null && $this->_sListClass) {\n            $this->_oList = oxNew($this->_sListType);\n            $this->_oList->clear();\n            $this->_oList->init($this->_sListClass);\n\n            $where = $this->buildWhere();\n\n            $listObject = $this->_oList->getBaseObject();\n\n            \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable('tabelle', $this->_sListClass);\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $this->_aViewData['listTable'] = $tableViewNameGenerator->getViewName($listObject->getCoreTableName());\n            \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->setGlobalParameter('ListCoreTable', $listObject->getCoreTableName());\n\n            if ($listObject->isMultilang()) {\n                // is the object multilingual?\n                /** @var \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel $listObject */\n                $listObject->setLanguage(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage());\n\n                if (isset($this->_blEmployMultilanguage)) {\n                    $listObject->setEnableMultilang($this->_blEmployMultilanguage);\n                }\n            }\n\n            $query = $this->buildSelectString($listObject);\n            $query = $this->prepareWhereQuery($where, $query);\n            $query = $this->prepareOrderByQuery($query);\n            $query = $this->changeselect($query);\n\n            // calculates count of list items\n            $this->calcListItemsCount($query);\n\n            // setting current list position (page)\n            $this->setCurrentListPosition(Registry::getRequest()->getRequestEscapedParameter('jumppage'));\n\n            // setting addition params for list: current list size\n            $this->_oList->setSqlLimit($this->_iCurrListPos, $this->getViewListSize());\n\n            $this->_oList->selectString($query);\n        }\n\n        return $this->_oList;\n    }\n\n    /**\n     * Clear items list\n     */\n    public function clearItemList()\n    {\n        $this->_oList = null;\n    }\n\n    /**\n     * Returns item list base object\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\BaseModel|null\n     */\n    public function getItemListBaseObject()\n    {\n        $baseObject = null;\n        if (($itemsList = $this->getItemList())) {\n            $baseObject = $itemsList->getBaseObject();\n        }\n\n        return $baseObject;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AdminNewsletter.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\Bridge\\NewsletterRecipientsDaoBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\DataMapper\\NewsletterRecipientsDataMapperInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\FileGenerator\\Bridge\\FileGeneratorBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Header\\Bridge\\HeaderGeneratorBridgeInterface;\n\n/**\n * Admin newsletter manager.\n * Returns template, that arranges template (\"newsletter\") to frame.\n * Admin Menu: Customer Info -> Newsletter.\n */\nclass AdminNewsletter extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'newsletter';\n\n    public function export(): void\n    {\n        $newsletterRecipientsList = $this->getNewsLetterRecipientsList();\n        $this->setCSVHeader();\n        $this->generateCSV($newsletterRecipientsList);\n\n        $oUtils = Registry::getUtils();\n        $oUtils->showMessageAndExit(\"\");\n    }\n\n    private function getNewsLetterRecipientsList(): array\n    {\n        return ContainerFacade::get(NewsletterRecipientsDaoBridgeInterface::class)\n            ->getNewsletterRecipients(\n                ContainerFacade::get(ContextInterface::class)\n                    ->getCurrentShopId()\n            );\n    }\n\n    private function setCSVHeader(): void\n    {\n        ContainerFacade::get(HeaderGeneratorBridgeInterface::class)\n            ->generate(\n                'Export_user_recipient_status_' . date('Y-m-d') . '.csv'\n            );\n    }\n\n    /**\n     * @param array $data\n     */\n    private function generateCSV(array $data): void\n    {\n        ContainerFacade::get(FileGeneratorBridgeInterface::class)\n            ->generate(\n                'php://output',\n                ContainerFacade::get(NewsletterRecipientsDataMapperInterface::class)\n                    ->mapRecipientListDataToArray($data)\n            );\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AdminOrder.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin order manager.\n * Returns template, that arranges two other templates (\"order_list\"\n * and \"order_overview\") to frame.\n * Admin Menu: Orders -> Display Orders.\n */\nclass AdminOrder extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'order';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AdminPayment.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin payment manager.\n * Returns template, that arranges two other templates (\"payment_list\"\n * and \"payment_main\") to frame.\n * Admin Menu: Shop Settings -> Payment Methods.\n */\nclass AdminPayment extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'admin_payment';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AdminPriceAlarm.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin admin_pricealarm manager.\n * Returns template, that arranges two other templates (\"apricealarm_list\"\n * and \"pricealarm_main\") to frame.\n * Admin Menu: Customer Info -> admin_pricealarm.\n */\nclass AdminPriceAlarm extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Default active tab number\n     *\n     * @var int\n     */\n    protected $_iDefEdit = 1;\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'admin_pricealarm';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AdminStart.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin starting page.\n * Returns template, that consists with title-admin-page.\n * Starting admin menu window.\n * Admin Menu.\n */\nclass AdminStart extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'start';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AdminUser.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin user manager.\n * Returns template, that arranges two other templates (\"user_list\"\n * and \"user_main\") to frame.\n * Admin Menu: User Administration -> Users.\n */\nclass AdminUser extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'user';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AdminWrapping.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin user manager.\n * Returns template, that arranges two other templates (\"user_list\"\n * and \"user_main\") to frame.\n * Admin Menu: User Administration -> Users.\n */\nclass AdminWrapping extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'wrapping';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AdminlinksList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin links collection.\n * Collects list of admin links. Links may be viewed by language, sorted by date,\n * url or any keyword.\n * Admin Menu: Customer Info -> Links.\n */\nclass AdminlinksList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'adminlinks_list';\n\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxlinks';\n\n    /**\n     * Default SQL sorting parameter (default null).\n     *\n     * @var string\n     */\n    protected $_sDefSortField = 'oxinsert';\n\n    /**\n     * Returns sorting fields array\n     *\n     * @return array\n     */\n    public function getListSorting()\n    {\n        $aSorting = parent::getListSorting();\n        if (isset($aSorting[\"oxlinks\"][$this->_sDefSortField])) {\n            $this->_blDesc = true;\n        }\n\n        return $aSorting;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AdminlinksMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse stdClass;\n\n/**\n * Admin links details manager.\n * Creates form for submitting new admin links or modifying old ones.\n * Admin Menu: Customer Info -> Links.\n */\nclass AdminlinksMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Sets link information data (or leaves empty), returns name of template\n     * file \"adminlinks_main\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $oLinks = oxNew(\n            \\OxidEsales\\Eshop\\Application\\Model\\Links::class,\n            $tableViewNameGenerator->getViewName('oxlinks')\n        );\n\n        if (isset($soxId) && $soxId != \"-1\") {\n            $oLinks->loadInLang($this->_iEditLang, $soxId);\n\n            $oOtherLang = $oLinks->getAvailableInLangs();\n            if (!isset($oOtherLang[$this->_iEditLang])) {\n                $oLinks->loadInLang(key($oOtherLang), $soxId);\n            }\n            $this->_aViewData[\"edit\"] = $oLinks;\n\n            //Disable editing for derived items\n            if ($oLinks->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n\n            // remove already created languages\n            $this->_aViewData[\"posslang\"] = array_diff(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->getLanguageNames(), $oOtherLang);\n\n            foreach ($oOtherLang as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $oLang;\n            }\n        }\n\n        // generate editor\n        $this->_aViewData[\"editor\"] = $this->generateTextEditor(\n            \"100%\",\n            255,\n            $oLinks,\n            \"oxlinks__oxurldesc\",\n            \"links.css\"\n        );\n\n        return \"adminlinks_main\";\n    }\n\n    /**\n     * Saves information about link (active, date, URL, description, etc.) to DB.\n     *\n     * @return mixed\n     */\n    public function save()\n    {\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n        // checkbox handling\n        if (!isset($aParams['oxlinks__oxactive'])) {\n            $aParams['oxlinks__oxactive'] = 0;\n        }\n\n        // adds space to the end of URL description to keep new added links visible\n        // if URL description left empty\n        if (isset($aParams['oxlinks__oxurldesc']) && strlen($aParams['oxlinks__oxurldesc']) == 0) {\n            $aParams['oxlinks__oxurldesc'] .= \" \";\n        }\n\n        if (!$aParams['oxlinks__oxinsert']) {\n            // sets default (?) date format to output\n            // else if possible - changes date format to system compatible\n            $sDate = date(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString(\"simpleDateFormat\"));\n            if ($sDate == \"simpleDateFormat\") {\n                $aParams['oxlinks__oxinsert'] = date(\"Y-m-d\");\n            } else {\n                $aParams['oxlinks__oxinsert'] = $sDate;\n            }\n        }\n\n        $iEditLanguage = Registry::getRequest()->getRequestEscapedParameter(\"editlanguage\");\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $oLinks = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Links::class, $tableViewNameGenerator->getViewName('oxlinks'));\n\n        if ($soxId != \"-1\") {\n            //$oLinks->load( $soxId );\n            $oLinks->loadInLang($iEditLanguage, $soxId);\n\n            //Disable editing for derived items\n            if ($oLinks->isDerived()) {\n                return;\n            }\n        } else {\n            $aParams['oxlinks__oxid'] = null;\n        }\n\n        //$aParams = $oLinks->ConvertNameArray2Idx( $aParams);\n\n        $oLinks->setLanguage(0);\n        $oLinks->assign($aParams);\n        $oLinks->setLanguage($iEditLanguage);\n        $oLinks->save();\n\n        parent::save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oLinks->getId());\n    }\n\n    /**\n     * Saves link description in different languages (eg. english).\n     *\n     * @return null\n     */\n    public function saveinnlang()\n    {\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n        // checkbox handling\n        if (!isset($aParams['oxlinks__oxactive'])) {\n            $aParams['oxlinks__oxactive'] = 0;\n        }\n\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $oLinks = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Links::class, $tableViewNameGenerator->getViewName('oxlinks'));\n        $iEditLanguage = Registry::getRequest()->getRequestEscapedParameter(\"editlanguage\");\n\n        if ($soxId != \"-1\") {\n            $oLinks->loadInLang($iEditLanguage, $soxId);\n        } else {\n            $aParams['oxlinks__oxid'] = null;\n            //$aParams = $oLinks->ConvertNameArray2Idx( $aParams);\n        }\n\n        //Disable editing for derived items\n        if ($oLinks->isDerived()) {\n            return;\n        }\n\n        $oLinks->setLanguage(0);\n        $oLinks->assign($aParams);\n\n        // apply new language\n        $oLinks->setLanguage(Registry::getRequest()->getRequestEscapedParameter(\"new_lang\"));\n        $oLinks->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oLinks->getId());\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticleAccessoriesAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Exception\\DatabaseConnectionException;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Model\\ListModel;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class controls article assignment to accessories\n */\nclass ArticleAccessoriesAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * If true extended column selection will be build\n     *\n     * @var bool\n     */\n    protected $_blAllowExtColumns = true;\n\n    /**\n     * Container ID\n     *\n     * @var string\n     */\n    private $containerId;\n\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,         visible, multilanguage, ident\n                                              ['oxartnum', 'oxarticles', 1, 0, 0],\n                                              ['oxtitle', 'oxarticles', 1, 1, 0],\n                                              ['oxean', 'oxarticles', 1, 0, 0],\n                                              ['oxmpn', 'oxarticles', 0, 0, 0],\n                                              ['oxprice', 'oxarticles', 0, 0, 0],\n                                              ['oxstock', 'oxarticles', 0, 0, 0],\n                                              ['oxid', 'oxarticles', 0, 0, 1]\n    ],\n                            'container2' => [\n                                ['oxartnum', 'oxarticles', 1, 0, 0],\n                                ['oxtitle', 'oxarticles', 1, 1, 0],\n                                ['oxsort', 'oxaccessoire2article', 1, 1, 0],\n                                ['oxean', 'oxarticles', 1, 0, 0],\n                                ['oxmpn', 'oxarticles', 0, 0, 0],\n                                ['oxprice', 'oxarticles', 0, 0, 0],\n                                ['oxstock', 'oxarticles', 0, 0, 0],\n                                ['oxid', 'oxaccessoire2article', 0, 0, 1]\n                            ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     * @throws DatabaseConnectionException\n     */\n    protected function getQuery()\n    {\n        $myConfig = Registry::getConfig();\n        $oxidId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $synchId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n        $db = DatabaseProvider::getDb();\n        $this->containerId = Registry::getRequest()->getRequestEscapedParameter('cmpid');\n\n        $articleTable = $this->getViewName('oxarticles');\n        $object2categoryTable = $this->getViewName('oxobject2category');\n\n        // category selected or not ?\n        if (!$oxidId) {\n            $outputQuery = \" from {$articleTable} where 1 \";\n            $outputQuery .= $myConfig->getConfigParam('blVariantsSelection') ? '' : \" and {$articleTable}.oxparentid = '' \";\n        } else {\n            // selected category ?\n            if ($synchId && $oxidId != $synchId) {\n                $blVariantsSelectionParameter = $myConfig->getConfigParam('blVariantsSelection');\n                $trueResponse = \" ( {$articleTable}.oxid=$object2categoryTable.oxobjectid \" .\n                                \"or {$articleTable}.oxparentid=$object2categoryTable.oxobjectid )\";\n                $failResponse = \" {$articleTable}.oxid=$object2categoryTable.oxobjectid \";\n                $variantSelectionSql = $blVariantsSelectionParameter ? $trueResponse : $failResponse;\n\n                $outputQuery = \" from $object2categoryTable left join {$articleTable} on {$variantSelectionSql}\" .\n                               \" where $object2categoryTable.oxcatnid = \" . $db->quote($oxidId) . \" \";\n            } else {\n                $outputQuery = \" from oxaccessoire2article left join {$articleTable} \" .\n                               \"on oxaccessoire2article.oxobjectid={$articleTable}.oxid \" .\n                               \" where oxaccessoire2article.oxarticlenid = \" . $db->quote($oxidId) . \" \";\n            }\n        }\n\n        if ($synchId && $synchId != $oxidId) {\n            // performance\n            $subSelect = ' select oxaccessoire2article.oxobjectid from oxaccessoire2article ';\n            $subSelect .= \" where oxaccessoire2article.oxarticlenid = \" . $db->quote($synchId) . \" \";\n            $outputQuery .= \" and {$articleTable}.oxid not in ( $subSelect )\";\n        }\n\n        // skipping self from list\n        $sId = ($synchId) ? $synchId : $oxidId;\n        $outputQuery .= \" and {$articleTable}.oxid != \" . $db->quote($sId) . \" \";\n\n        // creating AJAX component\n        return $outputQuery;\n    }\n\n\n    /**\n     * overide default sorting and replace it with OXSORT field\n     *\n     * @return string\n     */\n    protected function getSorting()\n    {\n        if ($this->containerId == 'container2') {\n            return ' order by _2,_0';\n        } else {\n            return ' order by _' . $this->getSortCol() . ' ' . $this->getSortDir() . ' ';\n        }\n    }\n\n    /**\n     * Removing article form accessories article list\n     */\n    public function removeArticleAcc()\n    {\n        $aChosenArt = $this->getActionIds('oxaccessoire2article.oxid');\n        // removing all\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxaccessoire2article.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif (is_array($aChosenArt)) {\n            $sChosenArticles = implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aChosenArt));\n            $sQ = \"delete from oxaccessoire2article where oxaccessoire2article.oxid in ({$sChosenArticles}) \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adding article to accessories article list\n     */\n    public function addArticleAcc()\n    {\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $aChosenArt = $this->getActionIds('oxarticles.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // adding\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sArtTable = $this->getViewName('oxarticles');\n            $aChosenArt = $this->getAll(parent::addFilter(\"select $sArtTable.oxid \" . $this->getQuery()));\n        }\n\n        if ($oArticle->load($soxId) && $soxId && $soxId != \"-1\" && is_array($aChosenArt)) {\n            foreach ($aChosenArt as $sChosenArt) {\n                $oNewGroup = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oNewGroup->init(\"oxaccessoire2article\");\n                $oNewGroup->oxaccessoire2article__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sChosenArt);\n                $oNewGroup->oxaccessoire2article__oxarticlenid = new \\OxidEsales\\Eshop\\Core\\Field($oArticle->oxarticles__oxid->value);\n                $oNewGroup->oxaccessoire2article__oxsort = new \\OxidEsales\\Eshop\\Core\\Field(0);\n                $oNewGroup->save();\n            }\n\n            $this->onArticleAccessoryRelationChange($oArticle);\n        }\n    }\n\n    /**\n     * Method is used to bind to accessory addition to article action.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $article\n     */\n    protected function onArticleAccessoryRelationChange($article)\n    {\n    }\n\n\n    /**\n     * Applies sorting for Accessories list\n     */\n    public function sortAccessoriesList()\n    {\n        $oxidRelationId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $selectedIdForSort = Registry::getRequest()->getRequestEscapedParameter('sortoxid');\n        $sortDirection = Registry::getRequest()->getRequestEscapedParameter('direction');\n\n        $accessoriesList = oxNew(ListModel::class);\n        $accessoriesList->init(\"oxbase\", \"oxaccessoire2article\");\n        $sortQuery = \"select * from  oxaccessoire2article where OXARTICLENID = :OXARTICLENID order by oxsort,oxid\";\n        $accessoriesList->selectString($sortQuery, [\n            'OXARTICLENID' => $oxidRelationId\n        ]);\n\n\n        $rebuildList = $this->rebuildAccessoriesSortIndexes($accessoriesList);\n\n        if (($selectedPosition = array_search($selectedIdForSort, $rebuildList)) !== false) {\n            $selectedSortRecord = $accessoriesList->offsetGet($rebuildList[$selectedPosition]);\n            $currentPosition = $selectedSortRecord->oxaccessoire2article__oxsort->value;\n\n            // get current selected row sort position\n\n            if (($sortDirection == 'up' && $currentPosition > 0) || ($sortDirection == 'down' && $currentPosition < count($rebuildList) - 1)) {\n                $newPosition = ($sortDirection == 'up') ? ($currentPosition - 1) : ($currentPosition + 1);\n\n                // exchanging indexes\n                $currentRecord = $accessoriesList->offsetGet($rebuildList[$currentPosition]);\n                $newRecord = $accessoriesList->offsetGet($rebuildList[$newPosition]);\n\n                $currentRecord->oxaccessoire2article__oxsort = new Field($newPosition);\n                $newRecord->oxaccessoire2article__oxsort = new Field($currentPosition);\n                $currentRecord->save();\n                $newRecord->save();\n            }\n        }\n\n        $outputQuery = $this->getQuery();\n\n        $normalQuery = 'select ' . $this->getQueryCols() . $outputQuery;\n        $countQuery = 'select count( * ) ' . $outputQuery;\n\n        $this->outputResponse($this->getData($countQuery, $normalQuery));\n    }\n\n\n    /**\n     * rebuild Accessories sort indexes\n     *\n     * @param ListModel $inputList\n     *\n     * @return array\n     */\n    private function rebuildAccessoriesSortIndexes(ListModel $inputList): array\n    {\n        $counter = 0;\n        $outputList = [];\n        foreach ($inputList as $key => $value) {\n            if (isset($value->oxaccessoire2article__oxsort)) {\n                if ($value->oxaccessoire2article__oxsort->value != $counter) {\n                    $value->oxaccessoire2article__oxsort = new Field($counter);\n                    $value->save();\n                }\n            }\n            $outputList[$counter] = $key;\n            $counter++;\n        }\n\n        return $outputList;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticleAttribute.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin article attributes/selections lists manager.\n * Collects available attributes/selections lists for chosen article, may add\n * or remove any of them to article, etc.\n * Admin Menu: Manage Products -> Articles -> Selection.\n */\nclass ArticleAttribute extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $this->_aViewData['edit'] = $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n\n        $soxId = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oArticle->load($soxId);\n\n            if ($oArticle->isDerived()) {\n                $this->_aViewData[\"readonly\"] = true;\n            }\n        }\n\n        $iAoc = Registry::getRequest()->getRequestEscapedParameter(\"aoc\");\n        if ($iAoc == 1) {\n            $oArticleAttributeAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleAttributeAjax::class);\n            $this->_aViewData['oxajax'] = $oArticleAttributeAjax->getColumns();\n\n            return \"popups/article_attribute\";\n        } elseif ($iAoc == 2) {\n            $oArticleSelectionAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleSelectionAjax::class);\n            $this->_aViewData['oxajax'] = $oArticleSelectionAjax->getColumns();\n\n            return \"popups/article_selection\";\n        }\n\n        return \"article_attribute\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticleAttributeAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class controls article assignment to attributes\n */\nclass ArticleAttributeAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,         visible, multilanguage, ident\n        ['oxtitle', 'oxattribute', 1, 1, 0],\n        ['oxid', 'oxattribute', 0, 0, 1]\n    ],\n                                 'container2' => [\n                                     ['oxtitle', 'oxattribute', 1, 1, 0],\n                                     ['oxid', 'oxobject2attribute', 0, 0, 1],\n                                     ['oxvalue', 'oxobject2attribute', 0, 1, 1],\n                                     ['oxattrid', 'oxobject2attribute', 0, 0, 1],\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $oDb = DatabaseProvider::getDb();\n        $sArtId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchArtId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        $sAttrViewName = $this->getViewName('oxattribute');\n        $sO2AViewName = $this->getViewName('oxobject2attribute');\n        if ($sArtId) {\n            // all categories article is in\n            $sQAdd = \" from {$sO2AViewName} left join {$sAttrViewName} \" .\n                     \"on {$sAttrViewName}.oxid={$sO2AViewName}.oxattrid \" .\n                     \" where {$sO2AViewName}.oxobjectid = \" . $oDb->quote($sArtId) . \" \";\n        } else {\n            $sQAdd = \" from {$sAttrViewName} where {$sAttrViewName}.oxid not in ( select {$sO2AViewName}.oxattrid \" .\n                     \"from {$sO2AViewName} left join {$sAttrViewName} \" .\n                     \"on {$sAttrViewName}.oxid={$sO2AViewName}.oxattrid \" .\n                     \" where {$sO2AViewName}.oxobjectid = \" . $oDb->quote($sSynchArtId) . \" ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes article attributes.\n     */\n    public function removeAttr()\n    {\n        $aChosenArt = $this->getActionIds('oxobject2attribute.oxid');\n        $sOxid = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sO2AViewName = $this->getViewName('oxobject2attribute');\n            $sQ = $this->addFilter(\"delete $sO2AViewName.* \" . $this->getQuery());\n            DatabaseProvider::getDb()->Execute($sQ);\n        } elseif (is_array($aChosenArt)) {\n            $sChosenArticles = implode(\", \", DatabaseProvider::getDb()->quoteArray($aChosenArt));\n            $sQ = \"delete from oxobject2attribute where oxobject2attribute.oxid in ({$sChosenArticles}) \";\n            DatabaseProvider::getDb()->Execute($sQ);\n        }\n\n        $this->onArticleAttributeRelationChange($sOxid);\n    }\n\n    /**\n     * Adds attributes to article.\n     */\n    public function addAttr()\n    {\n        $aAddCat = $this->getActionIds('oxattribute.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sAttrViewName = $this->getViewName('oxattribute');\n            $aAddCat = $this->getAll($this->addFilter(\"select $sAttrViewName.oxid \" . $this->getQuery()));\n        }\n\n        if ($soxId && $soxId != \"-1\" && is_array($aAddCat)) {\n            foreach ($aAddCat as $sAdd) {\n                $oNew = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oNew->init(\"oxobject2attribute\");\n                $oNew->oxobject2attribute__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                $oNew->oxobject2attribute__oxattrid = new \\OxidEsales\\Eshop\\Core\\Field($sAdd);\n                $oNew->save();\n            }\n\n            $this->onArticleAttributeRelationChange($soxId);\n        }\n    }\n\n    /**\n     * Saves attribute value\n     *\n     * @return null\n     */\n    public function saveAttributeValue()\n    {\n        $database = DatabaseProvider::getDb();\n        $this->resetContentCache();\n\n        $articleId = Registry::getRequest()->getRequestEscapedParameter(\"oxid\");\n        $attributeId = Registry::getRequest()->getRequestEscapedParameter(\"attr_oxid\");\n        $attributeValue = Registry::getRequest()->getRequestEscapedParameter(\"attr_value\");\n\n        $article = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        if ($article->load($articleId)) {\n            if ($article->isDerived()) {\n                return;\n            }\n\n            $this->onAttributeValueChange($article);\n\n            if (isset($attributeId) && (\"\" != $attributeId)) {\n                $viewName = $this->getViewName(\"oxobject2attribute\");\n                $quotedArticleId = $database->quote($article->oxarticles__oxid->value);\n                $select = \"select * from {$viewName} where {$viewName}.oxobjectid= {$quotedArticleId} and\n                            {$viewName}.oxattrid= \" . $database->quote($attributeId);\n                $objectToAttribute = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel::class);\n                $objectToAttribute->setLanguage(Registry::getRequest()->getRequestEscapedParameter('editlanguage'));\n                $objectToAttribute->init(\"oxobject2attribute\");\n\n                $record = DatabaseProvider::getDb()->select($select);\n                if ($record && $record->count() > 0) {\n                    $objectToAttribute->assign($record->fields);\n                    $objectToAttribute->oxobject2attribute__oxvalue->setValue($attributeValue);\n                    $objectToAttribute->save();\n                }\n            }\n        }\n    }\n\n    /**\n     * Method is used to bind to attribute and article relation change action.\n     *\n     * @param string $articleId\n     */\n    protected function onArticleAttributeRelationChange($articleId)\n    {\n    }\n\n    /**\n     * Method is used to bind to attribute value change.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $article\n     */\n    protected function onAttributeValueChange($article)\n    {\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticleBundleAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class controls article assignment to attributes\n */\nclass ArticleBundleAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * If true extended column selection will be build\n     *\n     * @var bool\n     */\n    protected $_blAllowExtColumns = true;\n\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,         visible, multilanguage, ident\n        ['oxartnum', 'oxarticles', 1, 0, 0],\n        ['oxtitle', 'oxarticles', 1, 1, 0],\n        ['oxean', 'oxarticles', 1, 0, 0],\n        ['oxmpn', 'oxarticles', 0, 0, 0],\n        ['oxprice', 'oxarticles', 0, 0, 0],\n        ['oxstock', 'oxarticles', 0, 0, 0],\n        ['oxid', 'oxarticles', 0, 0, 1]\n    ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sArticleTable = $this->getViewName('oxarticles');\n        $sView = $this->getViewName('oxobject2category');\n\n        $sSelId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchSelId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sSelId) {\n            $sQAdd = \" from $sArticleTable where 1 \";\n            $sQAdd .= $myConfig->getConfigParam('blVariantsSelection') ? '' : \" and $sArticleTable.oxparentid = '' \";\n        } else {\n            // selected category ?\n            if ($sSynchSelId) {\n                $blVariantsSelectionParameter = $myConfig->getConfigParam('blVariantsSelection');\n                $sSqlIfTrue = \" ({$sArticleTable}.oxid=oxobject2category.oxobjectid \" .\n                              \"or {$sArticleTable}.oxparentid=oxobject2category.oxobjectid)\";\n                $sSqlIfFalse = \" $sArticleTable.oxid=oxobject2category.oxobjectid \";\n                $sVariantsSqlSnippet = $blVariantsSelectionParameter ? $sSqlIfTrue : $sSqlIfFalse;\n\n                $sQAdd = \" from {$sView} as oxobject2category left join {$sArticleTable} on {$sVariantsSqlSnippet}\" .\n                         \" where oxobject2category.oxcatnid = \" . $oDb->quote($sSelId) . \" \";\n            }\n        }\n        // #1513C/#1826C - skip references, to not existing articles\n        $sQAdd .= \" and $sArticleTable.oxid IS NOT NULL \";\n\n        // skipping self from list\n        $sQAdd .= \" and $sArticleTable.oxid != \" . $oDb->quote($sSynchSelId) . \" \";\n\n        return $sQAdd;\n    }\n\n    /**\n     * Adds filter SQL to current query\n     *\n     * @param string $sQ query to add filter condition\n     *\n     * @return string\n     */\n    protected function addFilter($sQ)\n    {\n        $sArtTable = $this->getViewName('oxarticles');\n        $sQ = parent::addFilter($sQ);\n\n        // display variants or not ?\n        $sQ .= \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blVariantsSelection') ? ' group by ' . $sArtTable . '.oxid ' : '';\n\n        return $sQ;\n    }\n\n    /**\n     * Removing article from corssselling list\n     */\n    public function removeArticleBundle()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $sQ = \"update oxarticles set oxarticles.oxbundleid = '' where oxarticles.oxid = :oxid \";\n        $oDb->Execute(\n            $sQ,\n            ['oxid' => Registry::getRequest()->getRequestEscapedParameter('oxid')]\n        );\n    }\n\n    /**\n     * Adding article to corssselling list\n     */\n    public function addArticleBundle()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $sQ = \"update oxarticles set oxarticles.oxbundleid = :oxbundleid \" .\n              \"where oxarticles.oxid  = :oxid \";\n        $oDb->Execute(\n            $sQ,\n            [\n                'oxbundleid' => Registry::getRequest()->getRequestEscapedParameter('oxbundleid'),\n                'oxid' => Registry::getRequest()->getRequestEscapedParameter('oxid')\n            ]\n        );\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticleController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Sets template, that arranges two other templates (\"article_list\"\n * and \"article_main\") to frame.\n * Admin Menu: Manage Products -> Articles.\n */\nclass ArticleController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'article';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticleCrossselling.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin article crosselling/accesories manager.\n * Creates list of available articles, there is ability to assign or remove\n * assigning of article to crosselling/accesories with other products.\n * Admin Menu: Manage Products -> Articles -> Crosssell.\n */\nclass ArticleCrossselling extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $this->_aViewData['edit'] = $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n\n        // crossselling\n        $this->createCategoryTree(\"artcattree\");\n\n        // accessoires\n        $this->createCategoryTree(\"artcattree2\");\n\n        $soxId = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oArticle->load($soxId);\n\n            if ($oArticle->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n        }\n\n        $iAoc = Registry::getRequest()->getRequestEscapedParameter(\"aoc\");\n        if ($iAoc == 1) {\n            $oArticleCrossellingAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleCrosssellingAjax::class);\n            $this->_aViewData['oxajax'] = $oArticleCrossellingAjax->getColumns();\n\n            return \"popups/article_crossselling\";\n        } elseif ($iAoc == 2) {\n            $oArticleAccessoriesAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleAccessoriesAjax::class);\n            $this->_aViewData['oxajax'] = $oArticleAccessoriesAjax->getColumns();\n\n            return \"popups/article_accessories\";\n        }\n\n        return \"article_crossselling\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticleCrosssellingAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class controls article crossselling configuration\n */\nclass ArticleCrosssellingAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * If true extended column selection will be build\n     *\n     * @var bool\n     */\n    protected $_blAllowExtColumns = true;\n\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,         visible, multilanguage, ident\n        ['oxartnum', 'oxarticles', 1, 0, 0],\n        ['oxtitle', 'oxarticles', 1, 1, 0],\n        ['oxean', 'oxarticles', 1, 0, 0],\n        ['oxmpn', 'oxarticles', 0, 0, 0],\n        ['oxprice', 'oxarticles', 0, 0, 0],\n        ['oxstock', 'oxarticles', 0, 0, 0],\n        ['oxid', 'oxarticles', 0, 0, 1]\n    ],\n                                 'container2' => [\n                                     ['oxartnum', 'oxarticles', 1, 0, 0],\n                                     ['oxtitle', 'oxarticles', 1, 1, 0],\n                                     ['oxean', 'oxarticles', 1, 0, 0],\n                                     ['oxmpn', 'oxarticles', 0, 0, 0],\n                                     ['oxprice', 'oxarticles', 0, 0, 0],\n                                     ['oxstock', 'oxarticles', 0, 0, 0],\n                                     ['oxid', 'oxobject2article', 0, 0, 1]\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $sArticleTable = $this->getViewName('oxarticles');\n        $sView = $this->getViewName('oxobject2category');\n\n        $sSelId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchSelId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        // category selected or not ?\n        if (!$sSelId) {\n            $sQAdd = \" from {$sArticleTable} where 1 \";\n            $sQAdd .= $myConfig->getConfigParam('blVariantsSelection') ? '' : \" and {$sArticleTable}.oxparentid = '' \";\n        } elseif ($sSynchSelId && $sSelId != $sSynchSelId) {\n            // selected category ?\n            $blVariantsSelectionParameter = $myConfig->getConfigParam('blVariantsSelection');\n            $sSqlIfTrue = \" ({$sArticleTable}.oxid=oxobject2category.oxobjectid \" .\n                          \"or {$sArticleTable}.oxparentid=oxobject2category.oxobjectid)\";\n            $sSqlIfFalse = \" {$sArticleTable}.oxid=oxobject2category.oxobjectid \";\n            $sVariantsSelectionSnippet = $blVariantsSelectionParameter ? $sSqlIfTrue : $sSqlIfFalse;\n\n            $sQAdd = \" from {$sView} as oxobject2category left join {$sArticleTable} on {$sVariantsSelectionSnippet}\" .\n                     \" where oxobject2category.oxcatnid = \" . $oDb->quote($sSelId) . \" \";\n        } elseif ($myConfig->getConfigParam('blBidirectCross')) {\n            $sQAdd = \" from oxobject2article \" .\n                     \" inner join {$sArticleTable} on ( oxobject2article.oxobjectid = {$sArticleTable}.oxid \" .\n                     \" or oxobject2article.oxarticlenid = {$sArticleTable}.oxid ) \" .\n                     \" where ( oxobject2article.oxarticlenid = \" . $oDb->quote($sSelId) .\n                     \" or oxobject2article.oxobjectid = \" . $oDb->quote($sSelId) . \" ) \" .\n                     \" and {$sArticleTable}.oxid != \" . $oDb->quote($sSelId) . \" \";\n        } else {\n            $sQAdd = \" from oxobject2article left join {$sArticleTable} \" .\n                     \"on oxobject2article.oxobjectid={$sArticleTable}.oxid \" .\n                     \" where oxobject2article.oxarticlenid = \" . $oDb->quote($sSelId) . \" \";\n        }\n\n        if ($sSynchSelId && $sSynchSelId != $sSelId) {\n            if ($myConfig->getConfigParam('blBidirectCross')) {\n                $sSubSelect = \"select {$sArticleTable}.oxid from oxobject2article \" .\n                              \"left join {$sArticleTable} on (oxobject2article.oxobjectid={$sArticleTable}.oxid \" .\n                              \"or oxobject2article.oxarticlenid={$sArticleTable}.oxid) \" .\n                              \"where (oxobject2article.oxarticlenid = \" . $oDb->quote($sSynchSelId) .\n                              \" or oxobject2article.oxobjectid = \" . $oDb->quote($sSynchSelId) . \" )\";\n            } else {\n                $sSubSelect = \"select {$sArticleTable}.oxid from oxobject2article \" .\n                              \"left join {$sArticleTable} on oxobject2article.oxobjectid={$sArticleTable}.oxid \" .\n                              \"where oxobject2article.oxarticlenid = \" . $oDb->quote($sSynchSelId) . \" \";\n            }\n\n            $sSubSelect .= \" and {$sArticleTable}.oxid IS NOT NULL \";\n            $sQAdd .= \" and {$sArticleTable}.oxid not in ( $sSubSelect ) \";\n        }\n\n        // #1513C/#1826C - skip references, to not existing articles\n        $sQAdd .= \" and {$sArticleTable}.oxid IS NOT NULL \";\n\n        // skipping self from list\n        $sId = ($sSynchSelId) ? $sSynchSelId : $sSelId;\n        $sQAdd .= \" and {$sArticleTable}.oxid != \" . $oDb->quote($sId) . \" \";\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removing article from corssselling list\n     */\n    public function removeArticleCross()\n    {\n        $aChosenArt = $this->getActionIds('oxobject2article.oxid');\n        // removing all\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxobject2article.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif (is_array($aChosenArt)) {\n            $sChosenArticles = implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aChosenArt));\n            $sQ = \"delete from oxobject2article where oxobject2article.oxid in (\" . $sChosenArticles . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adding article to corssselling list\n     */\n    public function addArticleCross()\n    {\n        $aChosenArt = $this->getActionIds('oxarticles.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // adding\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sArtTable = $this->getViewName('oxarticles');\n            $aChosenArt = $this->getAll(parent::addFilter(\"select $sArtTable.oxid \" . $this->getQuery()));\n        }\n\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        if ($oArticle->load($soxId) && $soxId && $soxId != \"-1\" && is_array($aChosenArt)) {\n            foreach ($aChosenArt as $sAdd) {\n                $oNewGroup = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oNewGroup->init('oxobject2article');\n                $oNewGroup->oxobject2article__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sAdd);\n                $oNewGroup->oxobject2article__oxarticlenid = new \\OxidEsales\\Eshop\\Core\\Field($oArticle->oxarticles__oxid->value);\n                $oNewGroup->oxobject2article__oxsort = new \\OxidEsales\\Eshop\\Core\\Field(0);\n                $oNewGroup->save();\n            }\n\n            $this->onArticleAddingToCrossSelling($oArticle);\n        }\n    }\n\n    /**\n     * Method is used to overload and add additional actions.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $article\n     */\n    protected function onArticleAddingToCrossSelling($article)\n    {\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticleExtend.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Field;\nuse stdClass;\nuse Exception;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Admin article extended parameters manager.\n * Collects and updates (on user submit) extended article properties ( such as\n * weight, dimensions, purchase Price and etc.). There is ability to assign article\n * to any chosen article group.\n * Admin Menu: Manage Products -> Articles -> Extended.\n */\nclass ArticleExtend extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Unit array\n     *\n     * @var array\n     */\n    protected $_aUnitsArray = null;\n\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $this->_aViewData['edit'] = $article = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n\n        $oxId = $this->getEditObjectId();\n\n        $this->createCategoryTree(\"artcattree\");\n\n        // all categories\n        if (isset($oxId) && $oxId != \"-1\") {\n            // load object\n            $article->loadInLang($this->_iEditLang, $oxId);\n\n            $article = $this->updateArticle($article);\n\n            // load object in other languages\n            $otherLang = $article->getAvailableInLangs();\n            if (!isset($otherLang[$this->_iEditLang])) {\n                $article->loadInLang(key($otherLang), $oxId);\n            }\n\n            foreach ($otherLang as $id => $language) {\n                $lang = new stdClass();\n                $lang->sLangDesc = $language;\n                $lang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $lang;\n            }\n\n            // variant handling\n            if ($article->oxarticles__oxparentid->value) {\n                $parentArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n                $parentArticle->load($article->oxarticles__oxparentid->value);\n                $this->_aViewData[\"parentarticle\"] = $parentArticle;\n                $this->_aViewData[\"oxparentid\"] = $article->oxarticles__oxparentid->value;\n            }\n        }\n\n        $this->prepareBundledArticlesDataForView($article);\n\n        $iAoc = Registry::getRequest()->getRequestEscapedParameter(\"aoc\");\n        if ($iAoc == 1) {\n            $oArticleExtendAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleExtendAjax::class);\n            $this->_aViewData['oxajax'] = $oArticleExtendAjax->getColumns();\n\n            return \"popups/article_extend\";\n        } elseif ($iAoc == 2) {\n            $oArticleBundleAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleBundleAjax::class);\n            $this->_aViewData['oxajax'] = $oArticleBundleAjax->getColumns();\n\n            return \"popups/article_bundle\";\n        }\n\n        //load media files\n        $this->_aViewData['aMediaUrls'] = $article->getMediaUrls();\n\n        return \"article_extend\";\n    }\n\n    /**\n     * Saves modified extended article parameters.\n     *\n     * @return mixed\n     */\n    public function save()\n    {\n        parent::save();\n\n        $aMyFile = Registry::getConfig()->getUploadedFile(\"myfile\");\n        $aMediaFile = Registry::getConfig()->getUploadedFile(\"mediaFile\");\n        if (is_array($aMyFile['name']) && reset($aMyFile['name']) || $aMediaFile['name']) {\n            $myConfig = Registry::getConfig();\n            if ($myConfig->isDemoShop()) {\n                $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay::class);\n                $oEx->setMessage('ARTICLE_EXTEND_UPLOADISDISABLED');\n                Registry::getUtilsView()->addErrorToDisplay($oEx, false);\n\n                return;\n            }\n        }\n\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n        // checkbox handling\n        if (!isset($aParams['oxarticles__oxissearch'])) {\n            $aParams['oxarticles__oxissearch'] = 0;\n        }\n        if (!isset($aParams['oxarticles__oxblfixedprice'])) {\n            $aParams['oxarticles__oxblfixedprice'] = 0;\n        }\n\n        // new way of handling bundled articles\n        //#1517C - remove possibility to add Bundled Product\n        //$this->setBundleId($aParams, $soxId);\n\n        // default values\n        $aParams = $this->addDefaultValues($aParams);\n\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $oArticle->loadInLang($this->_iEditLang, $soxId);\n        $sTPriceField = 'oxarticles__oxtprice';\n        $sPriceField = 'oxarticles__oxprice';\n        $dTPrice = $aParams['oxarticles__oxtprice'];\n        if ($dTPrice && $dTPrice != $oArticle->$sTPriceField->value && $dTPrice <= $oArticle->$sPriceField->value) {\n            $this->_aViewData[\"errorsavingtprice\"] = 1;\n        }\n\n        $oArticle->setLanguage(0);\n        $oArticle->assign($aParams);\n        $oArticle->setLanguage($this->_iEditLang);\n        $oArticle = Registry::getUtilsFile()->processFiles($oArticle);\n        $oArticle->save();\n\n        //saving media file\n        $sMediaUrl = Registry::getRequest()->getRequestEscapedParameter(\"mediaUrl\");\n        $sMediaDesc = Registry::getRequest()->getRequestEscapedParameter(\"mediaDesc\");\n\n        if (($sMediaUrl && $sMediaUrl != 'http://') || $aMediaFile['name'] || $sMediaDesc) {\n            if (!$sMediaDesc) {\n                return Registry::getUtilsView()->addErrorToDisplay('EXCEPTION_NODESCRIPTIONADDED');\n            }\n\n            if ((!$sMediaUrl || $sMediaUrl == 'http://') && !$aMediaFile['name']) {\n                return Registry::getUtilsView()->addErrorToDisplay('EXCEPTION_NOMEDIAADDED');\n            }\n\n            $oMediaUrl = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\MediaUrl::class);\n            $oMediaUrl->setLanguage($this->_iEditLang);\n            $oMediaUrl->oxmediaurls__oxisuploaded = new Field(0, Field::T_RAW);\n\n            //handle uploaded file\n            if ($aMediaFile['name']) {\n                try {\n                    $sMediaUrl = Registry::getUtilsFile()->processFile('mediaFile', 'out/media/');\n                    $oMediaUrl->oxmediaurls__oxisuploaded = new Field(1, Field::T_RAW);\n                } catch (Exception $e) {\n                    return Registry::getUtilsView()->addErrorToDisplay($e->getMessage());\n                }\n            }\n\n            //save media url\n            $oMediaUrl->oxmediaurls__oxobjectid = new Field($soxId, Field::T_RAW);\n            $oMediaUrl->oxmediaurls__oxurl = new Field($sMediaUrl, Field::T_RAW);\n            $oMediaUrl->oxmediaurls__oxdesc = new Field($sMediaDesc, Field::T_RAW);\n            $oMediaUrl->save();\n        }\n\n        // renew price update time\n        oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class)->renewPriceUpdateTime();\n    }\n\n    /**\n     * Deletes media url (with possible linked files)\n     */\n    public function deletemedia()\n    {\n        $soxId = $this->getEditObjectId();\n        $sMediaId = Registry::getRequest()->getRequestEscapedParameter(\"mediaid\");\n        if ($sMediaId && $soxId) {\n            $oMediaUrl = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\MediaUrl::class);\n            $oMediaUrl->load($sMediaId);\n            $oMediaUrl->delete();\n        }\n    }\n\n    /**\n     * Adds default values for extended article parameters. Returns modified\n     * parameters array.\n     *\n     * @param array $aParams Article parameters array\n     *\n     * @return array\n     */\n    public function addDefaultValues($aParams)\n    {\n        return $aParams;\n    }\n\n    /**\n     * Updates existing media descriptions\n     */\n    public function updateMedia()\n    {\n        $aMediaUrls = Registry::getRequest()->getRequestEscapedParameter('aMediaUrls');\n        if (is_array($aMediaUrls)) {\n            foreach ($aMediaUrls as $sMediaId => $aMediaParams) {\n                $oMedia = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\MediaUrl::class);\n                if ($oMedia->load($sMediaId)) {\n                    $oMedia->setLanguage(0);\n                    $oMedia->assign($aMediaParams);\n                    $oMedia->setLanguage($this->_iEditLang);\n                    $oMedia->save();\n                }\n            }\n        }\n    }\n\n    /**\n     * Returns array of possible unit combination and its translation for edit language\n     *\n     * @return array\n     */\n    public function getUnitsArray()\n    {\n        if ($this->_aUnitsArray === null) {\n            $this->_aUnitsArray = Registry::getLang()->getSimilarByKey(\"_UNIT_\", $this->_iEditLang, false);\n        }\n\n        return $this->_aUnitsArray;\n    }\n\n    /**\n     * Method used to overload and update article.\n     *\n     * @param \\oxArticle $article\n     *\n     * @return \\oxArticle\n     */\n    protected function updateArticle($article)\n    {\n        return $article;\n    }\n\n    /**\n     * Adds data to _aViewData for later use in templates.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $article\n     */\n    protected function prepareBundledArticlesDataForView($article)\n    {\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDB();\n        $config = Registry::getConfig();\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $articleTable = $tableViewNameGenerator->getViewName('oxarticles', $this->_iEditLang);\n        $query = \"select {$articleTable}.oxtitle, {$articleTable}.oxartnum, {$articleTable}.oxvarselect \" .\n            \"from {$articleTable} where 1 \";\n        // #546\n        $isVariantSelectionEnabled = $config->getConfigParam('blVariantsSelection');\n        $bundleIdField = 'oxarticles__oxbundleid';\n        $query .= $isVariantSelectionEnabled ? '' : \" and {$articleTable}.oxparentid = '' \";\n        $query .= \" and {$articleTable}.oxid = :oxid\";\n\n        $resultFromDatabase = $database->select($query, [\n            'oxid' => $article->$bundleIdField->value\n        ]);\n        if ($resultFromDatabase != false && $resultFromDatabase->count() > 0) {\n            while (!$resultFromDatabase->EOF) {\n                $articleNumber = new Field($resultFromDatabase->fields['oxartnum']);\n                $articleTitle = new Field(\n                    $resultFromDatabase->fields['oxtitle'] . \" \" . $resultFromDatabase->fields['oxvarselect']\n                );\n                $resultFromDatabase->fetchRow();\n            }\n        }\n        $this->_aViewData['bundle_artnum'] = $articleNumber ?? null;\n        $this->_aViewData['bundle_title'] = $articleTitle ?? null;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticleExtendAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse Exception;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class controls article assignment to category.\n */\nclass ArticleExtendAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,         visible, multilanguage, ident\n        ['oxtitle', 'oxcategories', 1, 1, 0],\n        ['oxdesc', 'oxcategories', 1, 1, 0],\n        ['oxid', 'oxcategories', 0, 0, 0],\n        ['oxid', 'oxcategories', 0, 0, 1]\n    ],\n                                 'container2' => [\n                                     ['oxtitle', 'oxcategories', 1, 1, 0],\n                                     ['oxdesc', 'oxcategories', 1, 1, 0],\n                                     ['oxid', 'oxcategories', 0, 0, 0],\n                                     ['oxid', 'oxobject2category', 0, 0, 1],\n                                     ['oxtime', 'oxobject2category', 0, 0, 1],\n                                     ['oxid', 'oxcategories', 0, 0, 1]\n                                 ],\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $categoriesTable = $this->getViewName('oxcategories');\n        $objectToCategoryView = $this->getViewName('oxobject2category');\n        $database = DatabaseProvider::getDb();\n\n        $oxId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $synchOxid = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        if ($oxId) {\n            // all categories article is in\n            $query = \" from $objectToCategoryView left join $categoriesTable\"\n                . \" on $categoriesTable.oxid=$objectToCategoryView.oxcatnid \"\n                . \" where $objectToCategoryView.oxobjectid = \" . $database->quote($oxId)\n                . \" and $categoriesTable.oxid is not null \";\n        } else {\n            $query = \" from $categoriesTable where $categoriesTable.oxid not in ( \"\n                . \" select $categoriesTable.oxid from $objectToCategoryView \"\n                . \"left join $categoriesTable on $categoriesTable.oxid=$objectToCategoryView.oxcatnid \"\n                . \" where $objectToCategoryView.oxobjectid = \" . $database->quote($synchOxid)\n                . \" and $categoriesTable.oxid is not null ) and $categoriesTable.oxpriceto = '0'\";\n        }\n\n        return $query;\n    }\n\n    /**\n     * Returns array with DB records\n     *\n     * @param string $sQ SQL query\n     *\n     * @return array\n     */\n    protected function getDataFields($sQ)\n    {\n        $dataFields = parent::getDataFields($sQ);\n        if (Registry::getRequest()->getRequestEscapedParameter('oxid') && is_array($dataFields) && count($dataFields)) {\n            // looking for smallest time value to mark record as main category ..\n            $minimalPosition = null;\n            $minimalValue = null;\n            reset($dataFields);\n            foreach ($dataFields as $position => $fields) {\n                // already set ?\n                if ($fields['_3'] == '0') {\n                    $minimalPosition = null;\n                    break;\n                }\n\n                if (!$minimalValue) {\n                    $minimalValue = $fields['_3'];\n                    $minimalPosition = $position;\n                } elseif ($minimalValue > $fields['_3']) {\n                    $minimalPosition = $position;\n                }\n            }\n\n            // setting primary category\n            if (isset($minimalPosition)) {\n                $dataFields[$minimalPosition]['_3'] = '0';\n            }\n        }\n\n        return $dataFields;\n    }\n\n    /**\n     * Removes article from chosen category\n     */\n    public function removeCat()\n    {\n        $categoriesToRemove = $this->getActionIds('oxcategories.oxid');\n\n        $oxId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $dataBase = DatabaseProvider::getDb();\n\n        // adding\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $categoriesTable = $this->getViewName('oxcategories');\n            $categoriesToRemove = $this->getAll(\n                $this->addFilter(\"select {$categoriesTable}.oxid \" . $this->getQuery())\n            );\n        }\n\n        // removing all\n        if (is_array($categoriesToRemove) && count($categoriesToRemove)) {\n            $query = \"delete from oxobject2category where oxobject2category.oxobjectid = :oxobjectid and \";\n            $query = $this->updateQueryForRemovingArticleFromCategory($query);\n            $query .= \" oxcatnid in (\"\n                . implode(', ', DatabaseProvider::getDb()->quoteArray($categoriesToRemove))\n                . ')';\n            $dataBase->Execute($query, [\n                'oxobjectid' => $oxId\n            ]);\n\n            // updating oxtime values\n            $this->updateOxTime($oxId);\n        }\n\n        $this->resetArtSeoUrl($oxId, $categoriesToRemove);\n        $this->resetContentCache();\n\n        $this->onCategoriesRemoval($categoriesToRemove, $oxId);\n    }\n\n    /**\n     * Adds article to chosen category\n     *\n     * @throws Exception\n     */\n    public function addCat()\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $categoriesToAdd = $this->getActionIds('oxcategories.oxid');\n        $oxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n        $shopId = $config->getShopId();\n        $objectToCategoryView = $this->getViewName('oxobject2category');\n\n        // adding\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $categoriesTable = $this->getViewName('oxcategories');\n            $categoriesToAdd = $this->getAll($this->addFilter(\"select $categoriesTable.oxid \" . $this->getQuery()));\n        }\n\n        if (isset($categoriesToAdd) && is_array($categoriesToAdd)) {\n            // We force reading from master to prevent issues with slow replications or open transactions\n            // (see ESDEV-3804 and ESDEV-3822).\n            $database = DatabaseProvider::getMaster();\n\n            $objectToCategory = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Object2Category::class);\n\n            foreach ($categoriesToAdd as $sAdd) {\n                // check, if it's already in, then don't add it again\n                $sSelect = \"select 1 from \" . $objectToCategoryView . \" as oxobject2category \" .\n                    \"where oxobject2category.oxcatnid = :oxcatnid \" .\n                    \"and oxobject2category.oxobjectid = :oxobjectid\";\n                if ($database->getOne($sSelect, ['oxcatnid' => $sAdd, 'oxobjectid' => $oxId])) {\n                    continue;\n                }\n\n                $objectToCategory->setId(md5($oxId . $sAdd . $shopId));\n                $objectToCategory->oxobject2category__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($oxId);\n                $objectToCategory->oxobject2category__oxcatnid = new \\OxidEsales\\Eshop\\Core\\Field($sAdd);\n                $objectToCategory->oxobject2category__oxtime = new \\OxidEsales\\Eshop\\Core\\Field(time());\n\n                $objectToCategory->save();\n            }\n\n            $this->updateOxTime($oxId);\n\n            $this->resetArtSeoUrl($oxId);\n            $this->resetContentCache();\n            $this->onCategoriesAdd($categoriesToAdd);\n        }\n    }\n\n    /**\n     * Updates oxtime value for product\n     *\n     * @param string $oxId product id\n     */\n    protected function updateOxTime($oxId)\n    {\n        $database = DatabaseProvider::getDb();\n        $objectToCategoryView = $this->getViewName('oxobject2category');\n        $queryToEmbed = $this->formQueryToEmbedForUpdatingTime();\n\n        // updating oxtime values\n        $query = \"update oxobject2category set oxtime = 0 where oxobjectid = :oxobjectid {$queryToEmbed} and oxid = (\n                    select oxid from (\n                        select oxid from {$objectToCategoryView} where oxobjectid = :oxobjectid {$queryToEmbed}\n                        order by oxtime limit 1\n                    ) as _tmp\n                )\";\n        $database->execute($query, ['oxobjectid' => $oxId]);\n    }\n\n    /**\n     * Sets selected category as a default\n     */\n    public function setAsDefault()\n    {\n        $defCat = Registry::getRequest()->getRequestEscapedParameter(\"defcat\");\n        $oxId = Registry::getRequest()->getRequestEscapedParameter(\"oxid\");\n\n        $queryToEmbed = $this->formQueryToEmbedForSettingCategoryAsDefault();\n\n        // #0003650: increment all product references independent to active shop\n        $query = \"update oxobject2category set oxtime = oxtime + 10 where oxobjectid = :oxobjectid {$queryToEmbed}\";\n        DatabaseProvider::getInstance()->getDb()->execute($query, ['oxobjectid' => $oxId]);\n\n        // set main category for active shop\n        $query = \"update oxobject2category set oxtime = 0\n                  where oxobjectid = :oxobjectid and oxcatnid = :oxcatnid {$queryToEmbed}\";\n        DatabaseProvider::getInstance()->getDb()->execute($query, [\n            'oxobjectid' => $oxId,\n            'oxcatnid' => $defCat\n        ]);\n\n        // #0003366: invalidate article SEO for all shops\n        \\OxidEsales\\Eshop\\Core\\Registry::getSeoEncoder()->markAsExpired($oxId, null, 1, null, \"oxtype='oxarticle'\");\n        $this->resetContentCache();\n    }\n\n    /**\n     * Method used for overloading and embed query.\n     *\n     * @param string $query\n     *\n     * @return string\n     */\n    protected function updateQueryForRemovingArticleFromCategory($query)\n    {\n        return $query;\n    }\n\n    /**\n     * Method is used for overloading to do additional actions.\n     *\n     * @param array  $categoriesToRemove\n     * @param string $oxId\n     */\n    protected function onCategoriesRemoval($categoriesToRemove, $oxId)\n    {\n    }\n\n    /**\n     * Method is used for overloading.\n     *\n     * @param array $categories\n     */\n    protected function onCategoriesAdd($categories)\n    {\n    }\n\n    /**\n     * Method is used for overloading to insert additional query condition.\n     *\n     * @return string\n     */\n    protected function formQueryToEmbedForUpdatingTime()\n    {\n        return '';\n    }\n\n    /**\n     * Method is used for overloading to insert additional query condition.\n     *\n     * @return string\n     */\n    protected function formQueryToEmbedForSettingCategoryAsDefault()\n    {\n        return '';\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticleFiles.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse Exception;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin article files parameters manager.\n * Collects and updates (on user submit) files.\n * Admin Menu: Manage Products -> Articles -> Files.\n */\nclass ArticleFiles extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Template name\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'article_files';\n\n    /**\n     * Stores editing article\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected $_oArticle = null;\n\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        if (!\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blEnableDownloads')) {\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay('EXCEPTION_DISABLED_DOWNLOADABLE_PRODUCTS');\n        }\n        $oArticle = $this->getArticle();\n        // variant handling\n        if ($oArticle->oxarticles__oxparentid->value) {\n            $oParentArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            $oParentArticle->load($oArticle->oxarticles__oxparentid->value);\n            $oArticle->oxarticles__oxisdownloadable = new \\OxidEsales\\Eshop\\Core\\Field($oParentArticle->oxarticles__oxisdownloadable->value);\n            $this->_aViewData[\"oxparentid\"] = $oArticle->oxarticles__oxparentid->value;\n        }\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Saves editing article changes (oxisdownloadable)\n     * and updates oxFile object which are associated with editing object\n     */\n    public function save()\n    {\n        // save article changes\n        $aArticleChanges = Registry::getRequest()->getRequestEscapedParameter('editval');\n        $oArticle = $this->getArticle();\n        $oArticle->assign($aArticleChanges);\n        $oArticle->save();\n\n        //update article files\n        $aArticleFiles = Registry::getRequest()->getRequestEscapedParameter('article_files');\n        if (is_array($aArticleFiles)) {\n            foreach ($aArticleFiles as $sArticleFileId => $aArticleFileUpdate) {\n                $oArticleFile = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\File::class);\n                $oArticleFile->load($sArticleFileId);\n                $aArticleFileUpdate = $this->processOptions($aArticleFileUpdate);\n                $oArticleFile->assign($aArticleFileUpdate);\n\n                if ($oArticleFile->isUnderDownloadFolder()) {\n                    $oArticleFile->save();\n                } else {\n                    \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay('EXCEPTION_NOFILE');\n                }\n            }\n        }\n    }\n\n    /**\n     * Returns current oxarticle object\n     *\n     * @param bool $blReset Load article again\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    public function getArticle($blReset = false)\n    {\n        if ($this->_oArticle !== null && !$blReset) {\n            return $this->_oArticle;\n        }\n        $sProductId = $this->getEditObjectId();\n\n        $oProduct = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $oProduct->load($sProductId);\n\n        return $this->_oArticle = $oProduct;\n    }\n\n    /**\n     * Creates new oxFile object and stores newly uploaded file\n     *\n     * @return null\n     */\n    public function upload()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        if ($myConfig->isDemoShop()) {\n            $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay::class);\n            $oEx->setMessage('ARTICLE_EXTEND_UPLOADISDISABLED');\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay($oEx, false);\n\n            return;\n        }\n\n        $soxId = $this->getEditObjectId();\n\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"newfile\");\n        $aParams = $this->processOptions($aParams);\n        $aNewFile = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getUploadedFile(\"newArticleFile\");\n\n        //uploading and processing supplied file\n        $oArticleFile = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\File::class);\n        $oArticleFile->assign($aParams);\n\n        if (!$aNewFile['name'] && !$oArticleFile->oxfiles__oxfilename->value) {\n            return \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay('EXCEPTION_NOFILE');\n        }\n\n        if ($aNewFile['name']) {\n            $oArticleFile->oxfiles__oxfilename = new \\OxidEsales\\Eshop\\Core\\Field($aNewFile['name'], \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n            try {\n                $oArticleFile->processFile('newArticleFile');\n            } catch (Exception $e) {\n                return \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay($e->getMessage());\n            }\n        }\n\n        if (!$oArticleFile->isUnderDownloadFolder()) {\n            return \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay('EXCEPTION_NOFILE');\n        }\n\n        //save media url\n        $oArticleFile->oxfiles__oxartid = new \\OxidEsales\\Eshop\\Core\\Field($soxId, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        $oArticleFile->save();\n    }\n\n    /**\n     * Deletes article file from fileid parameter and checks if this file belongs to current article.\n     *\n     * @return void\n     */\n    public function deletefile()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        if ($myConfig->isDemoShop()) {\n            $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay::class);\n            $oEx->setMessage('ARTICLE_EXTEND_UPLOADISDISABLED');\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay($oEx, false);\n\n            return;\n        }\n\n        $sArticleId = $this->getEditObjectId();\n        $sArticleFileId = Registry::getRequest()->getRequestEscapedParameter('fileid');\n        $oArticleFile = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\File::class);\n        $oArticleFile->load($sArticleFileId);\n        if ($oArticleFile->hasValidDownloads()) {\n            return \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay('EXCEPTION_DELETING_VALID_FILE');\n        }\n        if ($oArticleFile->oxfiles__oxartid->value == $sArticleId) {\n            $oArticleFile->delete();\n        }\n    }\n\n    /**\n     * Returns real config option value\n     *\n     * @param int $iOption option value\n     *\n     * @return int\n     */\n    public function getConfigOptionValue($iOption)\n    {\n        return ($iOption < 0) ? \"\" : $iOption;\n    }\n\n    /**\n     * Process config options. If value is not set, save as \"-1\" to database\n     *\n     * @param array $aParams params\n     *\n     * @return array\n     */\n    protected function processOptions($aParams)\n    {\n        if (!is_array($aParams)) {\n            $aParams = [];\n        }\n\n        if (!isset($aParams[\"oxfiles__oxdownloadexptime\"]) || $aParams[\"oxfiles__oxdownloadexptime\"] == \"\") {\n            $aParams[\"oxfiles__oxdownloadexptime\"] = -1;\n        }\n        if (!isset($aParams[\"oxfiles__oxlinkexptime\"]) || $aParams[\"oxfiles__oxlinkexptime\"] == \"\") {\n            $aParams[\"oxfiles__oxlinkexptime\"] = -1;\n        }\n        if (!isset($aParams[\"oxfiles__oxmaxunregdownloads\"]) || $aParams[\"oxfiles__oxmaxunregdownloads\"] == \"\") {\n            $aParams[\"oxfiles__oxmaxunregdownloads\"] = -1;\n        }\n        if (!isset($aParams[\"oxfiles__oxmaxdownloads\"]) || $aParams[\"oxfiles__oxmaxdownloads\"] == \"\") {\n            $aParams[\"oxfiles__oxmaxdownloads\"] = -1;\n        }\n\n        return $aParams;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticleList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Model\\ListModel;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Admin article list manager.\n * Collects base article information (according to filtering rules), performs sorting,\n * deletion of articles, etc.\n * Admin Menu: Manage Products -> Articles.\n */\nclass ArticleList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxarticle';\n\n    /**\n     * Type of list.\n     *\n     * @var string\n     */\n    protected $_sListType = 'oxarticlelist';\n\n    /**\n     * Collects articles base data and passes them according to filtering rules,\n     * returns name of template file \"article_list\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        $listType = '';\n        $activeItemId = '';\n        $requestedCategory = Registry::getRequest()->getRequestEscapedParameter('art_category');\n        $requestedSearchField = Registry::getRequest()->getRequestEscapedParameter('pwrsearchfld');\n        $searchField = $requestedSearchField ? strtolower($requestedSearchField) : 'oxtitle';\n\n        $productList = $this->getItemList();\n        if ($productList && $productList->count()) {\n            $this->convertSearchFieldValueForProductsInList($productList, $searchField);\n            $this->setAdditionalSearchFieldForProductsInList($productList, $searchField);\n            $this->setIsActiveFieldForProductsInList($productList);\n        }\n\n        parent::render();\n\n        $this->_aViewData['pwrsearchfields'] = $productList->count() || $productList->getBaseObject()\n            ? $this->getSearchFields()\n            : null;\n        $this->_aViewData['pwrsearchfld'] = strtoupper($searchField);\n\n        $listFilter = $this->getListFilter();\n        if (isset($listFilter['oxarticles'][$searchField])) {\n            $this->_aViewData['pwrsearchinput'] = $listFilter['oxarticles'][$searchField];\n        }\n\n        if ($requestedCategory && strpos($requestedCategory, '@@') !== false) {\n            [$listType, $activeItemId] = explode('@@', $requestedCategory);\n        }\n        $this->_aViewData['art_category'] = $requestedCategory;\n\n        // parent categorie tree\n        $this->_aViewData['cattree'] = $this->getCategoryList($listType, $activeItemId);\n\n        // manufacturer list\n        $this->_aViewData['mnftree'] = $this->getManufacturerlist($listType, $activeItemId);\n\n        // vendor list\n        $this->_aViewData['vndtree'] = $this->getVendorList($listType, $activeItemId);\n\n        return \"article_list\";\n    }\n\n    /**\n     * Returns array of fields which may be used for product data search\n     *\n     * @return array\n     */\n    public function getSearchFields()\n    {\n        $aSkipFields = [\n            \"oxblfixedprice\",\n            \"oxvarselect\",\n            \"oxamitemid\",\n            \"oxamtaskid\",\n            \"oxpixiexport\",\n            \"oxpixiexported\"\n        ];\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n\n        return array_diff($oArticle->getFieldNames(), $aSkipFields);\n    }\n\n    /**\n     * Load category list, mark active category;\n     *\n     * @param string $sType  active list type\n     * @param string $sValue active list item id\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\CategoryList\n     */\n    public function getCategoryList($sType, $sValue)\n    {\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\CategoryList $oCatTree parent category tree */\n        $oCatTree = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\CategoryList::class);\n        $oCatTree->loadList();\n        if ($sType === 'cat') {\n            foreach ($oCatTree as $oCategory) {\n                if ($oCategory->oxcategories__oxid->value == $sValue) {\n                    $oCategory->selected = 1;\n                    break;\n                }\n            }\n        }\n\n        return $oCatTree;\n    }\n\n    /**\n     * Load manufacturer list, mark active category;\n     *\n     * @param string $sType  active list type\n     * @param string $sValue active list item id\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\ManufacturerList\n     */\n    public function getManufacturerList($sType, $sValue)\n    {\n        $oMnfTree = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ManufacturerList::class);\n        $oMnfTree->loadManufacturerList();\n        if ($sType === 'mnf') {\n            foreach ($oMnfTree as $oManufacturer) {\n                if ($oManufacturer->oxmanufacturers__oxid->value == $sValue) {\n                    $oManufacturer->selected = 1;\n                    break;\n                }\n            }\n        }\n\n        return $oMnfTree;\n    }\n\n    /**\n     * Load vendor list, mark active category;\n     *\n     * @param string $sType  active list type\n     * @param string $sValue active list item id\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\VendorList\n     */\n    public function getVendorList($sType, $sValue)\n    {\n        $oVndTree = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\VendorList::class);\n        $oVndTree->loadVendorList();\n        if ($sType === 'vnd') {\n            foreach ($oVndTree as $oVendor) {\n                if ($oVendor->oxvendor__oxid->value == $sValue) {\n                    $oVendor->selected = 1;\n                    break;\n                }\n            }\n        }\n\n        return $oVndTree;\n    }\n\n    /**\n     * Builds and returns SQL query string.\n     *\n     * @param object $oListObject list main object\n     *\n     * @return string\n     */\n    protected function buildSelectString($oListObject = null)\n    {\n        $sQ = parent::buildSelectString($oListObject);\n        if ($sQ) {\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $sTable = $tableViewNameGenerator->getViewName(\"oxarticles\");\n            $sQ .= \" and $sTable.oxparentid = '' \";\n\n            $sType = false;\n            $sArtCat = Registry::getRequest()->getRequestEscapedParameter(\"art_category\");\n            if ($sArtCat && strstr($sArtCat, \"@@\") !== false) {\n                list($sType, $sValue) = explode(\"@@\", $sArtCat);\n            }\n\n            switch ($sType) {\n                // add category\n                case 'cat':\n                    $oStr = Str::getStr();\n                    $sViewName = $tableViewNameGenerator->getViewName(\"oxobject2category\");\n                    $sInsert = \"from $sTable left join {$sViewName} on {$sTable}.oxid = {$sViewName}.oxobjectid \" .\n                               \"where {$sViewName}.oxcatnid = \" . DatabaseProvider::getDb()->quote($sValue) . \" and \";\n                    $sQ = $oStr->preg_replace(\"/from\\s+$sTable\\s+where/i\", $sInsert, $sQ);\n                    break;\n                // add category\n                case 'mnf':\n                    $sQ .= \" and $sTable.oxmanufacturerid = \" . DatabaseProvider::getDb()->quote($sValue);\n                    break;\n                // add vendor\n                case 'vnd':\n                    $sQ .= \" and $sTable.oxvendorid = \" . DatabaseProvider::getDb()->quote($sValue);\n                    break;\n            }\n        }\n\n        return $sQ;\n    }\n\n    /**\n     * Builds and returns array of SQL WHERE conditions.\n     *\n     * @return array\n     */\n    public function buildWhere()\n    {\n        // we override this to select only parent articles\n        $this->_aWhere = parent::buildWhere();\n\n        // adding folder check\n        $sFolder = Registry::getRequest()->getRequestEscapedParameter('folder');\n        if ($sFolder && $sFolder != '-1') {\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $this->_aWhere[$tableViewNameGenerator->getViewName(\"oxarticles\") . \".oxfolder\"] = $sFolder;\n        }\n\n        return $this->_aWhere;\n    }\n\n    /**\n     * Deletes entry from the database\n     */\n    public function deleteEntry()\n    {\n        $sOxId = $this->getEditObjectId();\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        if ($sOxId && $oArticle->load($sOxId)) {\n            parent::deleteEntry();\n        }\n    }\n\n    private function convertSearchFieldValueForProductsInList(ListModel $productList, string $searchField): void\n    {\n        $fieldName = \"oxarticles__{$searchField}\";\n        if (\n            Registry::getConfig()->getConfigParam('blSkipFormatConversion')\n            || !$this->isDateField($this->getFieldTypeForCurrentProduct($productList, $fieldName))\n        ) {\n            return;\n        }\n        foreach ($productList as $key => $product) {\n            $this->convertValueToDatabaseTimestamp($product->$fieldName);\n            $productList[$key] = $product;\n        }\n    }\n\n    private function getFieldTypeForCurrentProduct(ListModel $productList, string $fieldName): string\n    {\n        $currentProduct = $productList->offsetGet($productList->key());\n        return $currentProduct->$fieldName->fldtype;\n    }\n\n    private function isDateField(string $fieldType): bool\n    {\n        return in_array(\n            $fieldType,\n            [\n                'date',\n                'datetime',\n                'timestamp',\n            ]\n        );\n    }\n\n    private function setAdditionalSearchFieldForProductsInList(ListModel $productList, string $searchField): void\n    {\n        $fieldName = \"oxarticles__{$searchField}\";\n        foreach ($productList as $key => $product) {\n            $product->pwrsearchval = $product->$fieldName->value;\n            $productList[$key] = $product;\n        }\n    }\n\n    private function setIsActiveFieldForProductsInList(ListModel $productList): void\n    {\n        $useTimeCheck = Registry::getConfig()->getConfigParam('blUseTimeCheck');\n        foreach ($productList as $key => $product) {\n            $product->showActiveCheckInAdminPanel = $product->isProductAlwaysActive();\n\n            if ($useTimeCheck) {\n                $product->hasActiveTimeRange = $product->hasProductValidTimeRange();\n                $product->isActiveNow = $product->hasActiveTimeRange();\n            }\n            $productList[$key] = $product;\n        }\n    }\n\n    private function convertValueToDatabaseTimestamp(Field $field): void\n    {\n        if ($field->fldtype === 'datetime') {\n            Registry::getUtilsDate()->convertDBDateTime($field);\n        } elseif ($field->fldtype === 'timestamp') {\n            Registry::getUtilsDate()->convertDBTimestamp($field);\n        } elseif ($field->fldtype === 'date') {\n            Registry::getUtilsDate()->convertDBDate($field);\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticleMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Model\\BaseModel;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse stdClass;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Admin article main manager.\n * Collects and updates (on user submit) article base parameters data ( such as\n * title, article No., short Description and etc.).\n * Admin Menu: Manage Products -> Articles -> Main.\n */\nclass ArticleMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        Registry::getConfig()->setConfigParam('bl_perfLoadPrice', true);\n\n        $oArticle = $this->createArticle();\n        $oArticle->enablePriceLoad();\n\n        $this->_aViewData['edit'] = $oArticle;\n\n        $sOxId = $this->getEditObjectId();\n        $sVoxId = Registry::getRequest()->getRequestEscapedParameter(\"voxid\");\n        $sOxParentId = Registry::getRequest()->getRequestEscapedParameter(\"oxparentid\");\n\n        // new variant ?\n        if (isset($sVoxId) && $sVoxId == \"-1\" && isset($sOxParentId) && $sOxParentId && $sOxParentId != \"-1\") {\n            $oParentArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            $oParentArticle->load($sOxParentId);\n            $this->_aViewData[\"parentarticle\"] = $oParentArticle;\n            $this->_aViewData[\"oxparentid\"] = $sOxParentId;\n\n            $this->_aViewData[\"oxid\"] = $sOxId = \"-1\";\n        }\n\n        if ($sOxId && $sOxId != \"-1\") {\n            // load object\n            $oArticle = $this->updateArticle($oArticle, $sOxId);\n\n            // load object in other languages\n            $oOtherLang = $oArticle->getAvailableInLangs();\n            if (!isset($oOtherLang[$this->_iEditLang])) {\n                $oArticle->loadInLang(key($oOtherLang), $sOxId);\n            }\n\n            // variant handling\n            if ($oArticle->oxarticles__oxparentid->value) {\n                $oParentArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n                $oParentArticle->load($oArticle->oxarticles__oxparentid->value);\n                $this->_aViewData[\"parentarticle\"] = $oParentArticle;\n                $this->_aViewData[\"oxparentid\"] = $oArticle->oxarticles__oxparentid->value;\n                $this->_aViewData[\"issubvariant\"] = 1;\n            }\n\n            // #381A\n            $this->formJumpList($oArticle, $oParentArticle ?? null);\n\n            //hook for modules\n            $oArticle = $this->customizeArticleInformation($oArticle);\n\n            $aLang = array_diff(Registry::getLang()->getLanguageNames(), $oOtherLang);\n            if (count($aLang)) {\n                $this->_aViewData[\"posslang\"] = $aLang;\n            }\n\n            foreach ($oOtherLang as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $oLang;\n            }\n        }\n\n        $this->_aViewData[\"editor\"] = $this->generateTextEditor(\n            \"100%\",\n            300,\n            $oArticle,\n            \"oxarticles__oxlongdesc\",\n            \"details.css\"\n        );\n        $this->_aViewData[\"blUseTimeCheck\"] = Registry::getConfig()->getConfigParam('blUseTimeCheck');\n\n        return \"article_main\";\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function getEditValue($object, $fieldName)\n    {\n        return $object ? $object->getLongDescription()->getRawValue() : '';\n    }\n\n    /**\n     * Saves changes of article parameters.\n     */\n    public function save()\n    {\n        parent::save();\n\n        $oDb = DatabaseProvider::getDb();\n        $oConfig = Registry::getConfig();\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        // default values\n        $aParams = $this->addDefaultValues($aParams);\n\n        // null values\n        if (isset($aParams['oxarticles__oxvat']) && $aParams['oxarticles__oxvat'] === '') {\n            $aParams['oxarticles__oxvat'] = null;\n        }\n\n        // varianthandling\n        $soxparentId = Registry::getRequest()->getRequestEscapedParameter(\"oxparentid\");\n        if (isset($soxparentId) && $soxparentId && $soxparentId != \"-1\") {\n            $aParams['oxarticles__oxparentid'] = $soxparentId;\n        } else {\n            unset($aParams['oxarticles__oxparentid']);\n        }\n\n        $oArticle = $this->createArticle();\n        $oArticle->setLanguage($this->_iEditLang);\n\n        if ($soxId != \"-1\") {\n            $oArticle->loadInLang($this->_iEditLang, $soxId);\n        } else {\n            $aParams['oxarticles__oxid'] = null;\n            $aParams['oxarticles__oxissearch'] = 1;\n            $aParams['oxarticles__oxstockflag'] = 1;\n            if (empty($aParams['oxarticles__oxstock'])) {\n                $aParams['oxarticles__oxstock'] = 0;\n            }\n\n            if (!isset($aParams['oxarticles__oxactive'])) {\n                $aParams['oxarticles__oxactive'] = 0;\n            }\n        }\n\n        //article number handling, warns for artnum duplicates\n        if (\n            isset($aParams['oxarticles__oxartnum']) && strlen($aParams['oxarticles__oxartnum']) > 0 &&\n            $oConfig->getConfigParam('blWarnOnSameArtNums') &&\n            $oArticle->oxarticles__oxartnum->value != $aParams['oxarticles__oxartnum']\n        ) {\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $sSelect = \"select oxid from \" . $tableViewNameGenerator->getViewName('oxarticles');\n            $sSelect .= \" where oxartnum = \" . $oDb->quote($aParams['oxarticles__oxartnum']);\n            $sSelect .= \" and oxid != \" . $oDb->quote($aParams['oxarticles__oxid']);\n            $record = DatabaseProvider::getDb()->select($sSelect);\n            if ($record && $record->count() > 0) {\n                $oArticle->assign($record->fields);\n                $this->_aViewData[\"errorsavingatricle\"] = 1;\n            }\n        }\n\n        $oArticle->setLanguage(0);\n        //triming spaces from article title (M:876)\n        if (isset($aParams['oxarticles__oxtitle'])) {\n            $aParams['oxarticles__oxtitle'] = trim($aParams['oxarticles__oxtitle']);\n        }\n\n        $oArticle->assign($aParams);\n        $oArticle->setArticleLongDesc($this->processLongDesc($aParams['oxarticles__oxlongdesc']));\n        $oArticle->setLanguage($this->_iEditLang);\n        $oArticle = Registry::getUtilsFile()->processFiles($oArticle);\n        $oArticle->save();\n\n        // set oxid if inserted\n        if ($soxId == \"-1\") {\n            $sFastCat = Registry::getRequest()->getRequestEscapedParameter(\"art_category\");\n            if ($sFastCat != \"-1\") {\n                $this->addToCategory($sFastCat, $oArticle->getId());\n            }\n        }\n\n        $oArticle = $this->saveAdditionalArticleData($oArticle, $aParams);\n\n        $this->setEditObjectId($oArticle->getId());\n    }\n\n    /**\n     * Fixes html broken by html editor\n     *\n     * @param string $sValue value to fix\n     *\n     * @return string\n     */\n    protected function processLongDesc($sValue)\n    {\n        // TODO: the code below is redundant, optimize it, assignments should go smooth without conversions\n        // hack, if editor screws up text, htmledit tends to do so\n        $sValue = str_replace('&amp;nbsp;', '&nbsp;', $sValue);\n        $sValue = str_replace('&amp;', '&', $sValue);\n        $sValue = str_replace('&quot;', '\"', $sValue);\n        $sValue = str_replace('&lang=', '&amp;lang=', $sValue);\n        $sValue = str_replace('<p>&nbsp;</p>', '', $sValue);\n        $sValue = str_replace('<p>&nbsp; </p>', '', $sValue);\n\n        return $sValue;\n    }\n\n    /**\n     * Resets article categories counters\n     *\n     * @param string $sArticleId Article id\n     */\n    protected function resetCategoriesCounter($sArticleId)\n    {\n        $categories = DatabaseProvider::getDb()->getCol(\n            'select oxcatnid from oxobject2category where oxobjectid = :oxobjectid',\n            [\n            'oxobjectid' => $sArticleId\n            ]\n        );\n        foreach ($categories as $category) {\n            $this->resetCounter(\"catArticle\", $category);\n        }\n    }\n\n    /**\n     * Add article to category.\n     *\n     * @param string $sCatID Category id\n     * @param string $sOXID  Article id\n     */\n    public function addToCategory($sCatID, $sOXID)\n    {\n        $base = oxNew(BaseModel::class);\n        $base->init(\"oxobject2category\");\n        $base->oxobject2category__oxtime = new \\OxidEsales\\Eshop\\Core\\Field(0);\n        $base->oxobject2category__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sOXID);\n        $base->oxobject2category__oxcatnid = new \\OxidEsales\\Eshop\\Core\\Field($sCatID);\n\n        $base = $this->updateBase($base);\n\n        $base->save();\n    }\n\n    /**\n     * Copies article (with all parameters) to new articles.\n     *\n     * @param string $sOldId    old product id (default null)\n     * @param string $sNewId    new product id (default null)\n     * @param string $sParentId product parent id\n     */\n    public function copyArticle($sOldId = null, $sNewId = null, $sParentId = null)\n    {\n        $myConfig = Registry::getConfig();\n\n        $sOldId = $sOldId ? $sOldId : $this->getEditObjectId();\n        $sNewId = $sNewId ? $sNewId : Registry::getUtilsObject()->generateUID();\n\n        $oArticle = oxNew(BaseModel::class);\n        $oArticle->init('oxarticles');\n        if ($oArticle->load($sOldId)) {\n            if ($myConfig->getConfigParam('blDisableDublArtOnCopy')) {\n                $oArticle->oxarticles__oxactive->setValue(0);\n                $oArticle->oxarticles__oxactivefrom->setValue(0);\n                $oArticle->oxarticles__oxactiveto->setValue(0);\n            }\n\n            // setting parent id\n            if ($sParentId) {\n                $oArticle->oxarticles__oxparentid->setValue($sParentId);\n            }\n\n            // setting oxinsert/oxtimestamp\n            $iNow = date('Y-m-d H:i:s', Registry::getUtilsDate()->getTime());\n            $oArticle->oxarticles__oxinsert = new \\OxidEsales\\Eshop\\Core\\Field($iNow);\n\n            // mantis#0001590: OXRATING and OXRATINGCNT not set to 0 when copying article\n            $oArticle->oxarticles__oxrating = new \\OxidEsales\\Eshop\\Core\\Field(0);\n            $oArticle->oxarticles__oxratingcnt = new \\OxidEsales\\Eshop\\Core\\Field(0);\n            $oArticle->oxarticles__oxsoldamount = new \\OxidEsales\\Eshop\\Core\\Field(0);\n\n            $oArticle->setId($sNewId);\n            $oArticle->save();\n\n            //copy categories\n            $this->copyCategories($sOldId, $sNewId);\n\n            //atributes\n            $this->copyAttributes($sOldId, $sNewId);\n\n            //sellist\n            $this->copySelectlists($sOldId, $sNewId);\n\n            //crossseling\n            $this->copyCrossseling($sOldId, $sNewId);\n\n            //accessoire\n            $this->copyAccessoires($sOldId, $sNewId);\n\n            // #983A copying staffelpreis info\n            $this->copyStaffelpreis($sOldId, $sNewId);\n\n            //copy article extends (longdescription)\n            $this->copyArtExtends($sOldId, $sNewId);\n\n            //files\n            $this->copyFiles($sOldId, $sNewId);\n\n            $this->resetContentCache();\n\n            $myUtilsObject = Registry::getUtilsObject();\n            $database = DatabaseProvider::getDb();\n\n            $productsIds = DatabaseProvider::getDb()->getCol(\n                'select oxid from oxarticles where oxparentid = :oxparentid',\n                [\n                    'oxparentid' => $sOldId\n                ]\n            );\n            foreach ($productsIds as $productId) {\n                $this->copyArticle($productId, $myUtilsObject->generateUid(), $sNewId);\n            }\n\n            // only for top articles\n            if (!$sParentId) {\n                $this->setEditObjectId($oArticle->getId());\n\n                //article number handling, warns for artnum duplicates\n                $sFncParameter = Registry::getRequest()->getRequestEscapedParameter('fnc');\n                $sArtNumField = 'oxarticles__oxartnum';\n                if (\n                    $myConfig->getConfigParam('blWarnOnSameArtNums') &&\n                    $oArticle->$sArtNumField->value && $sFncParameter == 'copyArticle'\n                ) {\n                    $sSelect = \"select oxid from \" . $oArticle->getCoreTableName() .\n                               \" where oxartnum = \" . $database->quote($oArticle->$sArtNumField->value) .\n                               \" and oxid != \" . $database->quote($sNewId);\n\n                    $record = $database->select($sSelect);\n                    if ($record && $record->count() > 0) {\n                        $oArticle->assign($record->fields);\n                        $this->_aViewData[\"errorsavingatricle\"] = 1;\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * Copying category assignments\n     *\n     * @param string $sOldId       Id from old article\n     * @param string $newArticleId Id from new article\n     */\n    protected function copyCategories($sOldId, $newArticleId)\n    {\n        $myUtilsObject = Registry::getUtilsObject();\n        $database = DatabaseProvider::getDb();\n\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sO2CView = $tableViewNameGenerator->getViewName('oxobject2category');\n        $categories = $database->getAll(\n            sprintf('select oxcatnid, oxtime from %s where oxobjectid = :oxobjectid', $sO2CView),\n            [\n            'oxobjectid' => $sOldId\n            ]\n        );\n        foreach ($categories as $category) {\n            $uniqueId = $myUtilsObject->generateUid();\n            $sCatId = $category['oxcatnid'];\n            $sTime = $category['oxtime'];\n            $sSql = $this->formQueryForCopyingToCategory($newArticleId, $uniqueId, $sCatId, $sTime);\n            $database->execute($sSql);\n        }\n    }\n\n    /**\n     * Copying attributes assignments\n     *\n     * @param string $sOldId Id from old article\n     * @param string $sNewId Id from new article\n     */\n    protected function copyAttributes($sOldId, $sNewId)\n    {\n        $utilsObject = Registry::getUtilsObject();\n\n        $ArticleAttributesIds = DatabaseProvider::getDb()->getCol(\n            'select oxid from oxobject2attribute where oxobjectid = :oxobjectid',\n            [\n            'oxobjectid' => $sOldId\n            ]\n        );\n\n        foreach ($ArticleAttributesIds as $articleAttributesId) {\n            $articleAttribute = oxNew(BaseModel::class);\n            $articleAttribute->init(\"oxobject2attribute\");\n            $articleAttribute->load($articleAttributesId);\n            $articleAttribute->setId($utilsObject->generateUID());\n            $articleAttribute->oxobject2attribute__oxobjectid->setValue($sNewId);\n            $articleAttribute->save();\n        }\n    }\n\n    /**\n     * Copying files\n     *\n     * @param string $sOldId Id from old article\n     * @param string $sNewId Id from new article\n     */\n    protected function copyFiles($sOldId, $sNewId)\n    {\n        $myUtilsObject = Registry::getUtilsObject();\n        $oDb = DatabaseProvider::getDb();\n\n        $sQ = \"SELECT * FROM `oxfiles` WHERE `oxartid` = :oxartid\";\n        $oRs = $oDb->select($sQ, [\n            'oxartid' => $sOldId\n        ]);\n        if ($oRs !== false && $oRs->count() > 0) {\n            while (!$oRs->EOF) {\n                $oFile = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\File::class);\n                $oFile->setId($myUtilsObject->generateUID());\n                $oFile->oxfiles__oxartid = new \\OxidEsales\\Eshop\\Core\\Field($sNewId);\n                $oFile->oxfiles__oxfilename = new \\OxidEsales\\Eshop\\Core\\Field($oRs->fields['OXFILENAME']);\n                $oFile->oxfiles__oxfilesize = new \\OxidEsales\\Eshop\\Core\\Field($oRs->fields['OXFILESIZE']);\n                $oFile->oxfiles__oxstorehash = new \\OxidEsales\\Eshop\\Core\\Field($oRs->fields['OXSTOREHASH']);\n                $oFile->oxfiles__oxpurchasedonly = new \\OxidEsales\\Eshop\\Core\\Field($oRs->fields['OXPURCHASEDONLY']);\n                $oFile->save();\n                $oRs->fetchRow();\n            }\n        }\n    }\n\n    /**\n     * Copying selectlists assignments\n     *\n     * @param string $sOldId Id from old article\n     * @param string $sNewId Id from new article\n     */\n    protected function copySelectlists($sOldId, $sNewId)\n    {\n        $myUtilsObject = Registry::getUtilsObject();\n        $database = DatabaseProvider::getDb();\n\n        $selectListIds = $database->getCol('select oxselnid from oxobject2selectlist where oxobjectid = :oxobjectid', [\n            'oxobjectid' => $sOldId\n        ]);\n        foreach ($selectListIds as $selectListId) {\n            $database->execute(\n                'INSERT INTO oxobject2selectlist (oxid, oxobjectid, oxselnid) VALUES (:oxid, :oxobjectid, :oxselnid)',\n                [\n                    'oxid' => $myUtilsObject->generateUID(),\n                    'oxobjectid' => $sNewId,\n                    'oxselnid' => $selectListId,\n                ]\n            );\n        }\n    }\n\n    /**\n     * Copying crossseling assignments\n     *\n     * @param string $sOldId Id from old article\n     * @param string $sNewId Id from new article\n     */\n    protected function copyCrossseling($sOldId, $sNewId)\n    {\n        $myUtilsObject = Registry::getUtilsObject();\n        $database = DatabaseProvider::getDb();\n\n        $objects = $database->getCol(\n            'select oxobjectid from oxobject2article where oxarticlenid = :oxarticlenid',\n            [\n            'oxarticlenid' => $sOldId\n            ]\n        );\n        foreach ($objects as $object) {\n            $query = 'INSERT INTO oxobject2article (oxid, oxobjectid, oxarticlenid) ' .\n                'VALUES (:oxid, :oxobjectid, :oxarticlenid)';\n            $database->execute($query, [\n                'oxid' => $myUtilsObject->generateUID(),\n                'oxobjectid' => $object,\n                'oxarticlenid' => $sNewId\n            ]);\n        }\n    }\n\n    /**\n     * Copying accessoires assignments\n     *\n     * @param string $sOldId Id from old article\n     * @param string $sNewId Id from new article\n     */\n    protected function copyAccessoires($sOldId, $sNewId)\n    {\n        $myUtilsObject = Registry::getUtilsObject();\n        $database = DatabaseProvider::getDb();\n\n        $accessories = $database->getCol(\n            'select oxobjectid from oxaccessoire2article where oxarticlenid = :oxarticlenid',\n            [\n                'oxarticlenid' => $sOldId\n            ]\n        );\n        foreach ($accessories as $accessoryId) {\n            $sSql = 'INSERT INTO oxaccessoire2article (oxid, oxobjectid, oxarticlenid) ' .\n                'VALUES (:oxid, :oxobjectid, :oxarticlenid)';\n            $database->execute($sSql, [\n                'oxid' => $myUtilsObject->generateUid(),\n                'oxobjectid' => $accessoryId,\n                'oxarticlenid' => $sNewId\n            ]);\n        }\n    }\n\n    /**\n     * Copying staffelpreis assignments\n     *\n     * @param string $sOldId Id from old article\n     * @param string $sNewId Id from new article\n     */\n    protected function copyStaffelpreis($sOldId, $sNewId)\n    {\n        $sShopId = Registry::getConfig()->getShopId();\n        $oPriceList = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n        $oPriceList->init(\"oxbase\", \"oxprice2article\");\n        $sQ = \"select * from oxprice2article where oxartid = :oxartid and oxshopid = :oxshopid \" .\n              \"and (oxamount > 0 or oxamountto > 0) order by oxamount \";\n        $oPriceList->selectString($sQ, [\n            'oxartid' => $sOldId,\n            'oxshopid' => $sShopId\n        ]);\n        if ($oPriceList->count()) {\n            foreach ($oPriceList as $oItem) {\n                $oItem->oxprice2article__oxid->setValue($oItem->setId());\n                $oItem->oxprice2article__oxartid->setValue($sNewId);\n                $oItem->save();\n            }\n        }\n    }\n\n    /**\n     * Copying article extends\n     *\n     * @param string $sOldId Id from old article\n     * @param string $sNewId Id from new article\n     */\n    protected function copyArtExtends($sOldId, $sNewId)\n    {\n        $oExt = oxNew(BaseModel::class);\n        $oExt->init(\"oxartextends\");\n        $oExt->load($sOldId);\n        $oExt->setId($sNewId);\n        $oExt->save();\n    }\n\n    /**\n     * Saves article parameters in different language.\n     */\n    public function saveinnlang()\n    {\n        $this->save();\n    }\n\n    /**\n     * Sets default values for empty article (currently does nothing), returns\n     * array with parameters.\n     *\n     * @param array $aParams Parameters, to set default values\n     *\n     * @return array\n     */\n    public function addDefaultValues($aParams)\n    {\n        return $aParams;\n    }\n\n    /**\n     * Function forms article variants jump list.\n     *\n     * @param object $oArticle       article object\n     * @param object $oParentArticle article parent object\n     */\n    protected function formJumpList($oArticle, $oParentArticle)\n    {\n        $aJumpList = [];\n        //fetching parent article variants\n        $sOxIdField = 'oxarticles__oxid';\n        if (isset($oParentArticle)) {\n            $aJumpList[] = [$oParentArticle->$sOxIdField->value, $this->getTitle($oParentArticle)];\n            $sEditLanguageParameter = Registry::getRequest()->getRequestEscapedParameter(\"editlanguage\");\n            $oParentVariants = $oParentArticle->getAdminVariants($sEditLanguageParameter);\n            if ($oParentVariants->count()) {\n                foreach ($oParentVariants as $oVar) {\n                    $aJumpList[] = [$oVar->$sOxIdField->value, \" - \" . $this->getTitle($oVar)];\n                    if ($oVar->$sOxIdField->value == $oArticle->$sOxIdField->value) {\n                        $oVariants = $oArticle->getAdminVariants($sEditLanguageParameter);\n                        if ($oVariants->count()) {\n                            foreach ($oVariants as $oVVar) {\n                                $aJumpList[] = [$oVVar->$sOxIdField->value, \" -- \" . $this->getTitle($oVVar)];\n                            }\n                        }\n                    }\n                }\n            }\n        } else {\n            $aJumpList[] = [$oArticle->$sOxIdField->value, $this->getTitle($oArticle)];\n            //fetching this article variants data\n            $oVariants = $oArticle->getAdminVariants(\n                Registry::getRequest()->getRequestEscapedParameter(\"editlanguage\")\n            );\n            if ($oVariants && $oVariants->count()) {\n                foreach ($oVariants as $oVar) {\n                    $aJumpList[] = [$oVar->$sOxIdField->value, \" - \" . $this->getTitle($oVar)];\n                }\n            }\n        }\n        if (count($aJumpList) > 1) {\n            $this->_aViewData[\"thisvariantlist\"] = $aJumpList;\n        }\n    }\n\n    /**\n     * Returns formed variant title\n     *\n     * @param object $oObj product object\n     *\n     * @return string\n     */\n    protected function getTitle($oObj)\n    {\n        $sTitle = $oObj->oxarticles__oxtitle->value;\n        if (!strlen($sTitle)) {\n            $sTitle = $oObj->oxarticles__oxvarselect->value;\n        }\n\n        return $sTitle;\n    }\n\n    /**\n     * Returns shop manufacturers list\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\CategoryList\n     */\n    public function getCategoryList()\n    {\n        $oCatTree = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\CategoryList::class);\n        $oCatTree->loadList();\n\n        return $oCatTree;\n    }\n\n    /**\n     * Returns shop manufacturers list\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\VendorList\n     */\n    public function getVendorList()\n    {\n        $oVendorlist = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\VendorList::class);\n        $oVendorlist->loadVendorList();\n\n        return $oVendorlist;\n    }\n\n    /**\n     * Returns shop manufacturers list\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\ManufacturerList\n     */\n    public function getManufacturerList()\n    {\n        $oManufacturerList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ManufacturerList::class);\n        $oManufacturerList->loadManufacturerList();\n\n        return $oManufacturerList;\n    }\n\n    /**\n     * Loads language for article.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle\n     * @param string                                      $sOxId\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected function updateArticle($oArticle, $sOxId)\n    {\n        $oArticle->loadInLang($this->_iEditLang, $sOxId);\n\n        return $oArticle;\n    }\n\n    /**\n     * Forms query which is used for adding article to category.\n     *\n     * @param string $newArticleId\n     * @param string $sUid\n     * @param string $sCatId\n     * @param string $sTime\n     *\n     * @return string\n     */\n    protected function formQueryForCopyingToCategory($newArticleId, $sUid, $sCatId, $sTime)\n    {\n        $oDb = DatabaseProvider::getDb();\n        return \"insert into oxobject2category (oxid, oxobjectid, oxcatnid, oxtime) \" .\n            \"VALUES (\" . $oDb->quote($sUid) . \", \" . $oDb->quote($newArticleId) . \", \" .\n            $oDb->quote($sCatId) . \", \" . $oDb->quote($sTime) . \") \";\n    }\n\n    /**\n     * @param \\OxidEsales\\Eshop\\Core\\Model\\BaseModel $base\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\BaseModel $base\n     */\n    protected function updateBase($base)\n    {\n        return $base;\n    }\n\n    /**\n     * Customize article data for rendering.\n     * Intended to be used by modules.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $article\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected function customizeArticleInformation($article)\n    {\n        return $article;\n    }\n\n    /**\n     * Save non standard article information if needed.\n     * Intended to be used by modules.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $article\n     * @param array                                       $parameters\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected function saveAdditionalArticleData($article, $parameters)\n    {\n        return $article;\n    }\n\n    /**\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected function createArticle()\n    {\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n\n        return $oArticle;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticleOverview.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin article overview manager.\n * Collects and previews such article information as article creation date,\n * last modification date, sales rating and etc.\n * Admin Menu: Manage Products -> Articles -> Overview.\n */\nclass ArticleOverview extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        $config = Registry::getConfig();\n\n        parent::render();\n\n        $this->_aViewData['edit'] = $product = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n\n        $productEditId = $this->getEditObjectId();\n        if (isset($productEditId) && $productEditId != \"-1\") {\n            $database = $this->getDatabase();\n\n            $this->updateArticle($product, $productEditId);\n\n            $shopId = $config->getShopID();\n\n            $query = $this->formOrderAmountQuery($productEditId);\n            $this->_aViewData[\"totalordercnt\"] = $iTotalOrderCnt = (float) $database->getOne($query);\n\n            $query = $this->formSoldOutAmountQuery($productEditId);\n            $this->_aViewData[\"soldcnt\"] = $iSoldCnt = (float) $database->getOne($query);\n\n            $query = $this->formCanceledAmountQuery($productEditId);\n            $this->_aViewData[\"canceledcnt\"] = $iCanceledCnt = (float) $database->getOne($query);\n\n            $this->_aViewData[\"leftordercnt\"] = $iTotalOrderCnt - $iSoldCnt - $iCanceledCnt;\n\n            $query = \"select oxartid,sum(oxamount) as cnt from oxorderarticles \" .\n                       \"where oxordershopid = :oxordershopid group by oxartid order by cnt desc\";\n\n            $productIds = $database->getCol($query, [\n                'oxordershopid' => $shopId\n            ]);\n            $topPosition = 0;\n            $position = 0;\n\n            foreach ($productIds as $productId) {\n                $position++;\n                if ($productId == $productEditId) {\n                    $topPosition = $position;\n                }\n            }\n\n            $this->_aViewData[\"postopten\"] = $topPosition;\n            $this->_aViewData[\"toptentotal\"] = $position;\n        }\n\n        $this->_aViewData[\"afolder\"] = $config->getConfigParam('aProductfolder');\n        $this->_aViewData[\"aSubclass\"] = $config->getConfigParam('aArticleClasses');\n\n        return \"article_overview\";\n    }\n\n    /**\n     * @return \\OxidEsales\\Eshop\\Core\\Database\\Adapter\\DatabaseInterface\n     */\n    protected function getDatabase()\n    {\n        return \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n    }\n\n    /**\n     * Forms query to get total order count.\n     *\n     * @param string $oxId\n     *\n     * @return string\n     */\n    protected function formOrderAmountQuery($oxId)\n    {\n        $query = \"select sum(oxamount) from oxorderarticles \";\n        $query .= \"where oxartid=\" . $this->getDatabase()->quote($oxId);\n\n        return $query;\n    }\n\n    /**\n     * Forms query to get sold out amount count.\n     *\n     * @param string $oxId\n     *\n     * @return string\n     */\n    protected function formSoldOutAmountQuery($oxId)\n    {\n        return \"select sum(oxorderarticles.oxamount) from  oxorderarticles, oxorder \" .\n            \"where (oxorder.oxpaid>0 or oxorder.oxsenddate > 0) and oxorderarticles.oxstorno != '1' \" .\n            \"and oxorderarticles.oxartid=\" . $this->getDatabase()->quote($oxId) .\n            \"and oxorder.oxid =oxorderarticles.oxorderid\";\n    }\n\n    /**\n     * Forms query to get canceled amount count.\n     *\n     * @param string $soxId\n     *\n     * @return string\n     */\n    protected function formCanceledAmountQuery($soxId)\n    {\n        return \"select sum(oxamount) from oxorderarticles where oxstorno = '1' \" .\n            \"and oxartid=\" . $this->getDatabase()->quote($soxId);\n    }\n\n    /**\n     * Loads language for article object.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $article\n     * @param string                                      $oxId\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected function updateArticle($article, $oxId)\n    {\n        $article->loadInLang(Registry::getRequest()->getRequestEscapedParameter(\"editlanguage\"), $oxId);\n\n        return $article;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticlePictures.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\MediaUrlGeneratorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Dao\\ProductMediaDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRole;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\n\nclass ArticlePictures extends AdminDetailsController\n{\n    private string $detailImageSize;\n    private string $zoomImageSize;\n    private MediaUrlGeneratorInterface $mediaUrlGenerator;\n    private ProductMediaDaoInterface $productMediaDao;\n\n    public function __construct()\n    {\n        $this->mediaUrlGenerator = ContainerFacade::get(MediaUrlGeneratorInterface::class);\n        $this->productMediaDao = ContainerFacade::get(ProductMediaDaoInterface::class);\n\n        $this->detailImageSize = ContainerFacade::getParameter('oxid_esales.theme.admin.media.image_grid_size');\n        $this->zoomImageSize = ContainerFacade::getParameter('oxid_esales.theme.admin.media.image_zoom_size');\n\n        parent::__construct();\n    }\n\n    public function render()\n    {\n        parent::render();\n\n        $productId = Id::fromString($this->getEditObjectId());\n\n        $icon = $this->productMediaDao->getByRole($productId, ProductMediaRole::from(ProductMediaRole::ICON));\n        $thumbnail = $this->productMediaDao->getByRole($productId, ProductMediaRole::from(ProductMediaRole::THUMBNAIL));\n\n        $this->_aViewData['productImages'] = [\n            'icon' => $icon ? $this->buildImageData($icon) : null,\n            'thumbnail' => $thumbnail ? $this->buildImageData($thumbnail) : null,\n            'detailImages' => $this->productMediaDao\n                ->getAllByRole($productId, ProductMediaRole::from(ProductMediaRole::DETAIL))\n                ->map(fn(ProductMedia $media) => $this->buildImageData($media))\n                ->toArray(),\n        ];\n\n        return 'article_pictures';\n    }\n\n    private function buildImageData(ProductMedia $media): array\n    {\n        return [\n            'id' => (string) $media->getId(),\n            'productId' => (string) $media->getProductId(),\n            'url' => $this->mediaUrlGenerator->generateSizedImageUrl($media->getMedia(), $this->detailImageSize),\n            'zoomUrl' => $this->mediaUrlGenerator->generateSizedImageUrl($media->getMedia(), $this->zoomImageSize),\n            'position' => $media->getPosition(),\n            'active' => $media->isActive(),\n        ];\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticlePicturesAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\MediaValidationException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\FileExtensionMismatchException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\FileSizeTooLargeException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\FileSizeTooSmallException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\MimeBaseTypeMismatchException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\MimeGuessMismatchException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\MimeTypeGuessFailedException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\UploadInvalidException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRole;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaUploadProcessorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse Symfony\\Component\\HttpFoundation\\FileBag;\nuse Symfony\\Component\\HttpFoundation\\InputBag;\nuse Symfony\\Component\\HttpFoundation\\JsonResponse;\nuse Symfony\\Component\\HttpFoundation\\Request;\n\nclass ArticlePicturesAjax extends ListComponentAjax\n{\n    private readonly ProductMediaUploadProcessorInterface $productMediaUploadProcessor;\n    private readonly ProductMediaServiceInterface $productMediaService;\n    private readonly InputBag $requestData;\n    private readonly FileBag $requestFiles;\n\n    public function __construct()\n    {\n        parent::__construct();\n        $this->productMediaUploadProcessor = ContainerFacade::get(ProductMediaUploadProcessorInterface::class);\n        $this->productMediaService = ContainerFacade::get(ProductMediaServiceInterface::class);\n        $this->requestData = ContainerFacade::get(Request::class)->request;\n        $this->requestFiles = ContainerFacade::get(Request::class)->files;\n    }\n\n    public function addMedia(): void\n    {\n        $errors = $this->processUploadedFiles();\n\n        if ($errors !== []) {\n            $this->sendErrorsResponse($errors);\n        }\n    }\n\n    public function replaceMedia(): void\n    {\n        $errors = $this->processUploadedFiles();\n\n        if ($errors !== []) {\n            $this->sendErrorsResponse($errors);\n            return;\n        }\n\n        $this->removeRoleFromMedia();\n    }\n\n    public function removeMedia(): void\n    {\n        $this->removeRoleFromMedia();\n    }\n\n    public function toggleMediaActiveState(): void\n    {\n        $productMedia = $this->productMediaService->get($this->getProductMediaId());\n\n        if ($productMedia->isActive()) {\n            $this->productMediaService->deactivate($productMedia);\n        } else {\n            $this->productMediaService->activate($productMedia);\n        }\n    }\n\n    public function sortMedia(): void\n    {\n        $this->productMediaService->sort(\n            json_decode(\n                $this->requestData->getString('sorting'),\n                true,\n                512,\n                JSON_THROW_ON_ERROR\n            )\n        );\n    }\n\n    private function removeRoleFromMedia(): void\n    {\n        $productMedia = $this->productMediaService->get($this->getProductMediaId());\n        $productMedia->getRoleSet()->removeRole(ProductMediaRole::from($this->requestData->getString('role')));\n\n        if ($productMedia->getRoleSet()->getRoles()->isEmpty()) {\n            $this->productMediaService->remove($productMedia->getId());\n        } else {\n            $this->productMediaService->update($productMedia);\n        }\n    }\n\n    private function processUploadedFiles(): array\n    {\n        $errors = [];\n        $productId = Id::fromString($this->requestData->getString('productId'));\n        $role = ProductMediaRole::from($this->requestData->getString('role'));\n\n        foreach ($this->requestFiles->get('uploadedFiles') as $uploadedFile) {\n            try {\n                $productMedia = $this->productMediaUploadProcessor->process($productId, $uploadedFile);\n            } catch (MediaValidationException $e) {\n                $errors[] = $this->formatErrorWithFilename($e, $uploadedFile->getClientOriginalName());\n                continue;\n            }\n\n            $productMedia->getRoleSet()->addRole($role);\n            $this->productMediaService->add($productMedia);\n        }\n\n        return $errors;\n    }\n\n    private function getProductMediaId(): Id\n    {\n        return Id::fromString($this->requestData->getString('productMediaId'));\n    }\n\n    private function mapExceptionToTranslation(\\Throwable $e): array\n    {\n        return match (true) {\n            $e instanceof FileSizeTooSmallException =>\n                ['ERR_MEDIA_SIZE_TOO_SMALL', [$e->getActualFormatted(), $e->getMinFormatted()]],\n\n            $e instanceof FileSizeTooLargeException =>\n                ['ERR_MEDIA_SIZE_TOO_LARGE', [$e->getActualFormatted(), $e->getMaxFormatted()]],\n\n            $e instanceof MimeBaseTypeMismatchException =>\n                ['ERR_MEDIA_MIME_BASETYPE_MISMATCH', [$e->getGuessedMime(), $e->getRequiredBasePrefix()]],\n\n            $e instanceof MimeGuessMismatchException =>\n                ['ERR_MEDIA_MIME_GUESS_MISMATCH', [$e->getGuessedMime(), $e->getClientMime()]],\n\n            $e instanceof MimeTypeGuessFailedException =>\n                ['ERR_MEDIA_MIME_GUESS_FAILED', []],\n\n            $e instanceof FileExtensionMismatchException =>\n                ['ERR_MEDIA_EXTENSION_MISMATCH', [$e->getClientExtension(), \\implode(', ', $e->getValidExtensions())]],\n\n            $e instanceof UploadInvalidException => (function () use ($e): array {\n                $key = 'EXCEPTION_FILEUPLOADERROR_' . $e->getErrorCode();\n                $values = [];\n                if ($e->getErrorCode() === \\UPLOAD_ERR_INI_SIZE) {\n                    $values[] = (string) \\ini_get('upload_max_filesize');\n                }\n                return [$key, $values];\n            })(),\n        };\n    }\n\n    private function formatErrorWithFilename(\\Throwable $e, string $filename): string\n    {\n        [$key, $values] = $this->mapExceptionToTranslation($e);\n        $errorMessage = \\sprintf(Registry::getLang()->translateString($key), ...$values);\n\n        return \\sprintf('%s: %s', $filename, $errorMessage);\n    }\n\n    private function sendErrorsResponse(array $errors): void\n    {\n        (new JsonResponse([\n            'errors' => $errors,\n        ]))->send();\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticleReview.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin article review manager.\n * Collects customer review about article data. There ir possibility to update\n * review text or delete it.\n * Admin Menu: Manage Products -> Articles -> Review.\n */\nclass ArticleReview extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Loads selected article review information, returns name of template\n     * file \"article_review\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        parent::render();\n\n        $article = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $this->_aViewData[\"edit\"] = $article;\n\n        $articleId = $this->getEditObjectId();\n        $reviewId = Registry::getRequest()->getRequestEscapedParameter('rev_oxid');\n        if (isset($articleId) && $articleId != \"-1\") {\n            // load object\n            $article->load($articleId);\n\n            if ($article->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n\n            $reviewList = $this->getReviewList($article);\n\n            foreach ($reviewList as $review) {\n                if ($review->oxreviews__oxid->value == $reviewId) {\n                    $review->selected = 1;\n                    break;\n                }\n            }\n            $this->_aViewData[\"allreviews\"] = $reviewList;\n            $this->_aViewData[\"editlanguage\"] = $this->_iEditLang;\n\n            if (isset($reviewId)) {\n                $reviewForEditing = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Review::class);\n                $reviewForEditing->load($reviewId);\n                $this->_aViewData[\"editreview\"] = $reviewForEditing;\n\n                $user = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n                $user->load($reviewForEditing->oxreviews__oxuserid->value);\n                $this->_aViewData[\"user\"] = $user;\n            }\n            //show \"active\" checkbox if moderating is active\n            $this->_aViewData[\"blShowActBox\"] = $config->getConfigParam('blGBModerate');\n        }\n\n        return \"article_review\";\n    }\n\n    /**\n     * returns reviews list for article\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $article Article object\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    protected function getReviewList($article)\n    {\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $query = \"select oxreviews.* from oxreviews\n                     where oxreviews.OXOBJECTID = \" . $database->quote($article->oxarticles__oxid->value) . \"\n                     and oxreviews.oxtype = 'oxarticle'\";\n\n        $variantList = $article->getVariants();\n\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blShowVariantReviews') && count($variantList)) {\n            // verifying rights\n            foreach ($variantList as $variant) {\n                $query .= \"or oxreviews.oxobjectid = \" . $database->quote($variant->oxarticles__oxid->value) . \" \";\n            }\n        }\n\n        //$sSelect .= \"and oxreviews.oxtext\".\\OxidEsales\\Eshop\\Core\\Registry::getLang()->getLanguageTag($this->_iEditLang).\" != ''\";\n        $query .= \"and oxreviews.oxlang = '\" . $this->_iEditLang . \"'\";\n        $query .= \"and oxreviews.oxtext != '' \";\n\n        // all reviews\n        $reviewList = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n        $reviewList->init(\"oxreview\");\n        $reviewList->selectString($query);\n\n        return $reviewList;\n    }\n\n    /**\n     * Saves article review information changes.\n     */\n    public function save()\n    {\n        parent::save();\n\n        $parameters = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n        // checkbox handling\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blGBModerate') && !isset($parameters['oxreviews__oxactive'])) {\n            $parameters['oxreviews__oxactive'] = 0;\n        }\n\n        $review = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Review::class);\n        $review->load(Registry::getRequest()->getRequestEscapedParameter(\"rev_oxid\"));\n        $review->assign($parameters);\n        $review->save();\n    }\n\n    /**\n     * Deletes selected article review information.\n     */\n    public function delete()\n    {\n        $this->resetContentCache();\n\n        $reviewId = Registry::getRequest()->getRequestEscapedParameter(\"rev_oxid\");\n        $review = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Review::class);\n        $review->load($reviewId);\n        $review->delete();\n\n        // recalculating article average rating\n        $rating = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Rating::class);\n        $articleId = $this->getEditObjectId();\n\n        $article = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $article->load($articleId);\n\n        //switch database connection to master for the following read/write access.\n        \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster();\n        $article->setRatingAverage($rating->getRatingAverage($articleId, 'oxarticle'));\n        $article->setRatingCount($rating->getRatingCount($articleId, 'oxarticle'));\n        $article->save();\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticleSelectionAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse Exception;\n\n/**\n * Class controls article assignment to selection lists\n */\nclass ArticleSelectionAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = [\n        'container1' => [ // field , table, visible, multilanguage, ident\n            ['oxtitle', 'oxselectlist', 1, 1, 0],\n            ['oxident', 'oxselectlist', 1, 0, 0],\n            ['oxvaldesc', 'oxselectlist', 1, 0, 0],\n            ['oxid', 'oxselectlist', 0, 0, 1]\n        ],\n        'container2' => [\n            ['oxtitle', 'oxselectlist', 1, 1, 0],\n            ['oxident', 'oxselectlist', 1, 0, 0],\n            ['oxvaldesc', 'oxselectlist', 1, 0, 0],\n            ['oxid', 'oxobject2selectlist', 0, 0, 1]\n        ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $sSLViewName = $this->getViewName('oxselectlist');\n        $sArtViewName = $this->getViewName('oxarticles');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $sArtId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchArtId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        $sOxid = ($sArtId) ? $sArtId : $sSynchArtId;\n        $sQ = \"select oxparentid from {$sArtViewName} where oxid = :oxid and oxparentid != '' \";\n        $sQ .= \"and (select count(oxobjectid) from oxobject2selectlist \" .\n               \"where oxobjectid = :oxobjectid) = 0\";\n        // We force reading from master to prevent issues with slow replications or open transactions\n        // (see ESDEV-3804 and ESDEV-3822).\n        $sParentId = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster()->getOne($sQ, [\n            'oxid' => $sOxid,\n            'oxobjectid' => $sOxid\n        ]);\n\n        // all selectlists article is in\n        $sQAdd = \" from oxobject2selectlist left join {$sSLViewName} \" .\n                 \"on {$sSLViewName}.oxid=oxobject2selectlist.oxselnid  \" .\n                 \"where oxobject2selectlist.oxobjectid = \" . $oDb->quote($sOxid) . \" \";\n        if ($sParentId) {\n            $sQAdd .= \"or oxobject2selectlist.oxobjectid = \" . $oDb->quote($sParentId) . \" \";\n        }\n        // all not assigned selectlists\n        if ($sSynchArtId) {\n            $sQAdd = \" from {$sSLViewName}  \" .\n                     \"where {$sSLViewName}.oxid not in ( select oxobject2selectlist.oxselnid {$sQAdd} ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes article selection lists.\n     */\n    public function removeSel()\n    {\n        $aChosenArt = $this->getActionIds('oxobject2selectlist.oxid');\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxobject2selectlist.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif (is_array($aChosenArt)) {\n            $sChosenArticles = implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aChosenArt));\n            $sQ = \"delete from oxobject2selectlist \" .\n                  \"where oxobject2selectlist.oxid in (\" . $sChosenArticles . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n\n        $articleId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $this->onArticleSelectionListChange($articleId);\n    }\n\n    /**\n     * Adds selection lists to article.\n     *\n     * @throws Exception\n     */\n    public function addSel()\n    {\n        $aAddSel = $this->getActionIds('oxselectlist.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // adding\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sSLViewName = $this->getViewName('oxselectlist');\n            $aAddSel = $this->getAll($this->addFilter(\"select $sSLViewName.oxid \" . $this->getQuery()));\n        }\n\n        if ($soxId && $soxId != \"-1\" && is_array($aAddSel)) {\n            // We force reading from master to prevent issues with slow replications or open transactions\n            // (see ESDEV-3804).\n            $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster();\n            foreach ($aAddSel as $sAdd) {\n                $oNew = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oNew->init(\"oxobject2selectlist\");\n                $sObjectIdField = 'oxobject2selectlist__oxobjectid';\n                $sSelectetionIdField = 'oxobject2selectlist__oxselnid';\n                $sOxSortField = 'oxobject2selectlist__oxsort';\n\n                $oNew->$sObjectIdField = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                $oNew->$sSelectetionIdField = new \\OxidEsales\\Eshop\\Core\\Field($sAdd);\n\n                $sSql = \"select max(oxsort) + 1 from oxobject2selectlist where oxobjectid = :oxobjectid\";\n\n                $oNew->$sOxSortField = new \\OxidEsales\\Eshop\\Core\\Field((int) $database->getOne($sSql, [\n                    'oxobjectid' => $soxId\n                ]));\n                $oNew->save();\n            }\n\n            $this->onArticleSelectionListChange($soxId);\n        }\n    }\n\n    /**\n     * Method is used to bind to article selection list change.\n     *\n     * @param string $articleId\n     */\n    protected function onArticleSelectionListChange($articleId)\n    {\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticleSeo.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Article seo config class\n */\nclass ArticleSeo extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ObjectSeo\n{\n    /**\n     * Chosen category id\n     *\n     * @var string\n     */\n    protected $_sActCatId = null;\n\n    /**\n     * Product selections (categories, vendors etc assigned)\n     *\n     * @var array\n     */\n    protected $_aSelectionList = null;\n\n    /**\n     * Returns active selection type - oxcategory, oxmanufacturer, oxvendor\n     *\n     * @return string\n     */\n    public function getActCatType()\n    {\n        $sType = false;\n        $aData = Registry::getRequest()->getRequestEscapedParameter(\"aSeoData\");\n        if ($aData && isset($aData[\"oxparams\"])) {\n            $oStr = Str::getStr();\n            $iEndPos = $oStr->strpos($aData[\"oxparams\"], \"#\");\n            $sType = $oStr->substr($aData[\"oxparams\"], 0, $iEndPos);\n        } elseif ($aList = $this->getSelectionList()) {\n            reset($aList);\n            $sType = key($aList);\n        }\n\n        return $sType;\n    }\n\n    /**\n     * Returns active category (manufacturer/vendor) language id\n     *\n     * @return int\n     */\n    public function getActCatLang()\n    {\n        if (Registry::getRequest()->getRequestEscapedParameter(\"editlanguage\") !== null) {\n            return $this->_iEditLang;\n        }\n\n        $iLang = false;\n        $aData = Registry::getRequest()->getRequestEscapedParameter(\"aSeoData\");\n        if ($aData && isset($aData[\"oxparams\"])) {\n            $oStr = Str::getStr();\n            $iStartPos = $oStr->strpos($aData[\"oxparams\"], \"#\");\n            $iEndPos = $oStr->strpos($aData[\"oxparams\"], \"#\", $iStartPos + 1);\n            $iLang = $oStr->substr($aData[\"oxparams\"], $iEndPos + 1);\n        } elseif ($aList = $this->getSelectionList()) {\n            $aList = reset($aList);\n            $iLang = key($aList);\n        }\n\n        return (int) $iLang;\n    }\n\n    /**\n     * Returns active category (manufacturer/vendor) id\n     *\n     * @return false|string\n     */\n    public function getActCatId()\n    {\n        $sId = false;\n        $aData = Registry::getRequest()->getRequestEscapedParameter(\"aSeoData\");\n        if ($aData && isset($aData[\"oxparams\"])) {\n            $oStr = Str::getStr();\n            $iStartPos = $oStr->strpos($aData[\"oxparams\"], \"#\");\n            $iEndPos = $oStr->strpos($aData[\"oxparams\"], \"#\", $iStartPos + 1);\n            $iLen = $oStr->strlen($aData[\"oxparams\"]);\n\n            $sId = $oStr->substr($aData[\"oxparams\"], $iStartPos + 1, $iEndPos - $iLen);\n        } elseif ($aList = $this->getSelectionList()) {\n            $oItem = reset($aList[$this->getActCatType()][$this->getActCatLang()]);\n\n            $sId = $oItem->getId();\n        }\n\n        return $sId;\n    }\n\n    /**\n     * Returns product selections array [type][language] (categories, vendors etc assigned)\n     *\n     * @return array\n     */\n    public function getSelectionList()\n    {\n        if ($this->_aSelectionList === null) {\n            $this->_aSelectionList = [];\n\n            $oProduct = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            $oProduct->load($this->getEditObjectId());\n\n            if ($oCatList = $this->getCategoryList($oProduct)) {\n                $this->_aSelectionList[\"oxcategory\"][$this->_iEditLang] = $oCatList;\n            }\n\n            if ($oVndList = $this->getVendorList($oProduct)) {\n                $this->_aSelectionList[\"oxvendor\"][$this->_iEditLang] = $oVndList;\n            }\n\n            if ($oManList = $this->getManufacturerList($oProduct)) {\n                $this->_aSelectionList[\"oxmanufacturer\"][$this->_iEditLang] = $oManList;\n            }\n        }\n\n        return $this->_aSelectionList;\n    }\n\n    /**\n     * Returns array of product categories\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle Article object\n     *\n     * @return array\n     */\n    protected function getCategoryList($article)\n    {\n        $mainCategoryId = false;\n        if ($mainCategory = $article->getCategory()) {\n            $mainCategoryId = $mainCategory->getId();\n        }\n\n        $categoryList = [];\n        $language = $this->getEditLang();\n\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $view = $tableViewNameGenerator->getViewName('oxobject2category');\n        $queryForPriceCategories = $article->getSqlForPriceCategories('oxid');\n        $query = \"select oxobject2category.oxcatnid as oxid from {$view} as oxobject2category \" .\n              \"where oxobject2category.oxobjectid = :oxobjectid union \" . $queryForPriceCategories;\n\n        $categoriesIds = DatabaseProvider::getDb()->getCol($query, [\n            'oxobjectid' => $article->getId()\n        ]);\n        foreach ($categoriesIds as $categoryId) {\n            $category = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n            if ($category->loadInLang($language, $categoryId)) {\n                if ($mainCategoryId == $category->getId()) {\n                    $suffix = Registry::getLang()->translateString('(main category)', $this->getEditLang());\n                    $titleField = 'oxcategories__oxtitle';\n                    $title = $category->$titleField->getRawValue() . ' ' . $suffix;\n                    $category->$titleField = new Field($title, Field::T_RAW);\n                }\n                $categoryList[] = $category;\n            }\n        }\n\n        return $categoryList;\n    }\n\n    /**\n     * Returns array containing product vendor object\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle Article object\n     *\n     * @return array\n     */\n    protected function getVendorList($oArticle)\n    {\n        if ($oArticle->oxarticles__oxvendorid->value) {\n            $oVendor = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Vendor::class);\n            if ($oVendor->loadInLang($this->getEditLang(), $oArticle->oxarticles__oxvendorid->value)) {\n                return [$oVendor];\n            }\n        }\n    }\n\n    /**\n     * Returns array containing product manufacturer object\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle Article object\n     *\n     * @return array\n     */\n    protected function getManufacturerList($oArticle)\n    {\n        if ($oArticle->oxarticles__oxmanufacturerid->value) {\n            $oManufacturer = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Manufacturer::class);\n            if ($oManufacturer->loadInLang($this->getEditLang(), $oArticle->oxarticles__oxmanufacturerid->value)) {\n                return [$oManufacturer];\n            }\n        }\n    }\n\n    /**\n     * Returns active category object, used for seo url getter\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Category|null\n     */\n    public function getActCategory()\n    {\n        $oCat = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n\n        return ($oCat->load($this->getActCatId())) ? $oCat : null;\n    }\n\n    /**\n     * Returns active vendor object if available\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Vendor|null\n     */\n    public function getActVendor()\n    {\n        $oVendor = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Vendor::class);\n\n        return ($this->getActCatType() == 'oxvendor' && $oVendor->load($this->getActCatId())) ? $oVendor : null;\n    }\n\n    /**\n     * Returns active manufacturer object if available\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Manufacturer|null\n     */\n    public function getActManufacturer()\n    {\n        $oManufacturer = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Manufacturer::class);\n        $blLoaded = $this->getActCatType() == 'oxmanufacturer' && $oManufacturer->load($this->getActCatId());\n\n        return ($blLoaded) ? $oManufacturer : null;\n    }\n\n    /**\n     * Returns list type for current seo url\n     *\n     * @return string\n     */\n    public function getListType()\n    {\n        switch ($this->getActCatType()) {\n            case 'oxvendor':\n                return 'vendor';\n            case 'oxmanufacturer':\n                return 'manufacturer';\n        }\n    }\n\n    /**\n     * Returns editable object language id\n     *\n     * @return int\n     */\n    public function getEditLang()\n    {\n        return $this->getActCatLang();\n    }\n\n    /**\n     * Returns alternative seo entry id\n     *\n     * @return null\n     */\n    protected function getAltSeoEntryId()\n    {\n        return $this->getEditObjectId();\n    }\n\n    /**\n     * Returns url type\n     *\n     * @return string\n     */\n    protected function getType()\n    {\n        return 'oxarticle';\n    }\n\n    /**\n     * Processes parameter before writing to db\n     *\n     * @param string $sParam parameter to process\n     *\n     * @return string\n     */\n    public function processParam($sParam)\n    {\n        return $this->getActCatId();\n    }\n\n    /**\n     * Returns current object type seo encoder object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderArticle\n     */\n    protected function getEncoder()\n    {\n        return Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderArticle::class);\n    }\n\n    /**\n     * Returns seo uri\n     *\n     * @return string\n     */\n    public function getEntryUri()\n    {\n        $product = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n\n        if ($product->load($this->getEditObjectId())) {\n            $seoEncoder = $this->getEncoder();\n\n            switch ($this->getActCatType()) {\n                case 'oxvendor':\n                    return $seoEncoder->getArticleVendorUri($product, $this->getEditLang());\n                case 'oxmanufacturer':\n                    return $seoEncoder->getArticleManufacturerUri($product, $this->getEditLang());\n                default:\n                    if ($this->getActCatId()) {\n                        return $seoEncoder->getArticleUri($product, $this->getEditLang());\n                    } else {\n                        return $seoEncoder->getArticleMainUri($product, $this->getEditLang());\n                    }\n            }\n        }\n    }\n\n    /**\n     * Returns TRUE, as this view support category selector\n     *\n     * @return bool\n     */\n    public function showCatSelect()\n    {\n        return true;\n    }\n\n    /**\n     * Returns TRUE if current seo entry has fixed state\n     *\n     * @return bool\n     */\n    public function isEntryFixed()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $sId = $this->getSaveObjectId();\n        $iLang = (int) $this->getEditLang();\n        $iShopId = Registry::getConfig()->getShopId();\n        $sParam = $this->processParam($this->getActCatId());\n\n        $sQ = \"select oxfixed from oxseo where\n                   oxseo.oxobjectid = :oxobjectid and\n                   oxseo.oxshopid = :oxshopid and oxseo.oxlang = :oxlang and oxparams = :oxparams\";\n\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        return (bool) \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster()->getOne(\n            $sQ,\n            [\n                'oxobjectid' => $sId,\n                'oxshopid' => $iShopId,\n                'oxlang' => $iLang,\n                'oxparams' => $sParam\n            ]\n        );\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticleStock.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse stdClass;\n\n/**\n * Admin article inventory manager.\n * Collects such information about article as stock quantity, delivery status,\n * stock message, etc; Updates information (on user submit).\n * Admin Menu: Manage Products -> Articles -> Inventory.\n */\nclass ArticleStock extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Loads article Inventory information and\n     * returns the name of template file.\n     *\n     * @return string\n     */\n    public function render()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        parent::render();\n\n        $this->_aViewData[\"edit\"] = $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n\n        $soxId = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oArticle->loadInLang($this->_iEditLang, $soxId);\n\n            // load object in other languages\n            $oOtherLang = $oArticle->getAvailableInLangs();\n            if (!isset($oOtherLang[$this->_iEditLang])) {\n                $oArticle->loadInLang(key($oOtherLang), $soxId);\n            }\n\n            foreach ($oOtherLang as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $oLang;\n            }\n\n            if ($oArticle->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n\n            // variant handling\n            if ($oArticle->oxarticles__oxparentid->value) {\n                $oParentArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n                $oParentArticle->load($oArticle->oxarticles__oxparentid->value);\n                $this->_aViewData[\"parentarticle\"] = $oParentArticle;\n                $this->_aViewData[\"oxparentid\"] = $oArticle->oxarticles__oxparentid->value;\n            }\n\n            if ($myConfig->getConfigParam('blMallInterchangeArticles')) {\n                $sShopSelect = '1';\n            } else {\n                $sShopID = $myConfig->getShopID();\n                $sShopSelect = \" oxshopid =  '$sShopID' \";\n            }\n\n            $oPriceList = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n            $oPriceList->init('oxbase', \"oxprice2article\");\n            $sQ = \"select * from oxprice2article where oxartid = :oxartid \" .\n                  \"and {$sShopSelect} and (oxamount > 0 or oxamountto > 0) order by oxamount \";\n            $oPriceList->selectstring($sQ, [\n                'oxartid' => $soxId\n            ]);\n\n            $this->_aViewData[\"amountprices\"] = $oPriceList;\n        }\n\n        return \"article_stock\";\n    }\n\n    /**\n     * Saves article Inventori information changes.\n     */\n    public function save()\n    {\n        parent::save();\n\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $oArticle->loadInLang($this->_iEditLang, $soxId);\n\n        $oArticle->setLanguage(0);\n\n        // checkbox handling\n        if (!$oArticle->oxarticles__oxparentid->value && !isset($aParams['oxarticles__oxremindactive'])) {\n            $aParams['oxarticles__oxremindactive'] = 0;\n        }\n\n        $oArticle->assign($aParams);\n\n        //tells to article to save in different language\n        $oArticle->setLanguage($this->_iEditLang);\n        $oArticle = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsFile()->processFiles($oArticle);\n\n        $oArticle->resetRemindStatus();\n\n        $oArticle->updateVariantsRemind();\n\n        $oArticle->save();\n    }\n\n    /**\n     * Adds or updates amount price to article\n     *\n     * @param string $sOXID         Object ID\n     * @param array  $aUpdateParams Parameters\n     *\n     * @return null\n     */\n    public function addprice($sOXID = null, $aUpdateParams = null)\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $this->resetContentCache();\n\n        $sOxArtId = $this->getEditObjectId();\n        $this->onArticleAmountPriceChange($sOxArtId);\n\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        if (!is_array($aParams)) {\n            return;\n        }\n\n        if (isset($aUpdateParams) && is_array($aUpdateParams)) {\n            $aParams = array_merge($aParams, $aUpdateParams);\n        }\n\n        //replacing commas\n        foreach ($aParams as $key => $sParam) {\n            $aParams[$key] = str_replace(\",\", \".\", $sParam);\n        }\n\n        $aParams['oxprice2article__oxshopid'] = $myConfig->getShopID();\n\n        if (isset($sOXID)) {\n            $aParams['oxprice2article__oxid'] = $sOXID;\n        }\n\n        $aParams['oxprice2article__oxartid'] = $sOxArtId;\n        if (!isset($aParams['oxprice2article__oxamount']) || !$aParams['oxprice2article__oxamount']) {\n            $aParams['oxprice2article__oxamount'] = \"1\";\n        }\n\n        if (!$myConfig->getConfigParam('blAllowUnevenAmounts')) {\n            $aParams['oxprice2article__oxamount'] = round((string) $aParams['oxprice2article__oxamount']);\n            $aParams['oxprice2article__oxamountto'] = round((string) $aParams['oxprice2article__oxamountto']);\n        }\n\n        $dPrice = $aParams['price'];\n        $sType = $aParams['pricetype'];\n\n        $oArticlePrice = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n        $oArticlePrice->init(\"oxprice2article\");\n        $oArticlePrice->assign($aParams);\n\n        $oArticlePrice->$sType = new \\OxidEsales\\Eshop\\Core\\Field($dPrice);\n\n        //validating\n        if (\n            $oArticlePrice->$sType->value &&\n            $oArticlePrice->oxprice2article__oxamount->value &&\n            $oArticlePrice->oxprice2article__oxamountto->value &&\n            is_numeric($oArticlePrice->$sType->value) &&\n            is_numeric($oArticlePrice->oxprice2article__oxamount->value) &&\n            is_numeric($oArticlePrice->oxprice2article__oxamountto->value) &&\n            $oArticlePrice->oxprice2article__oxamount->value <= $oArticlePrice->oxprice2article__oxamountto->value\n        ) {\n            $oArticlePrice->save();\n        }\n\n        // check if abs price is lower than base price\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $oArticle->loadInLang($this->_iEditLang, $sOxArtId);\n        $sPriceField = 'oxarticles__oxprice';\n        if (\n            ($aParams['price'] >= $oArticle->$sPriceField->value) &&\n            ($aParams['pricetype'] == 'oxprice2article__oxaddabs')\n        ) {\n            if (is_null($sOXID)) {\n                $sOXID = $oArticlePrice->getId();\n            }\n            $this->_aViewData[\"errorscaleprice\"][] = $sOXID;\n        }\n    }\n\n    /**\n     * Updates all amount prices for article at once\n     */\n    public function updateprices()\n    {\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"updateval\");\n        if (is_array($aParams)) {\n            foreach ($aParams as $soxId => $aStockParams) {\n                $this->addprice($soxId, $aStockParams);\n            }\n        }\n\n        $sOxArtId = $this->getEditObjectId();\n        $this->onArticleAmountPriceChange($sOxArtId);\n    }\n\n\n    /**\n     * Adds amount price to article\n     */\n    public function deleteprice()\n    {\n        $this->resetContentCache();\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $articleId = $this->getEditObjectId();\n        $oDb->execute(\"delete from oxprice2article where oxid = :oxid and oxartid = :oxartid\", [\n            'oxid' => Registry::getRequest()->getRequestEscapedParameter(\"priceid\"),\n            'oxartid' => $articleId\n        ]);\n\n        $this->onArticleAmountPriceChange($articleId);\n    }\n\n    /**\n     * Method is used to bind to article amount price change.\n     *\n     * @param string $articleId\n     */\n    protected function onArticleAmountPriceChange($articleId)\n    {\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticleUserdef.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Class reserved for extending (for customization - you can add you own fields, etc.).\n */\nclass ArticleUserdef extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $this->_aViewData[\"edit\"] = $oArticle;\n\n        $soxId = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            if ($oArticle->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n\n            // load object\n            $oArticle->load($soxId);\n        }\n\n        return \"article_userdef\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ArticleVariant.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductVariantMediaServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse stdClass;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Admin article variants manager.\n * Collects and updates article variants data.\n * Admin Menu: Manage Products -> Articles -> Variants.\n */\nclass ArticleVariant extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Variant parent product object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected $_oProductParent = null;\n\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->getEditObjectId();\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sSLViewName = $tableViewNameGenerator->getViewName('oxselectlist');\n\n        // all selectlists\n        $oAllSel = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n        $oAllSel->init(\"oxselectlist\");\n        $sQ = \"select * from $sSLViewName\";\n        $oAllSel->selectString($sQ);\n        $this->_aViewData[\"allsel\"] = $oAllSel;\n\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $this->_aViewData[\"edit\"] = $oArticle;\n\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oArticle->loadInLang($this->_iEditLang, $soxId);\n\n            if ($oArticle->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n\n            $_POST[\"language\"] = $_GET[\"language\"] = $this->_iEditLang;\n            $oVariants = $oArticle->getAdminVariants($this->_iEditLang);\n\n            $this->_aViewData[\"mylist\"] = $oVariants;\n\n            // load object in other languages\n            $oOtherLang = $oArticle->getAvailableInLangs();\n            if (!isset($oOtherLang[$this->_iEditLang])) {\n                $oArticle->loadInLang(key($oOtherLang), $soxId);\n            }\n\n            foreach ($oOtherLang as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $oLang;\n            }\n\n            if ($oArticle->oxarticles__oxparentid->value) {\n                $this->_aViewData[\"parentarticle\"] = $this->getProductParent($oArticle->oxarticles__oxparentid->value);\n                $this->_aViewData[\"oxparentid\"] = $oArticle->oxarticles__oxparentid->value;\n                $this->_aViewData[\"issubvariant\"] = 1;\n                // A. disable variant information editing for variant\n                $this->_aViewData[\"readonly\"] = 1;\n            }\n            $this->_aViewData[\"editlanguage\"] = $this->_iEditLang;\n\n            $aLang = array_diff(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->getLanguageNames(), $oOtherLang);\n            if (count($aLang)) {\n                $this->_aViewData[\"posslang\"] = $aLang;\n            }\n\n            foreach ($oOtherLang as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = $oLang;\n            }\n        }\n\n        return \"article_variant\";\n    }\n\n    /**\n     * Saves article variant.\n     *\n     * @param string $sOXID   Object ID\n     * @param array  $aParams Parameters\n     *\n     * @return null\n     */\n    public function savevariant($sOXID = null, $aParams = null)\n    {\n        if (!isset($sOXID) && !isset($aParams)) {\n            $sOXID = Registry::getRequest()->getRequestEscapedParameter(\"voxid\");\n            $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n        }\n\n        // varianthandling\n        $soxparentId = $this->getEditObjectId();\n        if (isset($soxparentId) && $soxparentId && $soxparentId != \"-1\") {\n            $aParams['oxarticles__oxparentid'] = $soxparentId;\n        } else {\n            unset($aParams['oxarticles__oxparentid']);\n        }\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle */\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n\n        if ($sOXID != \"-1\") {\n            $oArticle->loadInLang($this->_iEditLang, $sOXID);\n        }\n\n        // checkbox handling\n        if (is_array($aParams) && !isset($aParams['oxarticles__oxactive'])) {\n            $aParams['oxarticles__oxactive'] = 0;\n        }\n\n        if (!$this->isAnythingChanged($oArticle, $aParams)) {\n            return;\n        }\n\n        $oArticle->setLanguage(0);\n        $oArticle->assign($aParams);\n        $oArticle->setLanguage($this->_iEditLang);\n\n        // #0004473\n        $oArticle->resetRemindStatus();\n\n        $isNewVariant = ($sOXID === \"-1\");\n\n        if ($isNewVariant) {\n            if ($oParent = $this->getProductParent($oArticle->oxarticles__oxparentid->value)) {\n                // assign field from parent for new variant\n                // #4406\n                $oArticle->oxarticles__oxisconfigurable = new \\OxidEsales\\Eshop\\Core\\Field($oParent->oxarticles__oxisconfigurable->value);\n                $oArticle->oxarticles__oxremindactive = new \\OxidEsales\\Eshop\\Core\\Field($oParent->oxarticles__oxremindactive->value);\n            }\n        }\n\n        $oArticle->save();\n\n        if ($isNewVariant && $oArticle->oxarticles__oxparentid->value) {\n            ContainerFacade::get(ProductVariantMediaServiceInterface::class)->assignFromParentToVariant(\n                Id::fromString($oArticle->oxarticles__oxparentid->value),\n                Id::fromString($oArticle->getId())\n            );\n        }\n    }\n\n    /**\n     * Checks if anything is changed in given data compared with existing product values.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oProduct Product to be checked.\n     * @param array                                       $aData    Data provided for check.\n     *\n     * @return bool\n     */\n    protected function isAnythingChanged($oProduct, $aData)\n    {\n        if (!is_array($aData)) {\n            return true;\n        }\n        foreach ($aData as $sKey => $sValue) {\n            if (isset($oProduct->$sKey) && $oProduct->$sKey->value != $aData[$sKey]) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns variant parent object\n     *\n     * @param string $sParentId parent product id\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected function getProductParent($sParentId)\n    {\n        if (\n            $this->_oProductParent === null ||\n            ($this->_oProductParent !== false && $this->_oProductParent->getId() != $sParentId)\n        ) {\n            $this->_oProductParent = false;\n            $oProduct = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            if ($oProduct->load($sParentId)) {\n                $this->_oProductParent = $oProduct;\n            }\n        }\n\n        return $this->_oProductParent;\n    }\n\n    /**\n     * Saves all article variants at once.\n     */\n    public function savevariants()\n    {\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n        if (is_array($aParams)) {\n            foreach ($aParams as $soxId => $aVarParams) {\n                $this->savevariant($soxId, $aVarParams);\n            }\n        }\n\n        $this->resetContentCache();\n    }\n\n    /**\n     * Deletes article variant.\n     *\n     * @return null\n     */\n    public function deleteVariant()\n    {\n        $editObjectOxid = $this->getEditObjectId();\n        $editObject = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $editObject->load($editObjectOxid);\n        if ($editObject->isDerived()) {\n            return;\n        }\n\n        $this->resetContentCache();\n\n        $variantOxid = Registry::getRequest()->getRequestParameter(\"voxid\");\n        $variant = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $variant->delete($variantOxid);\n    }\n\n    /**\n     * Changes name of variant.\n     */\n    public function changename()\n    {\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        $this->resetContentCache();\n\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        if ($soxId != \"-1\") {\n            $oArticle->loadInLang($this->_iEditLang, $soxId);\n        }\n\n        $oArticle->setLanguage(0);\n        $oArticle->assign($aParams);\n        $oArticle->setLanguage($this->_iEditLang);\n        $oArticle->save();\n    }\n\n\n    /**\n     * Add selection list\n     *\n     * @return null\n     */\n    public function addsel()\n    {\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        if ($oArticle->load($this->getEditObjectId())) {\n            //Disable editing for derived articles\n            if ($oArticle->isDerived()) {\n                return;\n            }\n\n            $this->resetContentCache();\n\n            if ($aSels = Registry::getRequest()->getRequestEscapedParameter(\"allsel\")) {\n                $oVariantHandler = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\VariantHandler::class);\n                $oVariantHandler->genVariantFromSell($aSels, $oArticle);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AttributeCategory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin category main attributes manager.\n * There is possibility to change attribute description, assign categories to\n * this attribute, etc.\n * Admin Menu: Manage Products -> Attributes -> Gruppen.\n */\nclass AttributeCategory extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oAttr = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Attribute::class);\n            $oAttr->load($soxId);\n            $this->_aViewData[\"edit\"] = $oAttr;\n        }\n\n        if (Registry::getRequest()->getRequestEscapedParameter(\"aoc\")) {\n            $oAttributeCategoryAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AttributeCategoryAjax::class);\n            $this->_aViewData['oxajax'] = $oAttributeCategoryAjax->getColumns();\n\n            return \"popups/attribute_category\";\n        }\n\n        return \"attribute_category\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AttributeCategoryAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse Exception;\n\n/**\n * Class manages category attributes\n */\nclass AttributeCategoryAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,         visible, multilanguage, ident\n        ['oxtitle', 'oxcategories', 1, 1, 0],\n        ['oxdesc', 'oxcategories', 1, 1, 0],\n        ['oxid', 'oxcategories', 0, 0, 0],\n        ['oxid', 'oxcategories', 0, 0, 1]\n    ],\n                                 'container2' => [\n                                     ['oxtitle', 'oxcategories', 1, 1, 0],\n                                     ['oxdesc', 'oxcategories', 1, 1, 0],\n                                     ['oxid', 'oxcategories', 0, 0, 0],\n                                     ['oxid', 'oxcategory2attribute', 0, 0, 1],\n                                     ['oxid', 'oxcategories', 0, 0, 1]\n                                 ],\n                                 'container3' => [\n                                     ['oxtitle', 'oxattribute', 1, 1, 0],\n                                     ['oxsort', 'oxcategory2attribute', 1, 0, 0],\n                                     ['oxid', 'oxcategory2attribute', 0, 0, 1]\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $sCatTable = $this->getViewName('oxcategories');\n        $sDiscountId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchDiscountId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sDiscountId) {\n            $sQAdd = \" from {$sCatTable} where {$sCatTable}.oxshopid = '\" . $myConfig->getShopId() . \"' \";\n            $sQAdd .= \" and {$sCatTable}.oxactive = '1' \";\n        } else {\n            $sQAdd = \" from {$sCatTable} left join oxcategory2attribute \" .\n                     \"on {$sCatTable}.oxid=oxcategory2attribute.oxobjectid \" .\n                     \" where oxcategory2attribute.oxattrid = \" . $oDb->quote($sDiscountId) .\n                     \" and {$sCatTable}.oxshopid = '\" . $myConfig->getShopId() . \"' \" .\n                     \" and {$sCatTable}.oxactive = '1' \";\n        }\n\n        if ($sSynchDiscountId && $sSynchDiscountId != $sDiscountId) {\n            $sQAdd .= \" and {$sCatTable}.oxid not in ( select {$sCatTable}.oxid \" .\n                      \"from {$sCatTable} left join oxcategory2attribute \" .\n                      \"on {$sCatTable}.oxid=oxcategory2attribute.oxobjectid \" .\n                      \" where oxcategory2attribute.oxattrid = \" . $oDb->quote($sSynchDiscountId) .\n                      \" and {$sCatTable}.oxshopid = '\" . $myConfig->getShopId() . \"' \" .\n                      \" and {$sCatTable}.oxactive = '1' ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes category from Attributes list\n     */\n    public function removeCatFromAttr()\n    {\n        $aChosenCat = $this->getActionIds('oxcategory2attribute.oxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxcategory2attribute.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif (is_array($aChosenCat)) {\n            $sChosenCategories = implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aChosenCat));\n            $sQ = \"delete from oxcategory2attribute where oxcategory2attribute.oxid in (\" . $sChosenCategories . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n\n        $this->resetContentCache();\n    }\n\n    /**\n     * Adds category to Attributes list\n     *\n     * @throws Exception\n     */\n    public function addCatToAttr()\n    {\n        $aAddCategory = $this->getActionIds('oxcategories.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        $oAttribute = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Attribute::class);\n        // adding\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sCatTable = $this->getViewName('oxcategories');\n            $aAddCategory = $this->getAll($this->addFilter(\"select $sCatTable.oxid \" . $this->getQuery()));\n        }\n\n        if ($oAttribute->load($soxId) && is_array($aAddCategory)) {\n            // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804 and ESDEV-3822).\n            $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster();\n            foreach ($aAddCategory as $sAdd) {\n                $oNewGroup = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oNewGroup->init(\"oxcategory2attribute\");\n                $sOxSortField = 'oxcategory2attribute__oxsort';\n                $sObjectIdField = 'oxcategory2attribute__oxobjectid';\n                $sAttributeIdField = 'oxcategory2attribute__oxattrid';\n                $sOxIdField = 'oxattribute__oxid';\n                $oNewGroup->$sObjectIdField = new \\OxidEsales\\Eshop\\Core\\Field($sAdd);\n                $oNewGroup->$sAttributeIdField = new \\OxidEsales\\Eshop\\Core\\Field($oAttribute->$sOxIdField->value);\n\n                $sSql = \"select max(oxsort) + 1 from oxcategory2attribute where oxobjectid = :oxobjectid\";\n\n                $oNewGroup->$sOxSortField = new \\OxidEsales\\Eshop\\Core\\Field((int) $database->getOne($sSql, [\n                    'oxobjectid' => $sAdd\n                ]));\n                $oNewGroup->save();\n            }\n        }\n\n        $this->resetContentCache();\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AttributeController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Returns template, that arranges two other templates (\"attribute_list\"\n * and \"attribute_main\") to frame.\n * Admin Menu: Manage Products -> Attributes.\n */\nclass AttributeController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'attribute';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AttributeList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin attributes manager.\n * Collects attributes base information (Description), there is ability to filter\n * them by Description or delete them.\n * Admin Menu: Manage Products -> Attributes.\n */\nclass AttributeList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'attribute_list';\n\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxattribute';\n\n    /**\n     * Default SQL sorting parameter (default null).\n     *\n     * @var string\n     */\n    protected $_sDefSortField = 'oxtitle';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AttributeMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse stdClass;\n\n/**\n * Admin article main attributes manager.\n * There is possibility to change attribute description, assign articles to\n * this attribute, etc.\n * Admin Menu: Manage Products -> Attributes -> Main.\n */\nclass AttributeMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $oAttr = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Attribute::class);\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n\n        // copy this tree for our article choose\n        if (isset($soxId) && $soxId != \"-1\") {\n            // generating category tree for select list\n            $this->createCategoryTree(\"artcattree\", $soxId);\n            // load object\n            $oAttr->loadInLang($this->_iEditLang, $soxId);\n\n            //Disable editing for derived items\n            if ($oAttr->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n\n            $oOtherLang = $oAttr->getAvailableInLangs();\n            if (!isset($oOtherLang[$this->_iEditLang])) {\n                $oAttr->loadInLang(key($oOtherLang), $soxId);\n            }\n\n            // remove already created languages\n            $aLang = array_diff(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->getLanguageNames(), $oOtherLang);\n            if (count($aLang)) {\n                $this->_aViewData[\"posslang\"] = $aLang;\n            }\n\n            foreach ($oOtherLang as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $oLang;\n            }\n        }\n\n        $this->_aViewData[\"edit\"] = $oAttr;\n\n        if (Registry::getRequest()->getRequestEscapedParameter(\"aoc\")) {\n            $oAttributeMainAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AttributeMainAjax::class);\n            $this->_aViewData['oxajax'] = $oAttributeMainAjax->getColumns();\n\n            return \"popups/attribute_main\";\n        }\n\n        return \"attribute_main\";\n    }\n\n    /**\n     * Saves article attributes.\n     *\n     * @return mixed\n     */\n    public function save()\n    {\n        parent::save();\n\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        $oAttr = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Attribute::class);\n\n        if ($soxId != \"-1\") {\n            $oAttr->loadInLang($this->_iEditLang, $soxId);\n        } else {\n            $aParams['oxattribute__oxid'] = null;\n            //$aParams = $oAttr->ConvertNameArray2Idx( $aParams);\n        }\n\n        //Disable editing for derived items\n        if ($oAttr->isDerived()) {\n            return;\n        }\n\n        $oAttr->setLanguage(0);\n        $oAttr->assign($aParams);\n        $oAttr->setLanguage($this->_iEditLang);\n        $oAttr = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsFile()->processFiles($oAttr);\n        $oAttr->save();\n\n        $this->setEditObjectId($oAttr->getId());\n    }\n\n    /**\n     * Saves attribute data to different language (eg. english).\n     *\n     * @return null\n     */\n    public function saveinnlang()\n    {\n        parent::save();\n\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        $oAttr = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Attribute::class);\n\n        if ($soxId != \"-1\") {\n            $oAttr->loadInLang($this->_iEditLang, $soxId);\n        } else {\n            $aParams['oxattribute__oxid'] = null;\n        }\n\n        //Disable editing for derived items\n        if ($oAttr->isDerived()) {\n            return;\n        }\n\n        $oAttr->setLanguage(0);\n        $oAttr->assign($aParams);\n\n        // apply new language\n        $oAttr->setLanguage(Registry::getRequest()->getRequestEscapedParameter(\"new_lang\"));\n        $oAttr->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oAttr->getId());\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AttributeMainAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Str;\n\n/**\n * Class manages article attributes\n */\nclass AttributeMainAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * If true extended column selection will be build\n     *\n     * @var bool\n     */\n    protected $_blAllowExtColumns = true;\n\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,         visible, multilanguage, ident\n        ['oxartnum', 'oxarticles', 1, 0, 0],\n        ['oxtitle', 'oxarticles', 1, 1, 0],\n        ['oxean', 'oxarticles', 1, 0, 0],\n        ['oxmpn', 'oxarticles', 0, 0, 0],\n        ['oxprice', 'oxarticles', 0, 0, 0],\n        ['oxstock', 'oxarticles', 0, 0, 0],\n        ['oxid', 'oxarticles', 0, 0, 1]\n    ],\n                                 'container2' => [\n                                     ['oxartnum', 'oxarticles', 1, 0, 0],\n                                     ['oxtitle', 'oxarticles', 1, 1, 0],\n                                     ['oxean', 'oxarticles', 1, 0, 0],\n                                     ['oxmpn', 'oxarticles', 0, 0, 0],\n                                     ['oxprice', 'oxarticles', 0, 0, 0],\n                                     ['oxstock', 'oxarticles', 0, 0, 0],\n                                     ['oxid', 'oxobject2attribute', 0, 0, 1]\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $sArticleTable = $this->getViewName('oxarticles');\n        $sOCatView = $this->getViewName('oxobject2category');\n        $sOAttrView = $this->getViewName('oxobject2attribute');\n\n        $sDelId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchDelId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sDelId) {\n            // performance\n            $sQAdd = \" from $sArticleTable where 1 \";\n            $sQAdd .= $myConfig->getConfigParam('blVariantsSelection') ? '' : \" and $sArticleTable.oxparentid = '' \";\n        } elseif ($sSynchDelId && $sDelId != $sSynchDelId) {\n            // selected category ?\n            $blVariantsSelectionParameter = $myConfig->getConfigParam('blVariantsSelection');\n            $sSqlIfTrue = \" ( {$sArticleTable}.oxid=oxobject2category.oxobjectid \" .\n                          \"or {$sArticleTable}.oxparentid=oxobject2category.oxobjectid)\";\n            $sSqlIfFalse = \" {$sArticleTable}.oxid=oxobject2category.oxobjectid \";\n            $sVariantSelectionSql = $blVariantsSelectionParameter ? $sSqlIfTrue : $sSqlIfFalse;\n            $sQAdd = \" from {$sOCatView} as oxobject2category left join {$sArticleTable} on {$sVariantSelectionSql}\" .\n                     \" where oxobject2category.oxcatnid = \" . $oDb->quote($sDelId) . \" \";\n        } else {\n            $sQAdd = \" from {$sOAttrView} left join {$sArticleTable} \" .\n                     \"on {$sArticleTable}.oxid={$sOAttrView}.oxobjectid \" .\n                     \"where {$sOAttrView}.oxattrid = \" . $oDb->quote($sDelId) .\n                     \" and {$sArticleTable}.oxid is not null \";\n        }\n\n        if ($sSynchDelId && $sSynchDelId != $sDelId) {\n            $sQAdd .= \" and {$sArticleTable}.oxid not in ( select {$sOAttrView}.oxobjectid from {$sOAttrView} \" .\n                      \"where {$sOAttrView}.oxattrid = \" . $oDb->quote($sSynchDelId) . \" ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Adds filter SQL to current query\n     *\n     * @param string $sQ query to add filter condition\n     *\n     * @return string\n     */\n    protected function addFilter($sQ)\n    {\n        $sQ = parent::addFilter($sQ);\n\n        // display variants or not ?\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blVariantsSelection')) {\n            $sQ .= ' group by ' . $this->getViewName('oxarticles') . '.oxid ';\n\n            $oStr = Str::getStr();\n            if ($oStr->strpos($sQ, \"select count( * ) \") === 0) {\n                $sQ = \"select count( * ) from ( {$sQ} ) as _cnttable\";\n            }\n        }\n\n        return $sQ;\n    }\n\n    /**\n     * Removes article from Attribute list\n     */\n    public function removeAttrArticle()\n    {\n        $aChosenCat = $this->getActionIds('oxobject2attribute.oxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sO2AttributeView = $this->getViewName('oxobject2attribute');\n\n            $sQ = parent::addFilter(\"delete $sO2AttributeView.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif (is_array($aChosenCat)) {\n            $sChosenCategories = implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aChosenCat));\n            $sQ = \"delete from oxobject2attribute where oxobject2attribute.oxid in (\" . $sChosenCategories . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adds article to Attribute list\n     */\n    public function addAttrArticle()\n    {\n        $aAddArticle = $this->getActionIds('oxarticles.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // adding\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sArticleTable = $this->getViewName('oxarticles');\n            $aAddArticle = $this->getAll($this->addFilter(\"select $sArticleTable.oxid \" . $this->getQuery()));\n        }\n\n        $oAttribute = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Attribute::class);\n\n        if ($oAttribute->load($soxId) && is_array($aAddArticle)) {\n            foreach ($aAddArticle as $sAdd) {\n                $oNewGroup = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oNewGroup->init(\"oxobject2attribute\");\n                $oNewGroup->oxobject2attribute__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sAdd);\n                $oNewGroup->oxobject2attribute__oxattrid = new \\OxidEsales\\Eshop\\Core\\Field($oAttribute->oxattribute__oxid->value);\n                $oNewGroup->save();\n\n                $this->onArticleAddToAttributeList($sAdd);\n            }\n        }\n    }\n\n    /**\n     * Method used to overload.\n     *\n     * @param string $articleId\n     */\n    protected function onArticleAddToAttributeList($articleId)\n    {\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/AttributeOrderAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages article select lists sorting\n */\nclass AttributeOrderAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [\n        ['oxtitle', 'oxattribute', 1, 1, 0],\n        ['oxsort', 'oxcategory2attribute', 1, 0, 0],\n        ['oxid', 'oxcategory2attribute', 0, 0, 1]\n    ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $sSelTable = $this->getViewName('oxattribute');\n        $sArtId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n\n        return \" from $sSelTable left join oxcategory2attribute on oxcategory2attribute.oxattrid = $sSelTable.oxid \" .\n                 \"where oxobjectid = \" . \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quote($sArtId) . \" \";\n    }\n\n    /**\n     * Returns SQL query addon for sorting\n     *\n     * @return string\n     */\n    protected function getSorting()\n    {\n        return 'order by oxcategory2attribute.oxsort ';\n    }\n\n    /**\n     * Applies sorting for selection lists\n     */\n    public function setSorting()\n    {\n        $sSelId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSelect = \"select * from oxcategory2attribute where oxobjectid = :oxobjectid order by oxsort\";\n\n        $oList = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n        $oList->init(\"oxbase\", \"oxcategory2attribute\");\n        $oList->selectString($sSelect, [\n            'oxobjectid' => $sSelId\n        ]);\n\n        // fixing indexes\n        $iSelCnt = 0;\n        $aIdx2Id = [];\n        foreach ($oList as $sKey => $oSel) {\n            if ($oSel->oxcategory2attribute__oxsort->value != $iSelCnt) {\n                $oSel->oxcategory2attribute__oxsort->setValue($iSelCnt);\n                // saving new index\n                $oSel->save();\n            }\n            $aIdx2Id[$iSelCnt] = $sKey;\n            $iSelCnt++;\n        }\n        //\n        if (($iKey = array_search(Registry::getRequest()->getRequestEscapedParameter('sortoxid'), $aIdx2Id)) !== false) {\n            $iDir = (Registry::getRequest()->getRequestEscapedParameter('direction') == 'up') ? ($iKey - 1) : ($iKey + 1);\n            if (isset($aIdx2Id[$iDir])) {\n                // exchanging indexes\n                $oDir1 = $oList->offsetGet($aIdx2Id[$iDir]);\n                $oDir2 = $oList->offsetGet($aIdx2Id[$iKey]);\n\n                $iCopy = $oDir1->oxcategory2attribute__oxsort->value;\n                $oDir1->oxcategory2attribute__oxsort->setValue($oDir2->oxcategory2attribute__oxsort->value);\n                $oDir2->oxcategory2attribute__oxsort->setValue($iCopy);\n                $oDir1->save();\n                $oDir2->save();\n            }\n        }\n\n        $sQAdd = $this->getQuery();\n\n        $sQ = 'select ' . $this->getQueryCols() . $sQAdd;\n        $sCountQ = 'select count( * ) ' . $sQAdd;\n\n        $this->outputResponse($this->getData($sCountQ, $sQ));\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/CategoryController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin article categories text manager.\n * Returns template, that arranges two other templates (\"category_list\"\n * and \"category_main\") to frame.\n * Admin Menu: Manage Products -> Categories.\n */\nclass CategoryController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'category';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/CategoryList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse stdClass;\n\n/**\n * Admin category list manager.\n * Collects attributes base information (sorting, title, etc.), there is ability to\n * filter them by sorting, title or delete them.\n * Admin Menu: Manage Products -> Categories.\n */\nclass CategoryList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxcategory';\n\n    /**\n     * Type of list.\n     *\n     * @var string\n     */\n    protected $_sListType = 'oxcategorylist';\n\n    /**\n     * Returns sorting fields array\n     *\n     * @return array\n     */\n    public function getListSorting()\n    {\n        $sSortParameter = Registry::getRequest()->getRequestEscapedParameter('sort');\n        if ($this->_aCurrSorting === null && !$sSortParameter && ($oBaseObject = $this->getItemListBaseObject())) {\n            $sCatView = $oBaseObject->getCoreTableName();\n\n            $this->_aCurrSorting[$sCatView][\"oxrootid\"] = \"desc\";\n            $this->_aCurrSorting[$sCatView][\"oxleft\"] = \"asc\";\n\n            return $this->_aCurrSorting;\n        } else {\n            return parent::getListSorting();\n        }\n    }\n\n    /**\n     * Loads category tree, passes data to template engine and returns name of\n     * template file \"category_list\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        $oLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n        $iLang = $oLang->getTplLanguage();\n\n        // parent category tree\n        $oCatTree = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\CategoryList::class);\n        $oCatTree->loadList();\n\n        // add Root as fake category\n        // rebuild list as we need the root entry at the first position\n        $aNewList = [];\n        $oRoot = new stdClass();\n        $oRoot->oxcategories__oxid = new \\OxidEsales\\Eshop\\Core\\Field(null, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        $oRoot->oxcategories__oxtitle = new \\OxidEsales\\Eshop\\Core\\Field($oLang->translateString(\"viewAll\", $iLang), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        $aNewList[] = $oRoot;\n\n        $oRoot = new stdClass();\n        $oRoot->oxcategories__oxid = new \\OxidEsales\\Eshop\\Core\\Field(\"oxrootid\", \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        $oRoot->oxcategories__oxtitle = new \\OxidEsales\\Eshop\\Core\\Field(\"-- \" . $oLang->translateString(\"mainCategory\", $iLang) . \" --\", \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        $aNewList[] = $oRoot;\n\n        foreach ($oCatTree as $oCategory) {\n            $aNewList[] = $oCategory;\n        }\n\n        $oCatTree->assign($aNewList);\n        $aFilter = $this->getListFilter();\n        if (is_array($aFilter) && isset($aFilter[\"oxcategories\"][\"oxparentid\"])) {\n            foreach ($oCatTree as $oCategory) {\n                if ($oCategory->oxcategories__oxid->value == $aFilter[\"oxcategories\"][\"oxparentid\"]) {\n                    $oCategory->selected = 1;\n                    break;\n                }\n            }\n        }\n\n        $this->_aViewData[\"cattree\"] = $oCatTree;\n\n        return \"category_list\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/CategoryMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse stdClass;\n\n/**\n * Admin article main categories manager.\n * There is possibility to change categories description, sorting, range of price\n * and etc.\n * Admin Menu: Manage Products -> Categories -> Main.\n */\nclass CategoryMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    const NEW_CATEGORY_ID = \"-1\";\n\n    /**\n     * Loads article category data,\n     * returns the name of the template file.\n     *\n     * @return string\n     */\n    public function render()\n    {\n        $myConfig = Registry::getConfig();\n\n        parent::render();\n\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\Category $oCategory */\n        $oCategory = $this->createCategory();\n\n        $categoryId = $this->getEditObjectId();\n\n        $this->_aViewData[\"edit\"] = $oCategory;\n        $this->_aViewData[\"oxid\"] = $categoryId;\n\n        if (isset($categoryId) && $categoryId != self::NEW_CATEGORY_ID) {\n            // generating category tree for select list\n            $this->createCategoryTree(\"artcattree\", $categoryId);\n\n            // load object\n            $oCategory->loadInLang($this->_iEditLang, $categoryId);\n\n            //Disable editing for derived items\n            if ($oCategory->isDerived()) {\n                $this->_aViewData['readonly_fields'] = true;\n            }\n\n            $oOtherLang = $oCategory->getAvailableInLangs();\n            if (!isset($oOtherLang[$this->_iEditLang])) {\n                $oCategory->loadInLang(key($oOtherLang), $categoryId);\n            }\n\n            // remove already created languages\n            $aLang = array_diff(Registry::getLang()->getLanguageNames(), $oOtherLang);\n            if (count($aLang)) {\n                $this->_aViewData[\"posslang\"] = $aLang;\n            }\n\n            foreach ($oOtherLang as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $oLang;\n            }\n\n            if ($oCategory->oxcategories__oxparentid->value == 'oxrootid') {\n                $oCategory->oxcategories__oxparentid->setValue('');\n            }\n\n            $this->getCategoryTree(\"cattree\", $oCategory->oxcategories__oxparentid->value, $oCategory->oxcategories__oxid->value, true, $oCategory->oxcategories__oxshopid->value);\n\n            $this->_aViewData[\"defsort\"] = $oCategory->oxcategories__oxdefsort->value;\n        } else {\n            $this->createCategoryTree(\"cattree\", \"\", true, $myConfig->getShopId());\n        }\n\n        $this->_aViewData[\"sortableFields\"] = $this->getSortableFields();\n\n        if ($this->getViewConfig()->isAltImageServerConfigured()) {\n            $this->_aViewData[\"imageUrl\"] = ContainerFacade::getParameter('oxid_esales.alternative_image_url');\n        }\n\n        if (Registry::getRequest()->getRequestEscapedParameter(\"aoc\")) {\n            /** @var \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\CategoryMainAjax $oCategoryMainAjax */\n            $oCategoryMainAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\CategoryMainAjax::class);\n            $this->_aViewData['oxajax'] = $oCategoryMainAjax->getColumns();\n\n            return \"popups/category_main\";\n        }\n\n        return \"category_main\";\n    }\n\n    /**\n     * Returns an array of article object DB fields, without multi language and unsortible fields.\n     *\n     * @return array\n     */\n    public function getSortableFields()\n    {\n        $aSkipFields = [\"OXID\", \"OXSHOPID\", \"OXMAPID\", \"OXPARENTID\", \"OXACTIVE\", \"OXACTIVEFROM\"\n        , \"OXACTIVETO\", \"OXSHORTDESC\"\n        , \"OXUNITNAME\", \"OXUNITQUANTITY\", \"OXEXTURL\", \"OXURLDESC\", \"OXURLIMG\", \"OXVAT\"\n        , \"OXTHUMB\", \"OXPIC1\", \"OXPIC2\", \"OXPIC3\", \"OXPIC4\", \"OXPIC5\"\n        , \"OXPIC6\", \"OXPIC7\", \"OXPIC8\", \"OXPIC9\", \"OXPIC10\", \"OXPIC11\", \"OXPIC12\", \"OXSTOCKFLAG\"\n        , \"OXSTOCKTEXT\", \"OXNOSTOCKTEXT\", \"OXDELIVERY\", \"OXFILE\", \"OXSEARCHKEYS\", \"OXTEMPLATE\"\n        , \"OXQUESTIONEMAIL\", \"OXISSEARCH\", \"OXISCONFIGURABLE\", \"OXBUNDLEID\", \"OXFOLDER\", \"OXSUBCLASS\"\n        , \"OXREMINDACTIVE\", \"OXREMINDAMOUNT\", \"OXVENDORID\", \"OXMANUFACTURERID\", \"OXSKIPDISCOUNTS\"\n        , \"OXBLFIXEDPRICE\", \"OXICON\", \"OXVARSELECT\", \"OXAMITEMID\", \"OXAMTASKID\", \"OXPIXIEXPORT\", \"OXPIXIEXPORTED\", \"OXSORT\"\n        , \"OXUPDATEPRICE\", \"OXUPDATEPRICEA\", \"OXUPDATEPRICEB\", \"OXUPDATEPRICEC\", \"OXUPDATEPRICETIME\", \"OXISDOWNLOADABLE\"\n        , \"OXVARMAXPRICE\", \"OXSHOWCUSTOMAGREEMENT\"\n        ];\n        /** @var \\OxidEsales\\Eshop\\Core\\DbMetaDataHandler $oDbHandler */\n        $oDbHandler = oxNew(\\OxidEsales\\Eshop\\Core\\DbMetaDataHandler::class);\n        $aFields = array_merge($oDbHandler->getMultilangFields('oxarticles'), array_keys($oDbHandler->getSinglelangFields('oxarticles', 0)));\n        $aFields = array_diff($aFields, $aSkipFields);\n        $aFields = array_unique($aFields);\n\n        return $aFields;\n    }\n\n    /**\n     * Saves article category data.\n     *\n     * @return mixed\n     */\n    public function save()\n    {\n        parent::save();\n\n        $soxId = $this->getEditObjectId();\n\n        $aParams = $this->parseRequestParametersForSave(\n            Registry::getRequest()->getRequestEscapedParameter(\"editval\")\n        );\n\n        if (!$this->validateRequestImages()) {\n            Registry::getUtilsView()->addErrorToDisplay('ERROR_MESSAGE_WRONG_IMAGE_FILE_TYPE');\n            return;\n        }\n\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\Category $oCategory */\n        $oCategory = $this->createCategory();\n\n        if ($soxId != self::NEW_CATEGORY_ID) {\n            $this->resetCounter(\"catArticle\", $soxId);\n            $this->resetCategoryPictures($oCategory, $aParams, $soxId);\n        }\n\n        //Disable editing for derived items\n        if ($oCategory->isDerived()) {\n            return;\n        }\n\n        $oCategory = $this->updateCategoryOnSave($oCategory, $aParams);\n\n        $oCategory->save();\n\n        $this->setEditObjectId($oCategory->getId());\n    }\n\n    /**\n     * Fixes html broken by html editor\n     *\n     * @param string $sValue value to fix\n     *\n     * @return string\n     */\n    protected function processLongDesc($sValue)\n    {\n        // workaround for firefox showing &lang= as &9001;= entity, mantis#0001272\n        return str_replace('&lang=', '&amp;lang=', $sValue);\n    }\n\n    /**\n     * Saves article category data to different language (eg. english).\n     */\n    public function saveinnlang()\n    {\n        $this->save();\n    }\n\n    /**\n     * Deletes selected master picture.\n     *\n     * @return null\n     */\n    public function deletePicture()\n    {\n        $myConfig = Registry::getConfig();\n\n        if ($myConfig->isDemoShop()) {\n            // disabling uploading pictures if this is demo shop\n            $oEx = new \\OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay();\n            $oEx->setMessage('CATEGORY_PICTURES_UPLOADISDISABLED');\n\n            /** @var \\OxidEsales\\Eshop\\Core\\UtilsView $oUtilsView */\n            $oUtilsView = Registry::getUtilsView();\n\n            $oUtilsView->addErrorToDisplay($oEx, false);\n\n            return;\n        }\n\n        $sOxId = $this->getEditObjectId();\n        $sField = Registry::getRequest()->getRequestEscapedParameter('masterPicField');\n        if (empty($sField)) {\n            return;\n        }\n\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\Category $oItem */\n        $oItem = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n        $oItem->load($sOxId);\n        $this->deleteCatPicture($oItem, $sField);\n    }\n\n    /**\n     * Delete category picture, specified in $sField parameter\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Category $item  active category object\n     * @param string                                       $field picture field name\n     *\n     * @return null\n     */\n    protected function deleteCatPicture($item, $field)\n    {\n        if ($item->isDerived()) {\n            return;\n        }\n\n        $myConfig = Registry::getConfig();\n        $sItemKey = 'oxcategories__' . $field;\n\n        switch ($field) {\n            case 'oxthumb':\n                $sImgType = 'TC';\n                break;\n\n            case 'oxicon':\n                $sImgType = 'CICO';\n                break;\n\n            case 'oxpromoicon':\n                $sImgType = 'PICO';\n                break;\n\n            default:\n                $sImgType = false;\n        }\n\n        if ($sImgType !== false) {\n            /** @var \\OxidEsales\\Eshop\\Core\\UtilsPic $myUtilsPic */\n            $myUtilsPic = Registry::getUtilsPic();\n            /** @var \\OxidEsales\\Eshop\\Core\\UtilsFile $oUtilsFile */\n            $oUtilsFile = Registry::getUtilsFile();\n\n            $sDir = $myConfig->getPictureDir(false);\n            $myUtilsPic->safePictureDelete($item->$sItemKey->value, $sDir . $oUtilsFile->getImageDirByType($sImgType), 'oxcategories', $field);\n\n            $item->$sItemKey = new \\OxidEsales\\Eshop\\Core\\Field();\n            $item->save();\n        }\n    }\n\n    /**\n     * Parse parameters prior to saving category.\n     *\n     * @param array $aReqParams Request parameters.\n     *\n     * @return array\n     */\n    protected function parseRequestParametersForSave($aReqParams)\n    {\n        // checkbox handling\n        if (!isset($aReqParams['oxcategories__oxactive'])) {\n            $aReqParams['oxcategories__oxactive'] = 0;\n        }\n        if (!isset($aReqParams['oxcategories__oxhidden'])) {\n            $aReqParams['oxcategories__oxhidden'] = 0;\n        }\n        if (!isset($aReqParams['oxcategories__oxdefsortmode'])) {\n            $aReqParams['oxcategories__oxdefsortmode'] = 0;\n        }\n\n        // null values\n        if (!isset($aReqParams['oxcategories__oxvat']) || $aReqParams['oxcategories__oxvat'] === '') {\n            $aReqParams['oxcategories__oxvat'] = null;\n        }\n\n        if ($this->getEditObjectId() == self::NEW_CATEGORY_ID) {\n            //#550A - if new category is made then is must be default activ\n            //#4051: Impossible to create inactive category\n            //$aReqParams['oxcategories__oxactive'] = 1;\n            $aReqParams['oxcategories__oxid'] = null;\n        }\n\n        if (isset($aReqParams[\"oxcategories__oxlongdesc\"])) {\n            $aReqParams[\"oxcategories__oxlongdesc\"] = $this->processLongDesc($aReqParams[\"oxcategories__oxlongdesc\"]);\n        }\n\n        if (empty($aReqParams['oxcategories__oxpricefrom'])) {\n            $aReqParams['oxcategories__oxpricefrom'] = 0;\n        }\n        if (empty($aReqParams['oxcategories__oxpriceto'])) {\n            $aReqParams['oxcategories__oxpriceto'] = 0;\n        }\n\n        return $aReqParams;\n    }\n\n    /**\n     * Set parameters, language and files to category object.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Category $category\n     * @param array                                        $params\n     * @param string                                       $categoryId\n     */\n    protected function resetCategoryPictures($category, $params, $categoryId)\n    {\n        $config = Registry::getConfig();\n        $category->load($categoryId);\n        $category->loadInLang($this->_iEditLang, $categoryId);\n\n        /** @var \\OxidEsales\\Eshop\\Core\\UtilsPic $utilsPic */\n        $utilsPic = Registry::getUtilsPic();\n\n        // #1173M - not all pic are deleted, after article is removed\n        $utilsPic->overwritePic($category, 'oxcategories', 'oxthumb', 'TC', '0', $params, $config->getPictureDir(false));\n        $utilsPic->overwritePic($category, 'oxcategories', 'oxicon', 'CICO', 'icon', $params, $config->getPictureDir(false));\n        $utilsPic->overwritePic($category, 'oxcategories', 'oxpromoicon', 'PICO', 'icon', $params, $config->getPictureDir(false));\n    }\n\n    /**\n     * Set parameters, language and files to category object.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Category $category\n     * @param array                                        $params\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Category\n     */\n    protected function updateCategoryOnSave($category, $params)\n    {\n        $category->assign($params);\n        $category->setLanguage($this->_iEditLang);\n\n        $utilsFile = Registry::getUtilsFile();\n\n        return $utilsFile->processFiles($category);\n    }\n\n    /**\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Category\n     */\n    protected function createCategory()\n    {\n        $category = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n\n        return $category;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/CategoryMainAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse Exception;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents\\AfterModelUpdateEvent;\n\n/**\n * Class manages category articles\n */\nclass CategoryMainAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * If true extended column selection will be build\n     *\n     * @var bool\n     */\n    protected $_blAllowExtColumns = true;\n\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,         visible, multilanguage, ident\n        ['oxartnum', 'oxarticles', 1, 0, 0],\n        ['oxtitle', 'oxarticles', 1, 1, 0],\n        ['oxean', 'oxarticles', 1, 0, 0],\n        ['oxmpn', 'oxarticles', 0, 0, 0],\n        ['oxprice', 'oxarticles', 0, 0, 0],\n        ['oxstock', 'oxarticles', 0, 0, 0],\n        ['oxid', 'oxarticles', 0, 0, 1]\n    ],\n                                 'container2' => [\n                                     ['oxartnum', 'oxarticles', 1, 0, 0],\n                                     ['oxtitle', 'oxarticles', 1, 1, 0],\n                                     ['oxean', 'oxarticles', 1, 0, 0],\n                                     ['oxmpn', 'oxarticles', 0, 0, 0],\n                                     ['oxprice', 'oxarticles', 0, 0, 0],\n                                     ['oxstock', 'oxarticles', 0, 0, 0],\n                                     ['oxid', 'oxarticles', 0, 0, 1]\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $sArticleTable = $this->getViewName('oxarticles');\n        $sO2CView = $this->getViewName('oxobject2category');\n\n        $sOxid = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchOxid = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        // category selected or not ?\n        if (!$sOxid && $sSynchOxid) {\n            // dodger performance\n            $sQAdd = ' from ' . $sArticleTable . ' where 1 ';\n        } else {\n            // copied from oxadminview\n            $sJoin = \" {$sArticleTable}.oxid={$sO2CView}.oxobjectid \";\n\n            $sSubSelect = '';\n            if ($sSynchOxid && $sOxid != $sSynchOxid) {\n                $sSubSelect = ' and ' . $sArticleTable . '.oxid not in ( ';\n                $sSubSelect .= \"select $sArticleTable.oxid from $sO2CView left join $sArticleTable \";\n                $sSubSelect .= \"on $sJoin where $sO2CView.oxcatnid =  \" . $oDb->quote($sSynchOxid) . \" \";\n                $sSubSelect .= 'and ' . $sArticleTable . '.oxid is not null ) ';\n            }\n\n            $sQAdd = \" from $sO2CView join $sArticleTable \";\n            $sQAdd .= \" on $sJoin where $sO2CView.oxcatnid = \" . $oDb->quote($sOxid);\n            $sQAdd .= \" and $sArticleTable.oxid is not null $sSubSelect \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Adds filter SQL to current query\n     *\n     * @param string $sQ query to add filter condition\n     *\n     * @return string\n     */\n    protected function addFilter($sQ)\n    {\n        $sArtTable = $this->getViewName('oxarticles');\n        $sQ = parent::addFilter($sQ);\n\n        // display variants or not ?\n        if (!\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blVariantsSelection')) {\n            $sQ .= \" and {$sArtTable}.oxparentid = '' \";\n        }\n\n        return $sQ;\n    }\n\n    /**\n     * Adds article to category\n     * Creates new list\n     *\n     * @throws Exception\n     */\n    public function addArticle()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $aArticles = $this->getActionIds('oxarticles.oxid');\n        $sCategoryID = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n        $sShopID = $myConfig->getShopId();\n\n        \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->startTransaction();\n        try {\n            $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $sArticleTable = $this->getViewName('oxarticles');\n\n            // adding\n            if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n                $aArticles = $this->getAll($this->addFilter(\"select $sArticleTable.oxid \" . $this->getQuery()));\n            }\n\n            if (is_array($aArticles)) {\n                $sO2CView = $this->getViewName('oxobject2category');\n\n                $oNew = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Object2Category::class);\n                $sProdIds = \"\";\n                foreach ($aArticles as $sAdd) {\n                    // check, if it's already in, then don't add it again\n                    $sSelect = sprintf(\n                        'select 1 from %s as oxobject2category where oxobject2category.oxcatnid = :oxcatnid '\n                        . ' and oxobject2category.oxobjectid = :oxobjectid',\n                        $sO2CView\n                    );\n                    // We force reading from master to prevent issues with slow replications or open transactions\n                    // (see ESDEV-3804).\n                    if ($database->getOne($sSelect, ['oxcatnid' => $sCategoryID, 'oxobjectid' => $sAdd])) {\n                        continue;\n                    }\n\n                    $oNew->oxobject2category__oxid = new Field($oNew->setId(md5($sAdd . $sCategoryID . $sShopID)));\n                    $oNew->oxobject2category__oxobjectid = new Field($sAdd);\n                    $oNew->oxobject2category__oxcatnid = new Field($sCategoryID);\n                    $oNew->oxobject2category__oxtime = new Field(time());\n\n                    $oNew->save();\n\n                    if ($sProdIds) {\n                        $sProdIds .= \",\";\n                    }\n                    $sProdIds .= $database->quote($sAdd);\n                }\n\n                // updating oxtime values\n                $this->updateOxTime($sProdIds);\n\n                $this->resetArtSeoUrl($aArticles);\n                $this->resetCounter(\"catArticle\", $sCategoryID);\n            }\n        } catch (Exception $exception) {\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->rollbackTransaction();\n            throw $exception;\n        }\n\n        \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->commitTransaction();\n    }\n\n    /**\n     * Updates oxtime value for products\n     *\n     * @param string $sProdIds product ids: \"id1\", \"id2\", \"id3\"\n     */\n    protected function updateOxTime($sProdIds)\n    {\n        if ($sProdIds) {\n            $sO2CView = $this->getViewName('oxobject2category');\n            $sSqlShopFilter = $this->getUpdateOxTimeQueryShopFilter();\n            $sSqlWhereShopFilter = $this->getUpdateOxTimeSqlWhereFilter();\n            $sQ = \"update oxobject2category set oxtime = 0 where oxid in (\n                      select _tmp.oxid from (\n                          select oxobject2category.oxid from (\n                              select min(oxtime) as oxtime, oxobjectid from {$sO2CView}\n                              where oxobjectid in ( {$sProdIds} ) {$sSqlShopFilter} group by oxobjectid\n                          ) as _subtmp\n                          left join oxobject2category on oxobject2category.oxtime = _subtmp.oxtime\n                           and oxobject2category.oxobjectid = _subtmp.oxobjectid\n                           {$sSqlWhereShopFilter}\n                      ) as _tmp\n                   ) {$sSqlShopFilter}\";\n\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->execute($sQ);\n        }\n    }\n\n    /**\n     * @return string\n     */\n    protected function getUpdateOxTimeQueryShopFilter()\n    {\n        return '';\n    }\n\n    /**\n     * Return where with \"true \" as this allows to concat query condition\n     * without knowing about other who changes this place (module or different edition).\n     *\n     * @return string\n     */\n    protected function getUpdateOxTimeSqlWhereFilter()\n    {\n        return 'where true ';\n    }\n\n    /**\n     * Removes article from category\n     */\n    public function removeArticle()\n    {\n        $aArticles = $this->getActionIds('oxarticles.oxid');\n        $sCategoryID = Registry::getRequest()->getRequestEscapedParameter('oxid');\n\n        // adding\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sArticleTable = $this->getViewName('oxarticles');\n            $aArticles = $this->getAll($this->addFilter(\"select $sArticleTable.oxid \" . $this->getQuery()));\n        }\n\n        // adding\n        if (is_array($aArticles) && count($aArticles)) {\n            $this->removeCategoryArticles($aArticles, $sCategoryID);\n        }\n\n        $this->resetArtSeoUrl($aArticles, $sCategoryID);\n        $this->resetCounter(\"catArticle\", $sCategoryID);\n\n        //notify services\n        $relation = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Object2Category::class);\n        $relation->setCategoryId($sCategoryID);\n        ContainerFacade::dispatch(new AfterModelUpdateEvent($relation));\n    }\n\n    /**\n     * Delete articles from category (from oxobject2category).\n     *\n     * @param array  $articles\n     * @param string $categoryID\n     */\n    protected function removeCategoryArticles($articles, $categoryID)\n    {\n        $db = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $prodIds = implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($articles));\n\n        $delete = \"delete from oxobject2category \";\n        $where = $this->getRemoveCategoryArticlesQueryFilter($categoryID, $prodIds);\n\n\n        $sQ = $delete . $where;\n        $db->execute($sQ);\n\n        // updating oxtime values\n        $this->updateOxTime($prodIds);\n    }\n\n    /**\n     * Form query filter to remove articles from category.\n     *\n     * @param string $categoryID\n     * @param string $prodIds\n     *\n     * @return string\n     */\n    protected function getRemoveCategoryArticlesQueryFilter($categoryID, $prodIds)\n    {\n        $db = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $where = \"where oxcatnid=\" . $db->quote($categoryID);\n\n        $whereProductIdIn = \" oxobjectid in ( {$prodIds} )\";\n        if (!\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blVariantsSelection')) {\n            $whereProductIdIn = \"( \" . $whereProductIdIn . \" OR oxobjectid in (\n                                        select oxid from oxarticles where oxparentid in ({$prodIds})\n                                        )\n            )\";\n        }\n        $where = $where . ' AND ' . $whereProductIdIn;\n\n        return $where;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/CategoryOrder.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin article categories order manager.\n * There is possibility to change category sorting.\n * Admin Menu: Manage Products -> Categories -> Order.\n */\nclass CategoryOrder extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Loads article category ordering info, passes it to template engine\n     * engine and returns name of template file \"category_order\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        $this->_aViewData['edit'] = $oCategory = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n\n        // resetting\n        \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable('neworder_sess', null);\n\n        $soxId = $this->getEditObjectId();\n\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oCategory->load($soxId);\n\n            //Disable editing for derived items\n            if ($oCategory->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n        }\n        if (Registry::getRequest()->getRequestEscapedParameter(\"aoc\")) {\n            $oCategoryOrderAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\CategoryOrderAjax::class);\n            $this->_aViewData['oxajax'] = $oCategoryOrderAjax->getColumns();\n\n            return \"popups/category_order\";\n        }\n\n        return \"category_order\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/CategoryOrderAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Object2Category;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages category articles order\n */\nclass CategoryOrderAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = [\n        'container1' => [ // field , table, visible, multilanguage, ident\n            ['oxartnum', 'oxarticles', 1, 0, 0],\n            ['oxtitle', 'oxarticles', 1, 1, 0],\n            ['oxpos', 'oxobject2category', 1, 0, 0],\n            ['oxean', 'oxarticles', 0, 0, 0],\n            ['oxmpn', 'oxarticles', 0, 0, 0],\n            ['oxprice', 'oxarticles', 0, 0, 0],\n            ['oxstock', 'oxarticles', 0, 0, 0],\n            ['oxid', 'oxarticles', 0, 0, 1]\n        ],\n         'container2' => [\n            ['oxartnum', 'oxarticles', 1, 0, 0],\n            ['oxtitle', 'oxarticles', 1, 1, 0],\n            ['oxean', 'oxarticles', 0, 0, 0],\n            ['oxmpn', 'oxarticles', 0, 0, 0],\n            ['oxprice', 'oxarticles', 0, 0, 0],\n            ['oxstock', 'oxarticles', 0, 0, 0],\n            ['oxid', 'oxarticles', 0, 0, 1]\n         ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        // looking for table/view\n        $sArtTable = $this->getViewName('oxarticles');\n        $sO2CView = $this->getViewName('oxobject2category');\n        $oDb = DatabaseProvider::getDb();\n\n        // category selected or not ?\n        if ($sSynchOxid = Registry::getRequest()->getRequestEscapedParameter('synchoxid')) {\n            $sQAdd = \" from $sArtTable left join $sO2CView on $sArtTable.oxid=$sO2CView.oxobjectid where\"\n                . \" $sO2CView.oxcatnid = \" . $oDb->quote($sSynchOxid);\n            if ($aSkipArt = \\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable('neworder_sess')) {\n                $sQAdd .= \" and $sArtTable.oxid not in ( \"\n                    . implode(\", \", DatabaseProvider::getDb()->quoteArray($aSkipArt))\n                    . \" ) \";\n            }\n        } else {\n            // which fields to load ?\n            $sQAdd = \" from $sArtTable where \";\n            if ($aSkipArt = \\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable('neworder_sess')) {\n                $sQAdd .= \" $sArtTable.oxid in ( \"\n                    . implode(\", \", DatabaseProvider::getDb()->quoteArray($aSkipArt))\n                    . \" ) \";\n            } else {\n                $sQAdd .= \" 1 = 0 \";\n            }\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Returns SQL query addon for sorting\n     *\n     * @return string\n     */\n    protected function getSorting()\n    {\n        $sOrder = '';\n        if (Registry::getRequest()->getRequestEscapedParameter('synchoxid')) {\n            $sOrder = parent::getSorting();\n        } elseif (($aSkipArt = \\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable('neworder_sess'))) {\n            $sOrderBy = '';\n            $sArtTable = $this->getViewName('oxarticles');\n            $sSep = '';\n            foreach ($aSkipArt as $sId) {\n                $sOrderBy = \" $sArtTable.oxid=\" . DatabaseProvider::getDb()->quote($sId) . \" \" . $sSep . $sOrderBy;\n                $sSep = \", \";\n            }\n            $sOrder = \"order by \" . $sOrderBy;\n        }\n\n        return $sOrder;\n    }\n\n    /**\n     * Removes article from list for sorting in category\n     */\n    public function removeCatOrderArticle()\n    {\n        $aRemoveArt = $this->getActionIds('oxarticles.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $aSkipArt = \\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable('neworder_sess');\n\n        if (is_array($aRemoveArt) && is_array($aSkipArt)) {\n            foreach ($aRemoveArt as $sRem) {\n                if (($iKey = array_search($sRem, $aSkipArt)) !== false) {\n                    unset($aSkipArt[$iKey]);\n                }\n            }\n            \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable('neworder_sess', $aSkipArt);\n\n            $sArticleTable = $this->getViewName('oxarticles');\n            $sO2CView = $this->getViewName('oxobject2category');\n\n            // checking if all articles were moved from one\n            $sSelect = \"select 1 from $sArticleTable left join $sO2CView on $sArticleTable.oxid=$sO2CView.oxobjectid \";\n            $sSelect .= \"where $sO2CView.oxcatnid = :oxcatnid\";\n            if (count($aSkipArt)) {\n                $sSelect .= \" and $sArticleTable.oxparentid = '' and $sArticleTable.oxid \";\n                $sSelect .= \"not in ( \" . implode(\", \", DatabaseProvider::getDb()->quoteArray($aSkipArt)) . \" ) \";\n            }\n\n            // Simply echoing \"1\" if some items found, and 0 if nothing was found\n            // We force reading from master to prevent issues with slow replications or open transactions\n            // (see ESDEV-3804).\n            echo (int) DatabaseProvider::getMaster()->getOne($sSelect, [\n                'oxcatnid' => $soxId\n            ]);\n        }\n    }\n\n    /**\n     * Adds article to list for sorting in category\n     */\n    public function addCatOrderArticle()\n    {\n        $aAddArticle = $this->getActionIds('oxarticles.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        $aOrdArt = \\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable('neworder_sess');\n        if (!is_array($aOrdArt)) {\n            $aOrdArt = [];\n        }\n\n        if (is_array($aAddArticle)) {\n            // storing newly ordered article seq.\n            foreach ($aAddArticle as $sAdd) {\n                if (array_search($sAdd, $aOrdArt) === false) {\n                    $aOrdArt[] = $sAdd;\n                }\n            }\n            \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable('neworder_sess', $aOrdArt);\n\n            $sArticleTable = $this->getViewName('oxarticles');\n            $sO2CView = $this->getViewName('oxobject2category');\n\n            // checking if all articles were moved from one\n            $sSelect = \"select 1 from $sArticleTable left join $sO2CView on $sArticleTable.oxid=$sO2CView.oxobjectid \"\n            . \"where $sO2CView.oxcatnid = :oxcatnid and $sArticleTable.oxparentid = '' and $sArticleTable.oxid \"\n            . \"not in ( \" . implode(\", \", DatabaseProvider::getDb()->quoteArray($aOrdArt)) . \" ) \";\n\n            // Simply echoing \"1\" if some items found, and 0 if nothing was found\n            // We force reading from master to prevent issues with slow replications or open transactions\n            // (see ESDEV-3804).\n            echo (int) DatabaseProvider::getMaster()->getOne($sSelect, [\n                'oxcatnid' => $soxId\n            ]);\n        }\n    }\n\n    /**\n     * Saves category articles ordering.\n     *\n     * @return null\n     */\n    public function saveNewOrder()\n    {\n        $oCategory = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n        $sId = Registry::getRequest()->getRequestEscapedParameter(\"oxid\");\n        if ($oCategory->load($sId)) {\n            //Disable editing for derived items\n            if ($oCategory->isDerived()) {\n                return;\n            }\n\n            $this->resetContentCache();\n\n            $aNewOrder = \\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable(\"neworder_sess\");\n            if (is_array($aNewOrder) && count($aNewOrder)) {\n                $sO2CView = $this->getViewName('oxobject2category');\n                $sSelect = \"select * from $sO2CView where $sO2CView.oxcatnid = :oxcatnid and $sO2CView.oxobjectid in (\"\n                    . implode(\", \", DatabaseProvider::getDb()->quoteArray($aNewOrder))\n                    . \" )\";\n                $oList = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n                $oList->init($this->getObject2CategoryClass(), 'oxobject2category');\n                $oList->selectString($sSelect, [\n                    'oxcatnid' => $oCategory->getId()\n                ]);\n\n                // setting new position\n                foreach ($oList as $oObj) {\n                    if (($iNewPos = array_search($oObj->oxobject2category__oxobjectid->value, $aNewOrder)) !== false) {\n                        $oObj->oxobject2category__oxpos->setValue($iNewPos);\n                        $oObj->save();\n                    }\n                }\n\n                \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable('neworder_sess', null);\n            }\n\n            $this->onCategoryChange($sId);\n        }\n    }\n\n    /**\n     * Removes category articles ordering set by saveneworder() method.\n     *\n     * @return null\n     */\n    public function remNewOrder()\n    {\n        $oCategory = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n        $sId = Registry::getRequest()->getRequestEscapedParameter(\"oxid\");\n        if ($oCategory->load($sId)) {\n            //Disable editing for derived items\n            if ($oCategory->isDerived()) {\n                return;\n            }\n\n            $oDb = DatabaseProvider::getDb();\n            $sSqlShopFilter = $this->updateQueryFilterForResetCategoryArticlesOrder();\n\n            $sSelect = sprintf(\n                \"update oxobject2category set oxpos = '0' where oxobject2category.oxcatnid = :id %s\",\n                $sSqlShopFilter\n            );\n            $oDb->execute($sSelect, ['id' => $oCategory->getId()]);\n\n            \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable('neworder_sess', null);\n\n            $this->onCategoryChange($sId);\n        }\n    }\n\n    /**\n     * @return string\n     */\n    protected function updateQueryFilterForResetCategoryArticlesOrder()\n    {\n        return '';\n    }\n\n    /**\n     * @param string $categoryId\n     */\n    protected function onCategoryChange($categoryId)\n    {\n    }\n\n    private function getObject2CategoryClass(): string\n    {\n        return Registry::getUtilsObject()->getClassName(Object2Category::class);\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/CategoryPictures.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin article categories thumbnail manager.\n * Category thumbnail manager (Previews assigned pictures).\n * Admin Menu: Manage Products -> Categories -> Thumbnail.\n */\nclass CategoryPictures extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $this->_aViewData['edit'] = $oCategory = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n\n        $soxId = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != '-1') {\n            // load object\n            $oCategory->load($soxId);\n        }\n\n        return \"category_pictures\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/CategorySeo.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Category seo config class\n */\nclass CategorySeo extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ObjectSeo\n{\n    /**\n     * Updating showsuffix field\n     *\n     * @return null\n     */\n    public function save()\n    {\n        $sOxid = $this->getEditObjectId();\n        $oCategory = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n        if ($oCategory->load($sOxid)) {\n            $blShowSuffixParameter = Registry::getRequest()->getRequestEscapedParameter('blShowSuffix');\n            $sShowSuffixField = 'oxcategories__oxshowsuffix';\n            $oCategory->$sShowSuffixField = new \\OxidEsales\\Eshop\\Core\\Field((int) $blShowSuffixParameter);\n            $oCategory->save();\n\n            $this->getEncoder()->markRelatedAsExpired($oCategory);\n        }\n\n        return parent::save();\n    }\n\n    /**\n     * Returns current object type seo encoder object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderCategory\n     */\n    protected function getEncoder()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderCategory::class);\n    }\n\n    /**\n     * This SEO object supports suffixes so return TRUE\n     *\n     * @return bool\n     */\n    public function isSuffixSupported()\n    {\n        return true;\n    }\n\n    /**\n     * Returns url type\n     *\n     * @return string\n     */\n    protected function getType()\n    {\n        return 'oxcategory';\n    }\n\n    /**\n     * Returns true if SEO object id has suffix enabled\n     *\n     * @return bool\n     */\n    public function isEntrySuffixed()\n    {\n        $oCategory = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n        if ($oCategory->load($this->getEditObjectId())) {\n            return (bool) $oCategory->oxcategories__oxshowsuffix->value;\n        }\n    }\n\n    /**\n     * Returns seo uri\n     *\n     * @return string\n     */\n    public function getEntryUri()\n    {\n        $oCategory = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n        if ($oCategory->load($this->getEditObjectId())) {\n            return $this->getEncoder()->getCategoryUri($oCategory, $this->getEditLang());\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/CategoryText.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse stdClass;\n\n/**\n * Admin article categories text manager.\n * Category text/description manager, enables editing of text.\n * Admin Menu: Manage Products -> Categories -> Text.\n */\nclass CategoryText extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $this->_aViewData['edit'] = $oCategory = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $iCatLang = Registry::getRequest()->getRequestEscapedParameter(\"catlang\");\n\n            if (!isset($iCatLang)) {\n                $iCatLang = $this->_iEditLang;\n            }\n\n            $this->_aViewData[\"catlang\"] = $iCatLang;\n\n            $oCategory->loadInLang($iCatLang, $soxId);\n\n            //Disable editing for derived items\n            if ($oCategory->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n\n            foreach (\\OxidEsales\\Eshop\\Core\\Registry::getLang()->getLanguageNames() as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $oLang;\n            }\n        }\n\n        $this->_aViewData[\"editor\"] = $this->generateTextEditor(\"100%\", 300, $oCategory, \"oxcategories__oxlongdesc\", \"list.css\");\n\n        return \"category_text\";\n    }\n\n    /**\n     * Saves category description text to DB.\n     *\n     * @return mixed\n     */\n    public function save()\n    {\n        parent::save();\n\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        $oCategory = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n        $iCatLang = Registry::getRequest()->getRequestEscapedParameter(\"catlang\");\n        $iCatLang = $iCatLang ? $iCatLang : 0;\n\n        if ($soxId != \"-1\") {\n            $oCategory->loadInLang($iCatLang, $soxId);\n        } else {\n            $aParams['oxcategories__oxid'] = null;\n        }\n\n        //Disable editing for derived items\n        if ($oCategory->isDerived()) {\n            return;\n        }\n\n        $oCategory->setLanguage(0);\n        $oCategory->assign($aParams);\n        $oCategory->setLanguage($iCatLang);\n        $oCategory->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oCategory->getId());\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/CategoryUpdate.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Class for updating category tree structure in DB.\n */\nclass CategoryUpdate extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = \"category_update\";\n\n    /**\n     * Category list object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\CategoryList\n     */\n    protected $_oCatList = null;\n\n    /**\n     * Returns category list object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\CategoryList\n     */\n    protected function getCategoryList()\n    {\n        if ($this->_oCatList == null) {\n            $this->_oCatList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\CategoryList::class);\n            $this->_oCatList->updateCategoryTree(false);\n        }\n\n        return $this->_oCatList;\n    }\n\n    /**\n     * Returns category list object\n     *\n     * @return array\n     */\n    public function getCatListUpdateInfo()\n    {\n        return $this->getCategoryList()->getUpdateInfo();\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ContentList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Admin Contents manager.\n * Collects Content base information (Description), there is ability to filter\n * them by Description or delete them.\n * Admin Menu: Customerinformations -> Content.\n */\nclass ContentList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxcontent';\n\n    /**\n     * Type of list.\n     *\n     * @var string\n     */\n    protected $_sListType = 'oxcontentlist';\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = \"content_list\";\n\n    /**\n     * Executes parent method parent::render() and returns current class template\n     * name.\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        $sFolder = \\OxidEsales\\Eshop\\Core\\Registry::getRequest()->getRequestEscapedParameter(\"folder\");\n        $sFolder = $sFolder ? $sFolder : -1;\n\n        $this->_aViewData[\"folder\"] = $sFolder;\n        $this->_aViewData[\"afolder\"] = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('aCMSfolder');\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Adding folder check and empty folder field check.\n     *\n     * @param array  $aWhere  SQL condition array\n     * @param string $sqlFull SQL query string\n     *\n     * @return string\n     */\n    protected function prepareWhereQuery($aWhere, $sqlFull)\n    {\n        $sQ = parent::prepareWhereQuery($aWhere, $sqlFull);\n        $sFolder = Registry::getRequest()->getRequestEscapedParameter('folder');\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sViewName = $tableViewNameGenerator->getViewName(\"oxcontents\");\n\n        //searchong for empty oxfolder fields\n        if ($sFolder == 'CMSFOLDER_NONE' || $sFolder == 'CMSFOLDER_NONE_RR') {\n            $sQ .= \" and {$sViewName}.oxfolder = '' \";\n        } elseif ($sFolder && $sFolder != '-1') {\n            $sFolder = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quote($sFolder);\n            $sQ .= \" and {$sViewName}.oxfolder = {$sFolder}\";\n        }\n\n        return $sQ;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ContentMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Content;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Html\\HtmlSanitizerInterface;\nuse stdClass;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse Throwable;\n\n/**\n * Admin content manager.\n * There is possibility to change content description, enter page text etc.\n * Admin Menu: Customerinformations -> Content.\n */\nclass ContentMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        $myConfig = Registry::getConfig();\n\n        parent::render();\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n\n        // categorie tree\n        $oCatTree = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\CategoryList::class);\n        $oCatTree->loadList();\n\n        $oContent = oxNew(Content::class);\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oContent->loadInLang($this->_iEditLang, $soxId);\n\n            $oOtherLang = $oContent->getAvailableInLangs();\n            if (!isset($oOtherLang[$this->_iEditLang])) {\n                $oContent->loadInLang(key($oOtherLang), $soxId);\n            }\n\n            // remove already created languages\n            $aLang = array_diff(Registry::getLang()->getLanguageNames(), $oOtherLang);\n            if (count($aLang)) {\n                $this->_aViewData[\"posslang\"] = $aLang;\n            }\n            foreach ($oOtherLang as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $oLang;\n            }\n            // mark selected\n            if ($oContent->oxcontents__oxcatid->value && isset($oCatTree[$oContent->oxcontents__oxcatid->value])) {\n                $oCatTree[$oContent->oxcontents__oxcatid->value]->selected = 1;\n            }\n        } else {\n            // create ident to make life easier\n            $sUId = Registry::getUtilsObject()->generateUId();\n            $oContent->oxcontents__oxloadid = new \\OxidEsales\\Eshop\\Core\\Field($sUId);\n        }\n\n        $this->_aViewData[\"edit\"] = $oContent;\n        $this->_aViewData[\"link\"] = \"[{ oxgetseourl ident=&quot;\" . $oContent->oxcontents__oxloadid->value . \"&quot; type=&quot;oxcontent&quot; }]\";\n        $this->_aViewData[\"cattree\"] = $oCatTree;\n\n        // generate editor\n        $sCSS = \"content.css\";\n        if ($oContent->oxcontents__oxsnippet->value == '1') {\n            $sCSS = null;\n        }\n\n        $this->_aViewData[\"editor\"] = $this->generateTextEditor(\"100%\", 300, $oContent, \"oxcontents__oxcontent\", $sCSS);\n        $this->_aViewData[\"afolder\"] = $myConfig->getConfigParam('aCMSfolder');\n\n        $this->_aViewData[\"activeSanitizer\"] = ContainerFacade::getParameter('oxid_esales.html_sanitizer_enabled');\n\n        return \"content_main\";\n    }\n\n    public function save()\n    {\n        parent::save();\n\n        $contentId = $this->getEditObjectId();\n        $requestParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        if (isset($requestParams['oxcontents__oxloadid'])) {\n            $requestParams['oxcontents__oxloadid'] = $this->prepareIdent($requestParams['oxcontents__oxloadid']);\n        }\n\n        if ($this->checkIdent($requestParams['oxcontents__oxloadid'], $contentId)) {\n            $this->_aViewData[\"blLoadError\"] = true;\n            $this->handleSaveError($contentId, $requestParams);\n\n            return;\n        }\n\n        if ($requestParams['oxcontents__oxtype'] == 0) {\n            $requestParams['oxcontents__oxsnippet'] = 1;\n        } else {\n            $requestParams['oxcontents__oxsnippet'] = 0;\n        }\n\n        if ($requestParams['oxcontents__oxfolder'] === 'CMSFOLDER_NONE') {\n            $requestParams['oxcontents__oxfolder'] = '';\n        }\n\n        $this->prepareAndSaveContent($requestParams, $contentId, $this->_iEditLang);\n    }\n\n    public function saveinnlang()\n    {\n        parent::save();\n\n        $contentId = $this->getEditObjectId();\n        $requestParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        if (isset($requestParams['oxcontents__oxloadid'])) {\n            $requestParams['oxcontents__oxloadid'] = $this->prepareIdent($requestParams['oxcontents__oxloadid']);\n        }\n\n        if ($this->checkIdent($requestParams['oxcontents__oxloadid'], $contentId)) {\n            $this->_aViewData[\"blLoadError\"] = true;\n            $this->handleSaveError($contentId, $requestParams);\n\n            return;\n        }\n\n        $this->prepareAndSaveContent(\n            $requestParams,\n            $contentId,\n            Registry::getRequest()->getRequestEscapedParameter(\"new_lang\")\n        );\n    }\n\n    /**\n     * Prepares ident (removes bad chars, leaves only thoose that fits in a-zA-Z0-9_ range)\n     *\n     * @param string $sIdent ident to filter\n     *\n     * @return string\n     */\n    protected function prepareIdent($sIdent)\n    {\n        if ($sIdent) {\n            return Str::getStr()->preg_replace(\"/[^a-zA-Z0-9_]*/\", \"\", $sIdent);\n        }\n    }\n\n    /**\n     * Check if ident is unique\n     *\n     * @param string $sIdent ident\n     * @param string $sOxId  Object id\n     *\n     * @return null\n     */\n    protected function checkIdent($sIdent, $sOxId)\n    {\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        $masterDb = DatabaseProvider::getMaster();\n\n        $blAllow = false;\n\n        // null not allowed\n        if (!strlen($sIdent)) {\n            $blAllow = true;\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        } elseif (\n            $masterDb->getOne(\"select oxid from oxcontents where oxloadid = :oxloadid and oxid != :oxid and oxshopid = :oxshopid\", [\n            'oxloadid' => $sIdent,\n            'oxid' => $sOxId,\n            'oxshopid' => \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId()\n            ])\n        ) {\n            $blAllow = true;\n        }\n\n        return $blAllow;\n    }\n\n    private function prepareAndSaveContent(array $requestParams, $contentId, $lang): void\n    {\n        if (isset($requestParams['oxcontents__oxcontent'])) {\n            $requestParams['oxcontents__oxcontent'] = ContainerFacade::get(HtmlSanitizerInterface::class)\n                ->sanitize($requestParams['oxcontents__oxcontent']);\n        }\n\n        if (!isset($requestParams['oxcontents__oxactive'])) {\n            $requestParams['oxcontents__oxactive'] = 0;\n        }\n\n        $content = oxNew(Content::class);\n\n        if ($contentId != \"-1\") {\n            $content->loadInLang($lang, $contentId);\n        } else {\n            $requestParams['oxcontents__oxid'] = null;\n        }\n\n        $content->setLanguage(0);\n        $content->assign($requestParams);\n        $content->setLanguage($lang);\n        $content->save();\n\n        $this->setEditObjectId($content->getId());\n    }\n\n    private function handleSaveError($contentId, $requestParams): void\n    {\n        $content = oxNew(Content::class);\n        if ($contentId != '-1') {\n            $content->load($contentId);\n        }\n        $content->assign($requestParams);\n        $this->_aViewData[\"edit\"] = $content;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ContentSeo.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse oxRegistry;\n\n/**\n * Content seo config class\n */\nclass ContentSeo extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ObjectSeo\n{\n    /**\n     * Returns url type\n     *\n     * @return string\n     */\n    protected function getType()\n    {\n        return 'oxcontent';\n    }\n\n    /**\n     * Returns current object type seo encoder object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderContent\n     */\n    protected function getEncoder()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderContent::class);\n    }\n\n    /**\n     * Returns seo uri\n     *\n     * @return string\n     */\n    public function getEntryUri()\n    {\n        $oContent = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Content::class);\n        if ($oContent->load($this->getEditObjectId())) {\n            return $this->getEncoder()->getContentUri($oContent, $this->getEditLang());\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/CountryController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Sets template, that arranges two other templates (\"article_list\"\n * and \"article_main\") to frame.\n * Admin Menu: Manage Products -> Articles.\n */\nclass CountryController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'country';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/CountryList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin selectlist list manager.\n */\nclass CountryList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxcountry';\n\n    /**\n     * Default SQL sorting parameter (default null).\n     *\n     * @var string\n     */\n    protected $_sDefSortField = 'oxactive';\n\n    /**\n     * Default second SQL sorting parameter.\n     *\n     * @var string\n     */\n    protected $sSecondDefSortField = 'oxtitle';\n\n    /**\n     * Enable/disable sorting by DESC (SQL) (default false - disable).\n     *\n     * @var bool\n     */\n    protected $_blDesc = false;\n\n    /**\n     * Executes parent method parent::render() and returns name of template\n     * file \"selectlist_list\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        return \"country_list\";\n    }\n\n    /**\n     * Returns sorting fields array. We extend this method for getting a second order by, which will give us not the\n     * undefined order behind the \"active\" countries.\n     *\n     * @return array\n     */\n    public function getListSorting()\n    {\n        $aListSorting = parent::getListSorting();\n\n        if (array_keys($aListSorting['oxcountry']) === ['oxactive']) {\n            $aListSorting['oxcountry'][$this->getSecondSortFieldName()] = 'asc';\n        }\n\n        return $aListSorting;\n    }\n\n    /**\n     * Getter for the second sort field name (for getting the expected order out of the database).\n     *\n     * @return string The name of the field we want to be the second order by argument.\n     */\n    protected function getSecondSortFieldName()\n    {\n        return $this->sSecondDefSortField;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/CountryMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Country;\nuse stdClass;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin article main selectlist manager.\n * Performs collection and updatind (on user submit) main item information.\n */\nclass CountryMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oCountry = oxNew(Country::class);\n            $oCountry->loadInLang($this->_iEditLang, $soxId);\n\n            if ($oCountry->isForeignCountry()) {\n                $this->_aViewData[\"blForeignCountry\"] = true;\n            } else {\n                $this->_aViewData[\"blForeignCountry\"] = false;\n            }\n\n            $oOtherLang = $oCountry->getAvailableInLangs();\n            if (!isset($oOtherLang[$this->_iEditLang])) {\n                $oCountry->loadInLang(key($oOtherLang), $soxId);\n            }\n            $this->_aViewData[\"edit\"] = $oCountry;\n\n            // remove already created languages\n            $aLang = array_diff(Registry::getLang()->getLanguageNames(), $oOtherLang);\n            if (count($aLang)) {\n                $this->_aViewData[\"posslang\"] = $aLang;\n            }\n\n            foreach ($oOtherLang as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $oLang;\n            }\n        } else {\n            $this->_aViewData[\"blForeignCountry\"] = true;\n        }\n\n        return \"country_main\";\n    }\n\n    /**\n     * Saves selection list parameters changes.\n     */\n    public function save()\n    {\n        parent::save();\n\n        $oxidId = $this->getEditObjectId();\n        $queryParameters = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        if ($queryParameters['oxcountry__oxvatstatus'] === '1' && empty($queryParameters['oxcountry__oxvatinprefix'])) {\n            Registry::getUtilsView()->addErrorToDisplay('ERROR_MESSAGE_INPUT_VAT_PREFIX_EMPTY');\n            return;\n        }\n\n        if (!isset($queryParameters['oxcountry__oxactive'])) {\n            $queryParameters['oxcountry__oxactive'] = 0;\n        }\n\n        $country = oxNew(Country::class);\n\n        if ($oxidId != \"-1\") {\n            $country->loadInLang($this->_iEditLang, $oxidId);\n        } else {\n            $queryParameters['oxcountry__oxid'] = null;\n        }\n\n        $country->setLanguage(0);\n        $country->assign($queryParameters);\n        $country->setLanguage($this->_iEditLang);\n        $country = Registry::getUtilsFile()->processFiles($country);\n        $country->save();\n\n        $this->setEditObjectId($country->getId());\n    }\n\n    /**\n     * Saves selection list parameters changes in different language (eg. english).\n     */\n    public function saveinnlang()\n    {\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        if (!isset($aParams['oxcountry__oxactive'])) {\n            $aParams['oxcountry__oxactive'] = 0;\n        }\n\n        $oCountry = oxNew(Country::class);\n\n        if ($soxId != \"-1\") {\n            $oCountry->loadInLang($this->_iEditLang, $soxId);\n        } else {\n            $aParams['oxcountry__oxid'] = null;\n            //$aParams = $oCountry->ConvertNameArray2Idx( $aParams);\n        }\n\n        $oCountry->setLanguage(0);\n        $oCountry->assign($aParams);\n        $oCountry->setLanguage($this->_iEditLang);\n\n        $oCountry->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oCountry->getId());\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliveryArticles.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin article main delivery manager.\n * There is possibility to change delivery name, article, user\n * and etc.\n * Admin Menu: Shop settings -> Shipping & Handling -> Main.\n */\nclass DeliveryArticles extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->getEditObjectId();\n\n        if (isset($soxId) && $soxId != \"-1\") {\n            $this->createCategoryTree(\"artcattree\");\n\n            // load object\n            $oDelivery = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Delivery::class);\n            $oDelivery->load($soxId);\n            $this->_aViewData[\"edit\"] = $oDelivery;\n\n            //Disable editing for derived articles\n            if ($oDelivery->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n        }\n\n        $iAoc = Registry::getRequest()->getRequestEscapedParameter(\"aoc\");\n        if ($iAoc == 1) {\n            $oDeliveryArticlesAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliveryArticlesAjax::class);\n            $this->_aViewData['oxajax'] = $oDeliveryArticlesAjax->getColumns();\n\n            return \"popups/delivery_articles\";\n        } elseif ($iAoc == 2) {\n            $oDeliveryCategoriesAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliveryCategoriesAjax::class);\n            $this->_aViewData['oxajax'] = $oDeliveryCategoriesAjax->getColumns();\n\n            return \"popups/delivery_categories\";\n        }\n\n        return \"delivery_articles\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliveryArticlesAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages delivery articles\n */\nclass DeliveryArticlesAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,         visible, multilanguage, ident\n        ['oxartnum', 'oxarticles', 1, 0, 0],\n        ['oxtitle', 'oxarticles', 1, 1, 0],\n        ['oxean', 'oxarticles', 1, 0, 0],\n        ['oxmpn', 'oxarticles', 0, 0, 0],\n        ['oxprice', 'oxarticles', 0, 0, 0],\n        ['oxstock', 'oxarticles', 0, 0, 0],\n        ['oxid', 'oxarticles', 0, 0, 1]\n    ],\n                                 'container2' => [\n                                     ['oxartnum', 'oxarticles', 1, 0, 0],\n                                     ['oxtitle', 'oxarticles', 1, 1, 0],\n                                     ['oxean', 'oxarticles', 1, 0, 0],\n                                     ['oxmpn', 'oxarticles', 0, 0, 0],\n                                     ['oxprice', 'oxarticles', 0, 0, 0],\n                                     ['oxstock', 'oxarticles', 0, 0, 0],\n                                     ['oxid', 'oxobject2delivery', 0, 0, 1]\n                                 ]\n    ];\n\n    /**\n     * If true extended column selection will be build\n     *\n     * @var bool\n     */\n    protected $_blAllowExtColumns = true;\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $request = \\OxidEsales\\Eshop\\Core\\Registry::getRequest();\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        // looking for table/view\n        $sArtTable = $this->getViewName('oxarticles');\n        $sO2CView = $this->getViewName('oxobject2category');\n\n        $sDelId = $request->getRequestParameter('oxid');\n        $sSynchDelId = $request->getRequestParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sDelId) {\n            // performance\n            $sQAdd = \" from $sArtTable where 1 \";\n            $sQAdd .= $config->getConfigParam('blVariantsSelection') ? '' : \"and $sArtTable.oxparentid = '' \";\n        } else {\n            // selected category ?\n            if ($sSynchDelId && $sDelId != $sSynchDelId) {\n                $sQAdd = \" from $sO2CView left join $sArtTable on \";\n                $sQAdd .= $config->getConfigParam('blVariantsSelection') ? \" ( $sArtTable.oxid=$sO2CView.oxobjectid or $sArtTable.oxparentid=$sO2CView.oxobjectid)\" : \" $sArtTable.oxid=$sO2CView.oxobjectid \";\n                $sQAdd .= \"where $sO2CView.oxcatnid = \" . $oDb->quote($sDelId);\n            } else {\n                $sQAdd = ' from oxobject2delivery left join ' . $sArtTable . ' on ' . $sArtTable . '.oxid=oxobject2delivery.oxobjectid ';\n                $sQAdd .= 'where oxobject2delivery.oxdeliveryid = ' . $oDb->quote($sDelId) . ' and oxobject2delivery.oxtype = \"oxarticles\" ';\n            }\n        }\n\n        if ($sSynchDelId && $sSynchDelId != $sDelId) {\n            $sQAdd .= 'and ' . $sArtTable . '.oxid not in ( ';\n            $sQAdd .= 'select oxobject2delivery.oxobjectid from oxobject2delivery ';\n            $sQAdd .= 'where oxobject2delivery.oxdeliveryid = ' . $oDb->quote($sSynchDelId) . ' and oxobject2delivery.oxtype = \"oxarticles\" ) ';\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes article from delivery configuration\n     */\n    public function removeArtFromDel()\n    {\n        $aChosenArt = $this->getActionIds('oxobject2delivery.oxid');\n        // removing all\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = parent::addFilter(\"delete oxobject2delivery.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif (is_array($aChosenArt)) {\n            $sQ = \"delete from oxobject2delivery where oxobject2delivery.oxid in (\" . implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aChosenArt)) . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adds article to delivery configuration\n     */\n    public function addArtToDel()\n    {\n        $aChosenArt = $this->getActionIds('oxarticles.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // adding\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sArtTable = $this->getViewName('oxarticles');\n            $aChosenArt = $this->getAll($this->addFilter(\"select $sArtTable.oxid \" . $this->getQuery()));\n        }\n\n        if ($soxId && $soxId != \"-1\" && is_array($aChosenArt)) {\n            foreach ($aChosenArt as $sChosenArt) {\n                $oObject2Delivery = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oObject2Delivery->init('oxobject2delivery');\n                $oObject2Delivery->oxobject2delivery__oxdeliveryid = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                $oObject2Delivery->oxobject2delivery__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sChosenArt);\n                $oObject2Delivery->oxobject2delivery__oxtype = new \\OxidEsales\\Eshop\\Core\\Field(\"oxarticles\");\n                $oObject2Delivery->save();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliveryCategoriesAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages delivery categories\n */\nclass DeliveryCategoriesAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,         visible, multilanguage, ident\n        ['oxtitle', 'oxcategories', 1, 1, 0],\n        ['oxdesc', 'oxcategories', 1, 1, 0],\n        ['oxid', 'oxcategories', 0, 0, 0],\n        ['oxid', 'oxcategories', 0, 0, 1]\n    ],\n                                 'container2' => [\n                                     ['oxtitle', 'oxcategories', 1, 1, 0],\n                                     ['oxdesc', 'oxcategories', 1, 1, 0],\n                                     ['oxid', 'oxcategories', 0, 0, 0],\n                                     ['oxid', 'oxobject2delivery', 0, 0, 1],\n                                     ['oxid', 'oxcategories', 0, 0, 1]\n                                 ],\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        // looking for table/view\n        $sCatTable = $this->getViewName('oxcategories');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sDelId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchDelId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sDelId) {\n            $sQAdd = \" from {$sCatTable} \";\n        } else {\n            $sQAdd = \" from oxobject2delivery left join {$sCatTable} \" .\n                     \"on {$sCatTable}.oxid=oxobject2delivery.oxobjectid \" .\n                     \" where oxobject2delivery.oxdeliveryid = \" . $oDb->quote($sDelId) .\n                     \" and oxobject2delivery.oxtype = 'oxcategories' \";\n        }\n\n        if ($sSynchDelId && $sSynchDelId != $sDelId) {\n            // performance\n            $sSubSelect = \" select {$sCatTable}.oxid from oxobject2delivery left join {$sCatTable} \" .\n                          \"on {$sCatTable}.oxid=oxobject2delivery.oxobjectid \" .\n                          \" where oxobject2delivery.oxdeliveryid = \" . $oDb->quote($sSynchDelId) .\n                          \" and oxobject2delivery.oxtype = 'oxcategories' \";\n            if (stristr($sQAdd, 'where') === false) {\n                $sQAdd .= ' where ';\n            } else {\n                $sQAdd .= ' and ';\n            }\n            $sQAdd .= \" {$sCatTable}.oxid not in ( $sSubSelect ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes category from delivery configuration\n     */\n    public function removeCatFromDel()\n    {\n        $aChosenCat = $this->getActionIds('oxobject2delivery.oxid');\n\n        // removing all\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxobject2delivery.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif (is_array($aChosenCat)) {\n            $sChosenCategoriess = implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aChosenCat));\n            $sQ = \"delete from oxobject2delivery where oxobject2delivery.oxid in (\" . $sChosenCategoriess . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adds category to delivery configuration\n     */\n    public function addCatToDel()\n    {\n        $aChosenCat = $this->getActionIds('oxcategories.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // adding\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sCatTable = $this->getViewName('oxcategories');\n            $aChosenCat = $this->getAll($this->addFilter(\"select $sCatTable.oxid \" . $this->getQuery()));\n        }\n\n        if (isset($soxId) && $soxId != \"-1\" && isset($aChosenCat) && $aChosenCat) {\n            foreach ($aChosenCat as $sChosenCat) {\n                $oObject2Delivery = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oObject2Delivery->init('oxobject2delivery');\n                $oObject2Delivery->oxobject2delivery__oxdeliveryid = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                $oObject2Delivery->oxobject2delivery__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sChosenCat);\n                $oObject2Delivery->oxobject2delivery__oxtype = new \\OxidEsales\\Eshop\\Core\\Field(\"oxcategories\");\n                $oObject2Delivery->save();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliveryController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin article delivery manager.\n * Returns template, that arranges two other templates (\"delivery_list\"\n * and \"delivery_main\") to frame.\n * Admin Menu: Shop settings -> Shipping & Handling.\n */\nclass DeliveryController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'delivery';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliveryGroupsAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages delivery groups\n */\nclass DeliveryGroupsAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,  visible, multilanguage, ident\n        ['oxtitle', 'oxgroups', 1, 0, 0],\n        ['oxid', 'oxgroups', 0, 0, 0],\n        ['oxid', 'oxgroups', 0, 0, 1],\n    ],\n                                 'container2' => [\n                                     ['oxtitle', 'oxgroups', 1, 0, 0],\n                                     ['oxid', 'oxgroups', 0, 0, 0],\n                                     ['oxid', 'oxobject2delivery', 0, 0, 1],\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        // active AJAX component\n        $sGroupTable = $this->getViewName('oxgroups');\n\n        $sId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sId) {\n            $sQAdd = \" from {$sGroupTable} where 1 \";\n        } else {\n            $sQAdd = \" from oxobject2delivery left join {$sGroupTable} \" .\n                     \"on {$sGroupTable}.oxid=oxobject2delivery.oxobjectid \" .\n                     \" where oxobject2delivery.oxdeliveryid = \" . $oDb->quote($sId) .\n                     \" and oxobject2delivery.oxtype = 'oxgroups' \";\n        }\n\n        if ($sSynchId && $sSynchId != $sId) {\n            $sQAdd .= \" and {$sGroupTable}.oxid not in ( select {$sGroupTable}.oxid \" .\n                      \"from oxobject2delivery left join {$sGroupTable} \" .\n                      \"on {$sGroupTable}.oxid=oxobject2delivery.oxobjectid \" .\n                      \" where oxobject2delivery.oxdeliveryid = \" . $oDb->quote($sSynchId) .\n                      \" and oxobject2delivery.oxtype = 'oxgroups' ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes user group from delivery configuration\n     */\n    public function removeGroupFromDel()\n    {\n        $aRemoveGroups = $this->getActionIds('oxobject2delivery.oxid');\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxobject2delivery.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif ($aRemoveGroups && is_array($aRemoveGroups)) {\n            $sRemoveGroups = implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aRemoveGroups));\n            $sQ = \"delete from oxobject2delivery where oxobject2delivery.oxid in (\" . $sRemoveGroups . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adds user group to delivery configuration\n     */\n    public function addGroupToDel()\n    {\n        $aChosenCat = $this->getActionIds('oxgroups.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // adding\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sGroupTable = $this->getViewName('oxgroups');\n            $aChosenCat = $this->getAll($this->addFilter(\"select $sGroupTable.oxid \" . $this->getQuery()));\n        }\n\n        if ($soxId && $soxId != \"-1\" && is_array($aChosenCat)) {\n            foreach ($aChosenCat as $sChosenCat) {\n                $oObject2Delivery = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oObject2Delivery->init('oxobject2delivery');\n                $oObject2Delivery->oxobject2delivery__oxdeliveryid = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                $oObject2Delivery->oxobject2delivery__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sChosenCat);\n                $oObject2Delivery->oxobject2delivery__oxtype = new \\OxidEsales\\Eshop\\Core\\Field('oxgroups');\n                $oObject2Delivery->save();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliveryList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin delivery list manager.\n * Collects delivery base information (description), there is ability to\n * filter them by description, title or delete them.\n * Admin Menu: Shop Settings -> Shipping & Handling.\n */\nclass DeliveryList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxdelivery';\n\n    /**\n     * Type of list.\n     *\n     * @var string\n     */\n    protected $_sListType = 'oxdeliverylist';\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'delivery_list';\n\n    /**\n     * Default SQL sorting parameter (default null).\n     *\n     * @var string\n     */\n    protected $_sDefSortField = 'oxsort';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliveryMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse stdClass;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin article main delivery manager.\n * There is possibility to change delivery name, article, user\n * and etc.\n * Admin Menu: Shop settings -> Shipping & Handling -> Main.\n */\nclass DeliveryMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $oLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n\n        // remove itm from list\n        unset($this->_aViewData[\"sumtype\"][2]);\n\n        // Deliverytypes\n        $aDelTypes = $this->getDeliveryTypes();\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oDelivery = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Delivery::class);\n            $oDelivery->loadInLang($this->_iEditLang, $soxId);\n\n            $oOtherLang = $oDelivery->getAvailableInLangs();\n            if (!isset($oOtherLang[$this->_iEditLang])) {\n                $oDelivery->loadInLang(key($oOtherLang), $soxId);\n            }\n\n            $this->_aViewData[\"edit\"] = $oDelivery;\n\n            //Disable editing for derived articles\n            if ($oDelivery->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n\n            // remove already created languages\n            $aLang = array_diff($oLang->getLanguageNames(), $oOtherLang);\n            if (count($aLang)) {\n                $this->_aViewData[\"posslang\"] = $aLang;\n            }\n\n            foreach ($oOtherLang as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $oLang;\n            }\n\n            // set selected delivery type\n            if (!$oDelivery->oxdelivery__oxdeltype->value) {\n                $oDelivery->oxdelivery__oxdeltype = new \\OxidEsales\\Eshop\\Core\\Field(\"a\"); // default\n            }\n            $aDelTypes[$oDelivery->oxdelivery__oxdeltype->value]->selected = true;\n        }\n\n        $this->_aViewData[\"deltypes\"] = $aDelTypes;\n\n        if (Registry::getRequest()->getRequestEscapedParameter(\"aoc\")) {\n            $oDeliveryMainAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliveryMainAjax::class);\n            $this->_aViewData['oxajax'] = $oDeliveryMainAjax->getColumns();\n\n            return \"popups/delivery_main\";\n        }\n\n        return \"delivery_main\";\n    }\n\n    /**\n     * Saves delivery information changes.\n     *\n     * @return mixed\n     */\n    public function save()\n    {\n        parent::save();\n\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        $oDelivery = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Delivery::class);\n\n        if ($soxId != \"-1\") {\n            $oDelivery->loadInLang($this->_iEditLang, $soxId);\n        } else {\n            $aParams['oxdelivery__oxid'] = null;\n        }\n\n        // checkbox handling\n        if (!isset($aParams['oxdelivery__oxactive'])) {\n            $aParams['oxdelivery__oxactive'] = 0;\n        }\n\n        if (!isset($aParams['oxdelivery__oxfixed'])) {\n            $aParams['oxdelivery__oxfixed'] = 0;\n        }\n\n        if (!isset($aParams['oxdelivery__oxfinalize'])) {\n            $aParams['oxdelivery__oxfinalize'] = 0;\n        }\n\n        if (!isset($aParams['oxdelivery__oxsort'])) {\n            $aParams['oxdelivery__oxsort'] = 9999;\n        }\n\n        //Disable editing for derived articles\n        if ($oDelivery->isDerived()) {\n            return;\n        }\n\n        $oDelivery->setLanguage(0);\n        $oDelivery->assign($aParams);\n        $oDelivery->setLanguage($this->_iEditLang);\n        $oDelivery = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsFile()->processFiles($oDelivery);\n        $oDelivery->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oDelivery->getId());\n    }\n\n    /**\n     * Saves delivery information changes.\n     *\n     * @return null\n     */\n    public function saveinnlang()\n    {\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        $oDelivery = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Delivery::class);\n\n        if ($soxId != \"-1\") {\n            $oDelivery->loadInLang($this->_iEditLang, $soxId);\n        } else {\n            $aParams['oxdelivery__oxid'] = null;\n        }\n\n        // checkbox handling\n        if (!isset($aParams['oxdelivery__oxactive'])) {\n            $aParams['oxdelivery__oxactive'] = 0;\n        }\n        if (!isset($aParams['oxdelivery__oxfixed'])) {\n            $aParams['oxdelivery__oxfixed'] = 0;\n        }\n\n        //Disable editing for derived articles\n        if ($oDelivery->isDerived()) {\n            return;\n        }\n\n        $oDelivery->setLanguage(0);\n        $oDelivery->assign($aParams);\n        $oDelivery->setLanguage($this->_iEditLang);\n        $oDelivery = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsFile()->processFiles($oDelivery);\n        $oDelivery->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oDelivery->getId());\n    }\n\n    /**\n     * returns delivery types\n     *\n     * @return array\n     */\n    public function getDeliveryTypes()\n    {\n        $oLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n        $iLang = $oLang->getTplLanguage();\n\n        $aDelTypes = [];\n        $oType = new stdClass();\n        $oType->sType = \"a\";      // amount\n        $oType->sDesc = $oLang->translateString(\"amount\", $iLang);\n        $aDelTypes['a'] = $oType;\n        $oType = new stdClass();\n        $oType->sType = \"s\";      // Size\n        $oType->sDesc = $oLang->translateString(\"size\", $iLang);\n        $aDelTypes['s'] = $oType;\n        $oType = new stdClass();\n        $oType->sType = \"w\";      // Weight\n        $oType->sDesc = $oLang->translateString(\"weight\", $iLang);\n        $aDelTypes['w'] = $oType;\n        $oType = new stdClass();\n        $oType->sType = \"p\";      // Price\n        $oType->sDesc = $oLang->translateString(\"price\", $iLang);\n        $aDelTypes['p'] = $oType;\n\n        return $aDelTypes;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliveryMainAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages delivery countries\n */\nclass DeliveryMainAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,         visible, multilanguage, ident\n        ['oxtitle', 'oxcountry', 1, 1, 0],\n        ['oxisoalpha2', 'oxcountry', 1, 0, 0],\n        ['oxisoalpha3', 'oxcountry', 0, 0, 0],\n        ['oxunnum3', 'oxcountry', 0, 0, 0],\n        ['oxid', 'oxcountry', 0, 0, 1]\n    ],\n                                 'container2' => [\n                                     ['oxtitle', 'oxcountry', 1, 1, 0],\n                                     ['oxisoalpha2', 'oxcountry', 1, 0, 0],\n                                     ['oxisoalpha3', 'oxcountry', 0, 0, 0],\n                                     ['oxunnum3', 'oxcountry', 0, 0, 0],\n                                     ['oxid', 'oxobject2delivery', 0, 0, 1]\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $sCountryTable = $this->getViewName('oxcountry');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sId) {\n            $sQAdd = \" from {$sCountryTable} where {$sCountryTable}.oxactive = '1' \";\n        } else {\n            $sQAdd = \" from oxobject2delivery left join {$sCountryTable} \" .\n                     \"on {$sCountryTable}.oxid=oxobject2delivery.oxobjectid \" .\n                     \" where oxobject2delivery.oxdeliveryid = \" . $oDb->quote($sId) .\n                     \" and oxobject2delivery.oxtype = 'oxcountry' \";\n        }\n\n        if ($sSynchId && $sSynchId != $sId) {\n            $sQAdd .= \" and {$sCountryTable}.oxid not in ( select {$sCountryTable}.oxid \" .\n                      \"from oxobject2delivery left join {$sCountryTable} \" .\n                      \"on {$sCountryTable}.oxid=oxobject2delivery.oxobjectid \" .\n                      \" where oxobject2delivery.oxdeliveryid = \" . $oDb->quote($sSynchId) .\n                      \" and oxobject2delivery.oxtype = 'oxcountry' ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes chosen countries from delivery list\n     */\n    public function removeCountryFromDel()\n    {\n        $aChosenCntr = $this->getActionIds('oxobject2delivery.oxid');\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxobject2delivery.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif (is_array($aChosenCntr)) {\n            $sQ = \"delete from oxobject2delivery where oxobject2delivery.oxid in (\" . implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aChosenCntr)) . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adds chosen countries to delivery list\n     */\n    public function addCountryToDel()\n    {\n        $aChosenCntr = $this->getActionIds('oxcountry.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // adding\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sCountryTable = $this->getViewName('oxcountry');\n            $aChosenCntr = $this->getAll($this->addFilter(\"select $sCountryTable.oxid \" . $this->getQuery()));\n        }\n\n        if ($soxId && $soxId != \"-1\" && is_array($aChosenCntr)) {\n            foreach ($aChosenCntr as $sChosenCntr) {\n                $oObject2Delivery = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oObject2Delivery->init('oxobject2delivery');\n                $oObject2Delivery->oxobject2delivery__oxdeliveryid = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                $oObject2Delivery->oxobject2delivery__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sChosenCntr);\n                $oObject2Delivery->oxobject2delivery__oxtype = new \\OxidEsales\\Eshop\\Core\\Field('oxcountry');\n                $oObject2Delivery->save();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliverySetController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin article deliveryset manager.\n * Returns template, that arranges two other templates (\"deliveryset_list\"\n * and \"deliveryset_main\") to frame.\n * Admin Menu: Shop settings -> Shipping & Handling Sets.\n */\nclass DeliverySetController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'deliveryset';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliverySetCountryAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages deliveryset countries\n */\nclass DeliverySetCountryAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,         visible, multilanguage, ident\n        ['oxtitle', 'oxcountry', 1, 1, 0],\n        ['oxisoalpha2', 'oxcountry', 1, 0, 0],\n        ['oxisoalpha3', 'oxcountry', 0, 0, 0],\n        ['oxunnum3', 'oxcountry', 0, 0, 0],\n        ['oxid', 'oxcountry', 0, 0, 1]\n    ],\n                                 'container2' => [\n                                     ['oxtitle', 'oxcountry', 1, 1, 0],\n                                     ['oxisoalpha2', 'oxcountry', 1, 0, 0],\n                                     ['oxisoalpha3', 'oxcountry', 0, 0, 0],\n                                     ['oxunnum3', 'oxcountry', 0, 0, 0],\n                                     ['oxid', 'oxobject2delivery', 0, 0, 1]\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        $sCountryTable = $this->getViewName('oxcountry');\n\n        // category selected or not ?\n        if (!$sId) {\n            $sQAdd = \" from {$sCountryTable} where {$sCountryTable}.oxactive = '1' \";\n        } else {\n            $sQAdd = \" from oxobject2delivery, {$sCountryTable} \" .\n                     \"where oxobject2delivery.oxdeliveryid = \" . $oDb->quote($sId) .\n                     \" and oxobject2delivery.oxobjectid = {$sCountryTable}.oxid \" .\n                     \"and oxobject2delivery.oxtype = 'oxdelset' \";\n        }\n\n        if ($sSynchId && $sSynchId != $sId) {\n            $sQAdd .= \"and {$sCountryTable}.oxid not in ( select {$sCountryTable}.oxid \" .\n                      \"from oxobject2delivery, {$sCountryTable} \" .\n                      \"where oxobject2delivery.oxdeliveryid = \" . $oDb->quote($sSynchId) .\n                      \"and oxobject2delivery.oxobjectid = {$sCountryTable}.oxid \" .\n                      \"and oxobject2delivery.oxtype = 'oxdelset' ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes chosen countries from delivery list\n     */\n    public function removeCountryFromSet()\n    {\n        $aChosenCntr = $this->getActionIds('oxobject2delivery.oxid');\n        // removing all\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxobject2delivery.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif (is_array($aChosenCntr)) {\n            $sChosenCountries = implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aChosenCntr));\n            $sQ = \"delete from oxobject2delivery where oxobject2delivery.oxid in (\" . $sChosenCountries . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adds chosen countries to delivery list\n     */\n    public function addCountryToSet()\n    {\n        $aChosenCntr = $this->getActionIds('oxcountry.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // adding\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sCountryTable = $this->getViewName('oxcountry');\n            $aChosenCntr = $this->getAll($this->addFilter(\"select $sCountryTable.oxid \" . $this->getQuery()));\n        }\n\n        if ($soxId && $soxId != \"-1\" && is_array($aChosenCntr)) {\n            foreach ($aChosenCntr as $sChosenCntr) {\n                $oObject2Delivery = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oObject2Delivery->init('oxobject2delivery');\n                $oObject2Delivery->oxobject2delivery__oxdeliveryid = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                $oObject2Delivery->oxobject2delivery__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sChosenCntr);\n                $oObject2Delivery->oxobject2delivery__oxtype = new \\OxidEsales\\Eshop\\Core\\Field(\"oxdelset\");\n                $oObject2Delivery->save();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliverySetGroupsAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages deliveryset groups\n */\nclass DeliverySetGroupsAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,  visible, multilanguage, ident\n        ['oxtitle', 'oxgroups', 1, 0, 0],\n        ['oxid', 'oxgroups', 0, 0, 0],\n        ['oxid', 'oxgroups', 0, 0, 1],\n    ],\n                                 'container2' => [\n                                     ['oxtitle', 'oxgroups', 1, 0, 0],\n                                     ['oxid', 'oxgroups', 0, 0, 0],\n                                     ['oxid', 'oxobject2delivery', 0, 0, 1],\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        $sgroupTable = $this->getViewName('oxgroups');\n\n        // category selected or not ?\n        if (!$sId) {\n            $sQAdd = \" from $sgroupTable where 1 \";\n        } else {\n            $sQAdd = \" from oxobject2delivery, {$sgroupTable} \" .\n                     \"where oxobject2delivery.oxdeliveryid = \" . $oDb->quote($sId) .\n                     \" and oxobject2delivery.oxobjectid = {$sgroupTable}.oxid \" .\n                     \"and oxobject2delivery.oxtype = 'oxdelsetg' \";\n        }\n\n        if ($sSynchId && $sSynchId != $sId) {\n            $sQAdd .= \" and {$sgroupTable}.oxid not in ( select {$sgroupTable}.oxid \" .\n                      \"from oxobject2delivery, {$sgroupTable} \" .\n                      \"where oxobject2delivery.oxdeliveryid = \" . $oDb->quote($sSynchId) .\n                      \" and oxobject2delivery.oxobjectid = $sgroupTable.oxid \" .\n                      \"and oxobject2delivery.oxtype = 'oxdelsetg' ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes user group from delivery sets config\n     */\n    public function removeGroupFromSet()\n    {\n        $aRemoveGroups = $this->getActionIds('oxobject2delivery.oxid');\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxobject2delivery.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif ($aRemoveGroups && is_array($aRemoveGroups)) {\n            $sRemoveGroups = implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aRemoveGroups));\n            $sQ = \"delete from oxobject2delivery where oxobject2delivery.oxid in (\" . $sRemoveGroups . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adds user group to delivery sets config\n     */\n    public function addGroupToSet()\n    {\n        $aChosenCat = $this->getActionIds('oxgroups.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // adding\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sGroupTable = $this->getViewName('oxgroups');\n            $aChosenCat = $this->getAll($this->addFilter(\"select $sGroupTable.oxid \" . $this->getQuery()));\n        }\n        if ($soxId && $soxId != \"-1\" && is_array($aChosenCat)) {\n            foreach ($aChosenCat as $sChosenCat) {\n                $oObject2Delivery = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oObject2Delivery->init('oxobject2delivery');\n                $oObject2Delivery->oxobject2delivery__oxdeliveryid = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                $oObject2Delivery->oxobject2delivery__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sChosenCat);\n                $oObject2Delivery->oxobject2delivery__oxtype = new \\OxidEsales\\Eshop\\Core\\Field(\"oxdelsetg\");\n                $oObject2Delivery->save();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliverySetList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin deliverysetset list manager.\n * Collects deliveryset base information (description), there is ability to\n * filter them by description, title or delete them.\n * Admin Menu: Shop Settings -> Shipping & Handling Sets.\n */\nclass DeliverySetList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxdeliveryset';\n\n    /**\n     * Type of list.\n     *\n     * @var string\n     */\n    protected $_sListType = 'oxdeliverysetlist';\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'deliveryset_list';\n\n    /**\n     * Default SQL sorting parameter (default null).\n     *\n     * @var string\n     */\n    protected $_sDefSortField = 'oxpos';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliverySetMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse stdClass;\n\n/**\n * Admin article main deliveryset manager.\n * There is possibility to change deliveryset name, article, user\n * and etc.\n * Admin Menu: Shop settings -> Shipping & Handling -> Main Sets.\n */\nclass DeliverySetMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $odeliveryset = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\DeliverySet::class);\n            $odeliveryset->loadInLang($this->_iEditLang, $soxId);\n\n            $oOtherLang = $odeliveryset->getAvailableInLangs();\n\n            if (!isset($oOtherLang[$this->_iEditLang])) {\n                $odeliveryset->loadInLang(key($oOtherLang), $soxId);\n            }\n\n            $this->_aViewData[\"edit\"] = $odeliveryset;\n            //Disable editing for derived articles\n            if ($odeliveryset->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n            // remove already created languages\n            $aLang = array_diff(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->getLanguageNames(), $oOtherLang);\n            if (count($aLang)) {\n                $this->_aViewData[\"posslang\"] = $aLang;\n            }\n\n            foreach ($oOtherLang as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $oLang;\n            }\n        }\n\n        if (Registry::getRequest()->getRequestEscapedParameter(\"aoc\")) {\n            $oDeliverysetMainAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliverySetMainAjax::class);\n            $this->_aViewData['oxajax'] = $oDeliverysetMainAjax->getColumns();\n\n            return \"popups/deliveryset_main\";\n        }\n\n        return \"deliveryset_main\";\n    }\n\n    /**\n     * Saves deliveryset information changes.\n     *\n     * @return mixed\n     */\n    public function save()\n    {\n        parent::save();\n\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        $oDelSet = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\DeliverySet::class);\n\n        if ($soxId != \"-1\") {\n            $oDelSet->loadInLang($this->_iEditLang, $soxId);\n        } else {\n            $aParams['oxdeliveryset__oxid'] = null;\n        }\n\n        // checkbox handling\n        if (!isset($aParams['oxdeliveryset__oxactive'])) {\n            $aParams['oxdeliveryset__oxactive'] = 0;\n        }\n\n        //Disable editing for derived articles\n        if ($oDelSet->isDerived()) {\n            return;\n        }\n\n        //$aParams = $oDelSet->ConvertNameArray2Idx( $aParams);\n        $oDelSet->setLanguage(0);\n        $oDelSet->assign($aParams);\n        $oDelSet->setLanguage($this->_iEditLang);\n        $oDelSet = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsFile()->processFiles($oDelSet);\n        $oDelSet->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oDelSet->getId());\n    }\n\n    /**\n     * Saves deliveryset data to different language (eg. english).\n     *\n     * @return null\n     */\n    public function saveinnlang()\n    {\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n        // checkbox handling\n        if (!isset($aParams['oxdeliveryset__oxactive'])) {\n            $aParams['oxdeliveryset__oxactive'] = 0;\n        }\n\n        $oDelSet = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\DeliverySet::class);\n\n        if ($soxId != \"-1\") {\n            $oDelSet->loadInLang($this->_iEditLang, $soxId);\n        } else {\n            $aParams['oxdeliveryset__oxid'] = null;\n            //$aParams = $oDelSet->ConvertNameArray2Idx( $aParams);\n        }\n\n        $oDelSet->setLanguage(0);\n        $oDelSet->assign($aParams);\n\n        //Disable editing for derived articles\n        if ($oDelSet->isDerived()) {\n            return;\n        }\n\n        // apply new language\n        $oDelSet->setLanguage(Registry::getRequest()->getRequestEscapedParameter(\"new_lang\"));\n        $oDelSet->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oDelSet->getId());\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliverySetMainAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse Exception;\n\n/**\n * Class manages deliveryset and delivery configuration\n */\nclass DeliverySetMainAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,         visible, multilanguage, ident\n        ['oxtitle', 'oxdelivery', 1, 1, 0],\n        ['oxaddsum', 'oxdelivery', 1, 0, 0],\n        ['oxaddsumtype', 'oxdelivery', 1, 0, 0],\n        ['oxid', 'oxdelivery', 0, 0, 1]\n    ],\n                                 'container2' => [\n                                     ['oxtitle', 'oxdelivery', 1, 1, 0],\n                                     ['oxaddsum', 'oxdelivery', 1, 0, 0],\n                                     ['oxaddsumtype', 'oxdelivery', 1, 0, 0],\n                                     ['oxid', 'oxdel2delset', 0, 0, 1]\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $sId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $sDeliveryViewName = $this->getViewName('oxdelivery');\n\n        // category selected or not ?\n        if (!$sId) {\n            $sQAdd = \" from $sDeliveryViewName where 1 \";\n        } else {\n            $sQAdd = \" from $sDeliveryViewName left join oxdel2delset on oxdel2delset.oxdelid=$sDeliveryViewName.oxid \";\n            $sQAdd .= \"where oxdel2delset.oxdelsetid = \" . $oDb->quote($sId);\n        }\n\n        if ($sSynchId && $sSynchId != $sId) {\n            $sQAdd .= \"and $sDeliveryViewName.oxid not in ( select $sDeliveryViewName.oxid from $sDeliveryViewName left join oxdel2delset on oxdel2delset.oxdelid=$sDeliveryViewName.oxid \";\n            $sQAdd .= \"where oxdel2delset.oxdelsetid = \" . $oDb->quote($sSynchId) . \" ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Remove this delivery cost from these sets\n     */\n    public function removeFromSet()\n    {\n        $aRemoveGroups = $this->getActionIds('oxdel2delset.oxid');\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxdel2delset.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif ($aRemoveGroups && is_array($aRemoveGroups)) {\n            $sQ = \"delete from oxdel2delset where oxdel2delset.oxid in (\" . implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aRemoveGroups)) . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adds this delivery cost to these sets\n     *\n     * @throws Exception\n     */\n    public function addToSet()\n    {\n        $aChosenSets = $this->getActionIds('oxdelivery.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // adding\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sDeliveryViewName = $this->getViewName('oxdelivery');\n            $aChosenSets = $this->getAll($this->addFilter(\"select $sDeliveryViewName.oxid \" . $this->getQuery()));\n        }\n        if ($soxId && $soxId != \"-1\" && is_array($aChosenSets)) {\n            // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804 and ESDEV-3822).\n            $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster();\n            foreach ($aChosenSets as $sChosenSet) {\n                // check if we have this entry already in\n                // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n                $sID = $database->getOne(\"select oxid from oxdel2delset where oxdelid = :oxdelid and oxdelsetid = :oxdelsetid\", [\n                    'oxdelid' => $sChosenSet,\n                    'oxdelsetid' => $soxId\n                ]);\n                if (!isset($sID) || !$sID) {\n                    $oDel2delset = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                    $oDel2delset->init('oxdel2delset');\n                    $oDel2delset->oxdel2delset__oxdelid = new \\OxidEsales\\Eshop\\Core\\Field($sChosenSet);\n                    $oDel2delset->oxdel2delset__oxdelsetid = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                    $oDel2delset->save();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliverySetPayment.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin deliveryset payment manager.\n * There is possibility to assign set to payment method\n * and etc.\n * Admin Menu: Shop settings -> Shipping & Handling Set -> Payment\n */\nclass DeliverySetPayment extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $odeliveryset = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\DeliverySet::class);\n            $odeliveryset->setLanguage($this->_iEditLang);\n            $odeliveryset->load($soxId);\n\n            $oOtherLang = $odeliveryset->getAvailableInLangs();\n\n            if (!isset($oOtherLang[$this->_iEditLang])) {\n                $odeliveryset->setLanguage(key($oOtherLang));\n                $odeliveryset->load($soxId);\n            }\n\n            $this->_aViewData[\"edit\"] = $odeliveryset;\n\n            //Disable editing for derived articles\n            if ($odeliveryset->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n        }\n\n        $iAoc = Registry::getRequest()->getRequestEscapedParameter(\"aoc\");\n        if ($iAoc == 1) {\n            $oDeliverysetPaymentAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliverySetPaymentAjax::class);\n            $this->_aViewData['oxajax'] = $oDeliverysetPaymentAjax->getColumns();\n\n            return \"popups/deliveryset_payment\";\n        } elseif ($iAoc == 2) {\n            $oDeliverysetCountryAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliverySetCountryAjax::class);\n            $this->_aViewData['oxajax'] = $oDeliverysetCountryAjax->getColumns();\n\n            return \"popups/deliveryset_country\";\n        }\n\n        return \"deliveryset_payment\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliverySetPaymentAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse Exception;\n\n/**\n * Class manages deliveryset payment\n */\nclass DeliverySetPaymentAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,         visible, multilanguage, ident\n        ['oxdesc', 'oxpayments', 1, 1, 0],\n        ['oxaddsum', 'oxpayments', 1, 0, 0],\n        ['oxaddsumtype', 'oxpayments', 0, 0, 0],\n        ['oxid', 'oxpayments', 0, 0, 1]\n    ],\n                                 'container2' => [\n                                     ['oxdesc', 'oxpayments', 1, 1, 0],\n                                     ['oxaddsum', 'oxpayments', 1, 0, 0],\n                                     ['oxaddsumtype', 'oxpayments', 0, 0, 0],\n                                     ['oxid', 'oxobject2payment', 0, 0, 1]\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        $sPayTable = $this->getViewName('oxpayments');\n\n        // category selected or not ?\n        if (!$sId) {\n            $sQAdd = \" from $sPayTable where 1 \";\n        } else {\n            $sQAdd = \" from oxobject2payment, $sPayTable where oxobject2payment.oxobjectid = \" . $oDb->quote($sId);\n            $sQAdd .= \" and oxobject2payment.oxpaymentid = $sPayTable.oxid and oxobject2payment.oxtype = 'oxdelset' \";\n        }\n\n        if ($sSynchId && $sSynchId != $sId) {\n            $sQAdd .= \"and $sPayTable.oxid not in ( select $sPayTable.oxid from oxobject2payment, $sPayTable where oxobject2payment.oxobjectid = \" . $oDb->quote($sSynchId);\n            $sQAdd .= \"and oxobject2payment.oxpaymentid = $sPayTable.oxid and oxobject2payment.oxtype = 'oxdelset' ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Remove these payments from this set\n     */\n    public function removePayFromSet()\n    {\n        $aChosenCntr = $this->getActionIds('oxobject2payment.oxid');\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxobject2payment.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif (is_array($aChosenCntr)) {\n            $sQ = \"delete from oxobject2payment where oxobject2payment.oxid in (\" . implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aChosenCntr)) . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adds this payments to this set\n     *\n     * @throws Exception\n     */\n    public function addPayToSet()\n    {\n        $aChosenSets = $this->getActionIds('oxpayments.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // adding\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sPayTable = $this->getViewName('oxpayments');\n            $aChosenSets = $this->getAll($this->addFilter(\"select $sPayTable.oxid \" . $this->getQuery()));\n        }\n        if ($soxId && $soxId != \"-1\" && is_array($aChosenSets)) {\n            // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804 and ESDEV-3822).\n            $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster();\n            foreach ($aChosenSets as $sChosenSet) {\n                // check if we have this entry already in\n                // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n                $sID = $database->getOne(\"select oxid from oxobject2payment where oxpaymentid = :oxpaymentid and oxobjectid = :oxobjectid and oxtype = 'oxdelset'\", [\n                    'oxpaymentid' => $sChosenSet,\n                    'oxobjectid' => $soxId\n                ]);\n                if (!isset($sID) || !$sID) {\n                    $oObject = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                    $oObject->init('oxobject2payment');\n                    $oObject->oxobject2payment__oxpaymentid = new \\OxidEsales\\Eshop\\Core\\Field($sChosenSet);\n                    $oObject->oxobject2payment__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                    $oObject->oxobject2payment__oxtype = new \\OxidEsales\\Eshop\\Core\\Field(\"oxdelset\");\n                    $oObject->save();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliverySetRdfa.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse stdClass;\n\n/**\n * Admin article RDFa deliveryset manager.\n * Performs collection and updatind (on user submit) main item information.\n * Admin Menu: Shop Settings -> Shipping & Handling -> RDFa.\n */\nclass DeliverySetRdfa extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\PaymentRdfa\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = \"deliveryset_rdfa\";\n\n    /**\n     * Predefined delivery methods\n     *\n     * @var array\n     */\n    protected $_aRDFaDeliveries = [\n        \"DeliveryModeDirectDownload\" => 0,\n        \"DeliveryModeFreight\"        => 0,\n        \"DeliveryModeMail\"           => 0,\n        \"DeliveryModeOwnFleet\"       => 0,\n        \"DeliveryModePickUp\"         => 0,\n        \"DHL\"                        => 1,\n        \"FederalExpress\"             => 1,\n        \"UPS\"                        => 1\n    ];\n\n    /**\n     * Saves changed mapping configurations\n     */\n    public function save()\n    {\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n        $aRDFaDeliveries = (array) Registry::getRequest()->getRequestEscapedParameter(\"ardfadeliveries\");\n\n        // Delete old mappings\n        $oDb = DatabaseProvider::getDb();\n        $sOxIdParameter = Registry::getRequest()->getRequestEscapedParameter(\"oxid\");\n        $sSql = \"DELETE FROM oxobject2delivery WHERE oxdeliveryid = :oxdeliveryid AND OXTYPE = 'rdfadeliveryset'\";\n        $oDb->execute($sSql, [\n            'oxdeliveryid' => $sOxIdParameter\n        ]);\n\n        // Save new mappings\n        foreach ($aRDFaDeliveries as $sDelivery) {\n            $oMapping = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n            $oMapping->init(\"oxobject2delivery\");\n            $oMapping->assign($aParams);\n            $oMapping->oxobject2delivery__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sDelivery);\n            $oMapping->save();\n        }\n    }\n\n    /**\n     * Returns an array including all available RDFa deliveries.\n     *\n     * @return array\n     */\n    public function getAllRDFaDeliveries()\n    {\n        $aRDFaDeliveries = [];\n        $aAssignedRDFaDeliveries = $this->getAssignedRDFaDeliveries();\n        foreach ($this->_aRDFaDeliveries as $sName => $iType) {\n            $oDelivery = new stdClass();\n            $oDelivery->name = $sName;\n            $oDelivery->type = $iType;\n            $oDelivery->checked = in_array($sName, $aAssignedRDFaDeliveries);\n            $aRDFaDeliveries[] = $oDelivery;\n        }\n\n        return $aRDFaDeliveries;\n    }\n\n    /**\n     * Returns array of RDFa deliveries which are assigned to current delivery\n     *\n     * @return array\n     */\n    public function getAssignedRDFaDeliveries()\n    {\n        return DatabaseProvider::getDb()->getCol(\n            'select oxobjectid from oxobject2delivery where oxdeliveryid = :oxdeliveryid'\n            . ' and oxtype = \"rdfadeliveryset\" ',\n            [\n                'oxdeliveryid' => Registry::getRequest()->getRequestEscapedParameter(\"oxid\")\n            ]\n        );\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliverySetUsers.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Admin deliveryset User manager.\n * There is possibility to add User, groups\n * and etc.\n * Admin Menu: Shop settings -> Shipping & Handling Sets -> Users.\n */\nclass DeliverySetUsers extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->getEditObjectId();\n\n        // all usergroups\n        $oGroups = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n        $oGroups->init('oxgroups');\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $oGroups->selectString(\"select * from \" . $tableViewNameGenerator->getViewName(\"oxgroups\", $this->_iEditLang));\n\n        $oRoot = new \\OxidEsales\\Eshop\\Application\\Model\\Groups();\n        $oRoot->oxgroups__oxid = new \\OxidEsales\\Eshop\\Core\\Field(\"\");\n        $oRoot->oxgroups__oxtitle = new \\OxidEsales\\Eshop\\Core\\Field(\"-- \");\n        // rebuild list as we need the \"no value\" entry at the first position\n        $aNewList = [];\n        $aNewList[] = $oRoot;\n\n        foreach ($oGroups as $val) {\n            $aNewList[$val->oxgroups__oxid->value] = new \\OxidEsales\\Eshop\\Application\\Model\\Groups();\n            $aNewList[$val->oxgroups__oxid->value]->oxgroups__oxid = new \\OxidEsales\\Eshop\\Core\\Field($val->oxgroups__oxid->value);\n            $aNewList[$val->oxgroups__oxid->value]->oxgroups__oxtitle = new \\OxidEsales\\Eshop\\Core\\Field($val->oxgroups__oxtitle->value);\n        }\n\n        $oGroups = $aNewList;\n\n        if (isset($soxId) && $soxId != \"-1\") {\n            $oDelivery = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\DeliverySet::class);\n            $oDelivery->load($soxId);\n\n            //Disable editing for derived articles\n            if ($oDelivery->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n        }\n\n        $this->_aViewData[\"allgroups2\"] = $oGroups;\n\n        $iAoc = Registry::getRequest()->getRequestEscapedParameter(\"aoc\");\n        if ($iAoc == 1) {\n            $oDeliverysetGroupsAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliverySetGroupsAjax::class);\n            $this->_aViewData['oxajax'] = $oDeliverysetGroupsAjax->getColumns();\n\n            return \"popups/deliveryset_groups\";\n        } elseif ($iAoc == 2) {\n            $oDeliverysetUsersAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliverySetUsersAjax::class);\n            $this->_aViewData['oxajax'] = $oDeliverysetUsersAjax->getColumns();\n\n            return \"popups/deliveryset_users\";\n        }\n\n        return \"deliveryset_users\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliverySetUsersAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages deliveryset users\n */\nclass DeliverySetUsersAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = [\n        // field , table,  visible, multilanguage, id\n        'container1' => [\n            ['oxusername', 'oxuser', 1, 0, 0],\n            ['oxlname', 'oxuser', 0, 0, 0],\n            ['oxfname', 'oxuser', 0, 0, 0],\n            ['oxstreet', 'oxuser', 0, 0, 0],\n            ['oxstreetnr', 'oxuser', 0, 0, 0],\n            ['oxcity', 'oxuser', 0, 0, 0],\n            ['oxzip', 'oxuser', 0, 0, 0],\n            ['oxfon', 'oxuser', 0, 0, 0],\n            ['oxbirthdate', 'oxuser', 0, 0, 0],\n            ['oxid', 'oxuser', 0, 0, 1],\n        ],\n         'container2' => [\n             ['oxusername', 'oxuser', 1, 0, 0],\n             ['oxlname', 'oxuser', 0, 0, 0],\n             ['oxfname', 'oxuser', 0, 0, 0],\n             ['oxstreet', 'oxuser', 0, 0, 0],\n             ['oxstreetnr', 'oxuser', 0, 0, 0],\n             ['oxcity', 'oxuser', 0, 0, 0],\n             ['oxzip', 'oxuser', 0, 0, 0],\n             ['oxfon', 'oxuser', 0, 0, 0],\n             ['oxbirthdate', 'oxuser', 0, 0, 0],\n             ['oxid', 'oxobject2delivery', 0, 0, 1],\n         ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        $sUserTable = $this->getViewName('oxuser');\n\n        // category selected or not ?\n        if (!$sId) {\n            $sQAdd = \" from $sUserTable where 1 \";\n            if (!$myConfig->getConfigParam('blMallUsers')) {\n                $sQAdd .= \"and $sUserTable.oxshopid = '\" . $myConfig->getShopId() . \"' \";\n            }\n        } elseif ($sSynchId && $sSynchId != $sId) {\n            // selected group ?\n            $sQAdd = \" from oxobject2group left join $sUserTable on $sUserTable.oxid = oxobject2group.oxobjectid \";\n            $sQAdd .= \" where oxobject2group.oxgroupsid = \" . $oDb->quote($sId);\n            if (!$myConfig->getConfigParam('blMallUsers')) {\n                $sQAdd .= \"and $sUserTable.oxshopid = '\" . $myConfig->getShopId() . \"' \";\n            }\n\n            // resetting\n            $sId = null;\n        } else {\n            $sQAdd = \" from oxobject2delivery, $sUserTable where oxobject2delivery.oxdeliveryid = \" . $oDb->quote($sId);\n            $sQAdd .= \"and oxobject2delivery.oxobjectid = $sUserTable.oxid and oxobject2delivery.oxtype = 'oxdelsetu' \";\n        }\n\n        if ($sSynchId && $sSynchId != $sId) {\n            $sQAdd .= \"and $sUserTable.oxid not in ( select $sUserTable.oxid from oxobject2delivery, $sUserTable where oxobject2delivery.oxdeliveryid = \" . $oDb->quote($sSynchId);\n            $sQAdd .= \"and oxobject2delivery.oxobjectid = $sUserTable.oxid and oxobject2delivery.oxtype = 'oxdelsetu' ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes users for delivery sets config\n     */\n    public function removeUserFromSet()\n    {\n        $aRemoveGroups = $this->getActionIds('oxobject2delivery.oxid');\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxobject2delivery.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif ($aRemoveGroups && is_array($aRemoveGroups)) {\n            $sQ = \"delete from oxobject2delivery where oxobject2delivery.oxid in (\" . implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aRemoveGroups)) . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adds users for delivery sets config\n     */\n    public function addUserToSet()\n    {\n        $aChosenUsr = $this->getActionIds('oxuser.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // adding\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sUserTable = $this->getViewName('oxuser');\n            $aChosenUsr = $this->getAll($this->addFilter(\"select $sUserTable.oxid \" . $this->getQuery()));\n        }\n        if ($soxId && $soxId != \"-1\" && is_array($aChosenUsr)) {\n            foreach ($aChosenUsr as $sChosenUsr) {\n                $oObject2Delivery = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oObject2Delivery->init('oxobject2delivery');\n                $oObject2Delivery->oxobject2delivery__oxdeliveryid = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                $oObject2Delivery->oxobject2delivery__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sChosenUsr);\n                $oObject2Delivery->oxobject2delivery__oxtype = new \\OxidEsales\\Eshop\\Core\\Field(\"oxdelsetu\");\n                $oObject2Delivery->save();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliveryUsers.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Admin article main delivery manager.\n * There is possibility to change delivery name, article, user\n * and etc.\n * Admin Menu: Shop settings -> Shipping & Handling -> Main.\n */\nclass DeliveryUsers extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->getEditObjectId();\n\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sViewName = $tableViewNameGenerator->getViewName(\"oxgroups\", $this->_iEditLang);\n        // all usergroups\n        $oGroups = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n        $oGroups->init('oxgroups');\n        $oGroups->selectString(\"select * from {$sViewName}\");\n\n        $oRoot = new \\OxidEsales\\Eshop\\Application\\Model\\Groups();\n        $oRoot->oxgroups__oxid = new \\OxidEsales\\Eshop\\Core\\Field(\"\");\n        $oRoot->oxgroups__oxtitle = new \\OxidEsales\\Eshop\\Core\\Field(\"-- \");\n        // rebuild list as we need the \"no value\" entry at the first position\n        $aNewList = [];\n        $aNewList[] = $oRoot;\n\n        foreach ($oGroups as $val) {\n            $aNewList[$val->oxgroups__oxid->value] = new \\OxidEsales\\Eshop\\Application\\Model\\Groups();\n            $aNewList[$val->oxgroups__oxid->value]->oxgroups__oxid = new \\OxidEsales\\Eshop\\Core\\Field($val->oxgroups__oxid->value);\n            $aNewList[$val->oxgroups__oxid->value]->oxgroups__oxtitle = new \\OxidEsales\\Eshop\\Core\\Field($val->oxgroups__oxtitle->value);\n        }\n\n        $oGroups = $aNewList;\n\n        if (isset($soxId) && $soxId != \"-1\") {\n            $oDelivery = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Delivery::class);\n            $oDelivery->load($soxId);\n\n            //Disable editing for derived articles\n            if ($oDelivery->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n        }\n\n        $this->_aViewData[\"allgroups2\"] = $oGroups;\n\n        $iAoc = Registry::getRequest()->getRequestEscapedParameter(\"aoc\");\n        if ($iAoc == 1) {\n            $oDeliveryUsersAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliveryUsersAjax::class);\n            $this->_aViewData['oxajax'] = $oDeliveryUsersAjax->getColumns();\n\n            return \"popups/delivery_users\";\n        } elseif ($iAoc == 2) {\n            $oDeliveryGroupsAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliveryGroupsAjax::class);\n            $this->_aViewData['oxajax'] = $oDeliveryGroupsAjax->getColumns();\n\n            return \"popups/delivery_groups\";\n        }\n\n        return \"delivery_users\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DeliveryUsersAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages delivery users\n */\nclass DeliveryUsersAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,  visible, multilanguage, ident\n        ['oxusername', 'oxuser', 1, 0, 0],\n        ['oxlname', 'oxuser', 0, 0, 0],\n        ['oxfname', 'oxuser', 0, 0, 0],\n        ['oxstreet', 'oxuser', 0, 0, 0],\n        ['oxstreetnr', 'oxuser', 0, 0, 0],\n        ['oxcity', 'oxuser', 0, 0, 0],\n        ['oxzip', 'oxuser', 0, 0, 0],\n        ['oxfon', 'oxuser', 0, 0, 0],\n        ['oxbirthdate', 'oxuser', 0, 0, 0],\n        ['oxid', 'oxuser', 0, 0, 1],\n    ],\n                                 'container2' => [\n                                     ['oxusername', 'oxuser', 1, 0, 0],\n                                     ['oxlname', 'oxuser', 0, 0, 0],\n                                     ['oxfname', 'oxuser', 0, 0, 0],\n                                     ['oxstreet', 'oxuser', 0, 0, 0],\n                                     ['oxstreetnr', 'oxuser', 0, 0, 0],\n                                     ['oxcity', 'oxuser', 0, 0, 0],\n                                     ['oxzip', 'oxuser', 0, 0, 0],\n                                     ['oxfon', 'oxuser', 0, 0, 0],\n                                     ['oxbirthdate', 'oxuser', 0, 0, 0],\n                                     ['oxid', 'oxobject2delivery', 0, 0, 1],\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $sUserTable = $this->getViewName('oxuser');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sId) {\n            $sQAdd = \" from $sUserTable where 1 \";\n            if (!$myConfig->getConfigParam('blMallUsers')) {\n                $sQAdd .= \" and $sUserTable.oxshopid = '\" . $myConfig->getShopId() . \"' \";\n            }\n        } elseif ($sSynchId && $sSynchId != $sId) {\n            // selected group ?\n            $sQAdd = \" from oxobject2group left join $sUserTable on $sUserTable.oxid = oxobject2group.oxobjectid \";\n            $sQAdd .= \" where oxobject2group.oxgroupsid = \" . $oDb->quote($sId);\n            if (!$myConfig->getConfigParam('blMallUsers')) {\n                $sQAdd .= \" and $sUserTable.oxshopid = '\" . $myConfig->getShopId() . \"' \";\n            }\n        } else {\n            $sQAdd = \" from oxobject2delivery left join $sUserTable on $sUserTable.oxid=oxobject2delivery.oxobjectid \";\n            $sQAdd .= \" where oxobject2delivery.oxdeliveryid = \" . $oDb->quote($sId) . \" and oxobject2delivery.oxtype = 'oxuser' and $sUserTable.oxid IS NOT NULL \";\n        }\n\n        if ($sSynchId && $sSynchId != $sId) {\n            $sQAdd .= \" and $sUserTable.oxid not in ( select $sUserTable.oxid from oxobject2delivery left join $sUserTable on $sUserTable.oxid=oxobject2delivery.oxobjectid \";\n            $sQAdd .= \" where oxobject2delivery.oxdeliveryid = \" . $oDb->quote($sSynchId) . \" and oxobject2delivery.oxtype = 'oxuser' and $sUserTable.oxid IS NOT NULL ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes user from delivery configuration\n     */\n    public function removeUserFromDel()\n    {\n        $aRemoveGroups = $this->getActionIds('oxobject2delivery.oxid');\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxobject2delivery.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif ($aRemoveGroups && is_array($aRemoveGroups)) {\n            $sQ = \"delete from oxobject2delivery where oxobject2delivery.oxid in (\" . implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aRemoveGroups)) . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adds user from delivery configuration\n     */\n    public function addUserToDel()\n    {\n        $aChosenUsr = $this->getActionIds('oxuser.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // adding\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sUserTable = $this->getViewName('oxuser');\n            $aChosenUsr = $this->getAll($this->addFilter(\"select $sUserTable.oxid \" . $this->getQuery()));\n        }\n\n        if ($soxId && $soxId != \"-1\" && is_array($aChosenUsr)) {\n            foreach ($aChosenUsr as $sChosenUsr) {\n                $oObject2Delivery = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oObject2Delivery->init('oxobject2delivery');\n                $oObject2Delivery->oxobject2delivery__oxdeliveryid = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                $oObject2Delivery->oxobject2delivery__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sChosenUsr);\n                $oObject2Delivery->oxobject2delivery__oxtype = new \\OxidEsales\\Eshop\\Core\\Field('oxuser');\n                $oObject2Delivery->save();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DiagnosticsController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Checks Version of System files.\n * Admin Menu: Service -> Diagnostics Tool.\n */\nclass DiagnosticsController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'diagnostics';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DiagnosticsList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Checks Version of System files.\n * Admin Menu: Service -> Version Checker.\n */\nclass DiagnosticsList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'diagnostics_list';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DiagnosticsMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Module\\Module;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ShopConfigurationDaoBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererBridgeInterface;\n\n/**\n * Checks Version of System files.\n * Admin Menu: Service -> Version Checker -> Main.\n */\nclass DiagnosticsMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * error tag\n     *\n     * @var boolean\n     */\n    protected $_blError = false;\n\n    /**\n     * error message\n     *\n     * @var string\n     */\n    protected $_sErrorMessage = null;\n\n    /**\n     * Diagnostic check object\n     *\n     * @var mixed\n     */\n    protected $_oDiagnostics = null;\n\n    /**\n     * Result output object\n     *\n     * @var mixed\n     */\n    protected $_oOutput = null;\n\n    /**\n     * Variable for storing shop root directory\n     *\n     * @var mixed|string\n     */\n    protected $_sShopDir = '';\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = \"diagnostics_main\";\n\n    /**\n     * Error status getter\n     *\n     * @return string\n     */\n    protected function hasError()\n    {\n        return $this->_blError;\n    }\n\n    /**\n     * Error status getter\n     *\n     * @return string\n     */\n    protected function getErrorMessage()\n    {\n        return $this->_sErrorMessage;\n    }\n\n\n    /**\n     * Calls parent constructor and initializes checker object\n     */\n    public function __construct()\n    {\n        parent::__construct();\n\n        $this->_sShopDir = ContainerFacade::getParameter('oxid_esales.shop_source_directory');\n        $this->_oOutput = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\DiagnosticsOutput::class);\n    }\n\n    /**\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        if ($this->hasError()) {\n            $this->_aViewData['sErrorMessage'] = $this->getErrorMessage();\n        }\n\n        return \"diagnostics_form\";\n    }\n\n    /**\n     * Checks system file versions\n     */\n    public function startDiagnostics()\n    {\n        $this->_oOutput->storeResult(\n            $this->getRenderedReport(\n                $this->runBasicDiagnostics()\n            )\n        );\n\n        $this->_aViewData['sResult'] = $this->_oOutput->readResultFile();\n    }\n\n    /**\n     * Performs main system diagnostic.\n     * Shop and module details, database health, php parameters, server information\n     *\n     * @return array\n     */\n    protected function runBasicDiagnostics()\n    {\n        $aViewData = [];\n        $oDiagnostics = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Diagnostics::class);\n\n        $oDiagnostics->setShopLink(ContainerFacade::getParameter('oxid_esales.shop_url'));\n        $oDiagnostics->setEdition(Registry::getConfig()->getFullEdition());\n        $oDiagnostics->setVersion(\n            oxNew(\\OxidEsales\\Eshop\\Core\\ShopVersion::class)->getVersion()\n        );\n\n        /**\n         * Shop\n         */\n        if ($this->getParam('runAnalysis')) {\n            $aViewData['runAnalysis'] = true;\n            $aViewData['aShopDetails'] = $oDiagnostics->getShopDetails();\n        }\n\n        /**\n         * Modules\n         */\n        if ($this->getParam('oxdiag_frm_modules')) {\n            $aViewData['oxdiag_frm_modules'] = true;\n            $aViewData['mylist'] = $this->getInstalledModules();\n        }\n\n        /**\n         * Health\n         */\n        if ($this->getParam('oxdiag_frm_health')) {\n            $oSysReq = oxNew(\\OxidEsales\\Eshop\\Core\\SystemRequirements::class);\n            $aViewData['oxdiag_frm_health'] = true;\n            $aViewData['aInfo'] = $oSysReq->getSystemInfo();\n            $aViewData['aCollations'] = $oSysReq->checkCollation();\n        }\n\n        /**\n         * PHP info\n         * Fetches a hand full of php configuration parameters and collects their values.\n         */\n        if ($this->getParam('oxdiag_frm_php')) {\n            $aViewData['oxdiag_frm_php'] = true;\n            $aViewData['aPhpConfigparams'] = $oDiagnostics->getPhpSelection();\n            $aViewData['sPhpDecoder'] = $oDiagnostics->getPhpDecoder();\n        }\n\n        /**\n         * Server info\n         */\n        if ($this->getParam('oxdiag_frm_server')) {\n            $aViewData['isExecAllowed'] = $oDiagnostics->isExecAllowed();\n            $aViewData['oxdiag_frm_server'] = true;\n            $aViewData['aServerInfo'] = $oDiagnostics->getServerInfo();\n        }\n\n        return $aViewData;\n    }\n\n    /**\n     * Downloads result of system file check\n     */\n    public function downloadResultFile()\n    {\n        $this->_oOutput->downloadResultFile();\n        exit(0);\n    }\n\n    /**\n     * Checks system file versions\n     *\n     * @return string\n     */\n    public function getSupportContactForm()\n    {\n        $aLinks = [\n            \"de\" => \"https://www.oxid-esales.com/ressourcen/anwenderbereich/supportangebot/\",\n            \"en\" => \"https://www.oxid-esales.com/en/resources/user-center/support-offer/\"\n        ];\n\n        $oLang = Registry::getLang();\n        $aLanguages = $oLang->getLanguageArray();\n        $iLangId = $oLang->getTplLanguage();\n        $sLangCode = $aLanguages[$iLangId]->abbr;\n\n        if (!array_key_exists($sLangCode, $aLinks)) {\n            $sLangCode = \"de\";\n        }\n\n        return $aLinks[$sLangCode];\n    }\n\n    /**\n     * Request parameter getter\n     *\n     * @param string $name\n     *\n     * @return string\n     */\n    public function getParam($name)\n    {\n        $request = Registry::get(\\OxidEsales\\Eshop\\Core\\Request::class);\n\n        return $request->getRequestEscapedParameter($name);\n    }\n\n    /**\n     * @return array\n     */\n    private function getInstalledModules(): array\n    {\n        $shopConfiguration = ContainerFacade::get(ShopConfigurationDaoBridgeInterface::class)\n            ->get();\n\n        $modules = [];\n\n        foreach ($shopConfiguration->getModuleConfigurations() as $moduleConfiguration) {\n            $module = oxNew(Module::class);\n            $module->load($moduleConfiguration->getId());\n            $modules[$moduleConfiguration->getId()] = $module;\n        }\n\n        return $modules;\n    }\n\n    /**\n     * @param array $diagnosticsResult\n     *\n     * @return string\n     */\n    private function getRenderedReport(array $diagnosticsResult): string\n    {\n        return ContainerFacade::get(TemplateRendererBridgeInterface::class)\n            ->getTemplateRenderer()\n            ->renderTemplate(\n                $this->_sThisTemplate,\n                $diagnosticsResult\n            );\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DiscountArticles.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin article main discount manager.\n * There is possibility to change discount name, article, user\n * and etc.\n * Admin Menu: Shop settings -> Shipping & Handling -> Main.\n */\nclass DiscountArticles extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != '-1') {\n            // load object\n            $oDiscount = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Discount::class);\n            $oDiscount->load($soxId);\n            $this->_aViewData['edit'] = $oDiscount;\n\n            //disabling derived items\n            if ($oDiscount->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n\n            // generating category tree for artikel choose select list\n            $this->createCategoryTree(\"artcattree\");\n        }\n\n        $iAoc = Registry::getRequest()->getRequestEscapedParameter(\"aoc\");\n        if ($iAoc == 1) {\n            $oDiscountArticlesAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\DiscountArticlesAjax::class);\n            $this->_aViewData['oxajax'] = $oDiscountArticlesAjax->getColumns();\n\n            return \"popups/discount_articles\";\n        } elseif ($iAoc == 2) {\n            $oDiscountCategoriesAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\DiscountCategoriesAjax::class);\n            $this->_aViewData['oxajax'] = $oDiscountCategoriesAjax->getColumns();\n\n            return \"popups/discount_categories\";\n        }\n\n        return 'discount_articles';\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DiscountArticlesAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages discount articles\n */\nclass DiscountArticlesAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    const NEW_DISCOUNT_LIST_ID = \"-1\";\n\n    /**\n     * If true extended column selection will be build\n     *\n     * @var bool\n     */\n    protected $_blAllowExtColumns = true;\n\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = [\n        // field , table, visible, multilanguage, id\n        'container1' => [\n            ['oxartnum', 'oxarticles', 1, 0, 0],\n            ['oxtitle', 'oxarticles', 1, 1, 0],\n            ['oxean', 'oxarticles', 1, 0, 0],\n            ['oxmpn', 'oxarticles', 0, 0, 0],\n            ['oxprice', 'oxarticles', 0, 0, 0],\n            ['oxstock', 'oxarticles', 0, 0, 0],\n            ['oxid', 'oxarticles', 0, 0, 1]\n        ],\n        'container2' => [\n            ['oxartnum', 'oxarticles', 1, 0, 0],\n            ['oxtitle', 'oxarticles', 1, 1, 0],\n            ['oxean', 'oxarticles', 1, 0, 0],\n            ['oxmpn', 'oxarticles', 0, 0, 0],\n            ['oxprice', 'oxarticles', 0, 0, 0],\n            ['oxstock', 'oxarticles', 0, 0, 0],\n            ['oxid', 'oxobject2discount', 0, 0, 1]\n        ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $oConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $sArticleTable = $this->getViewName('oxarticles');\n        $sO2CView = $this->getViewName('oxobject2category');\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sOxid = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchOxid = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sOxid && $sSynchOxid) {\n            $sQAdd = \" from $sArticleTable where 1 \";\n            $sQAdd .= $oConfig->getConfigParam('blVariantsSelection') ? '' : \"and $sArticleTable.oxparentid = '' \";\n        } else {\n            // selected category ?\n            if ($sSynchOxid && $sOxid != $sSynchOxid) {\n                $sQAdd = \" from $sO2CView left join $sArticleTable on \";\n                $sQAdd .= $oConfig->getConfigParam('blVariantsSelection') ? \"($sArticleTable.oxid=$sO2CView.oxobjectid or $sArticleTable.oxparentid=$sO2CView.oxobjectid)\" : \" $sArticleTable.oxid=$sO2CView.oxobjectid \";\n                $sQAdd .= \" where $sO2CView.oxcatnid = \" . $oDb->quote($sOxid) . \" and $sArticleTable.oxid is not null \";\n\n                // resetting\n                $sId = null;\n            } else {\n                $sQAdd = \" from oxobject2discount, $sArticleTable where $sArticleTable.oxid=oxobject2discount.oxobjectid \";\n                $sQAdd .= \" and oxobject2discount.oxdiscountid = \" . $oDb->quote($sOxid) . \" and oxobject2discount.oxtype = 'oxarticles' \";\n            }\n        }\n\n        if ($sSynchOxid && $sSynchOxid != $sOxid) {\n            // performance\n            $sSubSelect = \" select $sArticleTable.oxid from oxobject2discount, $sArticleTable where $sArticleTable.oxid=oxobject2discount.oxobjectid \";\n            $sSubSelect .= \" and oxobject2discount.oxdiscountid = \" . $oDb->quote($sSynchOxid) . \" and oxobject2discount.oxtype = 'oxarticles' \";\n\n            if (stristr($sQAdd, 'where') === false) {\n                $sQAdd .= ' where ';\n            } else {\n                $sQAdd .= ' and ';\n            }\n            $sQAdd .= \" $sArticleTable.oxid not in ( $sSubSelect ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes selected article (articles) from discount list\n     */\n    public function removeDiscArt()\n    {\n        $aChosenArt = $this->getActionIds('oxobject2discount.oxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = parent::addFilter(\"delete oxobject2discount.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->execute($sQ);\n        } elseif (is_array($aChosenArt)) {\n            $sQ = \"delete from oxobject2discount where oxobject2discount.oxid in (\" . implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aChosenArt)) . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->execute($sQ);\n        }\n    }\n\n    /**\n     * Adds selected article (articles) to discount list\n     */\n    public function addDiscArt()\n    {\n        $articleIds = $this->getActionIds('oxarticles.oxid');\n        $discountListId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // adding\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $articleTable = $this->getViewName('oxarticles');\n            $articleIds = $this->getAll(parent::addFilter(\"select $articleTable.oxid \" . $this->getQuery()));\n        }\n        if ($discountListId && $discountListId != self::NEW_DISCOUNT_LIST_ID && is_array($articleIds)) {\n            foreach ($articleIds as $articleId) {\n                $this->addArticleToDiscount($discountListId, $articleId);\n            }\n        }\n    }\n\n    /**\n     * Adds article to discount list\n     *\n     * @param string $discountListId\n     * @param string $articleId\n     */\n    protected function addArticleToDiscount($discountListId, $articleId)\n    {\n        $object2Discount = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n        $object2Discount->init('oxobject2discount');\n        $object2Discount->oxobject2discount__oxdiscountid = new \\OxidEsales\\Eshop\\Core\\Field($discountListId);\n        $object2Discount->oxobject2discount__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($articleId);\n        $object2Discount->oxobject2discount__oxtype = new \\OxidEsales\\Eshop\\Core\\Field(\"oxarticles\");\n\n        $object2Discount->save();\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DiscountCategoriesAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages discount categories\n */\nclass DiscountCategoriesAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /** If this discount id comes from request, it means that new discount should be created. */\n    const NEW_DISCOUNT_ID = \"-1\";\n\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = [\n        // field , table, visible, multilanguage, id\n        'container1' => [\n            ['oxtitle', 'oxcategories', 1, 1, 0],\n            ['oxdesc', 'oxcategories', 1, 1, 0],\n            ['oxid', 'oxcategories', 0, 0, 0],\n            ['oxid', 'oxcategories', 0, 0, 1]\n        ],\n         'container2' => [\n             ['oxtitle', 'oxcategories', 1, 1, 0],\n             ['oxdesc', 'oxcategories', 1, 1, 0],\n             ['oxid', 'oxcategories', 0, 0, 0],\n             ['oxid', 'oxobject2discount', 0, 0, 1],\n             ['oxid', 'oxcategories', 0, 0, 1]\n         ],\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $oConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $sId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        $sCategoryTable = $this->getViewName('oxcategories');\n\n        // category selected or not ?\n        if (!$sId) {\n            $sQAdd = \" from {$sCategoryTable}\";\n        } else {\n            $sQAdd = \" from oxobject2discount, {$sCategoryTable} \" .\n                     \"where {$sCategoryTable}.oxid=oxobject2discount.oxobjectid \" .\n                     \" and oxobject2discount.oxdiscountid = \" . $oDb->quote($sId) .\n                     \" and oxobject2discount.oxtype = 'oxcategories' \";\n        }\n\n        if ($sSynchId && $sSynchId != $sId) {\n            // performance\n            $sSubSelect = \" select {$sCategoryTable}.oxid from oxobject2discount, {$sCategoryTable} \" .\n                          \"where {$sCategoryTable}.oxid=oxobject2discount.oxobjectid \" .\n                          \" and oxobject2discount.oxdiscountid = \" . $oDb->quote($sSynchId) .\n                          \" and oxobject2discount.oxtype = 'oxcategories' \";\n            if (stristr($sQAdd, 'where') === false) {\n                $sQAdd .= ' where ';\n            } else {\n                $sQAdd .= ' and ';\n            }\n            $sQAdd .= \" {$sCategoryTable}.oxid not in ( $sSubSelect ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes selected category (categories) from discount list\n     */\n    public function removeDiscCat()\n    {\n        $categoryIds = $this->getActionIds('oxobject2discount.oxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $query = $this->addFilter(\"delete oxobject2discount.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($query);\n        } elseif (is_array($categoryIds)) {\n            $chosenCategories = implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($categoryIds));\n            $query = \"delete from oxobject2discount where oxobject2discount.oxid in (\" . $chosenCategories . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($query);\n        }\n    }\n\n    /**\n     * Adds selected category (categories) to discount list\n     */\n    public function addDiscCat()\n    {\n        $categoryIds = $this->getActionIds('oxcategories.oxid');\n        $discountId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $categoryTable = $this->getViewName('oxcategories');\n            $categoryIds = $this->getAll($this->addFilter(\"select $categoryTable.oxid \" . $this->getQuery()));\n        }\n        if ($discountId && $discountId != self::NEW_DISCOUNT_ID && is_array($categoryIds)) {\n            foreach ($categoryIds as $categoryId) {\n                $this->addCategoryToDiscount($discountId, $categoryId);\n            }\n        }\n    }\n\n    /**\n     * Adds category to discounts list.\n     *\n     * @param string $discountId\n     * @param string $categoryId\n     */\n    protected function addCategoryToDiscount($discountId, $categoryId)\n    {\n        $object2Discount = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n        $object2Discount->init('oxobject2discount');\n        $object2Discount->oxobject2discount__oxdiscountid = new \\OxidEsales\\Eshop\\Core\\Field($discountId);\n        $object2Discount->oxobject2discount__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($categoryId);\n        $object2Discount->oxobject2discount__oxtype = new \\OxidEsales\\Eshop\\Core\\Field(\"oxcategories\");\n\n        $object2Discount->save();\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DiscountController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin article discount manager.\n * Returns template, that arranges two other templates (\"discount_list\"\n * and \"discount_main\") to frame.\n * Admin Menu: Shop Settings -> Discounts.\n */\nclass DiscountController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'discount';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DiscountGroupsAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages discount groups\n */\nclass DiscountGroupsAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /** If this discount id comes from request, it means that new discount should be created. */\n    const NEW_DISCOUNT_ID = \"-1\";\n\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = [\n        // field , table,  visible, multilanguage, id\n        'container1' => [\n            ['oxtitle', 'oxgroups', 1, 0, 0],\n            ['oxid', 'oxgroups', 0, 0, 0],\n            ['oxid', 'oxgroups', 0, 0, 1],\n        ],\n         'container2' => [\n             ['oxtitle', 'oxgroups', 1, 0, 0],\n             ['oxid', 'oxgroups', 0, 0, 0],\n             ['oxid', 'oxobject2discount', 0, 0, 1],\n         ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        // active AJAX component\n        $sGroupTable = $this->getViewName('oxgroups');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sId) {\n            $sQAdd = \" from {$sGroupTable} where 1 \";\n        } else {\n            $sQAdd = \" from oxobject2discount, {$sGroupTable} where {$sGroupTable}.oxid=oxobject2discount.oxobjectid \";\n            $sQAdd .= \" and oxobject2discount.oxdiscountid = \" . $oDb->quote($sId) .\n                      \" and oxobject2discount.oxtype = 'oxgroups' \";\n        }\n\n        if ($sSynchId && $sSynchId != $sId) {\n            $sQAdd .= \" and {$sGroupTable}.oxid not in ( select {$sGroupTable}.oxid \" .\n                      \"from oxobject2discount, {$sGroupTable} where {$sGroupTable}.oxid=oxobject2discount.oxobjectid \" .\n                      \" and oxobject2discount.oxdiscountid = \" . $oDb->quote($sSynchId) .\n                      \" and oxobject2discount.oxtype = 'oxgroups' ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes user group from discount config\n     */\n    public function removeDiscGroup()\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $groupIds = $this->getActionIds('oxobject2discount.oxid');\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $query = $this->addFilter(\"delete oxobject2discount.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($query);\n        } elseif ($groupIds && is_array($groupIds)) {\n            $groupIdsQuoted = implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($groupIds));\n            $query = \"delete from oxobject2discount where oxobject2discount.oxid in (\" . $groupIdsQuoted . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($query);\n        }\n    }\n\n    /**\n     * Adds user group to discount config\n     */\n    public function addDiscGroup()\n    {\n        $groupIds = $this->getActionIds('oxgroups.oxid');\n        $discountId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $groupTable = $this->getViewName('oxgroups');\n            $groupIds = $this->getAll($this->addFilter(\"select $groupTable.oxid \" . $this->getQuery()));\n        }\n        if ($discountId && $discountId != self::NEW_DISCOUNT_ID && is_array($groupIds)) {\n            foreach ($groupIds as $groupId) {\n                $object2Discount = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $object2Discount->init('oxobject2discount');\n                $object2Discount->oxobject2discount__oxdiscountid = new \\OxidEsales\\Eshop\\Core\\Field($discountId);\n                $object2Discount->oxobject2discount__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($groupId);\n                $object2Discount->oxobject2discount__oxtype = new \\OxidEsales\\Eshop\\Core\\Field(\"oxgroups\");\n                $object2Discount->save();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DiscountItemAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\n\n/**\n * Class manages discount articles\n */\nclass DiscountItemAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = [\n        // field , table, visible, multilanguage, id\n        'container1' => [\n            ['oxartnum', 'oxarticles', 1, 0, 0],\n            ['oxtitle', 'oxarticles', 1, 1, 0],\n            ['oxean', 'oxarticles', 1, 0, 0],\n            ['oxmpn', 'oxarticles', 0, 0, 0],\n            ['oxprice', 'oxarticles', 0, 0, 0],\n            ['oxstock', 'oxarticles', 0, 0, 0],\n            ['oxid', 'oxarticles', 0, 0, 1]\n        ],\n         'container2' => [\n             ['oxartnum', 'oxarticles', 1, 0, 0],\n             ['oxtitle', 'oxarticles', 1, 1, 0],\n             ['oxean', 'oxarticles', 1, 0, 0],\n             ['oxmpn', 'oxarticles', 0, 0, 0],\n             ['oxprice', 'oxarticles', 0, 0, 0],\n             ['oxstock', 'oxarticles', 0, 0, 0],\n             ['oxitmartid', 'oxdiscount', 0, 0, 1]\n         ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $oConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $sArticleTable = $this->getViewName('oxarticles');\n        $sO2CView = $this->getViewName('oxobject2category');\n        $sDiscTable = $this->getViewName('oxdiscount');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sOxid = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchOxid = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sOxid && $sSynchOxid) {\n            $sQAdd = \" from $sArticleTable where 1 \";\n            $sQAdd .= $oConfig->getConfigParam('blVariantsSelection') ? '' : \"and $sArticleTable.oxparentid = '' \";\n\n            //#6027\n            //if we have variants then depending on config option the parent may be non buyable\n            //when the checkbox is checked, blVariantParentBuyable is true.\n            $sQAdd .= $oConfig->getConfigParam('blVariantParentBuyable') ?  '' : \"and $sArticleTable.oxvarcount = 0\";\n        } else {\n            // selected category ?\n            if ($sSynchOxid && $sOxid != $sSynchOxid) {\n                $sQAdd = \" from $sO2CView left join $sArticleTable on \";\n                $sQAdd .= $oConfig->getConfigParam('blVariantsSelection') ? \"($sArticleTable.oxid=$sO2CView.oxobjectid or $sArticleTable.oxparentid=$sO2CView.oxobjectid)\" : \" $sArticleTable.oxid=$sO2CView.oxobjectid \";\n                $sQAdd .= \" where $sO2CView.oxcatnid = \" . $oDb->quote($sOxid) . \" and $sArticleTable.oxid is not null \";\n                //#6027\n                $sQAdd .= $oConfig->getConfigParam('blVariantParentBuyable') ?  '' : \" and $sArticleTable.oxvarcount = 0\";\n\n                // resetting\n                $sId = null;\n            } else {\n                $sQAdd = \" from $sDiscTable left join $sArticleTable on $sArticleTable.oxid=$sDiscTable.oxitmartid \";\n                $sQAdd .= \" where $sDiscTable.oxid = \" . $oDb->quote($sOxid) . \" and $sDiscTable.oxitmartid != '' \";\n            }\n        }\n\n        if ($sSynchOxid && $sSynchOxid != $sOxid) {\n            // performance\n            $sSubSelect = \" select $sArticleTable.oxid from $sDiscTable, $sArticleTable where $sArticleTable.oxid=$sDiscTable.oxitmartid \";\n            $sSubSelect .= \" and $sDiscTable.oxid = \" . $oDb->quote($sSynchOxid);\n\n            if (stristr($sQAdd, 'where') === false) {\n                $sQAdd .= ' where ';\n            } else {\n                $sQAdd .= ' and ';\n            }\n            $sQAdd .= \" $sArticleTable.oxid not in ( $sSubSelect ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes selected article (articles) from discount list\n     */\n    public function removeDiscArt()\n    {\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $aChosenArt = $this->getActionIds('oxdiscount.oxitmartid');\n        if (is_array($aChosenArt)) {\n            $sQ = \"update oxdiscount set oxitmartid = '' where oxid = :oxid and oxitmartid = :oxitmartid\";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->execute($sQ, [\n                'oxid' => $soxId,\n                'oxitmartid' => reset($aChosenArt)\n            ]);\n        }\n    }\n\n    /**\n     * Adds selected article (articles) to discount list\n     */\n    public function addDiscArt()\n    {\n        $aChosenArt = $this->getActionIds('oxarticles.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n        if ($soxId && $soxId != \"-1\" && is_array($aChosenArt)) {\n            $sQ = \"update oxdiscount set oxitmartid = :oxitmartid where oxid = :oxid\";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->execute($sQ, [\n                'oxitmartid' => reset($aChosenArt),\n                'oxid' => $soxId\n            ]);\n        }\n    }\n\n    /**\n     * Formats and returns chunk of SQL query string with definition of\n     * fields to load from DB. Adds subselect to get variant title from parent article\n     *\n     * @return string\n     */\n    protected function getQueryCols()\n    {\n        $queryForIdColumns = $this->getQueryForIdentifierColumns();\n\n        return sprintf(\n            \" %s%s%s \",\n            $this->getQueryForVisibleColumns(),\n            $queryForIdColumns ? ', ' : '',\n            $queryForIdColumns\n        );\n    }\n\n\n    private function getQueryForVisibleColumns(): string\n    {\n        $query = '';\n        $languageSuffix = $this->getLanguageSuffix();\n        $selectVariantsEnabled = Registry::getConfig()->getConfigParam('blVariantsSelection');\n        foreach ($this->getVisibleColNames() as $key => [$columnName, $tableName]) {\n            $view = $this->getViewName($tableName);\n            if ($selectVariantsEnabled && $columnName === 'oxtitle') {\n                $query .= sprintf(\n                    ' IF( %s.%s != \\'\\', %1$s.%2$s, CONCAT((select oxart.%2$s from %1$s as oxart where oxart.oxid = %1$s.oxparentid),\\', \\',%1$s.oxvarselect%s)) as _%s',\n                    $view,\n                    $columnName,\n                    $languageSuffix,\n                    $key\n                );\n            } else {\n                $query .= \"{$view}.{$columnName} as _{$key}\";\n            }\n            $query .= ', ';\n        }\n        return $query ? rtrim($query, ', ') : $query;\n    }\n\n    private function getQueryForIdentifierColumns(): string\n    {\n        $query = '';\n        foreach ($this->getIdentColNames() as $key => [$columnName, $tableName]) {\n            $view = $this->getViewName($tableName);\n            $query .= \"{$view}.{$columnName} as _{$key}\";\n            $query .= ', ';\n        }\n        return $query ? rtrim($query, ', ') : $query;\n    }\n\n    private function getLanguageSuffix(): string\n    {\n        return ContainerFacade::getParameter('oxid_esales.skip_database_views_usage')\n            ? Registry::getLang()->getLanguageTag()\n            : '';\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DiscountList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin discount list manager.\n * Collects delivery base information (description), there is ability to\n * filter them by description, title or delete them.\n * Admin Menu: Shop Settings -> Discounts.\n */\nclass DiscountList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'discount_list';\n\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxdiscount';\n\n    /**\n     * Type of list.\n     *\n     * @var string\n     */\n    protected $_sListType = 'oxdiscountlist';\n\n    /**\n     * Default SQL sorting parameter (default null).\n     *\n     * @var string\n     */\n    protected $_sDefSortField = 'oxsort';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DiscountMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Exception\\InputException;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse stdClass;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Admin article main discount manager.\n * Performs collection and updating (on user submit) main item information.\n * Admin Menu: Shop Settings -> Discounts -> Main.\n */\nclass DiscountMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $sOxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        if (isset($sOxId) && $sOxId != \"-1\") {\n            // load object\n            $oDiscount = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Discount::class);\n            $oDiscount->loadInLang($this->_iEditLang, $sOxId);\n\n            $oOtherLang = $oDiscount->getAvailableInLangs();\n            if (!isset($oOtherLang[$this->_iEditLang])) {\n                $oDiscount->loadInLang(key($oOtherLang), $sOxId);\n            }\n\n            $this->_aViewData[\"edit\"] = $oDiscount;\n\n            //disabling derived items\n            if ($oDiscount->isDerived()) {\n                $this->_aViewData[\"readonly\"] = true;\n            }\n\n            // remove already created languages\n            $aLang = array_diff(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->getLanguageNames(), $oOtherLang);\n\n            if (count($aLang)) {\n                $this->_aViewData[\"posslang\"] = $aLang;\n            }\n\n            foreach ($oOtherLang as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $oLang;\n            }\n        }\n\n        if (($iAoc = Registry::getRequest()->getRequestEscapedParameter(\"aoc\"))) {\n            if ($iAoc == \"1\") {\n                $oDiscountMainAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\DiscountMainAjax::class);\n                $this->_aViewData['oxajax'] = $oDiscountMainAjax->getColumns();\n\n                return \"popups/discount_main\";\n            } elseif ($iAoc == \"2\") {\n                // generating category tree for artikel choose select list\n                $this->createCategoryTree(\"artcattree\");\n\n                $oDiscountItemAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\DiscountItemAjax::class);\n                $this->_aViewData['oxajax'] = $oDiscountItemAjax->getColumns();\n\n                return \"popups/discount_item\";\n            }\n        }\n\n        return \"discount_main\";\n    }\n\n    /**\n     * Returns item discount product title\n     *\n     * @return string\n     */\n    public function getItemDiscountProductTitle()\n    {\n        $sTitle = false;\n        $sOxId = $this->getEditObjectId();\n        if (isset($sOxId) && $sOxId != \"-1\") {\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $sViewName = $tableViewNameGenerator->getViewName(\"oxarticles\", $this->_iEditLang);\n            // Reading from slave is ok here (see ESDEV-3804 and ESDEV-3822).\n            $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $sQ = \"select concat( $sViewName.oxartnum, ' ', $sViewName.oxtitle ) from oxdiscount\n                   left join $sViewName on $sViewName.oxid=oxdiscount.oxitmartid\n                   where oxdiscount.oxitmartid != '' and oxdiscount.oxid = :oxid\";\n            $sTitle = $database->getOne($sQ, [\n                'oxid' => $sOxId\n            ]);\n        }\n\n        return $sTitle ? $sTitle : \" -- \";\n    }\n\n    /**\n     * Saves changed selected discount parameters.\n     *\n     * @return mixed\n     */\n    public function save()\n    {\n        parent::save();\n\n        $sOxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        $oDiscount = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Discount::class);\n        if ($sOxId != \"-1\") {\n            $oDiscount->load($sOxId);\n        } else {\n            $aParams['oxdiscount__oxid'] = null;\n        }\n\n        // checkbox handling\n        if (!isset($aParams['oxdiscount__oxactive'])) {\n            $aParams['oxdiscount__oxactive'] = 0;\n        }\n\n        //disabling derived items\n        if ($oDiscount->isDerived()) {\n            return;\n        }\n\n        //$aParams = $oAttr->ConvertNameArray2Idx( $aParams);\n        $oDiscount->setLanguage(0);\n        $oDiscount->assign($aParams);\n        $oDiscount->setLanguage($this->_iEditLang);\n        $oDiscount = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsFile()->processFiles($oDiscount);\n        try {\n            $oDiscount->save();\n        } catch (InputException $exception) {\n            $newException = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay::class);\n            $newException->setMessage($exception->getMessage());\n            $this->addTplParam('discount_title', $aParams['oxdiscount__oxtitle']);\n\n            if (false !== strpos($exception->getMessage(), 'DISCOUNT_ERROR_OXSORT')) {\n                $messageArgument = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('DISCOUNT_MAIN_SORT', \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getTplLanguage(), true);\n                $newException->setMessageArgs($messageArgument);\n            }\n\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay($newException);\n\n            return;\n        }\n\n        // set oxid if inserted\n        $this->setEditObjectId($oDiscount->getId());\n    }\n\n    /**\n     * Saves changed selected discount parameters in different language.\n     *\n     * @return null\n     */\n    public function saveinnlang()\n    {\n        parent::save();\n\n        $sOxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        $oAttr = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Discount::class);\n        if ($sOxId != \"-1\") {\n            $oAttr->load($sOxId);\n        } else {\n            $aParams['oxdiscount__oxid'] = null;\n        }\n        // checkbox handling\n        if (!isset($aParams['oxdiscount__oxactive'])) {\n            $aParams['oxdiscount__oxactive'] = 0;\n        }\n\n        //disabling derived items\n        if ($oAttr->isDerived()) {\n            return;\n        }\n\n        //$aParams = $oAttr->ConvertNameArray2Idx( $aParams);\n        $oAttr->setLanguage(0);\n        $oAttr->assign($aParams);\n        $oAttr->setLanguage($this->_iEditLang);\n        $oAttr = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsFile()->processFiles($oAttr);\n        $oAttr->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oAttr->getId());\n    }\n\n    /**\n     * Increment the maximum value of oxsort found in the database by certain amount and return it.\n     *\n     * @return int The incremented oxsort.\n     */\n    public function getNextOxsort()\n    {\n        $shopId = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId();\n        $nextSort = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Discount::class)->getNextOxsort($shopId);\n\n        return $nextSort;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DiscountMainAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages discount countries\n */\nclass DiscountMainAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,         visible, multilanguage, ident\n        ['oxtitle', 'oxcountry', 1, 1, 0],\n        ['oxisoalpha2', 'oxcountry', 1, 0, 0],\n        ['oxisoalpha3', 'oxcountry', 0, 0, 0],\n        ['oxunnum3', 'oxcountry', 0, 0, 0],\n        ['oxid', 'oxcountry', 0, 0, 1]\n    ],\n                                 'container2' => [\n                                     ['oxtitle', 'oxcountry', 1, 1, 0],\n                                     ['oxisoalpha2', 'oxcountry', 1, 0, 0],\n                                     ['oxisoalpha3', 'oxcountry', 0, 0, 0],\n                                     ['oxunnum3', 'oxcountry', 0, 0, 0],\n                                     ['oxid', 'oxobject2discount', 0, 0, 1]\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $oConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $sCountryTable = $this->getViewName('oxcountry');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sId) {\n            $sQAdd = \" from $sCountryTable where $sCountryTable.oxactive = '1' \";\n        } else {\n            $sQAdd = \" from oxobject2discount, $sCountryTable where $sCountryTable.oxid=oxobject2discount.oxobjectid \";\n            $sQAdd .= \"and oxobject2discount.oxdiscountid = \" . $oDb->quote($sId) . \" and oxobject2discount.oxtype = 'oxcountry' \";\n        }\n\n        if ($sSynchId && $sSynchId != $sId) {\n            $sQAdd .= \"and $sCountryTable.oxid not in ( select $sCountryTable.oxid from oxobject2discount, $sCountryTable where $sCountryTable.oxid=oxobject2discount.oxobjectid \";\n            $sQAdd .= \"and oxobject2discount.oxdiscountid = \" . $oDb->quote($sSynchId) . \" and oxobject2discount.oxtype = 'oxcountry' ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes chosen user group (groups) from delivery list\n     */\n    public function removeDiscCountry()\n    {\n        $aChosenCntr = $this->getActionIds('oxobject2discount.oxid');\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxobject2discount.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif (is_array($aChosenCntr)) {\n            $sQ = \"delete from oxobject2discount where oxobject2discount.oxid in (\" . implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aChosenCntr)) . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adds chosen user group (groups) to delivery list\n     */\n    public function addDiscCountry()\n    {\n        $aChosenCntr = $this->getActionIds('oxcountry.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sCountryTable = $this->getViewName('oxcountry');\n            $aChosenCntr = $this->getAll($this->addFilter(\"select $sCountryTable.oxid \" . $this->getQuery()));\n        }\n        if ($soxId && $soxId != \"-1\" && is_array($aChosenCntr)) {\n            foreach ($aChosenCntr as $sChosenCntr) {\n                $oObject2Discount = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oObject2Discount->init('oxobject2discount');\n                $oObject2Discount->oxobject2discount__oxdiscountid = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                $oObject2Discount->oxobject2discount__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sChosenCntr);\n                $oObject2Discount->oxobject2discount__oxtype = new \\OxidEsales\\Eshop\\Core\\Field(\"oxcountry\");\n                $oObject2Discount->save();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DiscountUsers.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse stdClass;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Admin article main discount manager.\n * There is possibility to change discount name, article, user\n * and etc.\n * Admin Menu: Shop settings -> Shipping & Handling -> Main.\n */\nclass DiscountUsers extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->getEditObjectId();\n\n        // all usergroups\n        $oGroups = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n        $oGroups->init('oxgroups');\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $oGroups->selectString(\"select * from \" . $tableViewNameGenerator->getViewName(\"oxgroups\", $this->_iEditLang));\n\n        $oRoot = new stdClass();\n        $oRoot->oxgroups__oxid = new \\OxidEsales\\Eshop\\Core\\Field(\"\");\n        $oRoot->oxgroups__oxtitle = new \\OxidEsales\\Eshop\\Core\\Field(\"-- \");\n        // rebuild list as we need the \"no value\" entry at the first position\n        $aNewList = [];\n        $aNewList[] = $oRoot;\n\n        foreach ($oGroups as $val) {\n            $aNewList[$val->oxgroups__oxid->value] = new stdClass();\n            $aNewList[$val->oxgroups__oxid->value]->oxgroups__oxid = new \\OxidEsales\\Eshop\\Core\\Field($val->oxgroups__oxid->value);\n            $aNewList[$val->oxgroups__oxid->value]->oxgroups__oxtitle = new \\OxidEsales\\Eshop\\Core\\Field($val->oxgroups__oxtitle->value);\n        }\n\n        $this->_aViewData[\"allgroups2\"] = $aNewList;\n\n        if (isset($soxId) && $soxId != \"-1\") {\n            $oDiscount = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Discount::class);\n            $oDiscount->load($soxId);\n\n            if ($oDiscount->isDerived()) {\n                $this->_aViewData[\"readonly\"] = true;\n            }\n        }\n\n        $iAoc = Registry::getRequest()->getRequestEscapedParameter(\"aoc\");\n        if ($iAoc == 1) {\n            $oDiscountGroupsAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\DiscountGroupsAjax::class);\n            $this->_aViewData['oxajax'] = $oDiscountGroupsAjax->getColumns();\n\n            return \"popups/discount_groups\";\n        } elseif ($iAoc == 2) {\n            $oDiscountUsersAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\DiscountUsersAjax::class);\n            $this->_aViewData['oxajax'] = $oDiscountUsersAjax->getColumns();\n\n            return \"popups/discount_users\";\n        }\n\n        return \"discount_users\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DiscountUsersAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages discount users\n */\nclass DiscountUsersAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,  visible, multilanguage, ident\n        ['oxusername', 'oxuser', 1, 0, 0],\n        ['oxlname', 'oxuser', 0, 0, 0],\n        ['oxfname', 'oxuser', 0, 0, 0],\n        ['oxstreet', 'oxuser', 0, 0, 0],\n        ['oxstreetnr', 'oxuser', 0, 0, 0],\n        ['oxcity', 'oxuser', 0, 0, 0],\n        ['oxzip', 'oxuser', 0, 0, 0],\n        ['oxfon', 'oxuser', 0, 0, 0],\n        ['oxbirthdate', 'oxuser', 0, 0, 0],\n        ['oxid', 'oxuser', 0, 0, 1],\n    ],\n                                 'container2' => [\n                                     ['oxusername', 'oxuser', 1, 0, 0],\n                                     ['oxlname', 'oxuser', 0, 0, 0],\n                                     ['oxfname', 'oxuser', 0, 0, 0],\n                                     ['oxstreet', 'oxuser', 0, 0, 0],\n                                     ['oxstreetnr', 'oxuser', 0, 0, 0],\n                                     ['oxcity', 'oxuser', 0, 0, 0],\n                                     ['oxzip', 'oxuser', 0, 0, 0],\n                                     ['oxfon', 'oxuser', 0, 0, 0],\n                                     ['oxbirthdate', 'oxuser', 0, 0, 0],\n                                     ['oxid', 'oxobject2discount', 0, 0, 1],\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $oConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $sUserTable = $this->getViewName('oxuser');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sId) {\n            $sQAdd = \" from $sUserTable where 1 \";\n            if (!$oConfig->getConfigParam('blMallUsers')) {\n                $sQAdd .= \" and oxshopid = '\" . $oConfig->getShopId() . \"' \";\n            }\n        } else {\n            // selected group ?\n            if ($sSynchId && $sSynchId != $sId) {\n                $sQAdd = \" from oxobject2group left join $sUserTable on $sUserTable.oxid = oxobject2group.oxobjectid where oxobject2group.oxgroupsid = \" . $oDb->quote($sId);\n                if (!$oConfig->getConfigParam('blMallUsers')) {\n                    $sQAdd .= \" and $sUserTable.oxshopid = '\" . $oConfig->getShopId() . \"' \";\n                }\n            } else {\n                $sQAdd = \" from oxobject2discount, $sUserTable where $sUserTable.oxid=oxobject2discount.oxobjectid \";\n                $sQAdd .= \" and oxobject2discount.oxdiscountid = \" . $oDb->quote($sId) . \" and oxobject2discount.oxtype = 'oxuser' \";\n            }\n        }\n\n        if ($sSynchId && $sSynchId != $sId) {\n            $sQAdd .= \" and $sUserTable.oxid not in ( select $sUserTable.oxid from oxobject2discount, $sUserTable where $sUserTable.oxid=oxobject2discount.oxobjectid \";\n            $sQAdd .= \" and oxobject2discount.oxdiscountid = \" . $oDb->quote($sSynchId) . \" and oxobject2discount.oxtype = 'oxuser' ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes user from discount config\n     */\n    public function removeDiscUser()\n    {\n        $aRemoveGroups = $this->getActionIds('oxobject2discount.oxid');\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxobject2discount.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif ($aRemoveGroups && is_array($aRemoveGroups)) {\n            $sQ = \"delete from oxobject2discount where oxobject2discount.oxid in (\" . implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aRemoveGroups)) . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adds user to discount config\n     */\n    public function addDiscUser()\n    {\n        $oConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $aChosenUsr = $this->getActionIds('oxuser.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sUserTable = $this->getViewName('oxuser');\n            $aChosenUsr = $this->getAll($this->addFilter(\"select $sUserTable.oxid \" . $this->getQuery()));\n        }\n        if ($soxId && $soxId != \"-1\" && is_array($aChosenUsr)) {\n            foreach ($aChosenUsr as $sChosenUsr) {\n                $oObject2Discount = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oObject2Discount->init('oxobject2discount');\n                $oObject2Discount->oxobject2discount__oxdiscountid = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                $oObject2Discount->oxobject2discount__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sChosenUsr);\n                $oObject2Discount->oxobject2discount__oxtype = new \\OxidEsales\\Eshop\\Core\\Field(\"oxuser\");\n                $oObject2Discount->save();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/DynamicExportBaseController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse stdClass;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse Symfony\\Component\\Filesystem\\Path;\n\n/**\n * Error constants\n */\nDEFINE(\"ERR_SUCCESS\", -2);\nDEFINE(\"ERR_GENERAL\", -1);\nDEFINE(\"ERR_FILEIO\", 1);\n\n/**\n * DynExportBase framework class encapsulating a method for defining implementation class.\n * Performs export function according to user chosen categories.\n *\n * @subpackage dyn\n */\nclass DynamicExportBaseController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Export class name\n     *\n     * @var string\n     */\n    public $sClassDo = \"\";\n\n    /**\n     * Export ui class name\n     *\n     * @var string\n     */\n    public $sClassMain = \"\";\n\n    /**\n     * Export output folder\n     *\n     * @var string\n     */\n    public $sExportPath = \"export/\";\n\n    /**\n     * Export file extension\n     *\n     * @var string\n     */\n    public $sExportFileType = \"txt\";\n\n    /**\n     * Export file name\n     *\n     * @var string\n     */\n    public $sExportFileName = \"dynexport\";\n\n    /**\n     * Export file resource\n     *\n     * @var object\n     */\n    public $fpFile = null;\n\n    /**\n     * Default number of records to export per tick\n     * Used if not set in config\n     *\n     * @var int\n     */\n    public $iExportPerTick = 30;\n\n    /**\n     * Number of records to export per tick\n     *\n     * @var int\n     */\n    protected $_iExportPerTick = null;\n\n    /**\n     * Full export file path\n     *\n     * @var string\n     */\n    protected $_sFilePath = null;\n\n    /**\n     * Export result set\n     *\n     * @var array\n     */\n    protected $_aExportResultset = [];\n\n    /**\n     * View template name\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = \"dynexportbase\";\n\n    /**\n     * Category data cache\n     *\n     * @var array\n     */\n    protected $_aCatLvlCache = null;\n\n    /**\n     * Calls parent costructor and initializes $this->_sFilePath parameter\n     */\n    public function __construct()\n    {\n        parent::__construct();\n\n        // set generic frame template\n        $this->_sFilePath = Path::join(\n            ContainerFacade::getParameter('oxid_esales.shop_source_directory'),\n            $this->sExportPath,\n            \"$this->sExportFileName.$this->sExportFileType\"\n        );\n    }\n\n    /**\n     * Calls parent rendering methods, sends implementation class names to template\n     * and returns default template name\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        // assign all member variables to template\n        $aClassVars = get_object_vars($this);\n        foreach ($aClassVars as $name => $value) {\n            $this->_aViewData[$name] = $value;\n        }\n\n        $this->_aViewData['sOutputFile'] = $this->_sFilePath;\n        $this->_aViewData['sDownloadFile'] =\n            Path::join(\n                ContainerFacade::getParameter('oxid_esales.shop_url'),\n                $this->sExportPath,\n                \"$this->sExportFileName.$this->sExportFileType\"\n            );\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Prepares and fill all data which all the dyn exports needs\n     */\n    public function createMainExportView()\n    {\n        // parent categorie tree\n        $this->_aViewData[\"cattree\"] = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\CategoryList::class);\n        $this->_aViewData[\"cattree\"]->loadList();\n\n        $oLangObj = oxNew(\\OxidEsales\\Eshop\\Core\\Language::class);\n        $aLangs = $oLangObj->getLanguageArray();\n        foreach ($aLangs as $id => $language) {\n            $language->selected = ($id == $this->_iEditLang);\n            $this->_aViewData[\"aLangs\"][$id] = clone $language;\n        }\n    }\n\n    /**\n     * Prepares Export\n     */\n    public function start()\n    {\n        // delete file, if its already there\n        $this->fpFile = @fopen($this->_sFilePath, \"w\");\n        if (!isset($this->fpFile) || !$this->fpFile) {\n            // we do have an error !\n            $this->stop(ERR_FILEIO);\n        } else {\n            $this->_aViewData['refresh'] = 0;\n            $this->_aViewData['iStart'] = 0;\n            fclose($this->fpFile);\n\n            // prepare it\n            $iEnd = $this->prepareExport();\n            Registry::getSession()->setVariable(\"iEnd\", $iEnd);\n            $this->_aViewData['iEnd'] = $iEnd;\n        }\n    }\n\n    /**\n     * Stops Export\n     *\n     * @param integer $iError error number\n     */\n    public function stop($iError = 0)\n    {\n        if ($iError) {\n            $this->_aViewData['iError'] = $iError;\n        }\n\n        // delete temporary heap table\n        \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->execute(\"drop TABLE if exists \" . $this->getHeapTableName());\n    }\n\n    /**\n     * virtual function must be overloaded\n     *\n     * @param integer $cnt counter\n     *\n     * @return bool\n     */\n    public function nextTick($cnt)\n    {\n        return false;\n    }\n\n    /**\n     * writes one line into open export file\n     *\n     * @param string $sLine exported line\n     */\n    public function write($sLine)\n    {\n        $sLine = $this->removeSID($sLine);\n        $sLine = str_replace([\"\\r\\n\", \"\\n\"], \"\", $sLine);\n        fwrite($this->fpFile, $sLine . \"\\r\\n\");\n    }\n\n    /**\n     * Does Export\n     */\n    public function run()\n    {\n        $blContinue = true;\n        $iExportedItems = 0;\n\n        $this->fpFile = @fopen($this->_sFilePath, \"a\");\n        if (!isset($this->fpFile) || !$this->fpFile) {\n            // we do have an error !\n            $this->stop(ERR_FILEIO);\n        } else {\n            // file is open\n            $iStart = Registry::getRequest()->getRequestEscapedParameter(\"iStart\");\n            // load from session\n            $this->_aExportResultset = Registry::getRequest()->getRequestEscapedParameter(\"aExportResultset\");\n            $iExportPerTick = $this->getExportPerTick();\n            for ($i = $iStart; $i < $iStart + $iExportPerTick; $i++) {\n                if (($iExportedItems = $this->nextTick($i)) === false) {\n                    // end reached\n                    $this->stop(ERR_SUCCESS);\n                    $blContinue = false;\n                    break;\n                }\n            }\n            if ($blContinue) {\n                // make ticker continue\n                $this->_aViewData['refresh'] = 0;\n                $this->_aViewData['iStart'] = $i;\n                $this->_aViewData['iExpItems'] = $iExportedItems;\n            }\n            fclose($this->fpFile);\n        }\n    }\n\n    /**\n     * Returns how many articles should be exported per tick\n     *\n     * @return int\n     */\n    public function getExportPerTick()\n    {\n        if ($this->_iExportPerTick === null) {\n            $this->_iExportPerTick = (int) Registry::getConfig()->getConfigParam(\"iExportNrofLines\");\n            if (!$this->_iExportPerTick) {\n                $this->_iExportPerTick = $this->iExportPerTick;\n            }\n        }\n\n        return $this->_iExportPerTick;\n    }\n\n    /**\n     * Sets how many articles should be exported per tick\n     *\n     * @param int $iCount articles count per tick\n     */\n    public function setExportPerTick($iCount)\n    {\n        $this->_iExportPerTick = $iCount;\n    }\n\n    /**\n     * Removes Session ID from $sInput\n     *\n     * @param string $sInput Input to process\n     *\n     * @return null\n     */\n    public function removeSid($sInput)\n    {\n        $session = Registry::getSession();\n        $sSid = $session->getId();\n\n        // remove sid from link\n        $sOutput = str_replace(\"sid={$sSid}/\", \"\", $sInput);\n        $sOutput = str_replace(\"sid/{$sSid}/\", \"\", $sOutput);\n        $sOutput = str_replace(\"sid={$sSid}&amp;\", \"\", $sOutput);\n        $sOutput = str_replace(\"sid={$sSid}&\", \"\", $sOutput);\n        $sOutput = str_replace(\"sid={$sSid}\", \"\", $sOutput);\n\n        return $sOutput;\n    }\n\n    /**\n     * Removes tags, shortens a string to $iMaxSize adding \"...\"\n     *\n     * @param string  $sInput          input to process\n     * @param integer $iMaxSize        maximum output size\n     * @param bool    $blRemoveNewline if true - \\n and \\r will be replaced by \" \"\n     *\n     * @return string\n     */\n    public function shrink($sInput, $iMaxSize, $blRemoveNewline = true)\n    {\n        if ($blRemoveNewline) {\n            $sInput = str_replace(\"\\r\\n\", \" \", $sInput);\n            $sInput = str_replace(\"\\n\", \" \", $sInput);\n        }\n\n        $sInput = str_replace(\"\\t\", \"    \", $sInput);\n\n        // remove html entities, remove html tags\n        $sInput = $this->unHtmlEntities(strip_tags($sInput));\n\n        $oStr = Str::getStr();\n        if ($oStr->strlen($sInput) > $iMaxSize - 3) {\n            $sInput = $oStr->substr($sInput, 0, $iMaxSize - 5) . \"...\";\n        }\n\n        return $sInput;\n    }\n\n    /**\n     * Loads all article parent categories and returns titles separated by $$separator\n     */\n    public function getCategoryString($article, $separator = \"/\")\n    {\n        $categoryTitles = '';\n\n        $sLang = Registry::getLang()->getBaseLanguage();\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $categoryViewName = $tableViewNameGenerator->getViewName('oxcategories', $sLang);\n        $objectToCategoryViewName = $tableViewNameGenerator->getViewName('oxobject2category', $sLang);\n\n        $query = sprintf(\n            'select c.oxleft as oxleft, c.oxright as oxright, c.oxrootid as oxrootid from %s as o2c left join '\n            . '%s as c on c.oxid = o2c.oxcatnid where o2c.oxobjectid = :oxobjectid and c.oxactive = 1 '\n            . 'order by o2c.oxtime',\n            $objectToCategoryViewName,\n            $categoryViewName\n        );\n\n        $categories = $database->select($query, [\n            'oxobjectid' => $article->getId()\n        ]);\n        if ($categories != false && $categories->count() > 0) {\n            $left = $categories->fields['oxleft'];\n            $right = $categories->fields['oxright'];\n            $rootId = $categories->fields['oxrootid'];\n\n            $query = sprintf('select oxtitle from %s where oxright >= :oxright and oxleft <= :oxleft and' .\n                ' oxrootid = :oxrootid order by oxleft', $categoryViewName);\n\n            $titles = $database->getCol(\n                $query,\n                [\n                    'oxright' => $right,\n                    'oxleft' => $left,\n                    'oxrootid' => $rootId\n                ]\n            );\n            foreach ($titles as $title) {\n                if ($categoryTitles) {\n                    $categoryTitles .= $separator;\n                }\n                $categoryTitles .= $title;\n            }\n        }\n\n        return $categoryTitles;\n    }\n\n    /**\n     * Loads article default category\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle Article object\n     */\n    public function getDefaultCategoryString($oArticle)\n    {\n        $sLang = Registry::getLang()->getBaseLanguage();\n        $oDB = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sCatView = $tableViewNameGenerator->getViewName('oxcategories', $sLang);\n        $sO2CView = $tableViewNameGenerator->getViewName('oxobject2category', $sLang);\n\n        //selecting category\n        $sQ = \"select $sCatView.oxtitle from $sO2CView as oxobject2category left join $sCatView on\"\n            . \" $sCatView.oxid = oxobject2category.oxcatnid where oxobject2category.oxobjectid = :oxobjectid\"\n            . \" and $sCatView.oxactive = 1 order by oxobject2category.oxtime \";\n\n        return $oDB->getOne($sQ, [\n            'oxobjectid' => $oArticle->getId()\n        ]);\n    }\n\n    /**\n     * Converts field for CSV\n     *\n     * @param string $sInput input to process\n     *\n     * @return string\n     */\n    public function prepareCSV($sInput)\n    {\n        $sInput = Registry::getUtilsString()->prepareCSVField($sInput);\n\n        return str_replace([\"&nbsp;\", \"&euro;\", \"|\"], [\" \", \"\", \"\"], $sInput);\n    }\n\n    /**\n     * Changes special chars to be XML compatible\n     *\n     * @param string $sInput string which have to be changed\n     *\n     * @return string\n     */\n    public function prepareXML($sInput)\n    {\n        $sOutput = str_replace(\"&\", \"&amp;\", $sInput);\n        $sOutput = str_replace(\"\\\"\", \"&quot;\", $sOutput);\n        $sOutput = str_replace(\">\", \"&gt;\", $sOutput);\n        $sOutput = str_replace(\"<\", \"&lt;\", $sOutput);\n        $sOutput = str_replace(\"'\", \"&apos;\", $sOutput);\n\n        return $sOutput;\n    }\n\n    /**\n     * Searches for deepest path to a categorie this article is assigned to\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle article object\n     *\n     * @return string\n     */\n    public function getDeepestCategoryPath($oArticle)\n    {\n        return $this->findDeepestCatPath($oArticle);\n    }\n\n    /**\n     * create export resultset\n     *\n     * @return int\n     */\n    public function prepareExport()\n    {\n        $oDB = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sHeapTable = $this->getHeapTableName();\n\n        // #1070 Saulius 2005.11.28\n        // check mySQL version\n        $oRs = $oDB->select('SELECT version() as version');\n        $sTableCharset = $this->generateTableCharSet($oRs->fields['version']);\n\n        // create heap table\n        if (!($this->createHeapTable($sHeapTable, $sTableCharset))) {\n            // error\n            Registry::getUtils()->showMessageAndExit(\"Could not create HEAP Table {$sHeapTable}\\n<br>\");\n        }\n\n        $sCatAdd = $this->getCatAdd(Registry::getRequest()->getRequestEscapedParameter(\"acat\"));\n        if (!$this->insertArticles($sHeapTable, $sCatAdd)) {\n            Registry::getUtils()->showMessageAndExit(\"Could not insert Articles in Table {$sHeapTable}\\n<br>\");\n        }\n\n        $this->removeParentArticles($sHeapTable);\n        $this->setSessionParams();\n\n        // get total cnt\n        return $oDB->getOne(\"select count(*) from {$sHeapTable}\");\n    }\n\n    /**\n     * get's one oxid for exporting\n     *\n     * @param integer $iCnt       counter\n     * @param bool    $blContinue false is used to stop exporting\n     *\n     * @return mixed\n     */\n    public function getOneArticle($iCnt, &$blContinue)\n    {\n        $myConfig = Registry::getConfig();\n\n        //[Alfonsas 2006-05-31] setting specific parameter\n        //to be checked in oxarticle.php init() method\n        $myConfig->setConfigParam('blExport', true);\n        $blContinue = false;\n\n        if (($oArticle = $this->initArticle($this->getHeapTableName(), $iCnt, $blContinue))) {\n            $blContinue = true;\n            $oArticle = $this->setCampaignDetailLink($oArticle);\n        }\n\n        //[Alfonsas 2006-05-31] unsetting specific parameter\n        //to be checked in oxarticle.php init() method\n        $myConfig->setConfigParam('blExport', false);\n\n        return $oArticle;\n    }\n\n    /**\n     * Make sure that string is never empty.\n     *\n     * @param string $sInput   string that will be replaced\n     * @param string $sReplace string that will replace\n     *\n     * @return string\n     */\n    public function assureContent($sInput, $sReplace = null)\n    {\n        $oStr = Str::getStr();\n        if (!$oStr->strlen($sInput)) {\n            if (!isset($sReplace) || !$oStr->strlen($sReplace)) {\n                $sReplace = \"-\";\n            }\n            $sInput = $sReplace;\n        }\n\n        return $sInput;\n    }\n\n    /**\n     * Replace HTML Entities\n     * Replacement for html_entity_decode which is only available from PHP 4.3.0 onj\n     *\n     * @param string $sInput string to replace\n     *\n     * @return string\n     */\n    protected function unHtmlEntities($sInput)\n    {\n        $aTransTbl = array_flip(get_html_translation_table(HTML_ENTITIES));\n\n        return strtr($sInput, $aTransTbl);\n    }\n\n    /**\n     * Create valid Heap table name\n     *\n     * @return string\n     */\n    protected function getHeapTableName()\n    {\n        // table name must not start with any digit\n        $session = Registry::getSession();\n        return \"tmp_\" . str_replace(\"0\", \"\", md5($session->getId()));\n    }\n\n    /**\n     * generates table charset\n     *\n     * @param string $sMysqlVersion MySql version\n     *\n     * @return string\n     */\n    protected function generateTableCharSet($sMysqlVersion)\n    {\n        $sTableCharset = \"\";\n\n        //if MySQL >= 4.1.0 set charsets and collations\n        if (version_compare($sMysqlVersion, '4.1.0', '>=') > 0) {\n            $oDB = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $oRs = $oDB->select(\"SHOW FULL COLUMNS FROM `oxarticles` WHERE field like 'OXID'\");\n            if (isset($oRs->fields['Collation']) && ($sMysqlCollation = $oRs->fields['Collation'])) {\n                $oRs = $oDB->select(\"SHOW COLLATION LIKE '{$sMysqlCollation}'\");\n                if (isset($oRs->fields['Charset']) && ($sMysqlCharacterSet = $oRs->fields['Charset'])) {\n                    $sTableCharset = \"DEFAULT CHARACTER SET {$sMysqlCharacterSet} COLLATE {$sMysqlCollation}\";\n                }\n            }\n        }\n\n        return $sTableCharset;\n    }\n\n    /**\n     * creates heaptable\n     *\n     * @param string $sHeapTable    table name\n     * @param string $sTableCharset table charset\n     *\n     * @return bool\n     */\n    protected function createHeapTable($sHeapTable, $sTableCharset)\n    {\n        $blDone = false;\n        $oDB = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sQ = sprintf(\n            \"CREATE TABLE IF NOT EXISTS %s ( `oxid` CHAR(32) NOT NULL default '' ) ENGINE=InnoDB %s\",\n            $sHeapTable,\n            $sTableCharset\n        );\n        if (($oDB->execute($sQ)) !== false) {\n            $blDone = true;\n            $oDB->execute(\"TRUNCATE TABLE {$sHeapTable}\");\n        }\n\n        return $blDone;\n    }\n\n    /**\n     * creates additional cat string\n     *\n     * @param array $aChosenCat Selected category array\n     *\n     * @return string\n     */\n    protected function getCatAdd($aChosenCat)\n    {\n        $sCatAdd = null;\n        if (is_array($aChosenCat) && count($aChosenCat)) {\n            $oDB = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $sCatAdd = \" and ( \";\n            $blSep = false;\n            foreach ($aChosenCat as $sCat) {\n                if ($blSep) {\n                    $sCatAdd .= \" or \";\n                }\n                $sCatAdd .= \"oxobject2category.oxcatnid = \" . $oDB->quote($sCat);\n                $blSep = true;\n            }\n            $sCatAdd .= \")\";\n        }\n\n        return $sCatAdd;\n    }\n\n    /**\n     * inserts articles into heaptable\n     *\n     * @param string $sHeapTable heap table name\n     * @param string $sCatAdd    category id filter (part of sql)\n     *\n     * @return bool\n     */\n    protected function insertArticles($sHeapTable, $sCatAdd)\n    {\n        $oDB = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $iExpLang = Registry::getRequest()->getRequestEscapedParameter(\"iExportLanguage\");\n        if (!isset($iExpLang)) {\n            $iExpLang = Registry::getSession()->getVariable(\"iExportLanguage\");\n        }\n\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $oArticle->setLanguage($iExpLang);\n\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sO2CView = $tableViewNameGenerator->getViewName('oxobject2category', $iExpLang);\n        $sArticleTable = $tableViewNameGenerator->getViewName(\"oxarticles\", $iExpLang);\n\n        $insertQuery = \"insert into {$sHeapTable} select {$sArticleTable}.oxid from {$sArticleTable}, {$sO2CView}\"\n            . \" as oxobject2category where \";\n        $insertQuery .= $oArticle->getSqlActiveSnippet();\n\n        if (!Registry::getRequest()->getRequestEscapedParameter(\"blExportVars\")) {\n            $insertQuery .= \" and {$sArticleTable}.oxid = oxobject2category.oxobjectid and\"\n                . \" {$sArticleTable}.oxparentid = '' \";\n        } else {\n            $insertQuery .= \" and ( {$sArticleTable}.oxid = oxobject2category.oxobjectid or {$sArticleTable}.oxparentid\"\n                . \" = oxobject2category.oxobjectid ) \";\n        }\n\n        $sSearchString = Registry::getRequest()->getRequestEscapedParameter(\"search\");\n        if (isset($sSearchString)) {\n            $insertQuery .= \"and ( {$sArticleTable}.OXTITLE like \" . $oDB->quote(\"%{$sSearchString}%\");\n            $insertQuery .= \" or {$sArticleTable}.OXSHORTDESC like \" . $oDB->quote(\"%$sSearchString%\");\n            $insertQuery .= \" or {$sArticleTable}.oxsearchkeys like \" . $oDB->quote(\"%$sSearchString%\") . \" ) \";\n        }\n\n        if ($sCatAdd) {\n            $insertQuery .= $sCatAdd;\n        }\n\n        // add minimum stock value\n        if (\n            Registry::getConfig()->getConfigParam('blUseStock')\n            && ($dMinStock = Registry::getRequest()->getRequestEscapedParameter(\"sExportMinStock\"))\n        ) {\n            $dMinStock = str_replace([\";\", \" \", \"/\", \"'\"], \"\", $dMinStock);\n            $insertQuery .= \" and {$sArticleTable}.oxstock >= \" . $oDB->quote($dMinStock);\n        }\n\n        $insertQuery .= \" group by {$sArticleTable}.oxid\";\n\n        return $oDB->execute($insertQuery) ? true : false;\n    }\n\n    /**\n     * removes parent articles so that we only have variants itself\n     *\n     * @param string $sHeapTable table name\n     */\n    protected function removeParentArticles($heapTable)\n    {\n        if (!(Registry::getRequest()->getRequestEscapedParameter(\"blExportMainVars\"))) {\n            $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $articleTable = $tableViewNameGenerator->getViewName('oxarticles');\n\n            $query = sprintf(\n                'select h.oxid from %s as h, %s as a where h.oxid = a.oxparentid group by h.oxid',\n                $heapTable,\n                $articleTable\n            );\n\n            $parentsArticle = $database->getCol($query);\n            $deleteQuery = \"delete from $heapTable where oxid in ( \";\n            $separator = false;\n            foreach ($parentsArticle as $parentArticle) {\n                if ($separator) {\n                    $deleteQuery .= \",\";\n                }\n                $deleteQuery .= $database->quote($parentArticle);\n                $separator = true;\n            }\n            $deleteQuery .= \" )\";\n            $database->execute($deleteQuery);\n        }\n    }\n\n    /**\n     * stores some info in session\n     */\n    protected function setSessionParams()\n    {\n        // reset it from session\n        Registry::getSession()->deleteVariable(\"sExportDelCost\");\n        $dDelCost = Registry::getRequest()->getRequestEscapedParameter(\"sExportDelCost\");\n        if (isset($dDelCost)) {\n            $dDelCost = str_replace([\";\", \" \", \"/\", \"'\"], \"\", $dDelCost);\n            $dDelCost = str_replace(\",\", \".\", $dDelCost);\n            Registry::getSession()->setVariable(\"sExportDelCost\", $dDelCost);\n        }\n\n        Registry::getSession()->deleteVariable(\"sExportMinPrice\");\n        $dMinPrice = Registry::getRequest()->getRequestEscapedParameter(\"sExportMinPrice\");\n        if (isset($dMinPrice)) {\n            $dMinPrice = str_replace([\";\", \" \", \"/\", \"'\"], \"\", $dMinPrice);\n            $dMinPrice = str_replace(\",\", \".\", $dMinPrice);\n            Registry::getSession()->setVariable(\"sExportMinPrice\", $dMinPrice);\n        }\n\n        // #827\n        Registry::getSession()->deleteVariable(\"sExportCampaign\");\n        $sCampaign = Registry::getRequest()->getRequestEscapedParameter(\"sExportCampaign\");\n        if (isset($sCampaign)) {\n            $sCampaign = str_replace([\";\", \" \", \"/\", \"'\"], \"\", $sCampaign);\n            Registry::getSession()->setVariable(\"sExportCampaign\", $sCampaign);\n        }\n\n        // reset it from session\n        Registry::getSession()->deleteVariable(\"blAppendCatToCampaign\");\n        // now retrieve it from get or post.\n        $blAppendCatToCampaign = Registry::getRequest()->getRequestEscapedParameter(\"blAppendCatToCampaign\");\n        if ($blAppendCatToCampaign) {\n            Registry::getSession()->setVariable(\"blAppendCatToCampaign\", $blAppendCatToCampaign);\n        }\n\n        // reset it from session\n        Registry::getSession()->deleteVariable(\"iExportLanguage\");\n        Registry::getSession()->setVariable(\n            \"iExportLanguage\",\n            Registry::getRequest()->getRequestEscapedParameter(\"iExportLanguage\")\n        );\n\n        //setting the custom header\n        Registry::getSession()->setVariable(\n            \"sExportCustomHeader\",\n            Registry::getRequest()->getRequestEscapedParameter(\"sExportCustomHeader\")\n        );\n    }\n\n    /**\n     * Load all root cat's == all trees\n     *\n     * @return null\n     */\n    protected function loadRootCats()\n    {\n        if ($this->_aCatLvlCache === null) {\n            $this->_aCatLvlCache = [];\n\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $categoryView = $tableViewNameGenerator->getViewName('oxcategories');\n            $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n            // Load all root categories\n            $rootCategoriesId = $database->getCol(\n                sprintf(\"select oxid from %s where oxparentid = 'oxrootid'\", $categoryView)\n            );\n            foreach ($rootCategoriesId as $rootCategoryId) {\n                $treeQuery = sprintf(\n                    \"SELECT s.oxid as oxid, s.oxtitle as oxtitle, s.oxparentid as oxparentid, count( * ) AS\"\n                    . \" LEVEL FROM %s v, %s s WHERE s.oxrootid = :oxrootid and v.oxrootid = :oxrootid and\"\n                    . \" s.oxleft BETWEEN v.oxleft AND v.oxright AND s.oxhidden = '0' GROUP BY s.oxleft order by level\",\n                    $categoryView,\n                    $categoryView\n                );\n\n                $tree = $database->select(\n                    $treeQuery,\n                    [\n                        'oxrootid' => $rootCategoryId\n                    ]\n                );\n                if ($tree != false && $tree->count() > 0) {\n                    while (!$tree->EOF) {\n                        $category = new stdClass();\n                        $category->_sOXID = $tree->fields['oxid'];\n                        $category->oxtitle = $tree->fields['oxtitle'];\n                        $category->oxparentid = $tree->fields['oxparentid'];\n                        $category->ilevel = $tree->fields['LEVEL'];\n                        $this->_aCatLvlCache[$category->_sOXID] = $category;\n\n                        $tree->fetchRow();\n                    }\n                }\n            }\n        }\n\n        return $this->_aCatLvlCache;\n    }\n\n    /**\n     * finds deepest category path\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle article object\n     *\n     * @return string\n     */\n    protected function findDeepestCatPath($oArticle)\n    {\n        $sRet = \"\";\n\n        // find deepest\n        $aIds = $oArticle->getCategoryIds();\n        if (is_array($aIds) && count($aIds)) {\n            if ($aCatLvlCache = $this->loadRootCats()) {\n                $sIdMax = null;\n                $dMaxLvl = 0;\n                foreach ($aIds as $sCatId) {\n                    if ($dMaxLvl < $aCatLvlCache[$sCatId]->ilevel) {\n                        $dMaxLvl = $aCatLvlCache[$sCatId]->ilevel;\n                        $sIdMax = $sCatId;\n                        $sRet = $aCatLvlCache[$sCatId]->oxtitle;\n                    }\n                }\n\n                // endless\n                while (true) {\n                    if (\n                        !isset($aCatLvlCache[$sIdMax]->oxparentid)\n                        || $aCatLvlCache[$sIdMax]->oxparentid == \"oxrootid\"\n                    ) {\n                        break;\n                    }\n                    $sIdMax = $aCatLvlCache[$sIdMax]->oxparentid;\n                    $sRet = $aCatLvlCache[$sIdMax]->oxtitle . \"/\" . $sRet;\n                }\n            }\n        }\n\n        return $sRet;\n    }\n\n    /**\n     * initialize article\n     *\n     * @param string $sHeapTable heap table name\n     * @param int    $iCnt       record number\n     * @param bool   $blContinue false is used to stop exporting\n     *\n     * @return object\n     */\n    protected function initArticle($sHeapTable, $iCnt, &$blContinue)\n    {\n        $oRs = $this->getDb()->selectLimit(\"select oxid from $sHeapTable\", 1, $iCnt);\n        if ($oRs != false && $oRs->count() > 0) {\n            $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            $oArticle->setLoadParentData(true);\n\n            $oArticle->setLanguage(Registry::getSession()->getVariable(\"iExportLanguage\"));\n\n            if ($oArticle->load($oRs->fields['oxid'])) {\n                // if article exists, do not stop export\n                $blContinue = true;\n                // check price\n                $dMinPrice = Registry::getRequest()->getRequestEscapedParameter(\"sExportMinPrice\");\n                if (\n                    !isset($dMinPrice)\n                    || (\n                        isset($dMinPrice)\n                        && ($oArticle->getPrice()->getBruttoPrice() >= $dMinPrice)\n                    )\n                ) {\n                    //Saulius: variant title added\n                    $sTitle = $oArticle->oxarticles__oxvarselect->value\n                        ? \" \" . $oArticle->oxarticles__oxvarselect->value\n                        : \"\";\n                    $oArticle->oxarticles__oxtitle->setValue($oArticle->oxarticles__oxtitle->value . $sTitle);\n\n                    $oArticle = $this->updateArticle($oArticle);\n\n                    return $oArticle;\n                }\n            }\n        }\n    }\n\n    /**\n     * sets detail link for campaigns\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle article object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected function setCampaignDetailLink($oArticle)\n    {\n        // #827\n        if ($sCampaign = Registry::getRequest()->getRequestEscapedParameter(\"sExportCampaign\")) {\n            // modify detaillink\n            //#1166R - pangora - campaign\n            $oArticle->appendLink(\"campaign={$sCampaign}\");\n\n            if (\n                Registry::getRequest()->getRequestEscapedParameter(\"blAppendCatToCampaign\") &&\n                ($sCat = $this->getCategoryString($oArticle))\n            ) {\n                $oArticle->appendLink(\"/$sCat\");\n            }\n        }\n\n        return $oArticle;\n    }\n\n    /**\n     * Returns view id ('dyn_interface')\n     *\n     * @return string\n     */\n    public function getViewId()\n    {\n        return 'dyn_interface';\n    }\n\n    /**\n     * Updates Article object. Method is used for overriding.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $article\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected function updateArticle($article)\n    {\n        return $article;\n    }\n\n    /**\n     * Get the actual database.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Database\\Adapter\\DatabaseInterface The database.\n     */\n    protected function getDb()\n    {\n        return \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/GenericExport.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin dyn General export manager.\n */\nclass GenericExport extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\DynamicExportBaseController\n{\n    /**\n     * Export class name\n     *\n     * @var string\n     */\n    public $sClassDo = 'genexport_do';\n\n    /**\n     * Export ui class name\n     *\n     * @var string\n     */\n    public $sClassMain = 'genexport_main';\n\n    /**\n     * Current view ID getter helps to identify navigation position.\n     * Bypassing dynexportbase::getViewId\n     *\n     * @return string\n     */\n    public function getViewId()\n    {\n        return \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController::getViewId();\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/GenericExportDo.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererInterface;\n\n/**\n * General export class.\n */\nclass GenericExportDo extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\DynamicExportBaseController\n{\n    /**\n     * Export class name\n     *\n     * @var string\n     */\n    public $sClassDo = \"genExport_do\";\n\n    /**\n     * Export ui class name\n     *\n     * @var string\n     */\n    public $sClassMain = \"genExport_main\";\n\n    /**\n     * Export file name\n     *\n     * @var string\n     */\n    public $sExportFileName = \"genexport\";\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = \"dynbase_do\";\n\n    /**\n     * Does Export line by line on position iCnt\n     *\n     * @param integer $cnt export position\n     *\n     * @return bool\n     */\n    public function nextTick($cnt)\n    {\n        $exportedItems = $cnt;\n        $continue = false;\n        if ($article = $this->getOneArticle($cnt, $continue)) {\n            $config = Registry::getConfig();\n            $article->longDescription = $this->prepareLongDescription($article);\n            $context = [\n                \"sCustomHeader\" => Registry::getSession()->getVariable(\"sExportCustomHeader\"),\n                \"linenr\"        => $cnt,\n                \"article\"       => $article,\n                \"spr\"           => $config->getConfigParam('sCSVSign'),\n                \"encl\"          => $config->getConfigParam('sGiCsvFieldEncloser')\n            ];\n            $context['oxEngineTemplateId'] = $this->getViewId();\n\n            $this->write(\n                $this->getRenderer()->renderTemplate(\n                    \"genexport\",\n                    $context\n                )\n            );\n\n            return ++$exportedItems;\n        }\n\n        return $continue;\n    }\n\n    private function prepareLongDescription(Article $article): string\n    {\n        if ($article->getLongDescription() && $article->getLongDescription()->getRawValue()) {\n            $activeLanguageId = Registry::getLang()->getTplLanguage();\n            $oxid = (string) $article->getId() . (string) $article->getLanguage();\n            return $this->getRenderer()->renderFragment(\n                $article->getLongDescription()->getRawValue(),\n                \"ox:{$oxid}{$activeLanguageId}\",\n                $this->getViewData()\n            );\n        }\n        return '';\n    }\n\n    private function getRenderer(): TemplateRendererInterface\n    {\n        return $this\n            ->getService(TemplateRendererBridgeInterface::class)\n            ->getTemplateRenderer();\n    }\n\n    /**\n     * writes one line into open export file\n     *\n     * @param string $sLine exported line\n     */\n    public function write($sLine)\n    {\n        $sLine = $this->removeSID($sLine);\n\n        $sLine = str_replace([\"\\r\\n\", \"\\n\"], \"\", $sLine);\n        $sLine = str_replace(\"<br>\", \"\\n\", $sLine);\n\n        fwrite($this->fpFile, $sLine . \"\\n\");\n    }\n\n    /**\n     * Current view ID getter helps to identify navigation position.\n     * Bypassing dynexportbase::getViewId\n     *\n     * @return string\n     */\n    public function getViewId()\n    {\n        return \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController::getViewId();\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/GenericExportMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin general export manager.\n */\nclass GenericExportMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\DynamicExportBaseController\n{\n    /**\n     * Export class name\n     *\n     * @var string\n     */\n    public $sClassDo = \"genExport_do\";\n\n    /**\n     * Export ui class name\n     *\n     * @var string\n     */\n    public $sClassMain = \"genExport_main\";\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = \"dyn_exportdefault\";\n\n    /** @inheritdoc */\n    public function render()\n    {\n        $this->createMainExportView();\n\n        return parent::render();\n    }\n\n    /**\n     * Current view ID getter helps to identify navigation position.\n     * Bypassing dynexportbase::getViewId\n     *\n     * @return string\n     */\n    public function getViewId()\n    {\n        return \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController::getViewId();\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/GenericImport.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin dyn General import manager.\n */\nclass GenericImport extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\GenericImportMain\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = \"genimport_main\";\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/GenericImportMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse Symfony\\Component\\Filesystem\\Path;\n\n/**\n * Admin general export manager.\n */\nclass GenericImportMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Export class name\n     *\n     * @var string\n     */\n    public $sClassDo = \"genImport_do\";\n\n    /**\n     * Export ui class name\n     *\n     * @var string\n     */\n    public $sClassMain = \"genImport_main\";\n\n    /**\n     * Csv file path\n     *\n     * @var string\n     */\n    protected $_sCsvFilePath = null;\n\n    /**\n     * Csv file field terminator\n     *\n     * @var string\n     */\n    protected $_sStringTerminator = null;\n\n    /**\n     * Csv file field encloser\n     *\n     * @var string\n     */\n    protected $_sStringEncloser = null;\n\n    /**\n     * Default Csv file field terminator\n     *\n     * @var string\n     */\n    protected $_sDefaultStringTerminator = \";\";\n\n    /**\n     * Default Csv file field encloser\n     *\n     * @var string\n     */\n    protected $_sDefaultStringEncloser = '\"';\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = \"genimport_main\";\n\n    /** @inheritdoc */\n    public function render()\n    {\n        $config = Registry::getConfig();\n\n        $genericImport = oxNew(\\OxidEsales\\Eshop\\Core\\GenericImport\\GenericImport::class);\n        $this->_sCsvFilePath = null;\n\n        $navigationStep = Registry::getRequest()->getRequestEscapedParameter('sNavStep');\n\n        if (!$navigationStep) {\n            $navigationStep = 1;\n        } else {\n            $navigationStep++;\n        }\n\n        $navigationStep = $this->checkErrors($navigationStep);\n\n        if ($navigationStep == 1) {\n            $this->_aViewData['sGiCsvFieldTerminator'] = \\OxidEsales\\Eshop\\Core\\Str::getStr()->htmlentities($this->getCsvFieldsTerminator());\n            $this->_aViewData['sGiCsvFieldEncloser'] = \\OxidEsales\\Eshop\\Core\\Str::getStr()->htmlentities($this->getCsvFieldsEncolser());\n        }\n\n        if ($navigationStep == 2) {\n            $noJsValidator = oxNew(\\OxidEsales\\Eshop\\Core\\NoJsValidator::class);\n            //saving csv field terminator and encloser to config\n            $terminator = Registry::getRequest()->getRequestEscapedParameter('sGiCsvFieldTerminator');\n            if ($terminator && !$noJsValidator->isValid($terminator)) {\n                $this->setErrorToView($terminator);\n            } else {\n                $this->_sStringTerminator = $terminator;\n                $config->saveShopConfVar('str', 'sGiCsvFieldTerminator', $terminator);\n            }\n\n            $encloser = Registry::getRequest()->getRequestEscapedParameter('sGiCsvFieldEncloser');\n            if ($encloser && !$noJsValidator->isValid($encloser)) {\n                $this->setErrorToView($encloser);\n            } else {\n                $this->_sStringEncloser = $encloser;\n                $config->saveShopConfVar('str', 'sGiCsvFieldEncloser', $encloser);\n            }\n\n            $type = Registry::getRequest()->getRequestEscapedParameter('sType');\n            $importObject = $genericImport->getImportObject($type);\n            $this->_aViewData['sType'] = $type;\n            $this->_aViewData['sImportTable'] = $importObject->getBaseTableName();\n            $this->_aViewData['aCsvFieldsList'] = $this->getCsvFieldsNames();\n            $this->_aViewData['aDbFieldsList'] = $importObject->getFieldList();\n        }\n\n        if ($navigationStep == 3) {\n            $csvFields = Registry::getRequest()->getRequestEscapedParameter('aCsvFields');\n            $type = Registry::getRequest()->getRequestEscapedParameter('sType');\n\n            $genericImport = oxNew(\\OxidEsales\\Eshop\\Core\\GenericImport\\GenericImport::class);\n            $genericImport->setImportType($type);\n            $genericImport->setCsvFileFieldsOrder($csvFields);\n            $genericImport->setCsvContainsHeader(\\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable('blCsvContainsHeader'));\n\n            $genericImport->importFile($this->getUploadedCsvFilePath());\n            $this->_aViewData['iTotalRows'] = $genericImport->getImportedRowCount();\n\n            //checking if errors occured during import\n            $this->checkImportErrors($genericImport);\n\n            //deleting uploaded csv file from temp dir\n            $this->deleteCsvFile();\n\n            //check if repeating import - then forsing first step\n            if (Registry::getRequest()->getRequestEscapedParameter('iRepeatImport')) {\n                $this->_aViewData['iRepeatImport'] = 1;\n                $navigationStep = 1;\n            }\n        }\n\n        if ($navigationStep == 1) {\n            $this->_aViewData['aImportTables'] = $genericImport->getImportObjectsList();\n            asort($this->_aViewData['aImportTables']);\n            $this->resetUploadedCsvData();\n        }\n\n        $this->_aViewData['sNavStep'] = $navigationStep;\n\n        return parent::render();\n    }\n\n    /**\n     * Deletes uploaded csv file from temp directory\n     */\n    protected function deleteCsvFile()\n    {\n        $sPath = $this->getUploadedCsvFilePath();\n        if (is_file($sPath)) {\n            @unlink($sPath);\n        }\n    }\n\n    /**\n     * Get columns names from CSV file header. If file has no header\n     * returns default columns names Column 1, Column 2..\n     *\n     * @return array\n     */\n    protected function getCsvFieldsNames()\n    {\n        $blCsvContainsHeader = Registry::getRequest()->getRequestEscapedParameter('blContainsHeader');\n        Registry::getSession()->setVariable('blCsvContainsHeader', $blCsvContainsHeader);\n        $this->getUploadedCsvFilePath();\n\n        $aFirstRow = $this->getCsvFirstRow();\n\n        if (!$blCsvContainsHeader) {\n            $iIndex = 1;\n            foreach ($aFirstRow as $sValue) {\n                $aCsvFields[$iIndex] = 'Column ' . $iIndex++;\n            }\n        } else {\n            foreach ($aFirstRow as $sKey => $sValue) {\n                $aFirstRow[$sKey] = \\OxidEsales\\Eshop\\Core\\Str::getStr()->htmlentities($sValue);\n            }\n\n            $aCsvFields = $aFirstRow;\n        }\n\n        return $aCsvFields;\n    }\n\n    /**\n     * Get first row from uploaded CSV file\n     *\n     * @return array\n     */\n    protected function getCsvFirstRow()\n    {\n        $sPath = $this->getUploadedCsvFilePath();\n        $iMaxLineLength = 8192;\n\n        //getting first row\n        if (($rFile = @fopen($sPath, \"r\")) !== false) {\n            $aRow = fgetcsv($rFile, $iMaxLineLength, $this->getCsvFieldsTerminator(), $this->getCsvFieldsEncolser());\n            fclose($rFile);\n        }\n\n        return $aRow;\n    }\n\n    /**\n     * Resets CSV parameters stored in session\n     */\n    protected function resetUploadedCsvData()\n    {\n        $this->_sCsvFilePath = null;\n        Registry::getSession()->setVariable('sCsvFilePath', null);\n        Registry::getSession()->setVariable('blCsvContainsHeader', null);\n    }\n\n    /**\n     * Checks current import navigation step errors.\n     * Returns step id in which error occured.\n     *\n     * @param int $iNavStep Navigation step id\n     *\n     * @return int\n     */\n    protected function checkErrors($iNavStep)\n    {\n        if ($iNavStep == 2) {\n            if (!$this->getUploadedCsvFilePath()) {\n                $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay::class);\n                $oEx->setMessage('GENIMPORT_ERRORUPLOADINGFILE');\n                Registry::getUtilsView()->addErrorToDisplay($oEx, false, true, 'genimport');\n\n                return 1;\n            }\n        }\n\n        if ($iNavStep == 3) {\n            $blIsEmpty = true;\n            $aCsvFields = Registry::getRequest()->getRequestEscapedParameter('aCsvFields');\n            foreach ($aCsvFields as $sValue) {\n                if ($sValue) {\n                    $blIsEmpty = false;\n                    break;\n                }\n            }\n\n            if ($blIsEmpty) {\n                $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay::class);\n                $oEx->setMessage('GENIMPORT_ERRORASSIGNINGFIELDS');\n                Registry::getUtilsView()->addErrorToDisplay($oEx, false, true, 'genimport');\n\n                return 2;\n            }\n        }\n\n        return $iNavStep;\n    }\n\n    /**\n     * Checks if CSV file was uploaded. If uploaded - moves it to temp dir\n     * and stores path to file in session. Return path to uploaded file.\n     *\n     * @return string\n     */\n    protected function getUploadedCsvFilePath()\n    {\n        $this->_sCsvFilePath ??= Registry::getSession()->getVariable('sCsvFilePath');\n        if ($this->_sCsvFilePath) {\n            return $this->_sCsvFilePath;\n        }\n        $upload = Registry::getConfig()->getUploadedFile('csvfile');\n        if (isset($upload['name']) && $upload['name']) {\n            $this->_sCsvFilePath = Path::join(\n                ContainerFacade::getParameter('oxid_esales.build_directory'),\n                basename($upload['tmp_name'])\n            );\n            move_uploaded_file($upload['tmp_name'], $this->_sCsvFilePath);\n            Registry::getSession()->setVariable('sCsvFilePath', $this->_sCsvFilePath);\n\n            return $this->_sCsvFilePath;\n        }\n    }\n\n    /**\n     * Checks if any error occured during import and displays them\n     *\n     * @param object $oErpImport Import object\n     */\n    protected function checkImportErrors($oErpImport)\n    {\n        foreach ($oErpImport->getStatistics() as $aValue) {\n            if (!$aValue ['r']) {\n                $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay::class);\n                $oEx->setMessage($aValue ['m']);\n                Registry::getUtilsView()->addErrorToDisplay($oEx, false, true, 'genimport');\n            }\n        }\n    }\n\n    /**\n     * Get csv field terminator symbol\n     *\n     * @return string\n     */\n    protected function getCsvFieldsTerminator()\n    {\n        if ($this->_sStringTerminator === null) {\n            $this->_sStringTerminator = $this->_sDefaultStringTerminator;\n            if ($char = Registry::getConfig()->getConfigParam('sGiCsvFieldTerminator')) {\n                $this->_sStringTerminator = $char;\n            }\n        }\n\n        return $this->_sStringTerminator;\n    }\n\n    /**\n     * Get csv field encloser symbol\n     *\n     * @return string\n     */\n    protected function getCsvFieldsEncolser()\n    {\n        if ($this->_sStringEncloser === null) {\n            $this->_sStringEncloser = $this->_sDefaultStringEncloser;\n            if ($char = Registry::getConfig()->getConfigParam('sGiCsvFieldEncloser')) {\n                $this->_sStringEncloser = $char;\n            }\n        }\n\n        return $this->_sStringEncloser;\n    }\n\n    /**\n     * @param string $invalidData\n     */\n    private function setErrorToView($invalidData)\n    {\n        $error = oxNew(\\OxidEsales\\Eshop\\Core\\DisplayError::class);\n        $error->setFormatParameters(htmlspecialchars($invalidData));\n        $error->setMessage(\"SHOP_CONFIG_ERROR_INVALID_VALUE\");\n        Registry::getUtilsView()->addErrorToDisplay($error);\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/LanguageController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Sets template, that arranges two other templates (\"article_list\"\n * and \"article_main\") to frame.\n * Admin Menu: Manage Products -> Articles.\n */\nclass LanguageController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'language';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/LanguageList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse oxRegistry;\nuse oxDb;\nuse Exception;\n\n/**\n * Admin selectlist list manager.\n */\nclass LanguageList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Default sorting parameter.\n     *\n     * @var string\n     */\n    protected $_sDefSortField = 'sort';\n\n    /**\n     * Default sorting order.\n     *\n     * @var string\n     */\n    protected $_sDefSortOrder = 'asc';\n\n    /**\n     * Checks for Malladmin rights\n     *\n     * @return null\n     */\n    public function deleteEntry()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $sOxId = $this->getEditObjectId();\n\n        $aLangData['params'] = $myConfig->getConfigParam('aLanguageParams');\n        $aLangData['lang'] = $myConfig->getConfigParam('aLanguages');\n        $aLangData['urls'] = $myConfig->getConfigParam('aLanguageURLs');\n        $aLangData['sslUrls'] = $myConfig->getConfigParam('aLanguageSSLURLs');\n\n        $iBaseId = (int) $aLangData['params'][$sOxId]['baseId'];\n\n        // preventing deleting main language with base id = 0\n        if ($iBaseId == 0) {\n            $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay::class);\n            $oEx->setMessage('LANGUAGE_DELETINGMAINLANG_WARNING');\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay($oEx);\n\n            return;\n        }\n\n        // unsetting selected lang from languages arrays\n        unset($aLangData['params'][$sOxId]);\n        unset($aLangData['lang'][$sOxId]);\n        unset($aLangData['urls'][$iBaseId]);\n        unset($aLangData['sslUrls'][$iBaseId]);\n\n        //saving languages info back to DB\n        $myConfig->saveShopConfVar('aarr', 'aLanguageParams', $aLangData['params']);\n        $myConfig->saveShopConfVar('aarr', 'aLanguages', $aLangData['lang']);\n        $myConfig->saveShopConfVar('arr', 'aLanguageURLs', $aLangData['urls']);\n        $myConfig->saveShopConfVar('arr', 'aLanguageSSLURLs', $aLangData['sslUrls']);\n\n        //if deleted language was default, setting defalt lang to 0\n        if ($iBaseId == $myConfig->getConfigParam('sDefaultLang')) {\n            $myConfig->saveShopConfVar('str', 'sDefaultLang', 0);\n        }\n    }\n\n    /**\n     * Executes parent method parent::render() and returns name of template\n     * file \"selectlist_list\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n        $this->_aViewData['mylist'] = $this->getLanguagesList();\n\n        return \"language_list\";\n    }\n\n    /**\n     * Collects shop languages list.\n     *\n     * @return array\n     */\n    protected function getLanguagesList()\n    {\n        $aLangParams = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('aLanguageParams');\n        $aLanguages = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getLanguageArray();\n        $sDefaultLang = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('sDefaultLang');\n\n        foreach ($aLanguages as $sKey => $sValue) {\n            $sOxId = $sValue->oxid;\n            $aLanguages[$sKey]->active = (!isset($aLangParams[$sOxId][\"active\"])) ? 1 : $aLangParams[$sOxId][\"active\"];\n            $aLanguages[$sKey]->default = ($aLangParams[$sOxId][\"baseId\"] == $sDefaultLang) ? true : false;\n            $aLanguages[$sKey]->sort = $aLangParams[$sOxId][\"sort\"];\n        }\n\n        if (is_array($aLangParams)) {\n            $aSorting = $this->getListSorting();\n\n            if (is_array($aSorting)) {\n                foreach ($aSorting as $aFieldSorting) {\n                    foreach ($aFieldSorting as $sField => $sDir) {\n                        $this->_sDefSortField = $sField;\n                        $this->_sDefSortOrder = $sDir;\n\n                        if ($sField == 'active') {\n                            //reverting sort order for field 'active'\n                            $this->_sDefSortOrder = 'desc';\n                        }\n                        break 2;\n                    }\n                }\n            }\n\n            uasort($aLanguages, [$this, 'sortLanguagesCallback']);\n        }\n\n        return $aLanguages;\n    }\n\n    /**\n     * Callback function for sorting languages objects. Sorts array according\n     * 'sort' parameter\n     *\n     * @param object $oLang1 language object\n     * @param object $oLang2 language object\n     *\n     * @return bool\n     */\n    protected function sortLanguagesCallback($oLang1, $oLang2)\n    {\n        $sSortParam = $this->_sDefSortField;\n        $sVal1 = is_string($oLang1->$sSortParam) ? strtolower($oLang1->$sSortParam) : $oLang1->$sSortParam;\n        $sVal2 = is_string($oLang2->$sSortParam) ? strtolower($oLang2->$sSortParam) : $oLang2->$sSortParam;\n\n        if ($this->_sDefSortOrder == 'asc') {\n            return ($sVal1 < $sVal2) ? -1 : 1;\n        } else {\n            return ($sVal1 > $sVal2) ? -1 : 1;\n        }\n    }\n\n    /**\n     * Resets all multilanguage fields with specific language id\n     * to default value in all tables.\n     *\n     * @param string $iLangId language ID\n     */\n    protected function resetMultiLangDbFields($iLangId)\n    {\n        $iLangId = (int) $iLangId;\n\n        //skipping reseting language with id = 0\n        if ($iLangId) {\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->startTransaction();\n\n            try {\n                $oDbMeta = oxNew(\\OxidEsales\\Eshop\\Core\\DbMetaDataHandler::class);\n                $oDbMeta->resetLanguage($iLangId);\n\n                \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->commitTransaction();\n            } catch (Exception $oEx) {\n                // if exception, rollBack everything\n                \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->rollbackTransaction();\n\n                //show warning\n                $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay::class);\n                $oEx->setMessage('LANGUAGE_ERROR_RESETING_MULTILANG_FIELDS');\n                \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay($oEx);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/LanguageMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse Exception;\n\n/**\n * Admin article main selectlist manager.\n * Performs collection and updatind (on user submit) main item information.\n */\nclass LanguageMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Current shop base languages\n     *\n     * @var array\n     */\n    protected $_aLangData = null;\n\n    /**\n     * Current shop base languages parameters\n     *\n     * @var array\n     */\n    protected $_aLangParams = null;\n\n    /**\n     * Current shop base languages base urls\n     *\n     * @var array\n     */\n    protected $_aLanguagesUrls = null;\n\n    /**\n     * Current shop base languages base ssl urls\n     *\n     * @var array\n     */\n    protected $_aLanguagesSslUrls = null;\n\n    /** @var \\OxidEsales\\Eshop\\Core\\NoJsValidator */\n    private $noJsValidator;\n\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $sOxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        //loading languages info from config\n        $this->_aLangData = $this->getLanguages();\n\n        if (isset($sOxId) && $sOxId != \"-1\") {\n            //checking if translations files exists\n            $this->checkLangTranslations($sOxId);\n            $this->_aViewData[\"edit\"] = $this->getLanguageInfo($sOxId);\n        }\n\n        return \"language_main\";\n    }\n\n    /**\n     * Saves selection list parameters changes.\n     *\n     * @return mixed\n     */\n    public function save()\n    {\n        parent::save();\n\n        $sOxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        if (!isset($aParams['active'])) {\n            $aParams['active'] = 0;\n        }\n\n        if (!isset($aParams['default'])) {\n            $aParams['default'] = false;\n        }\n\n        if (empty($aParams['sort'])) {\n            $aParams['sort'] = '99999';\n        }\n\n        //loading languages info from config\n        $this->_aLangData = $this->getLanguages();\n        //checking input errors\n        if (!$this->validateInput()) {\n            return;\n        }\n\n        $blViewError = false;\n\n        // if changed language abbervation, updating it for all arrays related with languages\n        if ($sOxId != -1 && $sOxId != $aParams['abbr']) {\n            // #0004850 preventing changing abbr for main language with base id = 0\n            if ((int) $this->_aLangData['params'][$sOxId]['baseId'] == 0) {\n                $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay::class);\n                $oEx->setMessage('LANGUAGE_ABBRCHANGEMAINLANG_WARNING');\n                \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay($oEx);\n                $aParams['abbr'] = $sOxId;\n            } else {\n                $this->updateAbbervation($sOxId, $aParams['abbr']);\n                $sOxId = $aParams['abbr'];\n                $this->setEditObjectId($sOxId);\n\n                $blViewError = true;\n            }\n        }\n\n        // if adding new language, setting lang id to abbervation\n        if ($blNewLanguage = ($sOxId == -1)) {\n            $sOxId = $aParams['abbr'];\n            $this->_aLangData['params'][$sOxId]['baseId'] = $this->getAvailableLangBaseId();\n            $this->setEditObjectId($sOxId);\n        }\n\n        //updating language description\n        $this->_aLangData['lang'][$sOxId] = $aParams['desc'];\n\n        //updating language parameters\n        $this->_aLangData['params'][$sOxId]['active'] = $aParams['active'];\n        $this->_aLangData['params'][$sOxId]['default'] = $aParams['default'];\n        $this->_aLangData['params'][$sOxId]['sort'] = $aParams['sort'];\n\n        //if setting lang as default\n        if ($aParams['default'] == '1') {\n            $this->setDefaultLang($sOxId);\n        }\n\n        //updating language urls\n        $iBaseId = $this->_aLangData['params'][$sOxId]['baseId'];\n        $this->_aLangData['urls'][$iBaseId] = $aParams['baseurl'];\n        $this->_aLangData['sslUrls'][$iBaseId] = $aParams['basesslurl'];\n\n        //sort parameters, urls and languages arrays by language base id\n        $this->sortLangArraysByBaseId();\n\n        $this->_aViewData[\"updatelist\"] = \"1\";\n\n        if ($this->isValidLanguageData($this->_aLangData)) {\n            //saving languages info\n            \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->saveShopConfVar('aarr', 'aLanguageParams', $this->_aLangData['params']);\n            \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->saveShopConfVar('aarr', 'aLanguages', $this->_aLangData['lang']);\n            \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->saveShopConfVar('arr', 'aLanguageURLs', $this->_aLangData['urls']);\n            \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->saveShopConfVar('arr', 'aLanguageSSLURLs', $this->_aLangData['sslUrls']);\n            //checking if added language already has created multilang fields\n            //with new base ID - if not, creating new fields\n            if ($blNewLanguage) {\n                if (!$this->checkMultilangFieldsExistsInDb($sOxId)) {\n                    $this->addNewMultilangFieldsToDb();\n                } else {\n                    $blViewError = true;\n                }\n            }\n            // show message for user to generate views\n            if ($blViewError) {\n                $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay::class);\n                $oEx->setMessage('LANGUAGE_ERRORGENERATEVIEWS');\n                \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay($oEx);\n            }\n        }\n    }\n\n    /**\n     * Get selected language info\n     *\n     * @param string $sOxId language abbervation\n     *\n     * @return array\n     */\n    protected function getLanguageInfo($sOxId)\n    {\n        $sDefaultLang = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('sDefaultLang');\n\n        $aLangData = $this->_aLangData['params'][$sOxId];\n        $aLangData['abbr'] = $sOxId;\n        $aLangData['desc'] = $this->_aLangData['lang'][$sOxId];\n        $aLangData['baseurl'] = $this->_aLangData['urls'][$aLangData['baseId']];\n        $aLangData['basesslurl'] = $this->_aLangData['sslUrls'][$aLangData['baseId']];\n        $aLangData['default'] = ($this->_aLangData['params'][$sOxId][\"baseId\"] == $sDefaultLang) ? true : false;\n\n        return $aLangData;\n    }\n\n    /**\n     * Languages array setter\n     *\n     * @param array $aLangData languages parameters array\n     */\n    protected function setLanguages($aLangData)\n    {\n        $this->_aLangData = $aLangData;\n    }\n\n    /**\n     * Loads from config all data related with languages.\n     * If no languages parameters array exists, sets default parameters values.\n     * Returns collected languages parameters array.\n     *\n     * @return array\n     */\n    protected function getLanguages()\n    {\n        $aLangData['params'] = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('aLanguageParams');\n        $aLangData['lang'] = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('aLanguages');\n        $aLangData['urls'] = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('aLanguageURLs');\n        $aLangData['sslUrls'] = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('aLanguageSSLURLs');\n\n        // empty languages parameters array - creating new one with default values\n        if (!is_array($aLangData['params'])) {\n            $aLangData['params'] = $this->assignDefaultLangParams($aLangData['lang']);\n        }\n\n        return $aLangData;\n    }\n\n    /**\n     * Replaces languages arrays keys by new value.\n     *\n     * @param string $sOldId old ID\n     * @param string $sNewId new ID\n     */\n    protected function updateAbbervation($sOldId, $sNewId)\n    {\n        foreach (array_keys($this->_aLangData) as $sTypeKey) {\n            if (is_array($this->_aLangData[$sTypeKey]) && count($this->_aLangData[$sTypeKey]) > 0) {\n                if ($sTypeKey == 'urls' || $sTypeKey == 'sslUrls') {\n                    continue;\n                }\n\n                $aKeys = array_keys($this->_aLangData[$sTypeKey]);\n                $aValues = array_values($this->_aLangData[$sTypeKey]);\n                //find and replace key\n                $iReplaceId = array_search($sOldId, $aKeys);\n                $aKeys[$iReplaceId] = $sNewId;\n\n                $this->_aLangData[$sTypeKey] = array_combine($aKeys, $aValues);\n            }\n        }\n    }\n\n    /**\n     * Sort languages, languages parameters, urls, ssl urls arrays according\n     * base land ID\n     */\n    protected function sortLangArraysByBaseId()\n    {\n        $aUrls = [];\n        $aSslUrls = [];\n        $aLanguages = [];\n\n        uasort($this->_aLangData['params'], [$this, 'sortLangParamsByBaseIdCallback']);\n\n        foreach ($this->_aLangData['params'] as $sAbbr => $aParams) {\n            $iId = (int) $aParams['baseId'];\n            $aUrls[$iId] = $this->_aLangData['urls'][$iId];\n            $aSslUrls[$iId] = $this->_aLangData['sslUrls'][$iId];\n            $aLanguages[$sAbbr] = $this->_aLangData['lang'][$sAbbr];\n        }\n\n        $this->_aLangData['lang'] = $aLanguages;\n        $this->_aLangData['urls'] = $aUrls;\n        $this->_aLangData['sslUrls'] = $aSslUrls;\n    }\n\n    /**\n     * Assign default values for eache language\n     *\n     * @param array $aLanguages language array\n     *\n     * @return array\n     */\n    protected function assignDefaultLangParams($aLanguages)\n    {\n        $aParams = [];\n        $iBaseId = 0;\n\n        foreach (array_keys($aLanguages) as $sOxId) {\n            $aParams[$sOxId]['baseId'] = $iBaseId;\n            $aParams[$sOxId]['active'] = 1;\n            $aParams[$sOxId]['sort'] = $iBaseId + 1;\n\n            $iBaseId++;\n        }\n\n        return $aParams;\n    }\n\n    /**\n     * Sets default language base ID to config var 'sDefaultLang'\n     *\n     * @param string $sOxId language abbervation\n     */\n    protected function setDefaultLang($sOxId)\n    {\n        $sDefaultId = $this->_aLangData['params'][$sOxId]['baseId'];\n        \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->saveShopConfVar('str', 'sDefaultLang', $sDefaultId);\n    }\n\n    /**\n     * Get availabale language base ID\n     *\n     * @return int\n     */\n    protected function getAvailableLangBaseId()\n    {\n        $aBaseId = [];\n        foreach ($this->_aLangData['params'] as $aLang) {\n            $aBaseId[] = $aLang['baseId'];\n        }\n\n        $iNewId = 0;\n        sort($aBaseId);\n        $iTotal = count($aBaseId);\n\n        //getting first available id\n        while ($iNewId <= $iTotal - 1) {\n            if ($iNewId !== $aBaseId[$iNewId]) {\n                break;\n            }\n            $iNewId++;\n        }\n\n        return $iNewId;\n    }\n\n    /**\n     * Check selected language has translation file lang.php\n     * If not - displays warning\n     *\n     * @param string $sOxId language abbervation\n     */\n    protected function checkLangTranslations($sOxId)\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $sDir = dirname($myConfig->getTranslationsDir('lang.php', $sOxId));\n\n        if (empty($sDir)) {\n            $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay::class);\n            $oEx->setMessage('LANGUAGE_NOTRANSLATIONS_WARNING');\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay($oEx);\n        }\n    }\n\n    /**\n     * Check if selected language already has multilanguage fields in DB\n     *\n     * @param string $sOxId language abbervation\n     *\n     * @return bool\n     */\n    protected function checkMultilangFieldsExistsInDb($sOxId)\n    {\n        $iBaseId = $this->_aLangData['params'][$sOxId]['baseId'];\n        $sTable = getLangTableName('oxarticles', $iBaseId);\n        $sColumn = 'oxtitle' . \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getLanguageTag($iBaseId);\n\n        $oDbMetadata = oxNew(\\OxidEsales\\Eshop\\Core\\DbMetaDataHandler::class);\n\n        return $oDbMetadata->tableExists($sTable) && $oDbMetadata->fieldExists($sColumn, $sTable);\n    }\n\n    /**\n     * Adding new language to DB - creating new multilangue fields with new\n     * language ID (e.g. oxtitle_4)\n     *\n     * @return null\n     */\n    protected function addNewMultilangFieldsToDb()\n    {\n        //creating new multilingual fields with new id over whole DB\n        $oDbMeta = oxNew(\\OxidEsales\\Eshop\\Core\\DbMetaDataHandler::class);\n\n        try {\n            $oDbMeta->addNewLangToDb();\n        } catch (Exception $oEx) {\n            //show warning\n            $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay::class);\n            $oEx->setMessage('LANGUAGE_ERROR_ADDING_MULTILANG_FIELDS');\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay($oEx);\n        }\n    }\n\n    /**\n     * Check if language already exists\n     *\n     * @param string $sAbbr language abbervation\n     *\n     * @return bool\n     */\n    protected function checkLangExists($sAbbr)\n    {\n        $aAbbrs = array_keys($this->_aLangData['lang']);\n\n        return in_array($sAbbr, $aAbbrs);\n    }\n\n    /**\n     * Callback function for sorting languages arraty. Sorts array according\n     * 'baseId' parameter\n     *\n     * @param object $oLang1 language array\n     * @param object $oLang2 language array\n     *\n     * @return bool\n     */\n    protected function sortLangParamsByBaseIdCallback($oLang1, $oLang2)\n    {\n        return ($oLang1['baseId'] < $oLang2['baseId']) ? -1 : 1;\n    }\n\n    /**\n     * Check language input errors\n     *\n     * @return bool\n     */\n    protected function validateInput()\n    {\n        $result = true;\n\n        $oxid = $this->getEditObjectId();\n        $parameters = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        // if creating new language, checking if language already exists with\n        // entered language abbreviation\n        if (($oxid == -1) && $this->checkLangExists($parameters['abbr'])) {\n            $this->addDisplayException('LANGUAGE_ALREADYEXISTS_ERROR');\n            $result = false;\n        }\n\n        // As the abbreviation is used in database view creation, check for allowed characters\n        if (!$this->checkAbbreviationAllowedCharacters($parameters['abbr'])) {\n            $this->addDisplayException('LANGUAGE_ABBREVIATION_INVALID_ERROR');\n            $result = false;\n        }\n\n        // checking if language name is not empty\n        if (empty($parameters['desc'])) {\n            $this->addDisplayException('LANGUAGE_EMPTYLANGUAGENAME_ERROR');\n            $result = false;\n        }\n\n        return $result;\n    }\n\n    /**\n     * Check if language abbreviation contains only allowed characters.\n     * Abbreviation is used for view creation, so to be on the safe side with MySQL,\n     * only allow characters [0-9,a-z,A-Z_] (basic Latin letters, digits 0-9, underscore).\n     * Allowing other characters means table names would have to be escaped with backticks in all queries.\n     *\n     * @param string $abbreviation language abbreviation\n     *\n     * @throws \\Exception if pattern does not match\n     *\n     * @return bool\n     */\n    protected function checkAbbreviationAllowedCharacters($abbreviation)\n    {\n        $pattern = '/^[a-zA-Z0-9_]*$/';\n        $result = preg_match($pattern, $abbreviation);\n        if ($result === false) {\n            throw new \\Exception(preg_last_error(), $pattern, $abbreviation);\n        }\n\n        return (bool) $result;\n    }\n\n    /**\n     * Add exception to be displayed in frontend.\n     *\n     * @param string $message Language constant\n     */\n    protected function addDisplayException($message)\n    {\n        $exception = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay::class);\n        $exception->setMessage($message);\n        \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay($exception);\n    }\n\n    /**\n     * Validates provided language data and sets error to view in case it is not valid.\n     *\n     * @param array $aLanguageData\n     *\n     * @return bool\n     */\n    protected function isValidLanguageData($aLanguageData)\n    {\n        $blValid = true;\n        $configValidator = $this->getNoJsValidator();\n        foreach ($aLanguageData as $mLanguageDataParameters) {\n            if (is_array($mLanguageDataParameters)) {\n                // Recursion till we gonna have a string.\n                $blDeepResult = $this->isValidLanguageData($mLanguageDataParameters);\n                $blValid = $blDeepResult === false ? $blDeepResult : $blValid;\n            } elseif (!$configValidator->isValid($mLanguageDataParameters)) {\n                $blValid = false;\n                $error = oxNew(\\OxidEsales\\Eshop\\Core\\DisplayError::class);\n                $error->setFormatParameters(htmlspecialchars($mLanguageDataParameters));\n                $error->setMessage(\"SHOP_CONFIG_ERROR_INVALID_VALUE\");\n                \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay($error);\n            }\n        }\n\n        return $blValid;\n    }\n\n    /**\n     * @return \\OxidEsales\\Eshop\\Core\\NoJsValidator\n     */\n    protected function getNoJsValidator()\n    {\n        if (is_null($this->noJsValidator)) {\n            $this->noJsValidator = oxNew(\\OxidEsales\\Eshop\\Core\\NoJsValidator::class);\n        }\n\n        return $this->noJsValidator;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ListComponentAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents\\AfterAdminAjaxRequestProcessedEvent;\n\n/**\n * AJAX call processor class\n */\nclass ListComponentAjax extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Possible sort keys\n     *\n     * @var array\n     */\n    protected $_aPosDir = ['asc', 'desc'];\n\n    /**\n     * Array of DB table columns which are loaded from DB\n     *\n     * @var array\n     */\n    protected $_aColumns = [];\n\n    /**\n     * Default limit of DB entries to load from DB\n     *\n     * @var int\n     */\n    protected $_iSqlLimit = 2500;\n\n    /**\n     * Ajax container name\n     *\n     * @var string\n     */\n    protected $_sContainer = null;\n\n    /**\n     * If true extended column selection will be build\n     * (currently checks if variants must be shown in lists and column name is \"oxtitle\")\n     *\n     * @var bool\n     */\n    protected $_blAllowExtColumns = false;\n\n    /**\n     * Gets columns array.\n     *\n     * @return array\n     */\n    public function getColumns()\n    {\n        return $this->_aColumns;\n    }\n\n    /**\n     * Sets columns array.\n     *\n     * @param array $aColumns columns array\n     */\n    public function setColumns($aColumns)\n    {\n        $this->_aColumns = $aColumns;\n    }\n\n    /**\n     * Required data fields are returned by indexes/position in _aColumns array. This method\n     * translates \"table_name.col_name\" into index definition and fetches request data according\n     * to it. This is usefull while using AJAX across versions.\n     *\n     * @param string $sId \"table_name.col_name\"\n     *\n     * @return array\n     */\n    protected function getActionIds($sId)\n    {\n        $aColumns = $this->getColNames();\n        foreach ($aColumns as $iPos => $aCol) {\n            if (isset($aCol[4]) && $aCol[4] == 1 && $sId == $aCol[1] . '.' . $aCol[0]) {\n                return Registry::getRequest()->getRequestEscapedParameter('_' . $iPos);\n            }\n        }\n    }\n\n    /**\n     * AJAX container name setter\n     *\n     * @param string $sName name of container\n     */\n    public function setName($sName)\n    {\n        $this->_sContainer = $sName;\n    }\n\n    /**\n     * Empty function, developer should override this method according requirements\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        return '';\n    }\n\n    /**\n     * Return fully formatted query for data loading\n     *\n     * @param string $sQ part of initial query\n     *\n     * @return string\n     */\n    protected function getDataQuery($sQ)\n    {\n        return 'select ' . $this->getQueryCols() . $sQ;\n    }\n\n    /**\n     * Return fully formatted query for data records count\n     *\n     * @param string $sQ part of initial query\n     *\n     * @return string\n     */\n    protected function getCountQuery($sQ)\n    {\n        return 'select count( * ) ' . $sQ;\n    }\n\n    /**\n     * AJAX call processor function\n     *\n     * @param string $function name of action to execute (optional)\n     */\n    public function processRequest($function = null)\n    {\n        if ($function) {\n            $this->$function();\n            ContainerFacade::dispatch(new AfterAdminAjaxRequestProcessedEvent());\n        } else {\n            $sQAdd = $this->getQuery();\n\n            // formatting SQL queries\n            $sQ = $this->getDataQuery($sQAdd);\n            $sCountQ = $this->getCountQuery($sQAdd);\n\n            $this->outputResponse($this->getData($sCountQ, $sQ));\n        }\n    }\n\n    /**\n     * Returns column id to sort\n     *\n     * @return int\n     */\n    protected function getSortCol()\n    {\n        $aVisibleNames = $this->getVisibleColNames();\n        $iCol = Registry::getRequest()->getRequestEscapedParameter('sort');\n        $iCol = $iCol ? ((int) str_replace('_', '', $iCol)) : 0;\n        $iCol = (!isset($aVisibleNames[$iCol])) ? 0 : $iCol;\n\n        return $iCol;\n    }\n\n\n    /**\n     * Returns array of cotainer DB cols which must be loaded. If id is not\n     * passed - all possible containers cols will be returned\n     *\n     * @param string $sId container id (optional)\n     *\n     * @return array\n     */\n    protected function getColNames($sId = null)\n    {\n        if ($sId === null) {\n            $sId = Registry::getRequest()->getRequestEscapedParameter('cmpid');\n        }\n\n        if ($sId && isset($this->_aColumns[$sId])) {\n            return $this->_aColumns[$sId];\n        }\n\n        return $this->_aColumns;\n    }\n\n    /**\n     * Returns array of identifiers which are used as identifiers for specific actions\n     * in AJAX and further in this processor class\n     *\n     * @return array\n     */\n    protected function getIdentColNames()\n    {\n        $aColNames = $this->getColNames();\n        $aCols = [];\n        foreach ($aColNames as $iKey => $aCol) {\n            // ident ?\n            if ($aCol[4]) {\n                $aCols[$iKey] = $aCol;\n            }\n        }\n\n        return $aCols;\n    }\n\n    /**\n     * Returns array of col names which are requested by AJAX call and will be fetched from DB\n     *\n     * @return array\n     */\n    protected function getVisibleColNames()\n    {\n        $aColNames = $this->getColNames();\n        $aUserCols = Registry::getRequest()->getRequestEscapedParameter('aCols');\n        $aVisibleCols = [];\n\n        // user defined some cols to load ?\n        if (is_array($aUserCols)) {\n            foreach ($aUserCols as $iKey => $sCol) {\n                $iCol = (int) str_replace('_', '', $sCol);\n                if (isset($aColNames[$iCol]) && !$aColNames[$iCol][4]) {\n                    $aVisibleCols[$iCol] = $aColNames[$iCol];\n                }\n            }\n        }\n\n        // no user defined valid cols ? setting defauls ..\n        if (!count($aVisibleCols)) {\n            foreach ($aColNames as $sName => $aCol) {\n                // visible ?\n                if ($aCol[1] && !$aColNames[$sName][4]) {\n                    $aVisibleCols[$sName] = $aCol;\n                }\n            }\n        }\n\n        return $aVisibleCols;\n    }\n\n    /**\n     * Formats and returns chunk of SQL query string with definition of\n     * fields to load from DB\n     *\n     * @return string\n     */\n    protected function getQueryCols()\n    {\n        $sQ = $this->buildColsQuery($this->getVisibleColNames(), false) . \", \";\n        $sQ .= $this->buildColsQuery($this->getIdentColNames());\n\n        return \" $sQ \";\n    }\n\n    /**\n     * Builds column selection query\n     *\n     * @param array $aIdentCols  columns\n     * @param bool  $blIdentCols if true, means ident columns part is build\n     *\n     * @return string\n     */\n    protected function buildColsQuery($aIdentCols, $blIdentCols = true)\n    {\n        $sQ = '';\n        foreach ($aIdentCols as $iCnt => $aCol) {\n            if ($sQ) {\n                $sQ .= ', ';\n            }\n\n            $sViewTable = $this->getViewName($aCol[1]);\n            if (!$blIdentCols && $this->isExtendedColumn($aCol[0])) {\n                $sQ .= $this->getExtendedColQuery($sViewTable, $aCol[0], $iCnt);\n            } else {\n                $sQ .= $sViewTable . '.' . $aCol[0] . ' as _' . $iCnt;\n            }\n        }\n\n        return $sQ;\n    }\n\n    /**\n     * Checks if current column is extended\n     * (currently checks if variants must be shown in lists and column name is \"oxtitle\")\n     *\n     * @param string $sColumn column name\n     *\n     * @return bool\n     */\n    protected function isExtendedColumn($sColumn)\n    {\n        $blVariantsSelectionParameter = Registry::getConfig()->getConfigParam('blVariantsSelection');\n\n        return $this->_blAllowExtColumns && $blVariantsSelectionParameter && $sColumn == 'oxtitle';\n    }\n\n    /**\n     * Returns extended query part for given view/column combination\n     * (if variants must be shown in lists and column name is \"oxtitle\")\n     *\n     * @param string $sViewTable view name\n     * @param string $sColumn    column name\n     * @param int    $iCnt       column count\n     *\n     * @return string\n     */\n    protected function getExtendedColQuery($sViewTable, $sColumn, $iCnt)\n    {\n        // multilanguage\n        $sVarSelect = \"$sViewTable.oxvarselect\";\n\n        return \" IF( {$sViewTable}.{$sColumn} != '', {$sViewTable}.{$sColumn}, CONCAT((select oxart.{$sColumn} \" .\n                \"from {$sViewTable} as oxart \" .\n                \"where oxart.oxid = {$sViewTable}.oxparentid),', ',{$sVarSelect})) as _{$iCnt}\";\n    }\n\n    /**\n     * Formats and returns part of SQL query for sorting\n     *\n     * @return string\n     */\n    protected function getSorting()\n    {\n        return ' order by _' . $this->getSortCol() . ' ' . $this->getSortDir() . ' ';\n    }\n\n    /**\n     * Returns part of SQL query for limiting number of entries from DB\n     *\n     * @param int $iStart start position\n     *\n     * @return string\n     */\n    protected function getLimit($iStart)\n    {\n        $iLimit = (int) Registry::getRequest()->getRequestEscapedParameter(\"results\");\n        $iLimit = $iLimit ? $iLimit : $this->_iSqlLimit;\n\n        return \" limit $iStart, $iLimit \";\n    }\n\n    /**\n     * Returns part of SQL query for filtering DB data\n     *\n     * @return string\n     */\n    protected function getFilter()\n    {\n        $sQ = '';\n        $aFilter = Registry::getRequest()->getRequestEscapedParameter('aFilter');\n        if (is_array($aFilter) && count($aFilter)) {\n            $aCols = $this->getVisibleColNames();\n            $oDb = DatabaseProvider::getDb();\n            $oStr = Str::getStr();\n\n            foreach ($aFilter as $sCol => $sValue) {\n                // skipping empty filters\n                if ($sValue === '') {\n                    continue;\n                }\n\n                $iCol = (int) str_replace('_', '', $sCol);\n                if (isset($aCols[$iCol])) {\n                    if ($sQ) {\n                        $sQ .= ' and ';\n                    }\n\n                    // escaping special characters\n                    $sValue = str_replace(['%', '_'], ['\\%', '\\_'], $sValue);\n\n                    // possibility to search in the middle ..\n                    $sValue = $oStr->preg_replace('/^\\*/', '%', $sValue);\n\n                    $sQ .= $this->getViewName($aCols[$iCol][1]) . '.' . $aCols[$iCol][0];\n                    $sQ .= ' like ' . $oDb->Quote('%' . $sValue . '%') . ' ';\n                }\n            }\n        }\n\n        return $sQ;\n    }\n\n    /**\n     * Adds filter SQL to current query\n     *\n     * @param string $sQ query to add filter condition\n     *\n     * @return string\n     */\n    protected function addFilter($sQ)\n    {\n        if ($sQ && ($sFilter = $this->getFilter())) {\n            $sQ .= ((stristr($sQ, 'where') === false) ? 'where' : ' and ') . $sFilter;\n        }\n\n        return $sQ;\n    }\n\n    /**\n     * Returns DB records as plain indexed array\n     *\n     * @param string $sQ SQL query\n     *\n     * @return array\n     */\n    protected function getAll($sQ)\n    {\n        return DatabaseProvider::getDb()->getCol($sQ);\n    }\n\n    /**\n     * Checks user input and returns SQL sorting direction key\n     *\n     * @return string\n     */\n    protected function getSortDir()\n    {\n        $sDir = Registry::getRequest()->getRequestEscapedParameter('dir');\n        if (!in_array($sDir, $this->_aPosDir)) {\n            $sDir = $this->_aPosDir[0];\n        }\n\n        return $sDir;\n    }\n\n    /**\n     * Returns position from where data must be loaded\n     *\n     * @return int\n     */\n    protected function getStartIndex()\n    {\n        return (int) Registry::getRequest()->getRequestEscapedParameter('startIndex');\n    }\n\n    /**\n     * Returns amount of records which can be found according to passed SQL query\n     *\n     * @param string $sQ SQL query\n     *\n     * @return int\n     */\n    protected function getTotalCount($sQ)\n    {\n        // TODO: implement caching here\n\n        // we can cache total count ...\n\n        // $sCountCacheKey = md5( $sQ );\n\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        return (int) DatabaseProvider::getMaster()->getOne($sQ);\n    }\n\n    /**\n     * Returns array with DB records\n     *\n     * @param string $sQ SQL query\n     *\n     * @return array\n     */\n    protected function getDataFields($sQ)\n    {\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        return DatabaseProvider::getMaster()->getAll($sQ);\n    }\n\n    /**\n     * Outputs JSON encoded data\n     *\n     * @param array $aData data to output\n     */\n    protected function outputResponse($aData)\n    {\n        $this->output(json_encode($aData));\n    }\n\n    /**\n     * Echoes given string\n     *\n     * @param string $sOut string to echo\n     */\n    protected function output($sOut)\n    {\n        echo $sOut;\n    }\n\n    /**\n     * Return the view name of the given table if a view exists, otherwise the table name itself\n     *\n     * @param string $sTable table name\n     *\n     * @return string\n     */\n    protected function getViewName($sTable)\n    {\n        return oxNew(TableViewNameGenerator::class)->getViewName(\n            $sTable,\n            Registry::getRequest()->getRequestEscapedParameter('editlanguage')\n        );\n    }\n\n    /**\n     * Formats data array which later will be processed by _outputResponse method\n     *\n     * @param string $sCountQ count query\n     * @param string $sQ      data load query\n     *\n     * @return array\n     */\n    protected function getData($sCountQ, $sQ)\n    {\n        $sQ = $this->addFilter($sQ);\n        $sCountQ = $this->addFilter($sCountQ);\n\n        $aResponse['startIndex'] = $iStart = $this->getStartIndex();\n        $aResponse['sort'] = '_' . $this->getSortCol();\n        $aResponse['dir'] = $this->getSortDir();\n\n        $debug = ContainerFacade::getParameter('oxid_esales.debug_mode');\n        if ($debug) {\n            $aResponse['countsql'] = $sCountQ;\n        }\n\n        $aResponse['records'] = [];\n\n        // skip further execution if no records were found ...\n        if (($iTotal = $this->getTotalCount($sCountQ))) {\n            $sQ .= $this->getSorting();\n            $sQ .= $this->getLimit($iStart);\n\n            if ($debug) {\n                $aResponse['datasql'] = $sQ;\n            }\n\n            $aResponse['records'] = $this->getDataFields($sQ);\n        }\n\n        $aResponse['totalRecords'] = $iTotal;\n\n        return $aResponse;\n    }\n\n    /**\n     * Marks article seo url as expired\n     *\n     * @param array $aArtIds article id's\n     * @param array $aCatIds ids if categories, which must be removed from oxseo\n     *\n     * @return null\n     */\n    public function resetArtSeoUrl($aArtIds, $aCatIds = null)\n    {\n        if (empty($aArtIds)) {\n            return;\n        }\n\n        if (!is_array($aArtIds)) {\n            $aArtIds = [$aArtIds];\n        }\n\n        $sShopId = Registry::getConfig()->getShopId();\n        foreach ($aArtIds as $sArtId) {\n            /** @var \\OxidEsales\\Eshop\\Core\\SeoEncoder $oSeoEncoder */\n            Registry::getSeoEncoder()->markAsExpired($sArtId, $sShopId, 1, null, \"oxtype='oxarticle'\");\n        }\n    }\n\n    /**\n     * Reset output cache\n     */\n    public function resetContentCache()\n    {\n        $blDeleteCacheOnLogout = Registry::getConfig()->getConfigParam('blClearCacheOnLogout');\n\n        if (!$blDeleteCacheOnLogout) {\n            $this->resetCaches();\n\n            Registry::getUtils()->oxResetFileCache();\n        }\n    }\n\n    /**\n     * Resets counters values from cache. Resets price category articles, category articles,\n     * vendor articles, manufacturer articles count.\n     *\n     * @param string $sCounterType counter type\n     * @param string $sValue       reset value\n     */\n    public function resetCounter($sCounterType, $sValue = null)\n    {\n        $blDeleteCacheOnLogout = Registry::getConfig()->getConfigParam('blClearCacheOnLogout');\n\n        if (!$blDeleteCacheOnLogout) {\n            $myUtilsCount = Registry::getUtilsCount();\n            switch ($sCounterType) {\n                case 'priceCatArticle':\n                    $myUtilsCount->resetPriceCatArticleCount($sValue);\n                    break;\n                case 'catArticle':\n                    $myUtilsCount->resetCatArticleCount($sValue);\n                    break;\n                case 'vendorArticle':\n                    $myUtilsCount->resetVendorArticleCount($sValue);\n                    break;\n                case 'manufacturerArticle':\n                    $myUtilsCount->resetManufacturerArticleCount($sValue);\n                    break;\n            }\n\n            $this->resetContentCache();\n        }\n    }\n\n    /**\n     * Resets output caches\n     */\n    protected function resetCaches()\n    {\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ListReview.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse oxAdminList;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * user list \"view\" class.\n */\nclass ListReview extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleList\n{\n    /**\n     * Type of list.\n     *\n     * @var string\n     */\n    protected $_sListType = 'oxlist';\n\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxreview';\n\n    /**\n     * Viewable list size getter\n     *\n     * @return int\n     */\n    public function getViewListSize()\n    {\n        return $this->getUserDefListSize();\n    }\n\n    /** @inheritdoc */\n    public function render()\n    {\n        oxAdminList::render();\n\n        $this->_aViewData[\"menustructure\"] = $this->getNavigation()->getDomXml()->documentElement->childNodes;\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $this->_aViewData[\"articleListTable\"] = $tableViewNameGenerator->getViewName('oxarticles');\n\n        return \"list_review\";\n    }\n\n    /**\n     * Returns select query string\n     *\n     * @param object $oObject list item object\n     *\n     * @return string\n     */\n    protected function buildSelectString($oObject = null)\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sArtTable = $tableViewNameGenerator->getViewName('oxarticles', $this->_iEditLang);\n\n        $sQ = \"select oxreviews.oxid, oxreviews.oxcreate, oxreviews.oxtext, oxreviews.oxobjectid, {$sArtTable}.oxparentid, {$sArtTable}.oxtitle as oxtitle, {$sArtTable}.oxvarselect as oxvarselect, oxparentarticles.oxtitle as parenttitle, \";\n        $sQ .= \"concat( {$sArtTable}.oxtitle, if(isnull(oxparentarticles.oxtitle), '', oxparentarticles.oxtitle), {$sArtTable}.oxvarselect) as arttitle from oxreviews \";\n        $sQ .= \"left join $sArtTable as {$sArtTable} on {$sArtTable}.oxid=oxreviews.oxobjectid and 'oxarticle' = oxreviews.oxtype \";\n        $sQ .= \"left join $sArtTable as oxparentarticles on oxparentarticles.oxid = {$sArtTable}.oxparentid \";\n        $sQ .= \"where 1 and oxreviews.oxlang = '{$this->_iEditLang}' \";\n\n\n        //removing parent id checking from sql\n        $sStr = \"/\\s+and\\s+\" . $sArtTable . \"\\.oxparentid\\s*=\\s*''/\";\n        $sQ = Str::getStr()->preg_replace($sStr, \" \", $sQ);\n\n        return \" $sQ and {$sArtTable}.oxid is not null \";\n    }\n\n    /**\n     * Adds filtering conditions to query string\n     *\n     * @param array  $aWhere filter conditions\n     * @param string $sSql   query string\n     *\n     * @return string\n     */\n    protected function prepareWhereQuery($aWhere, $sSql)\n    {\n        $sSql = parent::prepareWhereQuery($aWhere, $sSql);\n\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sArtTable = $tableViewNameGenerator->getViewName('oxarticles', $this->_iEditLang);\n        $sArtTitleField = \"{$sArtTable}.oxtitle\";\n\n        // if searching in article title field, updating sql for this case\n        if (isset($this->_aWhere[$sArtTitleField]) && $this->_aWhere[$sArtTitleField]) {\n            $sSqlForTitle = \" (CONCAT( {$sArtTable}.oxtitle, if(isnull(oxparentarticles.oxtitle), '', oxparentarticles.oxtitle), {$sArtTable}.oxvarselect)) \";\n            $sSql = Str::getStr()->preg_replace(\"/{$sArtTable}\\.oxtitle\\s+like/\", \"$sSqlForTitle like\", $sSql);\n        }\n\n        return $sSql;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ListUser.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse oxAdminList;\n\n/**\n * user list \"view\" class.\n */\nclass ListUser extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\UserList\n{\n    /**\n     * Viewable list size getter\n     *\n     * @return int\n     */\n    public function getViewListSize()\n    {\n        return $this->getUserDefListSize();\n    }\n\n    /**\n     * Sets SQL query parameters (such as sorting),\n     * executes parent method parent::Init().\n     */\n    public function init()\n    {\n        oxAdminList::init();\n    }\n\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n        $this->_aViewData[\"menustructure\"] = $this->getNavigation()->getDomXml()->documentElement->childNodes;\n\n        return \"list_user\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/LoginController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Exception\\CookieException;\nuse OxidEsales\\Eshop\\Core\\Exception\\UserException;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\ShopVersion;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\n\n/**\n * Administrator login form.\n * Performs administrator login form data collection.\n */\nclass LoginController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /** Login page view id. */\n    const VIEW_ID = 'login';\n\n    /**\n     * Sets value for _sThisAction to \"login\".\n     */\n    public function __construct()\n    {\n        Registry::getConfig()->setConfigParam('blAdmin', true);\n        $this->_sThisAction = \"login\";\n    }\n\n    /**\n     * Executes parent method parent::render(), creates shop object, sets template parameters\n     * and returns name of template file \"login\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        $myConfig = Registry::getConfig();\n\n        if (!$myConfig->isSsl()) {\n            $adminUrl = ContainerFacade::getParameter('oxid_esales.shop_admin_url');\n            if ($adminUrl && str_starts_with($adminUrl, 'https://')) {\n                Registry::getUtils()->redirect($adminUrl, false, 302);\n            }\n        }\n\n        //resets user once on this screen.\n        $oUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n        $oUser->logout();\n\n        \\OxidEsales\\Eshop\\Core\\Controller\\BaseController::render();\n\n        $this->setShopConfigParameters();\n\n        if ($myConfig->isDemoShop()) {\n            // demo\n            $this->addTplParam(\"user\", \"admin\");\n            $this->addTplParam(\"pwd\", \"admin\");\n        }\n        //#533 user profile\n        $this->addTplParam(\"profiles\", Registry::getUtils()->loadAdminProfile($myConfig->getConfigParam('aInterfaceProfiles')));\n\n        $aLanguages = $this->getAvailableLanguages();\n        $this->addTplParam(\"aLanguages\", $aLanguages);\n\n        // setting templates language to selected language id\n        foreach ($aLanguages as $iKey => $oLang) {\n            if ($aLanguages[$iKey]->selected) {\n                Registry::getLang()->setTplLanguage($iKey);\n                break;\n            }\n        }\n\n        return \"login\";\n    }\n\n    /**\n     * Sets configuration parameters related to current shop.\n     */\n    protected function setShopConfigParameters()\n    {\n        $myConfig = Registry::getConfig();\n\n        $oBaseShop = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Shop::class);\n        $oBaseShop->load($myConfig->getBaseShopId());\n        $this->getViewConfig()->setViewConfigParam('sShopVersion', oxNew(ShopVersion::class)->getVersion());\n    }\n\n    /**\n     * Checks user login data, on success returns \"admin_start\".\n     *\n     * @return mixed\n     */\n    public function checklogin()\n    {\n        $myUtilsServer = Registry::getUtilsServer();\n        $myUtilsView = Registry::getUtilsView();\n\n        $sUser = Registry::getRequest()->getRequestParameter('user');\n        $sPass = Registry::getRequest()->getRequestParameter('pwd');\n        $sProfile = Registry::getRequest()->getRequestEscapedParameter('profile');\n\n        try { // trying to login\n            $session = Registry::getSession();\n            $adminProfiles = $session->getVariable(\"aAdminProfiles\");\n            $session->initNewSession();\n            $session->setVariable(\"aAdminProfiles\", $adminProfiles);\n\n            /** @var \\OxidEsales\\Eshop\\Application\\Model\\User $oUser */\n            $oUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n            $oUser->login($sUser, $sPass);\n\n            if ($oUser->oxuser__oxrights->value === 'user') {\n                throw oxNew(UserException::class, 'ERROR_MESSAGE_USER_NOVALIDLOGIN');\n            }\n\n            $iSubshop = (int) $oUser->oxuser__oxrights->value;\n            if ($iSubshop) {\n                Registry::getSession()->setVariable(\"shp\", $iSubshop);\n                Registry::getSession()->setVariable('currentadminshop', $iSubshop);\n                Registry::getConfig()->setShopId($iSubshop);\n            }\n        } catch (UserException|CookieException $oEx) {\n            $myUtilsView->addErrorToDisplay($oEx);\n            $oStr = Str::getStr();\n            $this->addTplParam('user', $oStr->htmlspecialchars($sUser));\n            $this->addTplParam('pwd', $oStr->htmlspecialchars($sPass));\n            $this->addTplParam('profile', $sProfile ? $oStr->htmlspecialchars($sProfile) : '');\n\n            return;\n        } catch (\\OxidEsales\\Eshop\\Core\\Exception\\ConnectionException $oEx) {\n            $myUtilsView->addErrorToDisplay($oEx);\n        }\n\n        //execute onAdminLogin() event\n        $oEvenHandler = oxNew(\\OxidEsales\\Eshop\\Core\\SystemEventHandler::class);\n        $oEvenHandler->onAdminLogin(Registry::getConfig()->getShopId());\n\n        // #533\n        if (isset($sProfile)) {\n            $aProfiles = Registry::getSession()->getVariable(\"aAdminProfiles\");\n            if ($aProfiles && isset($aProfiles[$sProfile])) {\n                // setting cookie to store last locally used profile\n                $myUtilsServer->setOxCookie(\"oxidadminprofile\", $sProfile . \"@\" . implode(\"@\", $aProfiles[$sProfile]), time() + 31536000, \"/\");\n                Registry::getSession()->setVariable(\"profile\", $aProfiles[$sProfile]);\n            }\n        } else {\n            //deleting cookie info, as setting profile to default\n            $myUtilsServer->setOxCookie(\"oxidadminprofile\", \"\", time() - 3600, \"/\");\n        }\n\n        // languages\n        $iLang = Registry::getRequest()->getRequestEscapedParameter(\"chlanguage\");\n        $aLanguages = Registry::getLang()->getAdminTplLanguageArray();\n        if ($iLang === null || !isset($aLanguages[$iLang])) {\n            $iLang = key($aLanguages);\n        }\n\n        $myUtilsServer->setOxCookie(\"oxidadminlanguage\", $aLanguages[$iLang]->abbr, time() + 31536000, \"/\");\n\n        //P\n        //\\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable( \"blAdminTemplateLanguage\", $iLang );\n        Registry::getLang()->setTplLanguage($iLang);\n\n        return \"admin_start\";\n    }\n\n    /**\n     * Users are always authorized to use login page.\n     * Rewrites authorization method.\n     *\n     * @return boolean\n     */\n    protected function authorize()\n    {\n        return true;\n    }\n\n    /**\n     * Current view ID getter\n     *\n     * @return string\n     */\n    public function getViewId()\n    {\n        return self::VIEW_ID;\n    }\n\n    /**\n     * Get available admin interface languages\n     *\n     * @return array\n     */\n    protected function getAvailableLanguages()\n    {\n        $sDefLang = Registry::getUtilsServer()->getOxCookie('oxidadminlanguage');\n        $sDefLang = $sDefLang ? $sDefLang : $this->getBrowserLanguage();\n\n        $aLanguages = Registry::getLang()->getAdminTplLanguageArray();\n        foreach ($aLanguages as $oLang) {\n            $oLang->selected = ($sDefLang == $oLang->abbr) ? 1 : 0;\n        }\n\n        return $aLanguages;\n    }\n\n    /**\n     * Get detected user browser language abbervation\n     *\n     * @return string\n     */\n    protected function getBrowserLanguage()\n    {\n        return strtolower(substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2));\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ManufacturerController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Returns template, that arranges two other templates (\"manufacturer_list\"\n * and \"manufacturer_main\") to frame.\n * Admin Menu: Settings -> Manufacturers\n */\nclass ManufacturerController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'manufacturer';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ManufacturerList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin Manufacturer list manager.\n */\nclass ManufacturerList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'manufacturer_list';\n\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxmanufacturer';\n\n    /**\n     * Default SQL sorting parameter (default null).\n     *\n     * @var string\n     */\n    protected $_sDefSortField = 'oxtitle';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ManufacturerMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse stdClass;\n\n/**\n * Admin manufacturer main screen.\n * Performs collection and updating (on user submit) main item information.\n */\nclass ManufacturerMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Executes parent method parent::render(),\n     * and returns name of template file\n     * \"manufacturer_main\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oManufacturer = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Manufacturer::class);\n            $oManufacturer->loadInLang($this->_iEditLang, $soxId);\n\n            $oOtherLang = $oManufacturer->getAvailableInLangs();\n            if (!isset($oOtherLang[$this->_iEditLang])) {\n                $oManufacturer->loadInLang(key($oOtherLang), $soxId);\n            }\n            $this->_aViewData[\"edit\"] = $oManufacturer;\n\n            // category tree\n            $this->createCategoryTree(\"artcattree\");\n\n            //Disable editing for derived articles\n            if ($oManufacturer->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n\n            // remove already created languages\n            $aLang = array_diff(Registry::getLang()->getLanguageNames(), $oOtherLang);\n            if (count($aLang)) {\n                $this->_aViewData[\"posslang\"] = $aLang;\n            }\n\n            foreach ($oOtherLang as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $oLang;\n            }\n        }\n\n        if ($this->getViewConfig()->isAltImageServerConfigured()) {\n            $this->_aViewData[\"imageUrl\"] = ContainerFacade::getParameter('oxid_esales.alternative_image_url');\n        }\n\n        if (Registry::getRequest()->getRequestEscapedParameter(\"aoc\")) {\n            $oManufacturerMainAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ManufacturerMainAjax::class);\n            $this->_aViewData['oxajax'] = $oManufacturerMainAjax->getColumns();\n\n            return \"popups/manufacturer_main\";\n        }\n\n        return \"manufacturer_main\";\n    }\n\n    /**\n     * Saves selection list parameters changes.\n     *\n     * @return mixed\n     */\n    public function save()\n    {\n        parent::save();\n\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        if (!isset($aParams['oxmanufacturers__oxactive'])) {\n            $aParams['oxmanufacturers__oxactive'] = 0;\n        }\n\n        $oManufacturer = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Manufacturer::class);\n\n        if ($soxId != \"-1\") {\n            $oManufacturer->loadInLang($this->_iEditLang, $soxId);\n        } else {\n            $aParams['oxmanufacturers__oxid'] = null;\n        }\n\n        //Disable editing for derived articles\n        if ($oManufacturer->isDerived()) {\n            return;\n        }\n\n        $oManufacturer->setLanguage(0);\n        $oManufacturer->assign($aParams);\n        $oManufacturer->setLanguage($this->_iEditLang);\n        $oManufacturer->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oManufacturer->getId());\n    }\n\n    /**\n     * Saves selection list parameters changes in different language (eg. english).\n     *\n     * @return mixed\n     */\n    public function saveInnLang()\n    {\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        if (!isset($aParams['oxmanufacturers__oxactive'])) {\n            $aParams['oxmanufacturers__oxactive'] = 0;\n        }\n\n        $oManufacturer = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Manufacturer::class);\n\n        if ($soxId != \"-1\") {\n            $oManufacturer->loadInLang($this->_iEditLang, $soxId);\n        } else {\n            $aParams['oxmanufacturers__oxid'] = null;\n        }\n\n        //Disable editing for derived articles\n        if ($oManufacturer->isDerived()) {\n            return;\n        }\n\n        $oManufacturer->setLanguage(0);\n        $oManufacturer->assign($aParams);\n        $oManufacturer->setLanguage($this->_iEditLang);\n        $oManufacturer->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oManufacturer->getId());\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ManufacturerMainAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages manufacturer assignment to articles\n */\nclass ManufacturerMainAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * If true extended column selection will be build\n     *\n     * @var bool\n     */\n    protected $_blAllowExtColumns = true;\n\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = [\n        // field , table, visible, multilanguage, id\n        'container1' => [\n            ['oxartnum', 'oxarticles', 1, 0, 0],\n            ['oxtitle', 'oxarticles', 1, 1, 0],\n            ['oxean', 'oxarticles', 1, 0, 0],\n            ['oxmpn', 'oxarticles', 0, 0, 0],\n            ['oxprice', 'oxarticles', 0, 0, 0],\n            ['oxstock', 'oxarticles', 0, 0, 0],\n            ['oxid', 'oxarticles', 0, 0, 1]\n        ],\n        'container2' => [\n            ['oxartnum', 'oxarticles', 1, 0, 0],\n            ['oxtitle', 'oxarticles', 1, 1, 0],\n            ['oxean', 'oxarticles', 1, 0, 0],\n            ['oxmpn', 'oxarticles', 0, 0, 0],\n            ['oxprice', 'oxarticles', 0, 0, 0],\n            ['oxstock', 'oxarticles', 0, 0, 0],\n            ['oxid', 'oxarticles', 0, 0, 1]\n        ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        // looking for table/view\n        $articlesViewName = $this->getViewName('oxarticles');\n        $objectToCategoryViewName = $this->getViewName('oxobject2category');\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $manufacturerId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $syncedManufacturerId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // Manufacturer selected or not ?\n        if (!$manufacturerId) {\n            // performance\n            $query = ' from ' . $articlesViewName . ' where ' . $articlesViewName . '.oxshopid=\"' . $config->getShopId() . '\" and 1 ';\n            $query .= $config->getConfigParam('blVariantsSelection') ? '' : \" and $articlesViewName.oxparentid = '' and $articlesViewName.oxmanufacturerid != \" . $database->quote($syncedManufacturerId);\n        } elseif ($syncedManufacturerId && $syncedManufacturerId != $manufacturerId) {\n            // selected category ?\n            $query = \" from $objectToCategoryViewName left join $articlesViewName on \";\n            $query .= $config->getConfigParam('blVariantsSelection') ? \" ( $articlesViewName.oxid = $objectToCategoryViewName.oxobjectid or $articlesViewName.oxparentid = $objectToCategoryViewName.oxobjectid )\" : \" $articlesViewName.oxid = $objectToCategoryViewName.oxobjectid \";\n            $query .= 'where ' . $articlesViewName . '.oxshopid=\"' . $config->getShopId() . '\" and ' . $objectToCategoryViewName . '.oxcatnid = ' . $database->quote($manufacturerId) . ' and ' . $articlesViewName . '.oxmanufacturerid != ' . $database->quote($syncedManufacturerId);\n            $query .= $config->getConfigParam('blVariantsSelection') ? '' : \" and $articlesViewName.oxparentid = '' \";\n        } else {\n            $query = \" from $articlesViewName where $articlesViewName.oxmanufacturerid = \" . $database->quote($manufacturerId);\n            $query .= $config->getConfigParam('blVariantsSelection') ? '' : \" and $articlesViewName.oxparentid = '' \";\n        }\n\n        return $query;\n    }\n\n    /**\n     * Adds filter SQL to current query\n     *\n     * @param string $query query to add filter condition\n     *\n     * @return string\n     */\n    protected function addFilter($query)\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $articleViewName = $this->getViewName('oxarticles');\n        $query = parent::addFilter($query);\n\n        // display variants or not ?\n        $query .= $config->getConfigParam('blVariantsSelection') ? ' group by ' . $articleViewName . '.oxid ' : '';\n\n        return $query;\n    }\n\n    /**\n     * Removes article from Manufacturer config\n     */\n    public function removeManufacturer()\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $articleIds = $this->getActionIds('oxarticles.oxid');\n        $manufacturerId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter(\"all\")) {\n            $articleViewTable = $this->getViewName('oxarticles');\n            $articleIds = $this->getAll($this->addFilter(\"select $articleViewTable.oxid \" . $this->getQuery()));\n        }\n\n        if (is_array($articleIds) && !empty($articleIds)) {\n            $query = $this->formManufacturerRemovalQuery($articleIds);\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->execute($query);\n\n            $this->resetCounter(\"manufacturerArticle\", $manufacturerId);\n        }\n    }\n\n    /**\n     * Forms and returns query for manufacturers removal.\n     *\n     * @param array $articlesToRemove Ids of manufacturers which should be removed.\n     *\n     * @return string\n     */\n    protected function formManufacturerRemovalQuery($articlesToRemove)\n    {\n        return \"\n          UPDATE oxarticles\n          SET oxmanufacturerid = null\n          WHERE oxid IN ( \" . implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($articlesToRemove)) . \") \";\n    }\n\n    /**\n     * Adds article to Manufacturer config\n     */\n    public function addManufacturer()\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $articleIds = $this->getActionIds('oxarticles.oxid');\n        $manufacturerId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $articleViewName = $this->getViewName('oxarticles');\n            $articleIds = $this->getAll($this->addFilter(\"select $articleViewName.oxid \" . $this->getQuery()));\n        }\n\n        if ($manufacturerId && $manufacturerId != \"-1\" && is_array($articleIds)) {\n            $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n            $query = $this->formArticleToManufacturerAdditionQuery($manufacturerId, $articleIds);\n            $database->execute($query);\n            $this->resetCounter(\"manufacturerArticle\", $manufacturerId);\n        }\n    }\n\n    /**\n     * Forms and returns query for articles addition to manufacturer.\n     *\n     * @param string $manufacturerId Manufacturer id.\n     * @param array  $articlesToAdd  Array of article ids to be added to manufacturer.\n     *\n     * @return string\n     */\n    protected function formArticleToManufacturerAdditionQuery($manufacturerId, $articlesToAdd)\n    {\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        return \"\n            UPDATE oxarticles\n            SET oxmanufacturerid = \" . $database->quote($manufacturerId) . \"\n            WHERE oxid IN ( \" . implode(\", \", $database->quoteArray($articlesToAdd)) . \" )\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ManufacturerPicture.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController;\nuse OxidEsales\\Eshop\\Application\\Model\\Manufacturer;\nuse OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin manufacturer picture screen.\n * Handle manufacturer picture actions.\n */\nclass ManufacturerPicture extends AdminDetailsController\n{\n    public function render(): string\n    {\n        parent::render();\n\n        $this->_aViewData['edit'] = $manufacturer = oxNew(Manufacturer::class);\n\n        $oxid = $this->getEditObjectId();\n        if (isset($oxid) && $oxid != '-1') {\n            $manufacturer->load($oxid);\n        }\n\n        return \"manufacturer_picture\";\n    }\n\n    public function save(): void\n    {\n        if (Registry::getConfig()->isDemoShop()) {\n            $this->showError('MANUFACTURER_PICTURES_UPLOAD_IS_DISABLED');\n\n            return;\n        }\n\n        if (!$this->validateRequestImages()) {\n            Registry::getUtilsView()->addErrorToDisplay('ERROR_MESSAGE_WRONG_IMAGE_FILE_TYPE');\n            return;\n        }\n\n        parent::save();\n\n        $manufacturer = oxNew(Manufacturer::class);\n        if ($manufacturer->load($this->getEditObjectId())) {\n            $this->fetchChanges($manufacturer);\n            $manufacturer->assign(Registry::getRequest()->getRequestEscapedParameter(\"editval\"));\n            $manufacturer = Registry::getUtilsFile()->processFiles($manufacturer);\n\n            $this->checkNewImagesCount();\n\n            $manufacturer->save();\n            $this->setEditObjectId($manufacturer->getId());\n        }\n    }\n\n    public function deletePicture(): void\n    {\n        if (Registry::getConfig()->isDemoShop()) {\n            $this->showError('MANUFACTURER_PICTURES_UPLOAD_IS_DISABLED');\n\n            return;\n        }\n\n        $pictureFieldName = Registry::getRequest()->getRequestEscapedParameter('masterPictureField');\n        if (empty($pictureFieldName)) {\n            return;\n        }\n\n        $manufacturer = oxNew(Manufacturer::class);\n        $manufacturer->load($this->getEditObjectId());\n\n        $pictureKey = 'oxmanufacturers__' . $pictureFieldName;\n        $pictureType = $manufacturer->getImageType($pictureFieldName);\n\n        if ($pictureType !== false) {\n            $manufacturer->deletePicture($manufacturer->$pictureKey->value, $pictureType, $pictureFieldName);\n\n            $manufacturer->$pictureKey = new Field();\n            $manufacturer->save();\n        }\n    }\n\n    private function fetchChanges(Manufacturer $manufacturer): array\n    {\n        $changes = [];\n\n        foreach (Registry::getRequest()->getRequestEscapedParameter(\"editval\") as $fieldName => $value) {\n            if ($manufacturer->$fieldName->value !== $value) {\n                $changes[] = $manufacturer->$fieldName->value;\n            }\n        }\n\n        return $changes;\n    }\n\n    private function checkNewImagesCount(): void\n    {\n        if (Registry::getUtilsFile()->getNewFilesCounter() == 0) {\n            $this->showError('NO_PICTURES_CHANGES');\n        }\n    }\n\n    private function showError(string $message, bool $isBlFull = false): void\n    {\n        $exception = oxNew(ExceptionToDisplay::class);\n        $exception->setMessage($message);\n        Registry::getUtilsView()->addErrorToDisplay($exception, $isBlFull);\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ManufacturerSeo.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Manufacturer seo config class\n */\nclass ManufacturerSeo extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ObjectSeo\n{\n    /**\n     * Updating showsuffix field\n     *\n     * @return null\n     */\n    public function save()\n    {\n        $oManufacturer = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n        $oManufacturer->init('oxmanufacturers');\n        if ($oManufacturer->load($this->getEditObjectId())) {\n            $sShowSuffixField = 'oxmanufacturers__oxshowsuffix';\n            $blShowSuffixParameter = Registry::getRequest()->getRequestEscapedParameter('blShowSuffix');\n            $oManufacturer->$sShowSuffixField = new \\OxidEsales\\Eshop\\Core\\Field((int) $blShowSuffixParameter);\n            $oManufacturer->save();\n        }\n\n        return parent::save();\n    }\n\n    /**\n     * Returns current object type seo encoder object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderManufacturer\n     */\n    protected function getEncoder()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderManufacturer::class);\n    }\n\n    /**\n     * This SEO object supports suffixes so return TRUE\n     *\n     * @return bool\n     */\n    public function isSuffixSupported()\n    {\n        return true;\n    }\n\n    /**\n     * Returns url type\n     *\n     * @return string\n     */\n    protected function getType()\n    {\n        return 'oxmanufacturer';\n    }\n\n    /**\n     * Returns true if SEO object id has suffix enabled\n     *\n     * @return bool\n     */\n    public function isEntrySuffixed()\n    {\n        $oManufacturer = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Manufacturer::class);\n        if ($oManufacturer->load($this->getEditObjectId())) {\n            return (bool) $oManufacturer->oxmanufacturers__oxshowsuffix->value;\n        }\n    }\n\n    /**\n     * Returns seo uri\n     *\n     * @return string\n     */\n    public function getEntryUri()\n    {\n        $oManufacturer = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Manufacturer::class);\n        if ($oManufacturer->load($this->getEditObjectId())) {\n            return $this->getEncoder()->getManufacturerUri($oManufacturer, $this->getEditLang());\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ModuleConfiguration.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Module\\Module;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ModuleConfigurationDaoBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setting\\Event\\SettingChangedEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setting\\Setting;\n\n#[\\AllowDynamicProperties]\nclass ModuleConfiguration extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ShopConfiguration\n{\n    /**\n     * Add additional config type for modules.\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->_aConfParams['password'] = 'confpassword';\n    }\n\n    /** @inheritdoc */\n    public function render()\n    {\n        $this->_sModuleId = $this->getSelectedModuleId();\n        $moduleId = $this->_sModuleId;\n\n        try {\n            $moduleConfiguration = ContainerFacade::get(ModuleConfigurationDaoBridgeInterface::class)\n                ->get($moduleId);\n            if (!empty($moduleConfiguration->getModuleSettings())) {\n                $formatModuleSettings = $this\n                    ->formatModuleSettingsForTemplate($moduleConfiguration->getModuleSettings());\n\n                $this->_aViewData[\"var_constraints\"] = $formatModuleSettings['constraints'];\n                $this->_aViewData[\"var_grouping\"] = $formatModuleSettings['grouping'];\n\n                foreach ($this->_aConfParams as $sType => $sParam) {\n                    $this->_aViewData[$sParam] = $formatModuleSettings['vars'][$sType] ?? null;\n                }\n            }\n        } catch (\\Throwable $throwable) {\n            Registry::getUtilsView()->addErrorToDisplay($throwable);\n            Registry::getLogger()->error($throwable->getMessage());\n        }\n\n        $module = oxNew(Module::class);\n        $module->load($moduleId);\n\n        $this->_aViewData['oModule'] = $module;\n\n        return 'module_config';\n    }\n\n    /**\n     * Saves shop configuration variables\n     */\n    public function saveConfVars()\n    {\n        $this->resetContentCache();\n        $this->_sModuleId = $this->getSelectedModuleId();\n\n        try {\n            $this->saveModuleConfigVariables($this->_sModuleId, $this->getConfigVariablesFromRequest());\n        } catch (\\Throwable $throwable) {\n            Registry::getUtilsView()->addErrorToDisplay($throwable);\n            Registry::getLogger()->error($throwable->getMessage());\n        }\n    }\n\n    /**\n     * @return string\n     */\n    private function getSelectedModuleId(): string\n    {\n        $moduleId = $this->_sEditObjectId\n            ?? Registry::getRequest()->getRequestEscapedParameter('oxid')\n            ?? Registry::getSession()->getVariable('saved_oxid');\n\n        if ($moduleId === null) {\n            throw new \\InvalidArgumentException('Module id not found.');\n        }\n\n        return $moduleId;\n    }\n\n    private function saveModuleConfigVariables(string $moduleId, array $variables): void\n    {\n        $moduleConfigurationDaoBridge = ContainerFacade::get(ModuleConfigurationDaoBridgeInterface::class);\n        $moduleConfiguration = $moduleConfigurationDaoBridge->get($moduleId);\n\n        foreach ($variables as $name => $value) {\n            if ($moduleConfiguration->hasModuleSetting($name)) {\n                $moduleSetting = $moduleConfiguration->getModuleSetting($name);\n\n                if ($moduleSetting->getType() === 'aarr') {\n                    $value = $this->multilineToAarray($value);\n                }\n                if ($moduleSetting->getType() === 'arr') {\n                    $value = $this->multilineToArray($value);\n                }\n                if ($moduleSetting->getType() === 'bool') {\n                    $value = filter_var($value, FILTER_VALIDATE_BOOLEAN);\n                }\n                if ($moduleSetting->getType() === 'num' && filter_var($value, FILTER_VALIDATE_INT) !== false) {\n                    $value = (int)$value;\n                } elseif ($moduleSetting->getType() === 'num' && filter_var($value, FILTER_VALIDATE_FLOAT) !== false) {\n                    $value = (float)$value;\n                }\n\n                $moduleSetting->setValue($value);\n\n                ContainerFacade::dispatch(new SettingChangedEvent(\n                    $name,\n                    (int)Registry::getConfig()->getShopId(),\n                    $moduleId\n                ));\n            }\n        }\n\n        $moduleConfigurationDaoBridge->save($moduleConfiguration);\n    }\n\n    /**\n     * @return array\n     */\n    private function getConfigVariablesFromRequest(): array\n    {\n        $settings = [];\n\n        foreach ($this->_aConfParams as $requestParameterKey) {\n            $settingsFromRequest = Registry::getRequest()->getRequestEscapedParameter($requestParameterKey);\n\n            if (\\is_array($settingsFromRequest)) {\n                foreach ($settingsFromRequest as $name => $value) {\n                    $settings[$name] = $value;\n                }\n            }\n        }\n\n        return $settings;\n    }\n\n    /**\n     * @param Setting[] $moduleSettings\n     * @return array\n     */\n    private function formatModuleSettingsForTemplate(array $moduleSettings): array\n    {\n        $confVars = [\n            'bool'     => [],\n            'str'      => [],\n            'arr'      => [],\n            'aarr'     => [],\n            'select'   => [],\n            'password' => [],\n        ];\n        $constraints = [];\n        $grouping = [];\n\n        foreach ($moduleSettings as $setting) {\n            $name = $setting->getName();\n            $valueType = $setting->getType();\n            $value = null;\n\n            if ($setting->getValue() !== null) {\n                switch ($setting->getType()) {\n                    case 'arr':\n                        $value = $this->arrayToMultiline($setting->getValue());\n                        break;\n                    case 'aarr':\n                        $value = $this->aarrayToMultiline($setting->getValue());\n                        break;\n                    case 'bool':\n                        $value = filter_var($setting->getValue(), FILTER_VALIDATE_BOOLEAN);\n                        break;\n                    default:\n                        $value = $setting->getValue();\n                        break;\n                }\n                $value = Str::getStr()->htmlentities($value);\n            }\n\n            $group = $setting->getGroupName();\n\n\n            $confVars[$valueType][$name] = $value;\n            $constraints[$name] = $setting->getConstraints() ?? '';\n\n            if ($group) {\n                if (!isset($grouping[$group])) {\n                    $grouping[$group] = [$name => $valueType];\n                } else {\n                    $grouping[$group][$name] = $valueType;\n                }\n            }\n        }\n\n        return [\n            'vars'        => $confVars,\n            'constraints' => $constraints,\n            'grouping'    => $grouping,\n        ];\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ModuleController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin theme manager.\n * Returns template, that arranges two other templates (\"theme_list\"\n * and \"theme_main\") to frame.\n * Admin Menu: Main Menu -> Theme.\n */\nclass ModuleController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Executes parent method parent::render() and returns name of template\n     * file \"theme\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        return \"module\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ModuleList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Module\\Module;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ShopConfigurationDaoBridgeInterface;\n\n/**\n * Admin actionss manager.\n * Sets list template, list object class ('oxactions') and default sorting\n * field ('oxactions.oxtitle').\n * Admin Menu: Manage Products -> Actions.\n */\nclass ModuleList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * @var array Loaded modules array\n     */\n    protected $_aModules = [];\n\n\n    /**\n     * Calls parent::render() and returns name of template to render\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        $this->_aViewData['mylist'] = $this->getInstalledModules();\n\n        return 'module_list';\n    }\n\n    /**\n     * @return array\n     */\n    private function getInstalledModules(): array\n    {\n        $shopConfiguration = ContainerFacade::get(ShopConfigurationDaoBridgeInterface::class)\n            ->get();\n\n        $modules = [];\n\n        foreach ($shopConfiguration->getModuleConfigurations() as $moduleConfiguration) {\n            $module = oxNew(Module::class);\n            $module->load($moduleConfiguration->getId());\n            $modules[] = $module;\n        }\n\n        $modules = $this->sortModulesByTitleAlphabetically($modules);\n        $modules = $this->convertModulesToAssociativeArray($modules);\n\n        return $modules;\n    }\n\n    /**\n     * @param array $modules\n     * @return array\n     */\n    private function sortModulesByTitleAlphabetically(array $modules): array\n    {\n        usort($modules, function ($a, $b) {\n            return strcmp($a->getTitle(), $b->getTitle());\n        });\n\n        return $modules;\n    }\n\n    /**\n     * @param array $modules\n     * @return array\n     */\n    private function convertModulesToAssociativeArray(array $modules): array\n    {\n        $modulesAssociativeArray = [];\n\n        foreach ($modules as $module) {\n            $modulesAssociativeArray[$module->getId()] = $module;\n        }\n\n        return $modulesAssociativeArray;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ModuleMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridgeInterface;\n\nclass ModuleMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        if (Registry::getRequest()->getRequestEscapedParameter(\"moduleId\")) {\n            $sModuleId = Registry::getRequest()->getRequestEscapedParameter(\"moduleId\");\n        } else {\n            $sModuleId = $this->getEditObjectId();\n        }\n\n        $oModule = oxNew(\\OxidEsales\\Eshop\\Core\\Module\\Module::class);\n\n        if ($sModuleId) {\n            if ($oModule->load($sModuleId)) {\n                $iLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getTplLanguage();\n\n                $this->_aViewData[\"oModule\"] = $oModule;\n                $this->_aViewData[\"sModuleName\"] = basename($oModule->getInfo(\"title\", $iLang));\n                $this->_aViewData[\"sModuleId\"] = $oModule->getId();\n            } else {\n                \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay(new \\OxidEsales\\Eshop\\Core\\Exception\\StandardException('EXCEPTION_MODULE_NOT_LOADED'));\n            }\n        }\n\n        parent::render();\n\n        return 'module_main';\n    }\n\n    /**\n     * Activate module\n     *\n     * @return null\n     */\n    public function activateModule()\n    {\n        if (Registry::getConfig()->isDemoShop()) {\n            Registry::getUtilsView()->addErrorToDisplay('MODULE_ACTIVATION_NOT_POSSIBLE_IN_DEMOMODE');\n            return;\n        }\n\n        try {\n            ContainerFacade::get(ModuleActivationBridgeInterface::class)\n                ->activate(\n                    $this->getEditObjectId(),\n                    Registry::getConfig()->getShopId()\n                );\n\n            $this->_aViewData['updatenav'] = '1';\n        } catch (\\Exception $exception) {\n            Registry::getUtilsView()->addErrorToDisplay($exception);\n            Registry::getLogger()->error($exception->getMessage(), [$exception]);\n        }\n    }\n\n    /**\n     * Deactivate module\n     *\n     * @return null\n     */\n    public function deactivateModule()\n    {\n        if (Registry::getConfig()->isDemoShop()) {\n            Registry::getUtilsView()->addErrorToDisplay('MODULE_ACTIVATION_NOT_POSSIBLE_IN_DEMOMODE');\n            return;\n        }\n\n        try {\n            ContainerFacade::get(ModuleActivationBridgeInterface::class)\n                ->deactivate(\n                    $this->getEditObjectId(),\n                    Registry::getConfig()->getShopId()\n                );\n\n            $this->_aViewData['updatenav'] = '1';\n        } catch (\\Exception $exception) {\n            Registry::getUtilsView()->addErrorToDisplay($exception);\n            Registry::getLogger()->error($exception->getMessage(), [$exception]);\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ModuleSortList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ShopConfigurationDaoBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\Chain\\ClassExtensionsChainDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ClassExtensionsChain;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\n\n/**\n * Extensions sorting list handler.\n * Admin Menu: Extensions -> Module -> Installed Shop Modules.\n */\nclass ModuleSortList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * It is unsave to use a backslash as HTML id in conjunction with UI.sortable, so it will be replaced in the\n     * view and restored in the controller\n     */\n    const BACKSLASH_REPLACEMENT = '---';\n\n    /**\n     * Executes parent method parent::render(), loads active and disabled extensions,\n     * checks if there are some deleted and registered modules and returns name of template file \"module_sortlist\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        $oModuleList = oxNew(\\OxidEsales\\Eshop\\Core\\Module\\ModuleList::class);\n\n        $classExtensionsChain = $this\n            ->getShopConfiguration()\n            ->getClassExtensionsChain();\n\n        $sanitizedExtendClass = [];\n        foreach ($classExtensionsChain as $extendedClass => $classChain) {\n            $sanitizedKey = str_replace(\"\\\\\", self::BACKSLASH_REPLACEMENT, $extendedClass);\n            $sanitizedExtendClass[$sanitizedKey] = $classChain;\n        }\n\n        $this->_aViewData[\"aExtClasses\"] = $sanitizedExtendClass;\n        $this->_aViewData[\"aDisabledModules\"] = $oModuleList->getDisabledModuleClasses();\n\n        // checking if there are any deleted extensions\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable(\"blSkipDeletedExtChecking\") == false) {\n            $aDeletedExt = $oModuleList->getDeletedExtensions();\n\n            if (!empty($aDeletedExt)) {\n                $this->_aViewData[\"aDeletedExt\"] = $aDeletedExt;\n            }\n        }\n\n        return 'module_sortlist';\n    }\n\n    /**\n     * Saves updated aModules config var\n     */\n    public function save()\n    {\n        $classExtensionsChainFromRequest = json_decode(\n            Registry::getRequest()->getRequestEscapedParameter('aModules'),\n            true\n        );\n\n        $sanitizedClassExtensionsChain = $this->sanitizeClassExtensionsChain($classExtensionsChainFromRequest);\n\n        ContainerFacade::get(ClassExtensionsChainDaoInterface::class)->saveChain(\n            ContainerFacade::get(ContextInterface::class)->getCurrentShopId(),\n            new ClassExtensionsChain($sanitizedClassExtensionsChain)\n        );\n    }\n\n    /**\n     * Removes extension metadata from eShop\n     *\n     * @return null\n     */\n    public function remove()\n    {\n        //if user selected not to update modules, skipping all updates\n        if (Registry::getRequest()->getRequestEscapedParameter(\"noButton\")) {\n            \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable(\"blSkipDeletedExtChecking\", true);\n\n            return;\n        }\n\n        $oModuleList = oxNew(\\OxidEsales\\Eshop\\Core\\Module\\ModuleList::class);\n        $oModuleList->cleanup();\n    }\n\n    /**\n     * @param array $chain\n     * @return array\n     */\n    private function sanitizeClassExtensionsChain(array $chain): array\n    {\n        $sanitizedClassExtensionsChain = [];\n\n        foreach ($chain as $key => $value) {\n            $sanitizedKey = str_replace(self::BACKSLASH_REPLACEMENT, \"\\\\\", $key);\n            $sanitizedClassExtensionsChain[$sanitizedKey] = $value;\n        }\n\n        return $sanitizedClassExtensionsChain;\n    }\n\n    /**\n     * @return ShopConfiguration\n     */\n    private function getShopConfiguration(): ShopConfiguration\n    {\n        return ContainerFacade::get(ShopConfigurationDaoBridgeInterface::class)\n            ->get();\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/NavigationController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Shop;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\ShopVersion;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\n\n/**\n * Administrator GUI navigation manager class.\n */\nclass NavigationController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Allowed host url\n     *\n     * @var string\n     */\n    protected $_sAllowedHost = \"http://admin.oxid-esales.com\";\n\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $request = Registry::getRequest();\n        $session = Registry::getSession();\n        $utilsServer = Registry::getUtilsServer();\n\n        $itemParam = $request->getRequestEscapedParameter('item');\n        $item = $itemParam ? basename($itemParam) : false;\n\n        if (!$item) {\n            $item = 'nav_frame';\n            $favoritesParam = $request->getRequestEscapedParameter('favorites');\n            if (is_array($favoritesParam)) {\n                $utilsServer->setOxCookie('oxidadminfavorites', implode('|', $favoritesParam));\n            }\n        } else {\n            $navTree = $this->getNavigation();\n            $this->_aViewData[\"menustructure\"] = $navTree->getDomXml()->documentElement->childNodes;\n            $this->_aViewData[\"sVersion\"] = ShopVersion::getVersion();\n\n            if (!$request->getRequestEscapedParameter(\"navReload\")) {\n                $templateExtension = ContainerFacade::getParameter('oxid_esales.templating.engine_template_extension');\n                if ($item === \"home.$templateExtension\") {\n                    $this->_aViewData['aMessage'] = $this->doStartUpChecks();\n                }\n            } else {\n                $session->remove('navReload');\n            }\n\n            $favoritesCookie = $utilsServer->getOxCookie('oxidadminfavorites');\n            $favorites = is_string($favoritesCookie) ? explode('|', $favoritesCookie) : [];\n            if ($favorites) {\n                $this->_aViewData[\"menufavorites\"] = $navTree->getListNodes($favorites);\n                $this->_aViewData[\"aFavorites\"] = $favorites;\n            }\n\n            $historyCookie = $utilsServer->getOxCookie('oxidadminhistory');\n            $history = is_string($historyCookie) ? explode('|', $historyCookie) : [];\n            if ($history) {\n                $this->_aViewData[\"menuhistory\"] = $navTree->getListNodes($history);\n            }\n\n            $this->_aViewData[\"blOpenHistory\"] = $request->getRequestEscapedParameter('openHistory');\n        }\n\n        $isMallAdmin = $session->getVariable('malladmin');\n        $shopList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ShopList::class);\n        if ($isMallAdmin) {\n            $shopList->getIdTitleList();\n        } else {\n            $shopId = $session->getVariable('actshop');\n            $shop = oxNew(Shop::class);\n            $shop->load($shopId);\n            $shopList->add($shop);\n        }\n\n        $this->_aViewData['shoplist'] = $shopList;\n        $this->_aViewData[\"shopURL\"] = Registry::getConfig()->getShopURL();\n\n        return $item;\n    }\n\n    /**\n     * Changing active shop\n     */\n    public function chshp()\n    {\n        parent::chshp();\n\n        // informing about basefrm parameters\n        $this->_aViewData['loadbasefrm'] = true;\n        $this->_aViewData['listview'] = Registry::getRequest()->getRequestEscapedParameter('listview');\n        $this->_aViewData['editview'] = Registry::getRequest()->getRequestEscapedParameter('editview');\n        $this->_aViewData['actedit'] = Registry::getRequest()->getRequestEscapedParameter('actedit');\n    }\n\n    /**\n     * Destroy session, redirects to admin login and clears cache\n     */\n    public function logout()\n    {\n        $session = Registry::getSession();\n        $myConfig = Registry::getConfig();\n\n        $oUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n        $oUser->logout();\n\n        // kill session\n        $session->destroy();\n\n        //resetting content cache if needed\n        if ($myConfig->getConfigParam('blClearCacheOnLogout')) {\n            $this->resetContentCache(true);\n        }\n\n        Registry::getUtils()->redirect('index.php', true, 302);\n    }\n\n    /**\n     * Caches external url file locally, adds <base> tag with original url to load images and other links correcly\n     */\n    public function exturl()\n    {\n        $myUtils = Registry::getUtils();\n        if ($sUrl = Registry::getRequest()->getRequestEscapedParameter(\"url\")) {\n            // Caching not allowed, redirecting\n            $myUtils->redirect($sUrl, true, 302);\n        }\n\n        $myUtils->showMessageAndExit(\"\");\n    }\n\n    /**\n     * Every Time Admin starts we perform these checks\n     * returns some messages if there is something to display\n     *\n     * @return array\n     */\n    protected function doStartUpChecks()\n    {\n        $messages = [];\n        $session = Registry::getSession();\n\n        if (Registry::getConfig()->getConfigParam('blCheckSysReq') !== false) {\n            // check if system requirements are ok\n            $oSysReq = oxNew(\\OxidEsales\\Eshop\\Core\\SystemRequirements::class);\n            if (!$oSysReq->getSysReqStatus()) {\n                $messages['warning'] = Registry::getLang()->translateString('NAVIGATION_SYSREQ_MESSAGE');\n                $messages['warning'] .= '<a href=\"?cl=sysreq&amp;stoken=' .\n                    $session->getSessionChallengeToken() . '\" target=\"basefrm\">';\n                $messages['warning'] .= Registry::getLang()->translateString('NAVIGATION_SYSREQ_MESSAGE2') . '</a>';\n            }\n        } else {\n            $messages['message'] = Registry::getLang()->translateString('NAVIGATION_SYSREQ_MESSAGE_INACTIVE');\n            $messages['message'] .= '<a href=\"?cl=sysreq&amp;stoken=' .\n                $session->getSessionChallengeToken() . '\" target=\"basefrm\">';\n            $messages['message'] .= Registry::getLang()->translateString('NAVIGATION_SYSREQ_MESSAGE2') . '</a>';\n        }\n\n        // version check\n        if (Registry::getConfig()->getConfigParam('blCheckForUpdates')) {\n            if ($sVersionNotice = $this->checkVersion()) {\n                $messages['message'] .= $sVersionNotice;\n            }\n        }\n\n        return $messages;\n    }\n\n    /**\n     * Checks if newer shop version available. If true - returns message\n     *\n     * @return string\n     */\n    protected function checkVersion()\n    {\n        $query = 'https://admin.oxid-esales.com/' . $this->getShopEdition() . '/onlinecheck.php?getlatestversion';\n        $latestVersion = Registry::getUtilsFile()->readRemoteFileAsString($query);\n        if ($latestVersion) {\n            $currentVersion = ShopVersion::getVersion();\n            if (version_compare($currentVersion, $latestVersion, '<')) {\n                return \\sprintf(\n                    Registry::getLang()->translateString('NAVIGATION_NEW_VERSION_AVAILABLE'),\n                    $currentVersion,\n                    $latestVersion\n                );\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/NavigationTree.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse DOMDocument;\nuse DOMElement;\nuse DOMXPath;\nuse OxidEsales\\Eshop\\Core\\Base;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse stdClass;\nuse Symfony\\Contracts\\Cache\\TagAwareCacheInterface;\nuse Symfony\\Contracts\\Cache\\ItemInterface;\n\nclass NavigationTree extends Base\n{\n    /**\n     * stores DOM object for all navigation tree\n     */\n    protected $_oDom = null;\n\n    /**\n     * keeps unmodified dom\n     */\n    protected $_oInitialDom = null;\n\n    /**\n     * Default EXPATH supported encodings\n     *\n     * @var array\n     */\n    protected $_aSupportedExpathXmlEncodings = ['utf-8', 'utf-16', 'iso-8859-1', 'us-ascii'];\n\n    /**\n     * clean empty nodes from tree\n     *\n     * @param object $dom         dom object\n     * @param string $parentXPath parent xpath\n     * @param string $childXPath  child xpath from parent\n     */\n    protected function cleanEmptyParents($dom, $parentXPath, $childXPath)\n    {\n        $xPath = new DomXPath($dom);\n        $nodeList = $xPath->query($parentXPath);\n\n        foreach ($nodeList as $node) {\n            $id = $node->getAttribute('id');\n            $childList = $xPath->query(\"{$parentXPath}[@id='$id']/$childXPath\");\n            if (!$childList->length) {\n                $node->parentNode->removeChild($node);\n            }\n        }\n    }\n\n    /**\n     * Adds links to xml nodes to resolve paths\n     *\n     * @param DomDocument $dom where to add links\n     */\n    protected function addLinks($dom)\n    {\n        $url = 'index.php?'; // session parameters will be included later (after cache processor)\n        $xPath = new DomXPath($dom);\n\n        // building\n        $nodeList = $xPath->query(\"//SUBMENU[@cl]\");\n        foreach ($nodeList as $node) {\n            // fetching class\n            $cl = $node->getAttribute('cl');\n            $cl = $cl ? \"cl=$cl\" : '';\n\n            // fetching params\n            $param = $node->getAttribute('clparam');\n            $param = $param ? \"&$param\" : '';\n\n            // setting link\n            $node->setAttribute('link', \"{$url}{$cl}{$param}\");\n        }\n    }\n\n    /**\n     * Loads data form XML file, and merges it with main oDomXML.\n     *\n     * @param string      $menuFile which file to load\n     * @param DomDocument $dom      where to load\n     */\n    protected function loadFromFile($menuFile, $dom)\n    {\n        $merge = false;\n        $domFile = new DomDocument();\n        $domFile->preserveWhiteSpace = false;\n        if (!@$domFile->load($menuFile)) {\n            $merge = true;\n        } elseif (is_readable($menuFile) && ($xml = @file_get_contents($menuFile))) {\n            // looking for non supported character encoding\n            if (Str::getStr()->preg_match(\"/encoding\\=(.*)\\?\\>/\", $xml, $matches) !== 0) {\n                if (isset($matches[1])) {\n                    $currEncoding = trim($matches[1], \"\\\"\");\n                    if (!in_array(strtolower($currEncoding), $this->_aSupportedExpathXmlEncodings)) {\n                        $xml = str_replace($matches[1], \"\\\"UTF-8\\\"\", $xml);\n                        $xml = iconv($currEncoding, \"UTF-8\", $xml);\n                    }\n                }\n            }\n\n            // load XML as string\n            if (@$domFile->loadXml($xml)) {\n                $merge = true;\n            }\n        }\n\n        if ($merge) {\n            $this->merge($domFile, $dom);\n        }\n    }\n\n    /**\n     * add session parameters to local urls\n     *\n     * @param object $dom dom element to add links\n     */\n    protected function sessionizeLocalUrls($dom)\n    {\n        $url = $this->getAdminUrl();\n        $xPath = new DomXPath($dom);\n        $str = Str::getStr();\n        foreach (['url', 'link'] as $attrType) {\n            foreach ($xPath->query(\"//OXMENU//*[@$attrType]\") as $node) {\n                $localUrl = $node->getAttribute($attrType);\n                if (strpos($localUrl, 'index.php?') === 0) {\n                    $localUrl = $str->preg_replace('#^index.php\\?#', $url, $localUrl);\n                    $node->setAttribute($attrType, $localUrl);\n                }\n            }\n        }\n    }\n\n    /**\n     * Removes form tree elements which does not have required user rights\n     *\n     * @param object $dom DOMDocument\n     */\n    protected function checkRights($dom)\n    {\n        $xPath = new DomXPath($dom);\n        $nodeList = $xPath->query('//*[@rights or @norights]');\n\n        foreach ($nodeList as $node) {\n            // only allowed modules/user rights or so\n            if (($req = $node->getAttribute('rights'))) {\n                $perms = explode(',', $req);\n                foreach ($perms as $perm) {\n                    if ($perm && !$this->hasRights($perm)) {\n                        $node->parentNode->removeChild($node);\n                    }\n                }\n                // not allowed modules/user rights or so\n            } elseif (($noReq = $node->getAttribute('norights'))) {\n                $perms = explode(',', $noReq);\n                foreach ($perms as $perm) {\n                    if ($perm && $this->hasRights($perm)) {\n                        $node->parentNode->removeChild($node);\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * Removes from tree elements which don't have required groups\n     *\n     * @param DOMDocument $dom document to check group\n     */\n    protected function checkGroups($dom)\n    {\n        $xPath = new DomXPath($dom);\n        $nodeList = $xPath->query(\"//*[@nogroup or @group]\");\n\n        foreach ($nodeList as $node) {\n            // allowed only for groups\n            if (($req = $node->getAttribute('group'))) {\n                $perms = explode(',', $req);\n                foreach ($perms as $perm) {\n                    if ($perm && !$this->hasGroup($perm)) {\n                        $node->parentNode->removeChild($node);\n                    }\n                }\n                // not allowed for groups\n            } elseif (($noReq = $node->getAttribute('nogroup'))) {\n                $perms = explode(',', $noReq);\n                foreach ($perms as $perm) {\n                    if ($perm && $this->hasGroup($perm)) {\n                        $node->parentNode->removeChild($node);\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * Removes form tree elements if this is demo shop and elements have disableForDemoShop=\"1\"\n     *\n     * @param DOMDocument $dom document to check group\n     *\n     * @return null\n     */\n    protected function checkDemoShopDenials($dom)\n    {\n        if (!\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->isDemoShop()) {\n            // nothing to check for non demo shop\n            return;\n        }\n\n        $xPath = new DomXPath($dom);\n        $nodeList = $xPath->query(\"//*[@disableForDemoShop]\");\n        foreach ($nodeList as $node) {\n            if ($node->getAttribute('disableForDemoShop')) {\n                $node->parentNode->removeChild($node);\n            }\n        }\n    }\n\n    /**\n     * Removes node from tree elements if it is marked as not visible (visible=\"0\")\n     *\n     * @param DOMDocument $dom document to check group\n     */\n    protected function removeInvisibleMenuNodes($dom)\n    {\n        $xPath = new DomXPath($dom);\n        $nodeList = $xPath->query(\"//*[@visible]\");\n        foreach ($nodeList as $node) {\n            if (!$node->getAttribute('visible')) {\n                $node->parentNode->removeChild($node);\n            }\n        }\n    }\n\n    /**\n     * Copys attributes form one element to another\n     *\n     * @param object $domElemTo   DOMElement\n     * @param object $domElemFrom DOMElement\n     */\n    protected function copyAttributes($domElemTo, $domElemFrom)\n    {\n        foreach ($domElemFrom->attributes as $attr) {\n            $domElemTo->setAttribute($attr->nodeName, $attr->nodeValue);\n        }\n    }\n\n    /**\n     * Merges nodes of newly added menu xml file\n     *\n     * @param object $domElemTo   merge target\n     * @param object $domElemFrom merge source\n     * @param object $xPathTo     node path\n     * @param object $domDocTo    node to append child\n     * @param string $queryStart  node query\n     */\n    protected function mergeNodes($domElemTo, $domElemFrom, $xPathTo, $domDocTo, $queryStart)\n    {\n        foreach ($domElemFrom->childNodes as $fromNode) {\n            if ($fromNode->nodeType === XML_ELEMENT_NODE) {\n                $fromAttrName = $fromNode->getAttribute('id');\n                $fromNodeName = $fromNode->tagName;\n\n                // find current item\n                $query = \"{$queryStart}/{$fromNodeName}[@id='{$fromAttrName}']\";\n                $curNode = $xPathTo->query($query);\n\n                // if not found - append\n                if ($curNode->length == 0) {\n                    $domElemTo->appendChild($domDocTo->importNode($fromNode, true));\n                } else {\n                    $curNode = $curNode->item(0);\n\n                    // if found copy all attributes and check childnodes\n                    $this->copyAttributes($curNode, $fromNode);\n\n                    if ($fromNode->childNodes->length) {\n                        $this->mergeNodes($curNode, $fromNode, $xPathTo, $domDocTo, $query);\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * If oDomXML exist meges nodes\n     *\n     * @param DomDocument $domNew what to merge\n     * @param DomDocument $dom    where to merge\n     */\n    protected function merge($domNew, $dom)\n    {\n        $xPath = new DOMXPath($dom);\n        $this->mergeNodes($dom->documentElement, $domNew->documentElement, $xPath, $dom, '/OX');\n    }\n\n    /**\n     * Returns from oDomXML tree tabs DOMNodeList, which belongs to $id\n     *\n     * @param string $id        class name\n     * @param int    $act       current tab number\n     * @param bool   $setActive marks tab as active\n     *\n     * @return \\DOMNodeList\n     */\n    public function getTabs($id, $act, $setActive = true)\n    {\n        $xPath = new DOMXPath($this->getDomXml());\n        //$nodeList = $xPath->query( \"//SUBMENU[@cl='$id' or @list='$id']/TAB | //SUBMENU/../TAB[@cl='$id']\" );\n        $nodeList = $xPath->query(\"//SUBMENU[@cl='$id']/TAB | //SUBMENU[@list='$id']/TAB | //SUBMENU/../TAB[@cl='$id']\");\n\n        $act = ($act > $nodeList->length) ? ($nodeList->length - 1) : $act;\n\n        if ($setActive) {\n            foreach ($nodeList as $pos => $node) {\n                if ($pos == $act) {\n                    // marking active node\n                    $node->setAttribute('active', 1);\n                }\n            }\n        }\n\n        return $nodeList;\n    }\n\n    /**\n     * Returns active TAB class name\n     *\n     * @param string $id  class name\n     * @param int    $act active tab number\n     *\n     * @return string\n     */\n    public function getActiveTab($id, $act)\n    {\n        $nodeList = $this->getTabs($id, $act, false);\n        $act = ($act > $nodeList->length) ? ($nodeList->length - 1) : $act;\n        if ($nodeList->length && ($node = $nodeList->item($act))) {\n            return $node->getAttribute('cl');\n        }\n    }\n\n    /**\n     * returns from oDomXML tree buttons stdClass, which belongs to $class\n     *\n     * @param string $class class name\n     *\n     * @return mixed\n     */\n    public function getBtn($class)\n    {\n        $buttons = null;\n        $xPath = new DOMXPath($this->getDomXml());\n        $nodeList = $xPath->query(\"//TAB[@cl='$class']/../BTN\");\n        if ($nodeList->length) {\n            $buttons = new stdClass();\n            foreach ($nodeList as $node) {\n                $btnId = $node->getAttribute('id');\n                $buttons->$btnId = 1;\n            }\n        }\n\n        return $buttons;\n    }\n\n    /**\n     * Returns array with paths + names ox menu xml files. Paths are checked\n     *\n     * @return array\n     */\n    protected function getMenuFiles()\n    {\n        return ContainerFacade::get('oxid_esales.templating.admin.navigation.file.locator')\n            ->locate();\n    }\n\n    /**\n     * Method is used for overriding.\n     *\n     * @param string $cacheContents\n     *\n     * @return string\n     */\n    protected function processCachedFile($cacheContents)\n    {\n        return $cacheContents;\n    }\n\n    protected function getInitialDom()\n    {\n        if ($this->_oInitialDom !== null) {\n            return $this->_oInitialDom;\n        }\n\n        $filesToLoad = $this->getMenuFiles();\n        if (!is_array($filesToLoad)) {\n            return null;\n        }\n\n        $templateLanguageCode = $this->getTemplateLanguageCode();\n        $cacheName = 'shop_menu_cache_' . $templateLanguageCode;\n        $cache = ContainerFacade::get(TagAwareCacheInterface::class);\n\n        if ($this->isMenuCacheOutdated($cache, $cacheName, $filesToLoad)) {\n            $cache->delete($cacheName);\n        }\n\n        $cacheContents = $cache->get($cacheName, function (ItemInterface $item) use ($filesToLoad): array {\n            $item->tag('oxid_esales.cache.menu');\n            return [\n                'creation_time' => time(),\n                'menu_dom' => $this->generateInitialMenuDomXml($filesToLoad)\n            ];\n        });\n\n        $this->_oInitialDom = new DOMDocument();\n        $this->_oInitialDom->preserveWhiteSpace = false;\n        $this->_oInitialDom->loadXML($cacheContents['menu_dom']);\n\n        $this->sessionizeLocalUrls($this->_oInitialDom);\n\n        return $this->_oInitialDom;\n    }\n\n    /**\n     * Returns DomXML\n     *\n     * @return DOMDocument\n     */\n    public function getDomXml()\n    {\n        if ($this->_oDom === null) {\n            $this->_oDom = clone $this->getInitialDom();\n\n            // removes items denied by user group\n            $this->checkGroups($this->_oDom);\n\n            // removes items denied by user rights\n            $this->checkRights($this->_oDom);\n\n            // removes items marked as not visible\n            $this->removeInvisibleMenuNodes($this->_oDom);\n\n            // check config params\n            $this->checkDemoShopDenials($this->_oDom);\n            $this->onGettingDomXml();\n            $this->cleanEmptyParents($this->_oDom, '//SUBMENU[@id][@list]', 'TAB');\n            $this->cleanEmptyParents($this->_oDom, '//MAINMENU[@id]', 'SUBMENU');\n        }\n\n        return $this->_oDom;\n    }\n\n    /**\n     * Returns DOMNodeList of given navigation classes\n     *\n     * @param array $nodes Node array\n     *\n     * @return \\DOMNodeList\n     */\n    public function getListNodes($nodes)\n    {\n        $xPath = new DOMXPath($this->getDomXml());\n        $nodeList = $xPath->query(\"//SUBMENU[@cl='\" . implode(\"' or @cl='\", $nodes) . \"']\");\n\n        return ($nodeList->length) ? $nodeList : null;\n    }\n\n    /**\n     * Marks passed node as active\n     *\n     * @param string $nodeId node id\n     */\n    public function markNodeActive($nodeId)\n    {\n        $xPath = new DOMXPath($this->getDomXml());\n        $nodeList = $xPath->query(\"//*[@cl='{$nodeId}' or @list='{$nodeId}']\");\n\n        if ($nodeList->length) {\n            foreach ($nodeList as $node) {\n                // special case for external resources\n                $node->setAttribute('active', 1);\n                $node->parentNode->setAttribute('active', 1);\n            }\n        }\n    }\n\n    /**\n     * Formats and returns url for list area\n     *\n     * @param string $id tab related class\n     *\n     * @return string\n     */\n    public function getListUrl($id)\n    {\n        $xPath = new DOMXPath($this->getDomXml());\n        $nodeList = $xPath->query(\"//SUBMENU[@cl='{$id}']\");\n        if ($nodeList->length && ($node = $nodeList->item(0))) {\n            $cl = $node->getAttribute('list');\n            $cl = $cl ? \"cl=$cl\" : '';\n\n            $params = $node->getAttribute('listparam');\n            $params = $params ? \"&$params\" : '';\n\n            return \"{$cl}{$params}\";\n        }\n    }\n\n    /**\n     * Formats and returns url for edit area\n     *\n     * @param string $id     tab related class\n     * @param int    $actTab active tab\n     *\n     * @return string\n     */\n    public function getEditUrl($id, $actTab)\n    {\n        $xPath = new DOMXPath($this->getDomXml());\n        $nodeList = $xPath->query(\"//SUBMENU[@cl='{$id}']/TAB\");\n\n        $actTab = ($actTab > $nodeList->length) ? ($nodeList->length - 1) : $actTab;\n        if ($nodeList->length && ($actTab = $nodeList->item($actTab))) {\n            // special case for external resources\n            if ($actTab->getAttribute('external')) {\n                return $actTab->getAttribute('location');\n            }\n            $cl = $actTab->getAttribute('cl');\n            $cl = $cl ? \"cl={$cl}\" : '';\n\n            $params = $actTab->getAttribute('clparam');\n            $params = $params ? \"&{$params}\" : '';\n\n            return \"{$cl}{$params}\";\n        }\n    }\n\n    /**\n     * Admin url getter\n     *\n     * @return string\n     */\n    protected function getAdminUrl()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        if (($adminUrl = ContainerFacade::getParameter('oxid_esales.shop_admin_url'))) {\n            $url = trim($adminUrl, '/');\n        } else {\n            $url = trim(ContainerFacade::getParameter('oxid_esales.shop_url'), '/') . '/admin';\n        }\n\n        return \\OxidEsales\\Eshop\\Core\\Registry::getUtilsUrl()->processUrl(\"{$url}/index.php\", false);\n    }\n\n    /**\n     * Checks if user has required rights\n     *\n     * @param string $rights session user rights\n     *\n     * @return bool\n     */\n    protected function hasRights($rights)\n    {\n        return $this->getUser()->oxuser__oxrights->value == $rights;\n    }\n\n    /**\n     * Checks if user in required group\n     *\n     * @param string $groupId active group id\n     *\n     * @return bool\n     */\n    protected function hasGroup($groupId)\n    {\n        return $this->getUser()->inGroup($groupId);\n    }\n\n    /**\n     * Returns id of class assigned to current node\n     *\n     * @param string $className active class name\n     *\n     * @return string\n     */\n    public function getClassId($className)\n    {\n        $xPath = new DOMXPath($this->getInitialDom());\n        $nodeList = $xPath->query(\"//*[@cl='{$className}' or @list='{$className}']\");\n        if ($nodeList->length && ($firstItem = $nodeList->item(0))) {\n            return $firstItem->getAttribute('id');\n        }\n    }\n\n\n    /**\n     * Get template language code\n     *\n     * @return string\n     */\n    protected function getTemplateLanguageCode()\n    {\n        $language = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n        $templateLanguageCode = $language->getLanguageArray()[$language->getTplLanguage()]->abbr;\n\n        return $templateLanguageCode;\n    }\n\n    /**\n     * Method is used for overriding.\n     */\n    protected function onGettingDomXml()\n    {\n    }\n\n    private function isMenuCacheOutdated($cache, string $cacheName, array $filesToLoad): bool\n    {\n        $cacheItem = $cache->getItem($cacheName);\n\n        if (!$cacheItem->isHit()) {\n            return true;\n        }\n\n        $cacheCreationTime = $cacheItem->get()['creation_time'];\n        foreach ($filesToLoad as $filePath) {\n            if ($cacheCreationTime < filemtime($filePath)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    private function generateInitialMenuDomXml(array $filesToLoad): string\n    {\n        $initialDom = new DOMDocument();\n        $initialDom->appendChild(new DOMElement('OX'));\n\n        foreach ($filesToLoad as $filePath) {\n            $this->loadFromFile($filePath, $initialDom);\n        }\n\n        $this->addLinks($initialDom);\n\n        return $initialDom->saveXML();\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ObjectSeo.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse stdClass;\n\n/**\n * Base seo config class.\n */\nclass ObjectSeo extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Executes parent method parent::render(),\n     * and returns name of template file\n     * \"object_main\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        if ($sType = $this->getType()) {\n            $oObject = oxNew($sType);\n            if ($oObject->load($this->getEditObjectId())) {\n                $oOtherLang = $oObject->getAvailableInLangs();\n                $languageId = $this->getDocumentationLanguageId();\n\n                if (!isset($oOtherLang[$languageId])) {\n                    $oObject->loadInLang(key($oOtherLang), $this->getEditObjectId());\n                }\n                $this->_aViewData['edit'] = $oObject;\n            }\n\n            if ($oObject->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n        }\n\n        $iLang = $this->getEditLang();\n        $aLangs = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getLanguageNames();\n        foreach ($aLangs as $sLangId => $sLanguage) {\n            $oLang = new stdClass();\n            $oLang->sLangDesc = $sLanguage;\n            $oLang->selected = ($sLangId == $iLang);\n            $this->_aViewData['otherlang'][$sLangId] = clone $oLang;\n        }\n\n        return 'object_seo';\n    }\n\n    /**\n     * Saves selection list parameters changes.\n     */\n    public function save()\n    {\n        // saving/updating seo params\n        if (($sOxid = $this->getSaveObjectId())) {\n            $aSeoData = Registry::getRequest()->getRequestEscapedParameter('aSeoData');\n            $iShopId = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId();\n            $iLang = $this->getEditLang();\n\n            // checkbox handling\n            if (!isset($aSeoData['oxfixed'])) {\n                $aSeoData['oxfixed'] = 0;\n            }\n\n            $sParams = $this->getAdditionalParamsFromSeoData($aSeoData);\n\n            $oEncoder = $this->getEncoder();\n            // marking self and page links as expired\n            $oEncoder->markAsExpired($sOxid, $iShopId, 1, $iLang, $sParams);\n\n            // saving\n            $oEncoder->addSeoEntry(\n                $sOxid,\n                $iShopId,\n                $iLang,\n                $this->getStdUrl($sOxid),\n                $aSeoData['oxseourl'],\n                $this->getSeoEntryType(),\n                $aSeoData['oxfixed'],\n                trim($aSeoData['oxkeywords']),\n                trim($aSeoData['oxdescription']),\n                $this->processParam($aSeoData['oxparams']),\n                true,\n                $this->getAltSeoEntryId()\n            );\n        }\n    }\n\n    /**\n     * Gets additional params from aSeoData['oxparams'] if it is set.\n     *\n     * @param array $aSeoData Seo data array\n     *\n     * @return null|string\n     */\n    protected function getAdditionalParamsFromSeoData($aSeoData)\n    {\n        $sParams = null;\n        if (isset($aSeoData['oxparams'])) {\n            if (preg_match('/([a-z]*#)?(?<objectseo>[a-z0-9]+)(#[0-9])?/i', $aSeoData['oxparams'], $aMatches)) {\n                $sQuotedObjectSeoId = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quote($aMatches['objectseo']);\n                $sParams = \"oxparams = {$sQuotedObjectSeoId}\";\n            }\n        }\n        return $sParams;\n    }\n\n    /**\n     * Returns id of object which must be saved\n     *\n     * @return string\n     */\n    protected function getSaveObjectId()\n    {\n        return $this->getEditObjectId();\n    }\n\n    /**\n     * Returns object seo data\n     *\n     * @param string $sMetaType meta data type (oxkeywords/oxdescription)\n     *\n     * @return string\n     */\n    public function getEntryMetaData($sMetaType)\n    {\n        return $this->getEncoder()->getMetaData($this->getEditObjectId(), $sMetaType, \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId(), $this->getEditLang());\n    }\n\n    /**\n     * Returns TRUE if current seo entry has fixed state\n     *\n     * @return bool\n     */\n    public function isEntryFixed()\n    {\n        $iLang = (int) $this->getEditLang();\n        $iShopId = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId();\n\n        $sQ = \"select oxfixed from oxseo where\n                   oxseo.oxobjectid = :oxobjectid and\n                   oxseo.oxshopid = :oxshopid and oxseo.oxlang = :oxlang and oxparams = '' \";\n\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        return (bool) \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster()->getOne(\n            $sQ,\n            [\n                'oxobjectid' => $this->getEditObjectId(),\n                'oxshopid' => $iShopId,\n                'oxlang' => $iLang\n            ]\n        );\n    }\n\n    /**\n     * Returns url type\n     */\n    protected function getType()\n    {\n    }\n\n    /**\n     * Returns objects std url\n     *\n     * @param string $sOxid object id\n     *\n     * @return string\n     */\n    protected function getStdUrl($sOxid)\n    {\n        if ($sType = $this->getType()) {\n            $oObject = oxNew($sType);\n            if ($oObject->load($sOxid)) {\n                return $oObject->getBaseStdLink($this->getEditLang(), true, false);\n            }\n        }\n    }\n\n    /**\n     * Returns edit language id\n     *\n     * @return int\n     */\n    public function getEditLang()\n    {\n        return $this->_iEditLang;\n    }\n\n    /**\n     * Returns alternative seo entry id\n     */\n    protected function getAltSeoEntryId()\n    {\n    }\n\n    /**\n     * Returns seo entry type\n     *\n     * @return string\n     */\n    protected function getSeoEntryType()\n    {\n        return $this->getType();\n    }\n\n    /**\n     * Processes parameter before writing to db\n     *\n     * @param string $sParam parameter to process\n     *\n     * @return string\n     */\n    public function processParam($sParam)\n    {\n        return $sParam;\n    }\n\n    /**\n     * Returns current object type seo encoder object\n     */\n    protected function getEncoder()\n    {\n    }\n\n    /**\n     * Returns seo uri\n     */\n    public function getEntryUri()\n    {\n    }\n\n    /**\n     * Returns true if SEO object id has suffix enabled. Default is FALSE\n     *\n     * @return bool\n     */\n    public function isEntrySuffixed()\n    {\n        return false;\n    }\n\n    /**\n     * Returns TRUE if seo object supports suffixes. Default is FALSE\n     *\n     * @return bool\n     */\n    public function isSuffixSupported()\n    {\n        return false;\n    }\n\n    /**\n     * Returns FALSE, as this view does not support category selector\n     *\n     * @return bool\n     */\n    public function showCatSelect()\n    {\n        return false;\n    }\n\n    /**\n     * Returns FALSE, as this view does not support active selection type\n     *\n     * @return bool\n     */\n    public function getActCatType()\n    {\n        return false;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/OrderAddress.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin order address manager.\n * Collects order addressing information, updates it on user submit, etc.\n * Admin Menu: Orders -> Display Orders -> Address.\n */\nclass OrderAddress extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oOrder = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Order::class);\n            $oOrder->load($soxId);\n\n            $this->_aViewData[\"edit\"] = $oOrder;\n        }\n\n        $oCountryList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\CountryList::class);\n        $oCountryList->loadActiveCountries(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->getObjectTplLanguage());\n\n        $this->_aViewData[\"countrylist\"] = $oCountryList;\n\n        return \"order_address\";\n    }\n\n    /**\n     * Iterates through data array, checks if specified fields are filled\n     * in, cleanups not needed data\n     *\n     * @param array  $aData          data to process\n     * @param string $sTypeToProcess data type to process e.g. \"oxorder__oxdel\"\n     * @param array  $aIgnore        fields which must be ignored while processing\n     *\n     * @return null\n     */\n    protected function processAddress($aData, $sTypeToProcess, $aIgnore)\n    {\n        // empty address fields?\n        $blEmpty = true;\n\n        // here we will store names of fields which needs to be cleaned up\n        $aFields = [];\n\n        foreach ($aData as $sName => $sValue) {\n            // if field type matches..\n            if (strpos($sName, $sTypeToProcess) !== false) {\n                // storing which fields must be unset..\n                $aFields[] = $sName;\n\n                // ignoring whats need to be ignored and testing values\n                if (!in_array($sName, $aIgnore) && $sValue) {\n                    // something was found - means leaving as is..\n                    $blEmpty = false;\n                    break;\n                }\n            }\n        }\n\n        // cleanup if empty\n        if ($blEmpty) {\n            foreach ($aFields as $sName) {\n                $aData[$sName] = \"\";\n            }\n        }\n\n        return $aData;\n    }\n\n    /**\n     * Saves ordering address information.\n     */\n    public function save()\n    {\n        parent::save();\n\n        $soxId = $this->getEditObjectId();\n        $aParams = (array) Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        $oOrder = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Order::class);\n        if ($soxId != \"-1\") {\n            $oOrder->load($soxId);\n        } else {\n            $aParams['oxorder__oxid'] = null;\n        }\n\n        $aParams = $this->processAddress($aParams, \"oxorder__oxdel\", [\"oxorder__oxdelsal\"]);\n        $oOrder->assign($aParams);\n        $oOrder->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oOrder->getId());\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/OrderArticle.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Admin order article manager.\n * Collects order articles information, updates it on user submit, etc.\n * Admin Menu: Orders -> Display Orders -> Articles.\n */\nclass OrderArticle extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Product which was currently found by search\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected $_oSearchProduct = null;\n\n    /**\n     * Product list:\n     *  - if product is not variant - list contains only product which was found by search;\n     *  - if product is variant - list consist with variant paret and its variants\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    protected $_oSearchProductList = null;\n\n    /**\n     * Product found by search. If product is variant - it keeps parent object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected $_oMainSearchProduct = null;\n\n    /**\n     * Active order object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Order\n     */\n    protected $_oEditObject = null;\n\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        if ($oOrder = $this->getEditObject()) {\n            $this->_aViewData[\"edit\"] = $oOrder;\n            $this->_aViewData[\"aProductVats\"] = $oOrder->getProductVats(true);\n        }\n\n        return \"order_article\";\n    }\n\n    /**\n     * Returns editable order object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Order\n     */\n    public function getEditObject()\n    {\n        $soxId = $this->getEditObjectId();\n        if ($this->_oEditObject === null && isset($soxId) && $soxId != \"-1\") {\n            $this->_oEditObject = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Order::class);\n            $this->_oEditObject->load($soxId);\n        }\n\n        return $this->_oEditObject;\n    }\n\n    /**\n     * Returns user written product number\n     *\n     * @return string\n     */\n    public function getSearchProductArtNr()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('sSearchArtNum');\n    }\n\n    /**\n     * If possible returns searched/found oxarticle object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article|false\n     */\n    public function getSearchProduct()\n    {\n        if ($this->_oSearchProduct === null) {\n            $this->_oSearchProduct = false;\n            $sSearchArtNum = $this->getSearchProductArtNr();\n\n            foreach ($this->getProductList() as $oProduct) {\n                if ($oProduct->oxarticles__oxartnum->value == $sSearchArtNum) {\n                    $this->_oSearchProduct = $oProduct;\n                    break;\n                }\n            }\n        }\n\n        return $this->_oSearchProduct;\n    }\n\n    /**\n     * Returns product found by search. If product is variant - returns parent object\n     *\n     * @return object\n     */\n    public function getMainProduct()\n    {\n        if ($this->_oMainSearchProduct === null && ($sArtNum = $this->getSearchProductArtNr())) {\n            $this->_oMainSearchProduct = false;\n\n            $database = DatabaseProvider::getDb();\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $table = $tableViewNameGenerator->getViewName(\"oxarticles\");\n\n            $products = $database->select(\n                sprintf('select oxid, oxparentid from %s where oxartnum = :oxartnum limit 1', $table),\n                [\n                    'oxartnum' => $sArtNum\n                ]\n            );\n            if ($products != false && $products->count() > 0) {\n                $articleId = $products->fields['OXPARENTID'] ?: $products->fields['OXID'];\n\n                $product = oxNew(Article::class);\n                if ($product->load($articleId)) {\n                    $this->_oMainSearchProduct = $product;\n                }\n            }\n        }\n\n        return $this->_oMainSearchProduct;\n    }\n\n    /**\n     * Returns product list containing searchable product or its parent and its variants\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    public function getProductList()\n    {\n        if ($this->_oSearchProductList === null) {\n            $this->_oSearchProductList = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n\n            // main search product is found?\n            if ($oMainSearchProduct = $this->getMainProduct()) {\n                // storing self to first list position\n                $this->_oSearchProductList->offsetSet($oMainSearchProduct->getId(), $oMainSearchProduct);\n\n                // adding variants..\n                foreach ($oMainSearchProduct->getVariants() as $oVariant) {\n                    $this->_oSearchProductList->offsetSet($oVariant->getId(), $oVariant);\n                }\n            }\n        }\n\n        return $this->_oSearchProductList;\n    }\n\n    /**\n     * Adds article to order list.\n     */\n    public function addThisArticle()\n    {\n        $sOxid = Registry::getRequest()->getRequestEscapedParameter('aid');\n        $dAmount = Registry::getRequest()->getRequestEscapedParameter('am');\n        $oProduct = oxNew(Article::class);\n\n        if ($sOxid && $dAmount && $oProduct->load($sOxid)) {\n            $sOrderId = $this->getEditObjectId();\n            $oOrder = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Order::class);\n            if ($sOrderId && $oOrder->load($sOrderId)) {\n                $oOrderArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\OrderArticle::class);\n                $oOrderArticle->oxorderarticles__oxartid = new Field($oProduct->getId());\n                $oOrderArticle->oxorderarticles__oxartnum = new Field($oProduct->oxarticles__oxartnum->value);\n                $oOrderArticle->oxorderarticles__oxamount = new Field($dAmount);\n                $oOrderArticle->oxorderarticles__oxselvariant = new Field(\n                    Registry::getRequest()->getRequestEscapedParameter('sel')\n                );\n                $oOrder->recalculateOrder([$oOrderArticle]);\n            }\n        }\n    }\n\n    /**\n     * Removes article from order list.\n     */\n    public function deleteThisArticle()\n    {\n        // get article id\n        $sOrderArtId = Registry::getRequest()->getRequestEscapedParameter('sArtID');\n        $sOrderId = $this->getEditObjectId();\n\n        $oOrderArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\OrderArticle::class);\n        $oOrder = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Order::class);\n\n        // order and order article exits?\n        if ($oOrderArticle->load($sOrderArtId) && $oOrder->load($sOrderId)) {\n            // deleting record\n            $oOrderArticle->delete();\n\n            // recalculating order\n            $oOrder->recalculateOrder();\n        }\n    }\n\n    /**\n     * Cancels order item\n     */\n    public function storno()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $sOrderArtId = Registry::getRequest()->getRequestEscapedParameter('sArtID');\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\OrderArticle::class);\n        $oArticle->load($sOrderArtId);\n\n        if ($oArticle->oxorderarticles__oxstorno->value == 1) {\n            $oArticle->oxorderarticles__oxstorno->setValue(0);\n            $sStockSign = -1;\n        } else {\n            $oArticle->oxorderarticles__oxstorno->setValue(1);\n            $sStockSign = 1;\n        }\n\n        // stock information\n        if ($myConfig->getConfigParam('blUseStock')) {\n            $oArticle->updateArticleStock(\n                $oArticle->oxorderarticles__oxamount->value * $sStockSign,\n                $myConfig->getConfigParam('blAllowNegativeStock')\n            );\n        }\n\n        $oDb = DatabaseProvider::getDb();\n        $sQ = \"update oxorderarticles set oxstorno = :oxstorno where oxid = :oxid\";\n        $oDb->execute($sQ, ['oxstorno' => $oArticle->oxorderarticles__oxstorno->value, 'oxid' => $sOrderArtId]);\n\n        //get article id\n        $sQ = \"select oxartid from oxorderarticles where oxid = :oxid\";\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        if (($sArtId = DatabaseProvider::getMaster()->getOne($sQ, ['oxid' => $sOrderArtId]))) {\n            $oOrder = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Order::class);\n            if ($oOrder->load($this->getEditObjectId())) {\n                $oOrder->recalculateOrder();\n            }\n        }\n    }\n\n    /**\n     * Updates order articles stock and recalculates order\n     */\n    public function updateOrder()\n    {\n        $aOrderArticles = Registry::getRequest()->getRequestEscapedParameter('aOrderArticles');\n\n        $oOrder = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Order::class);\n        if (is_array($aOrderArticles) && $oOrder->load($this->getEditObjectId())) {\n            $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n            $oOrderArticles = $oOrder->getOrderArticles(true);\n\n            $blUseStock = $myConfig->getConfigParam('blUseStock');\n            foreach ($oOrderArticles as $oOrderArticle) {\n                $sItemId = $oOrderArticle->getId();\n                if (isset($aOrderArticles[$sItemId])) {\n                    // update stock\n                    if ($blUseStock) {\n                        $oOrderArticle->setNewAmount($aOrderArticles[$sItemId]['oxamount']);\n                    } else {\n                        $oOrderArticle->assign($aOrderArticles[$sItemId]);\n                        $oOrderArticle->save();\n                    }\n                }\n            }\n\n            // recalculating order\n            $oOrder->recalculateOrder();\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/OrderDownloads.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin order article manager.\n * Collects order articles information, updates it on user submit, etc.\n * Admin Menu: Orders -> Display Orders -> Articles.\n */\nclass OrderDownloads extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Active order object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Order\n     */\n    protected $_oEditObject = null;\n\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        if ($oOrder = $this->getEditObject()) {\n            $this->_aViewData[\"edit\"] = $oOrder;\n        }\n\n        return \"order_downloads\";\n    }\n\n    /**\n     * Returns editable order object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Order\n     */\n    public function getEditObject()\n    {\n        $soxId = $this->getEditObjectId();\n        if ($this->_oEditObject === null && isset($soxId) && $soxId != \"-1\") {\n            $this->_oEditObject = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\OrderFileList::class);\n            $this->_oEditObject->loadOrderFiles($soxId);\n        }\n\n        return $this->_oEditObject;\n    }\n\n    /**\n     * Returns editable order object\n     */\n    public function resetDownloadLink()\n    {\n        $sOrderFileId = Registry::getRequest()->getRequestEscapedParameter('oxorderfileid');\n        $oOrderFile = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\OrderFile::class);\n        if ($oOrderFile->load($sOrderFileId)) {\n            $oOrderFile->reset();\n            $oOrderFile->save();\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/OrderList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin order list manager.\n * Performs collection and managing (such as filtering or deleting) function.\n * Admin Menu: Orders -> Display Orders.\n */\nclass OrderList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxorder';\n\n    /**\n     * Enable/disable sorting by DESC (SQL) (defaultfalse - disable).\n     *\n     * @var bool\n     */\n    protected $_blDesc = true;\n\n    /**\n     * Default SQL sorting parameter (default null).\n     *\n     * @var string\n     */\n    protected $_sDefSortField = \"oxorderdate\";\n\n    /**\n     * Executes parent method parent::render() and returns name of template\n     * file \"order_list\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        $folders = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('aOrderfolder');\n        $folder = Registry::getRequest()->getRequestEscapedParameter(\"folder\");\n        // first display new orders\n        if (!$folder && is_array($folders)) {\n            $names = array_keys($folders);\n            $folder = $names[0];\n        }\n\n        $search = ['oxorderarticles' => 'ARTID', 'oxpayments' => 'PAYMENT'];\n        $searchQuery = Registry::getRequest()->getRequestEscapedParameter(\"addsearch\");\n        $searchField = Registry::getRequest()->getRequestEscapedParameter(\"addsearchfld\");\n\n        $this->_aViewData[\"folder\"] = $folder ? $folder : -1;\n        $this->_aViewData[\"addsearchfld\"] = $searchField ? $searchField : -1;\n        $this->_aViewData[\"asearch\"] = $search;\n        $this->_aViewData[\"addsearch\"] = $searchQuery;\n        $this->_aViewData[\"afolder\"] = $folders;\n\n        return \"order_list\";\n    }\n\n    /**\n     * Cancels order and its order articles\n     * Calls init() to reload list items after cancellation.\n     */\n    public function cancelOrder()\n    {\n        $order = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Order::class);\n        if ($order->load($this->getEditObjectId())) {\n            $order->cancelOrder();\n        }\n\n        $this->resetContentCache();\n\n        $this->init();\n    }\n\n    /**\n     * Returns sorting fields array\n     *\n     * @return array\n     */\n    public function getListSorting()\n    {\n        $sorting = parent::getListSorting();\n        if (isset($sorting[\"oxorder\"][\"oxbilllname\"])) {\n            $this->_blDesc = false;\n        }\n\n        return $sorting;\n    }\n\n    /**\n     * Adding folder check\n     *\n     * @param array  $whereQuery SQL condition array\n     * @param string $fullQuery  SQL query string\n     *\n     * @return string\n     */\n    protected function prepareWhereQuery($whereQuery, $fullQuery)\n    {\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $query = parent::prepareWhereQuery($whereQuery, $fullQuery);\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $folders = $config->getConfigParam('aOrderfolder');\n        $folder = Registry::getRequest()->getRequestEscapedParameter('folder');\n        // Searching for empty oxfolder fields\n        if ($folder && $folder != '-1') {\n            $query .= \" and ( oxorder.oxfolder = \" . $database->quote($folder) . \" )\";\n        } elseif (!$folder && is_array($folders)) {\n            $folderNames = array_keys($folders);\n            $query .= \" and ( oxorder.oxfolder = \" . $database->quote($folderNames[0]) . \" )\";\n        }\n\n        return $query;\n    }\n\n    /**\n     * Builds and returns SQL query string. Adds additional order check.\n     *\n     * @param object $listObject list main object\n     *\n     * @return string\n     */\n    protected function buildSelectString($listObject = null)\n    {\n        $query = parent::buildSelectString($listObject);\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $searchQuery = Registry::getRequest()->getRequestEscapedParameter('addsearch');\n        $searchQuery = trim($searchQuery);\n        $searchField = Registry::getRequest()->getRequestEscapedParameter('addsearchfld');\n\n        if ($searchQuery) {\n            switch ($searchField) {\n                case 'oxorderarticles':\n                    $queryPart = \"oxorder left join oxorderarticles on oxorderarticles.oxorderid=oxorder.oxid where ( oxorderarticles.oxartnum like \" . $database->quote(\"%{$searchQuery}%\") . \" or oxorderarticles.oxtitle like \" . $database->quote(\"%{$searchQuery}%\") . \" ) and \";\n                    break;\n                case 'oxpayments':\n                    $queryPart = \"oxorder left join oxpayments on oxpayments.oxid=oxorder.oxpaymenttype where oxpayments.oxdesc like \" . $database->quote(\"%{$searchQuery}%\") . \" and \";\n                    break;\n                default:\n                    $queryPart = \"oxorder where oxorder.oxpaid like \" . $database->quote(\"%{$searchQuery}%\") . \" and \";\n                    break;\n            }\n            $query = str_replace('oxorder where', $queryPart, $query);\n        }\n\n        return $query;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/OrderMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin article main order manager.\n * Performs collection and updatind (on user submit) main item information.\n * Admin Menu: Orders -> Display Orders -> Main.\n */\nclass OrderMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Whitelist of parameters whose change does not require a full order recalculation.\n     *\n     * @var array\n     */\n    protected $fieldsTriggerNoOrderRecalculation = [\n        'oxorder__oxordernr',\n        'oxorder__oxbillnr',\n        'oxorder__oxtrackcode',\n        'oxorder__oxpaid'\n    ];\n\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oOrder = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Order::class);\n            $oOrder->load($soxId);\n\n            // paid ?\n            $sOxPaidField = 'oxorder__oxpaid';\n            $sDelTypeField = 'oxorder__oxdeltype';\n\n            if ($oOrder->$sOxPaidField->value != \"0000-00-00 00:00:00\") {\n                $oOrder->blIsPaid = true;\n                /** @var \\OxidEsales\\Eshop\\Core\\UtilsDate $oUtilsDate */\n                $oUtilsDate = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate();\n                $oOrder->$sOxPaidField = new \\OxidEsales\\Eshop\\Core\\Field($oUtilsDate->formatDBDate($oOrder->$sOxPaidField->value));\n            }\n\n            $this->_aViewData[\"edit\"] = $oOrder;\n            $this->_aViewData[\"paymentType\"] = $oOrder->getPaymentType();\n            $this->_aViewData[\"oShipSet\"] = $oOrder->getShippingSetList();\n\n            if ($oOrder->$sDelTypeField->value) {\n                // order user\n                $oUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n                $oUser->load($oOrder->oxorder__oxuserid->value);\n\n                // order sum in default currency\n                $dPrice = $oOrder->oxorder__oxtotalbrutsum->value / $oOrder->oxorder__oxcurrate->value;\n\n                /** @var \\OxidEsales\\Eshop\\Application\\Model\\PaymentList $oPaymentList */\n                $oPaymentList = \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\PaymentList::class);\n                $this->_aViewData[\"oPayments\"] =\n                                        $oPaymentList->getPaymentList($oOrder->$sDelTypeField->value, $dPrice, $oUser);\n            }\n\n            // any voucher used ?\n            $this->_aViewData[\"aVouchers\"] = $oOrder->getVoucherNrList();\n        }\n\n        $this->_aViewData[\"sNowValue\"] = date(\"Y-m-d H:i:s\", \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime());\n\n        return \"order_main\";\n    }\n\n    /**\n     * Saves main orders configuration parameters.\n     */\n    public function save()\n    {\n        parent::save();\n\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        $oOrder = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Order::class);\n        if ($soxId != \"-1\") {\n            $oOrder->load($soxId);\n        } else {\n            $aParams['oxorder__oxid'] = null;\n        }\n\n        $needOrderRecalculate = false;\n        if (is_array($aParams)) {\n            foreach ($aParams as $parameter => $value) {\n                //parameter changes for not whitelisted parameters trigger order recalculation\n                $orderField = $oOrder->$parameter;\n                if (($value != $orderField->value) && !in_array($parameter, $this->fieldsTriggerNoOrderRecalculation)) {\n                    $needOrderRecalculate = true;\n                    continue;\n                }\n            }\n        }\n\n        //change payment\n        $sPayId = Registry::getRequest()->getRequestEscapedParameter(\"setPayment\");\n        if (!empty($sPayId) && ($sPayId != $oOrder->oxorder__oxpaymenttype->value)) {\n            $aParams['oxorder__oxpaymenttype'] = $sPayId;\n            $needOrderRecalculate = true;\n        }\n\n        $oOrder->assign($aParams);\n\n        $aDynvalues = Registry::getRequest()->getRequestEscapedParameter(\"dynvalue\");\n        if (isset($aDynvalues)) {\n            $oPayment = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\UserPayment::class);\n            $oPayment->load($oOrder->oxorder__oxpaymentid->value);\n            $oPayment->oxuserpayments__oxvalue->setValue(\\OxidEsales\\Eshop\\Core\\Registry::getUtils()->assignValuesToText($aDynvalues));\n            $oPayment->save();\n            $needOrderRecalculate = true;\n        }\n        //change delivery set\n        $sDelSetId = Registry::getRequest()->getRequestEscapedParameter(\"setDelSet\");\n        if (!empty($sDelSetId) && ($sDelSetId != $oOrder->oxorder__oxdeltype->value)) {\n            $oOrder->oxorder__oxpaymenttype->setValue(\"oxempty\");\n            $oOrder->setDelivery($sDelSetId);\n            $needOrderRecalculate = true;\n        } else {\n            // keeps old delivery cost\n            $oOrder->reloadDelivery(false);\n        }\n\n        if ($needOrderRecalculate) {\n            // keeps old discount\n            $oOrder->reloadDiscount(false);\n            $oOrder->recalculateOrder();\n        } else {\n            //nothing changed in order that requires a full recalculation\n            $oOrder->save();\n        }\n\n        // set oxid if inserted\n        $this->setEditObjectId($oOrder->getId());\n    }\n\n    /**\n     * Sends order.\n     */\n    public function sendOrder()\n    {\n        $soxId = $this->getEditObjectId();\n        $oOrder = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Order::class);\n        if ($oOrder->load($soxId)) {\n            // #632A\n            $oOrder->oxorder__oxsenddate = new \\OxidEsales\\Eshop\\Core\\Field(date(\"Y-m-d H:i:s\", \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime()));\n            $oOrder->save();\n\n            // #1071C\n            $oOrder->getOrderArticles(true);\n            if (Registry::getRequest()->getRequestEscapedParameter(\"sendmail\")) {\n                // send eMail\n                $oEmail = oxNew(\\OxidEsales\\Eshop\\Core\\Email::class);\n                $oEmail->sendSendedNowMail($oOrder);\n            }\n            $this->onOrderSend();\n        }\n    }\n\n    /**\n     * Sends download links.\n     */\n    public function sendDownloadLinks()\n    {\n        $soxId = $this->getEditObjectId();\n        $oOrder = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Order::class);\n        if ($oOrder->load($soxId)) {\n            $oEmail = oxNew(\\OxidEsales\\Eshop\\Core\\Email::class);\n            $oEmail->sendDownloadLinksMail($oOrder);\n        }\n    }\n\n    /**\n     * Resets order shipping date.\n     */\n    public function resetOrder()\n    {\n        $oOrder = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Order::class);\n        if ($oOrder->load($this->getEditObjectId())) {\n            $oOrder->oxorder__oxsenddate = new \\OxidEsales\\Eshop\\Core\\Field(\"0000-00-00 00:00:00\");\n            $oOrder->save();\n\n            $this->onOrderReset();\n        }\n    }\n\n    /**\n     * Method is used for overriding.\n     */\n    protected function onOrderSend()\n    {\n    }\n\n    /**\n     * Method is used for overriding.\n     */\n    protected function onOrderReset()\n    {\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/OrderOverview.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Order;\nuse OxidEsales\\Eshop\\Core\\Price;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin order overview manager.\n * Collects order overview information, updates it on user submit, etc.\n * Admin Menu: Orders -> Display Orders -> Overview.\n */\nclass OrderOverview extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        $myConfig = Registry::getConfig();\n        parent::render();\n\n        $oOrder = oxNew(Order::class);\n        $oCur = $myConfig->getActShopCurrencyObject();\n        $oLang = Registry::getLang();\n\n        $soxId = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            $oOrder->load($soxId);\n\n            $this->_aViewData[\"edit\"] = $oOrder;\n            $this->_aViewData[\"aProductVats\"] = $oOrder->getProductVats();\n            $this->_aViewData[\"orderArticles\"] = $oOrder->getOrderArticles();\n            $this->_aViewData[\"giftCard\"] = $oOrder->getGiftCard();\n            $this->_aViewData[\"paymentType\"] = $this->getPaymentType($oOrder);\n            $this->_aViewData[\"deliveryType\"] = $oOrder->getDelSet();\n            if ($oOrder->getFieldData('oxtsprotectcosts')) {\n                $this->_aViewData[\"tsprotectcosts\"] = $oLang->formatCurrency($oOrder->getFieldData('oxtsprotectcosts'), $oCur);\n            }\n        }\n\n        $todaySum = Price::getPriceInActCurrency($oOrder->getOrderSum(true));\n        $this->_aViewData[\"ordersum\"] = $oLang->formatCurrency($todaySum, $oCur);\n        $this->_aViewData[\"ordercnt\"] = $oOrder->getOrderCnt(true);\n\n        $totalSum = Price::getPriceInActCurrency($oOrder->getOrderSum());\n        $this->_aViewData[\"ordertotalsum\"] = $oLang->formatCurrency($totalSum, $oCur);\n        $this->_aViewData[\"ordertotalcnt\"] = $oOrder->getOrderCnt();\n        $this->_aViewData[\"afolder\"] = $myConfig->getConfigParam('aOrderfolder');\n        $this->_aViewData[\"alangs\"] = $oLang->getLanguageNames();\n\n        $this->_aViewData[\"currency\"] = $oCur;\n\n        return \"order_overview\";\n    }\n\n    /**\n     * Returns user payment used for current order. In case current order was executed using\n     * just for preview user payment is set from oxPayment\n     *\n     * @param object $oOrder Order object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Payment\n     */\n    protected function getPaymentType($oOrder)\n    {\n        if (!($oUserPayment = $oOrder->getPaymentType()) && $oOrder->oxorder__oxpaymenttype->value) {\n            $oPayment = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Payment::class);\n            if ($oPayment->load($oOrder->oxorder__oxpaymenttype->value)) {\n                // in case due to security reasons payment info was not kept in db\n                $oUserPayment = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\UserPayment::class);\n                $oUserPayment->oxpayments__oxdesc = new \\OxidEsales\\Eshop\\Core\\Field($oPayment->oxpayments__oxdesc->value);\n            }\n        }\n\n        return $oUserPayment;\n    }\n\n    /**\n     * Gets proper file name\n     *\n     * @param string $sFilename file name\n     *\n     * @return string\n     */\n    public function makeValidFileName($sFilename)\n    {\n        $sFilename = preg_replace('/[\\s]+/', '_', $sFilename);\n        $sFilename = preg_replace('/[^a-zA-Z0-9_\\.-]/', '', $sFilename);\n\n        return str_replace(' ', '_', $sFilename);\n    }\n\n    /**\n     * Sends order.\n     */\n    public function sendorder()\n    {\n        $oOrder = oxNew(Order::class);\n        if ($oOrder->load($this->getEditObjectId())) {\n            $oOrder->oxorder__oxsenddate = new \\OxidEsales\\Eshop\\Core\\Field(date(\"Y-m-d H:i:s\", Registry::getUtilsDate()->getTime()));\n            $oOrder->save();\n\n            // #1071C\n            $oOrderArticles = $oOrder->getOrderArticles();\n            foreach ($oOrderArticles as $sOxid => $oArticle) {\n                // remove canceled articles from list\n                if ($oArticle->oxorderarticles__oxstorno->value == 1) {\n                    $oOrderArticles->offsetUnset($sOxid);\n                }\n            }\n\n            if (($blMail = Registry::getRequest()->getRequestEscapedParameter(\"sendmail\"))) {\n                // send eMail\n                $oEmail = oxNew(\\OxidEsales\\Eshop\\Core\\Email::class);\n                $oEmail->sendSendedNowMail($oOrder);\n            }\n        }\n    }\n\n    /**\n     * Resets order shipping date.\n     */\n    public function resetorder()\n    {\n        $oOrder = oxNew(Order::class);\n        if ($oOrder->load($this->getEditObjectId())) {\n            $oOrder->oxorder__oxsenddate = new \\OxidEsales\\Eshop\\Core\\Field(\"0000-00-00 00:00:00\");\n            $oOrder->save();\n        }\n    }\n\n    /**\n     * Get information about shipping status\n     *\n     * @return bool\n     */\n    public function canResetShippingDate()\n    {\n        $oOrder = oxNew(Order::class);\n        $blCan = false;\n        if ($oOrder->load($this->getEditObjectId())) {\n            $blCan = $oOrder->oxorder__oxstorno->value == \"0\" &&\n                     !($oOrder->oxorder__oxsenddate->value == \"0000-00-00 00:00:00\" || $oOrder->oxorder__oxsenddate->value == \"-\");\n        }\n\n        return $blCan;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/OrderRemark.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin order remark manager.\n * Collects order remark information, updates it on user submit, etc.\n * Admin Menu: Orders -> Display Orders -> History.\n */\nclass OrderRemark extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->getEditObjectId();\n        $sRemoxId = Registry::getRequest()->getRequestEscapedParameter(\"rem_oxid\");\n        if (isset($soxId) && $soxId != \"-1\") {\n            $oOrder = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Order::class);\n            $oOrder->load($soxId);\n\n            // all remark\n            $oRems = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n            $oRems->init(\"oxremark\");\n            $sUserIdField = 'oxorder__oxuserid';\n            $sSelect = \"select * from oxremark where oxparentid = :oxparentid order by oxcreate desc\";\n            $oRems->selectString($sSelect, [\n                'oxparentid' => $oOrder->$sUserIdField->value\n            ]);\n            foreach ($oRems as $key => $val) {\n                if ($val->oxremark__oxid->value == $sRemoxId) {\n                    $val->selected = 1;\n                    $oRems[$key] = $val;\n                    break;\n                }\n            }\n\n            $this->_aViewData[\"allremark\"] = $oRems;\n\n            if (isset($sRemoxId)) {\n                $oRemark = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Remark::class);\n                $oRemark->load($sRemoxId);\n                $this->_aViewData[\"remarktext\"] = $oRemark->oxremark__oxtext->value;\n                $this->_aViewData[\"remarkheader\"] = $oRemark->oxremark__oxheader->value;\n            }\n        }\n\n        return \"order_remark\";\n    }\n\n    /**\n     * Saves order history item text changes.\n     */\n    public function save()\n    {\n        parent::save();\n\n        $oOrder = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Order::class);\n        if ($oOrder->load($this->getEditObjectId())) {\n            $oRemark = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Remark::class);\n            $oRemark->load(Registry::getRequest()->getRequestEscapedParameter(\"rem_oxid\"));\n\n            $oRemark->oxremark__oxtext = new \\OxidEsales\\Eshop\\Core\\Field(Registry::getRequest()->getRequestEscapedParameter(\"remarktext\"));\n            $oRemark->oxremark__oxheader = new \\OxidEsales\\Eshop\\Core\\Field(Registry::getRequest()->getRequestEscapedParameter(\"remarkheader\"));\n            $oRemark->oxremark__oxtype = new \\OxidEsales\\Eshop\\Core\\Field(\"r\");\n            $oRemark->oxremark__oxparentid = new \\OxidEsales\\Eshop\\Core\\Field($oOrder->oxorder__oxuserid->value);\n            $oRemark->save();\n        }\n    }\n\n    /**\n     * Deletes order history item.\n     */\n    public function delete()\n    {\n        $oRemark = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Remark::class);\n        $oRemark->delete(Registry::getRequest()->getRequestEscapedParameter(\"rem_oxid\"));\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/PaymentCountry.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse stdClass;\n\n/**\n * Admin article main payment manager.\n * Performs collection and updatind (on user submit) main item information.\n * Admin Menu: Shop Settings -> Payment Methods -> Main.\n */\nclass PaymentCountry extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        // remove itm from list\n        unset($this->_aViewData[\"sumtype\"][2]);\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oPayment = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Payment::class);\n            $oPayment->loadInLang($this->_iEditLang, $soxId);\n\n            $oOtherLang = $oPayment->getAvailableInLangs();\n            if (!isset($oOtherLang[$this->_iEditLang])) {\n                $oPayment->loadInLang(key($oOtherLang), $soxId);\n            }\n            $this->_aViewData[\"edit\"] = $oPayment;\n\n            // remove already created languages\n            $aLang = array_diff(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->getLanguageNames(), $oOtherLang);\n            if (count($aLang)) {\n                $this->_aViewData[\"posslang\"] = $aLang;\n            }\n\n            foreach ($oOtherLang as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $oLang;\n            }\n        }\n\n        if (Registry::getRequest()->getRequestEscapedParameter(\"aoc\")) {\n            $oPaymentCountryAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\PaymentCountryAjax::class);\n            $this->_aViewData['oxajax'] = $oPaymentCountryAjax->getColumns();\n\n            return \"popups/payment_country\";\n        }\n\n        return \"payment_country\";\n    }\n\n    /**\n     * Adds chosen user group (groups) to delivery list\n     */\n    public function addcountry()\n    {\n        $sOxId = $this->getEditObjectId();\n        $aChosenCntr = Registry::getRequest()->getRequestEscapedParameter(\"allcountries\");\n        if (isset($sOxId) && $sOxId != \"-1\" && is_array($aChosenCntr)) {\n            foreach ($aChosenCntr as $sChosenCntr) {\n                $oObject2Payment = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oObject2Payment->init('oxobject2payment');\n                $oObject2Payment->oxobject2payment__oxpaymentid = new \\OxidEsales\\Eshop\\Core\\Field($sOxId);\n                $oObject2Payment->oxobject2payment__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sChosenCntr);\n                $oObject2Payment->oxobject2payment__oxtype = new \\OxidEsales\\Eshop\\Core\\Field(\"oxcountry\");\n                $oObject2Payment->save();\n            }\n        }\n    }\n\n    /**\n     * Removes chosen user group (groups) from delivery list\n     */\n    public function removecountry()\n    {\n        $sOxId = $this->getEditObjectId();\n        $aChosenCntr = Registry::getRequest()->getRequestEscapedParameter(\"countries\");\n        if (isset($sOxId) && $sOxId != \"-1\" && is_array($aChosenCntr)) {\n            foreach ($aChosenCntr as $sChosenCntr) {\n                $oObject2Payment = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oObject2Payment->init('oxobject2payment');\n                $oObject2Payment->delete($sChosenCntr);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/PaymentCountryAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages payment countries\n */\nclass PaymentCountryAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,         visible, multilanguage, ident\n        ['oxtitle', 'oxcountry', 1, 1, 0],\n        ['oxisoalpha2', 'oxcountry', 1, 0, 0],\n        ['oxisoalpha3', 'oxcountry', 0, 0, 0],\n        ['oxunnum3', 'oxcountry', 0, 0, 0],\n        ['oxid', 'oxcountry', 0, 0, 1]\n    ],\n                                 'container2' => [\n                                     ['oxtitle', 'oxcountry', 1, 1, 0],\n                                     ['oxisoalpha2', 'oxcountry', 1, 0, 0],\n                                     ['oxisoalpha3', 'oxcountry', 0, 0, 0],\n                                     ['oxunnum3', 'oxcountry', 0, 0, 0],\n                                     ['oxid', 'oxobject2payment', 0, 0, 1]\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        // looking for table/view\n        $sCountryTable = $this->getViewName('oxcountry');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sCountryId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchCountryId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sCountryId) {\n            // which fields to load ?\n            $sQAdd = \" from $sCountryTable where $sCountryTable.oxactive = '1' \";\n        } else {\n            $sQAdd = \" from oxobject2payment left join $sCountryTable on $sCountryTable.oxid=oxobject2payment.oxobjectid \";\n            $sQAdd .= \"where $sCountryTable.oxactive = '1' and oxobject2payment.oxpaymentid = \" . $oDb->quote($sCountryId) . \" and oxobject2payment.oxtype = 'oxcountry' \";\n        }\n\n        if ($sSynchCountryId && $sSynchCountryId != $sCountryId) {\n            $sQAdd .= \"and $sCountryTable.oxid not in ( \";\n            $sQAdd .= \"select $sCountryTable.oxid from oxobject2payment left join $sCountryTable on $sCountryTable.oxid=oxobject2payment.oxobjectid \";\n            $sQAdd .= \"where oxobject2payment.oxpaymentid = \" . $oDb->quote($sSynchCountryId) . \" and oxobject2payment.oxtype = 'oxcountry' ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Adds chosen user group (groups) to delivery list\n     */\n    public function addPayCountry()\n    {\n        $aChosenCntr = $this->getActionIds('oxcountry.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sCountryTable = $this->getViewName('oxcountry');\n            $aChosenCntr = $this->getAll($this->addFilter(\"select $sCountryTable.oxid \" . $this->getQuery()));\n        }\n        if ($soxId && $soxId != \"-1\" && is_array($aChosenCntr)) {\n            foreach ($aChosenCntr as $sChosenCntr) {\n                $oObject2Payment = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oObject2Payment->init('oxobject2payment');\n                $oObject2Payment->oxobject2payment__oxpaymentid = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                $oObject2Payment->oxobject2payment__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sChosenCntr);\n                $oObject2Payment->oxobject2payment__oxtype = new \\OxidEsales\\Eshop\\Core\\Field(\"oxcountry\");\n                $oObject2Payment->save();\n            }\n        }\n    }\n\n    /**\n     * Removes chosen user group (groups) from delivery list\n     */\n    public function removePayCountry()\n    {\n        $aChosenCntr = $this->getActionIds('oxobject2payment.oxid');\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxobject2payment.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif (is_array($aChosenCntr)) {\n            $sQ = \"delete from oxobject2payment where oxobject2payment.oxid in (\" . implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aChosenCntr)) . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/PaymentList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin payment list manager.\n * Performs collection and managing (such as filtering or deleting) function.\n * Admin Menu: Shop Settings -> Payment Methods.\n */\nclass PaymentList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'payment_list';\n\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxpayment';\n\n    /**\n     * Default SQL sorting parameter (default null).\n     *\n     * @var string\n     */\n    protected $_sDefSortField = \"oxdesc\";\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/PaymentMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse stdClass;\n\n/**\n * Admin article main payment manager.\n * Performs collection and updatind (on user submit) main item information.\n * Admin Menu: Shop Settings -> Payment Methods -> Main.\n */\nclass PaymentMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Keeps all act. fields to store\n     */\n    protected $_aFieldArray = null;\n\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        // remove itm from list\n        unset($this->_aViewData[\"sumtype\"][2]);\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        $oPayment = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Payment::class);\n\n        if (isset($soxId) && $soxId != \"-1\") {\n            $oPayment->loadInLang($this->_iEditLang, $soxId);\n\n            $oOtherLang = $oPayment->getAvailableInLangs();\n            if (!isset($oOtherLang[$this->_iEditLang])) {\n                $oPayment->loadInLang(key($oOtherLang), $soxId);\n            }\n            $this->_aViewData[\"edit\"] = $oPayment;\n\n            // remove already created languages\n            $aLang = array_diff(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->getLanguageNames(), $oOtherLang);\n            if (count($aLang)) {\n                $this->_aViewData[\"posslang\"] = $aLang;\n            }\n\n            foreach ($oOtherLang as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $oLang;\n            }\n\n            // #708\n            $this->_aViewData['aFieldNames'] = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->assignValuesFromText($oPayment->oxpayments__oxvaldesc->value);\n        }\n\n        if (Registry::getRequest()->getRequestEscapedParameter(\"aoc\")) {\n            $oPaymentMainAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\PaymentMainAjax::class);\n            $this->_aViewData['oxajax'] = $oPaymentMainAjax->getColumns();\n\n            return \"popups/payment_main\";\n        }\n\n        $this->_aViewData[\"editor\"] = $this->generateTextEditor(\"100%\", 300, $oPayment, \"oxpayments__oxlongdesc\");\n\n        return \"payment_main\";\n    }\n\n    /**\n     * Saves payment parameters changes.\n     */\n    public function save()\n    {\n        parent::save();\n\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n        // checkbox handling\n        if (!isset($aParams['oxpayments__oxactive'])) {\n            $aParams['oxpayments__oxactive'] = 0;\n        }\n        if (!isset($aParams['oxpayments__oxchecked'])) {\n            $aParams['oxpayments__oxchecked'] = 0;\n        }\n\n        $oPayment = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Payment::class);\n\n        if ($soxId != \"-1\") {\n            $oPayment->loadInLang($this->_iEditLang, $soxId);\n        } else {\n            $aParams['oxpayments__oxid'] = null;\n            //$aParams = $oPayment->ConvertNameArray2Idx( $aParams);\n        }\n\n        $oPayment->setLanguage(0);\n        $oPayment->assign($aParams);\n\n        // setting add sum calculation rules\n        $aRules = (array) Registry::getRequest()->getRequestEscapedParameter(\"oxpayments__oxaddsumrules\");\n        // if sum eqals 0, show notice, that default value will be used.\n        if (empty($aRules)) {\n            $this->_aViewData[\"noticeoxaddsumrules\"] = 1;\n        }\n        $oPayment->oxpayments__oxaddsumrules = new \\OxidEsales\\Eshop\\Core\\Field(array_sum($aRules));\n\n\n        //#708\n        if (!is_array($this->_aFieldArray)) {\n            $this->_aFieldArray = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->assignValuesFromText($oPayment->oxpayments__oxvaldesc->value);\n        }\n\n        // build value\n        $sValdesc = \"\";\n        foreach ($this->_aFieldArray as $oField) {\n            $sValdesc .= $oField->name . \"__@@\";\n        }\n\n        $oPayment->oxpayments__oxvaldesc = new \\OxidEsales\\Eshop\\Core\\Field($sValdesc, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        $oPayment->setLanguage($this->_iEditLang);\n        $oPayment->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oPayment->getId());\n    }\n\n    /**\n     * Saves payment parameters data in dofferent language (eg. english).\n     */\n    public function saveinnlang()\n    {\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        $oObj = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Payment::class);\n\n        if ($soxId != \"-1\") {\n            $oObj->loadInLang($this->_iEditLang, $soxId);\n        } else {\n            $aParams['oxpayments__oxid'] = null;\n            //$aParams = $oObj->ConvertNameArray2Idx( $aParams);\n        }\n\n        $oObj->setLanguage(0);\n        $oObj->assign($aParams);\n\n        // apply new language\n        $oObj->setLanguage(Registry::getRequest()->getRequestEscapedParameter(\"new_lang\"));\n        $oObj->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oObj->getId());\n    }\n\n    /**\n     * Deletes field from field array and stores object\n     */\n    public function delFields()\n    {\n        $oPayment = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Payment::class);\n        if ($oPayment->loadInLang($this->_iEditLang, $this->getEditObjectId())) {\n            $aDelFields = Registry::getRequest()->getRequestEscapedParameter(\"aFields\");\n            $this->_aFieldArray = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->assignValuesFromText($oPayment->oxpayments__oxvaldesc->value);\n\n            if (is_array($aDelFields) && count($aDelFields)) {\n                foreach ($aDelFields as $sDelField) {\n                    foreach ($this->_aFieldArray as $sKey => $oField) {\n                        if ($oField->name == $sDelField) {\n                            unset($this->_aFieldArray[$sKey]);\n                            break;\n                        }\n                    }\n                }\n                $this->save();\n            }\n        }\n    }\n\n    /**\n     * Adds a field to field array and stores object\n     */\n    public function addField()\n    {\n        $oPayment = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Payment::class);\n        if ($oPayment->loadInLang($this->_iEditLang, $this->getEditObjectId())) {\n            $this->_aFieldArray = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->assignValuesFromText($oPayment->oxpayments__oxvaldesc->value);\n\n            $oField = new stdClass();\n            $oField->name = Registry::getRequest()->getRequestEscapedParameter(\"sAddField\");\n\n            if (!empty($oField->name)) {\n                $this->_aFieldArray[] = $oField;\n            }\n            $this->save();\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/PaymentMainAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages payment user groups\n */\nclass PaymentMainAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = [\n        // field , table,  visible, multilanguage, id\n        'container1' => [\n            ['oxtitle', 'oxgroups', 1, 0, 0],\n            ['oxid', 'oxgroups', 0, 0, 0],\n            ['oxid', 'oxgroups', 0, 0, 1],\n        ],\n        'container2' => [\n            ['oxtitle', 'oxgroups', 1, 0, 0],\n            ['oxid', 'oxgroups', 0, 0, 0],\n            ['oxid', 'oxobject2group', 0, 0, 1],\n        ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        // looking for table/view\n        $sGroupTable = $this->getViewName('oxgroups');\n        $sGroupId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchGroupId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        // category selected or not ?\n        if (!$sGroupId) {\n            $sQAdd = \" from {$sGroupTable} \";\n        } else {\n            $sQAdd = \" from {$sGroupTable}, oxobject2group where \";\n            $sQAdd .= \" oxobject2group.oxobjectid = \" . $oDb->quote($sGroupId) .\n                      \" and oxobject2group.oxgroupsid = {$sGroupTable}.oxid \";\n        }\n\n        if (!$sSynchGroupId) {\n            $sSynchGroupId = Registry::getRequest()->getRequestEscapedParameter('oxajax_synchfid');\n        }\n        if ($sSynchGroupId && $sSynchGroupId != $sGroupId) {\n            if (!$sGroupId) {\n                $sQAdd .= 'where ';\n            } else {\n                $sQAdd .= 'and ';\n            }\n            $sQAdd .= \" {$sGroupTable}.oxid not in ( select {$sGroupTable}.oxid from {$sGroupTable}, oxobject2group \" .\n                      \"where  oxobject2group.oxobjectid = \" . $oDb->quote($sSynchGroupId) .\n                      \" and oxobject2group.oxgroupsid = $sGroupTable.oxid ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes group of users that may pay using selected method(s).\n     */\n    public function removePayGroup()\n    {\n        $aRemoveGroups = $this->getActionIds('oxobject2group.oxid');\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxobject2group.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif ($aRemoveGroups && is_array($aRemoveGroups)) {\n            $sRemoveGroups = implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aRemoveGroups));\n            $sQ = \"delete from oxobject2group where oxobject2group.oxid in (\" . $sRemoveGroups . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adds group of users that may pay using selected method(s).\n     */\n    public function addPayGroup()\n    {\n        $aAddGroups = $this->getActionIds('oxgroups.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sGroupTable = $this->getViewName('oxgroups');\n            $aAddGroups = $this->getAll($this->addFilter(\"select $sGroupTable.oxid \" . $this->getQuery()));\n        }\n        if ($soxId && $soxId != \"-1\" && is_array($aAddGroups)) {\n            foreach ($aAddGroups as $sAddgroup) {\n                $oNewGroup = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Object2Group::class);\n                $oNewGroup->oxobject2group__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                $oNewGroup->oxobject2group__oxgroupsid = new \\OxidEsales\\Eshop\\Core\\Field($sAddgroup);\n                $oNewGroup->save();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/PaymentRdfa.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Payment;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse stdClass;\n\n/**\n * Admin article RDFa payment manager.\n * Performs collection and updating (on user submit) main item information.\n * Admin Menu: Shop Settings -> Payment Methods -> RDFa.\n */\nclass PaymentRdfa extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = \"payment_rdfa\";\n\n    /**\n     * Predefined RDFa payment methods\n     * 0 value have general payments, 1 have credit card payments\n     *\n     * @var array\n     */\n    protected $_aRDFaPayments = [\n        \"ByBankTransferInAdvance\" => 0,\n        \"ByInvoice\" => 0,\n        \"Cash\" => 0,\n        \"CheckInAdvance\" => 0,\n        \"COD\" => 0,\n        \"DirectDebit\" => 0,\n        \"GoogleCheckout\" => 0,\n        \"PayPal\" => 0,\n        \"PaySwarm\" => 0,\n        \"AmericanExpress\" => 1,\n        \"DinersClub\" => 1,\n        \"Discover\" => 1,\n        \"JCB\" => 1,\n        \"MasterCard\" => 1,\n        \"VISA\" => 1,\n    ];\n\n    public function render()\n    {\n        $paymentId = $this->getEditObjectId();\n        if (isset($paymentId)) {\n            $this->_aViewData['edit'] = $this->getPayment($paymentId);\n        }\n        return parent::render();\n    }\n\n    /**\n     * Saves changed mapping configurations\n     */\n    public function save()\n    {\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n        $aRDFaPayments = (array) Registry::getRequest()->getRequestEscapedParameter(\"ardfapayments\");\n\n        // Delete old mappings\n        $oDb = DatabaseProvider::getDb();\n        $oDb->execute(\"DELETE FROM oxobject2payment WHERE oxpaymentid = :oxpaymentid AND OXTYPE = 'rdfapayment'\", [\n            'oxpaymentid' => Registry::getRequest()->getRequestEscapedParameter(\"oxid\")\n        ]);\n\n        // Save new mappings\n        foreach ($aRDFaPayments as $sPayment) {\n            $oMapping = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n            $oMapping->init(\"oxobject2payment\");\n            $oMapping->assign($aParams);\n            $oMapping->oxobject2payment__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sPayment);\n            $oMapping->save();\n        }\n    }\n\n    /**\n     * Returns an array including all available RDFa payments.\n     *\n     * @return array\n     */\n    public function getAllRDFaPayments()\n    {\n        $aRDFaPayments = [];\n        $aAssignedRDFaPayments = $this->getAssignedRDFaPayments();\n        foreach ($this->_aRDFaPayments as $sName => $iType) {\n            $oPayment = new stdClass();\n            $oPayment->name = $sName;\n            $oPayment->type = $iType;\n            $oPayment->checked = in_array($sName, $aAssignedRDFaPayments);\n            $aRDFaPayments[] = $oPayment;\n        }\n\n        return $aRDFaPayments;\n    }\n\n    /**\n     * Returns array of RDFa payments which are assigned to current payment\n     *\n     * @return array\n     */\n    public function getAssignedRDFaPayments()\n    {\n        return DatabaseProvider::getDb()->getCol(\n            'select oxobjectid from oxobject2payment where oxpaymentid = :oxpaymentid and oxtype = \"rdfapayment\"',\n            [\n                'oxpaymentid' => Registry::getRequest()->getRequestEscapedParameter(\"oxid\")\n            ]\n        );\n    }\n\n    /**\n     * @param string $paymentId\n     * @return Payment\n     */\n    private function getPayment(string $paymentId): Payment\n    {\n        $payment = oxNew(Payment::class);\n        $payment->loadInLang($this->_iEditLang, $paymentId);\n        return $payment;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/PriceAlarmList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Admin pricealarm list manager.\n * Performs collection and managing (such as filtering or deleting) function.\n * Admin Menu: Customer Info -> pricealarm.\n */\nclass PriceAlarmList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'pricealarm_list';\n\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxpricealarm';\n\n    /**\n     * Default SQL sorting parameter (default null).\n     *\n     * @var string\n     */\n    protected $_sDefSortField = \"oxuserid\";\n\n    /**\n     * Modifying SQL query to load additional article and customer data\n     *\n     * @param object $oListObject list main object\n     *\n     * @return string\n     */\n    protected function buildSelectString($oListObject = null)\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sViewName = $tableViewNameGenerator->getViewName(\"oxarticles\", (int) \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam(\"sDefaultLang\"));\n        $sSql = \"select oxpricealarm.*, {$sViewName}.oxtitle AS articletitle, \";\n        $sSql .= \"oxuser.oxlname as userlname, oxuser.oxfname as userfname \";\n        $sSql .= \"from oxpricealarm left join {$sViewName} on {$sViewName}.oxid = oxpricealarm.oxartid \";\n        $sSql .= \"left join oxuser on oxuser.oxid = oxpricealarm.oxuserid WHERE 1 \";\n\n        return $sSql;\n    }\n\n    /**\n     * Builds and returns array of SQL WHERE conditions\n     *\n     * @return array\n     */\n    public function buildWhere()\n    {\n        $this->_aWhere = parent::buildWhere();\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sViewName = $tableViewNameGenerator->getViewName(\"oxpricealarm\");\n        $sArtViewName = $tableViewNameGenerator->getViewName(\"oxarticles\");\n\n        // updating price fields values for correct search in DB\n        if (isset($this->_aWhere[$sViewName . '.oxprice'])) {\n            $sPriceParam = (float) str_replace(['%', ','], ['', '.'], $this->_aWhere[$sViewName . '.oxprice']);\n            $this->_aWhere[$sViewName . '.oxprice'] = '%' . $sPriceParam . '%';\n        }\n\n        if (isset($this->_aWhere[$sArtViewName . '.oxprice'])) {\n            $sPriceParam = (float) str_replace(['%', ','], ['', '.'], $this->_aWhere[$sArtViewName . '.oxprice']);\n            $this->_aWhere[$sArtViewName . '.oxprice'] = '%' . $sPriceParam . '%';\n        }\n\n        return $this->_aWhere;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/PriceAlarmMail.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse oxDb;\n\n/**\n * Admin article main pricealarm manager.\n * Performs collection and updatind (on user submit) main item information.\n * Admin Menu: Customer Info -> pricealarm -> Main.\n */\nclass PriceAlarmMail extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        parent::render();\n\n        $shopId = $config->getShopId();\n        //articles price in subshop and baseshop can be different\n        $this->_aViewData['iAllCnt'] = 0;\n        $query = \"\n            SELECT oxprice, oxartid\n            FROM oxpricealarm\n            WHERE oxsended = '000-00-00 00:00:00' AND oxshopid = :oxshopid\";\n        $result = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->select(\n            $query,\n            [\n                'oxshopid' => $shopId\n            ]\n        );\n        if ($result != false && $result->count() > 0) {\n            $simpleCache = [];\n            while (!$result->EOF) {\n                $price = $result->fields['oxprice'];\n                $articleId = $result->fields['oxartid'];\n                if (isset($simpleCache[$articleId])) {\n                    if ($simpleCache[$articleId] <= $price) {\n                        $this->_aViewData['iAllCnt'] += 1;\n                    }\n                } else {\n                    $article = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n                    if ($article->load($articleId)) {\n                        $articlePrice = $simpleCache[$articleId] = $article->getPrice()->getBruttoPrice();\n                        if ($articlePrice <= $price) {\n                            $this->_aViewData['iAllCnt'] += 1;\n                        }\n                    }\n                }\n                $result->fetchRow();\n            }\n        }\n\n        return \"pricealarm_mail\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/PriceAlarmMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse stdClass;\n\n/**\n * Admin article main pricealarm manager.\n * Performs collection and updatind (on user submit) main item information.\n * Admin Menu: Customer Info -> pricealarm -> Main.\n */\nclass PriceAlarmMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $this->_aViewData['iAllCnt'] = $this->getActivePriceAlarmsCount();\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oPricealarm = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\PriceAlarm::class);\n            $oPricealarm->load($soxId);\n\n            // customer info\n            $oUser = null;\n            if ($oPricealarm->oxpricealarm__oxuserid->value) {\n                $oUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n                $oUser->load($oPricealarm->oxpricealarm__oxuserid->value);\n                $oPricealarm->oUser = $oUser;\n            }\n\n            //\n            $oShop = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Shop::class);\n            $oShop->load($config->getShopId());\n            $this->addGlobalParams($oShop);\n\n            if (!($iLang = $oPricealarm->oxpricealarm__oxlang->value)) {\n                $iLang = 0;\n            }\n\n            $oLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n            $aLanguages = $oLang->getLanguageNames();\n            $this->_aViewData[\"edit_lang\"] = $aLanguages[$iLang];\n            // rendering mail message text\n            $oLetter = new stdClass();\n            $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n            if (isset($aParams['oxpricealarm__oxlongdesc']) && $aParams['oxpricealarm__oxlongdesc']) {\n                $oLetter->oxpricealarm__oxlongdesc = new \\OxidEsales\\Eshop\\Core\\Field(stripslashes($aParams['oxpricealarm__oxlongdesc']), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n            } else {\n                $oEmail = oxNew(\\OxidEsales\\Eshop\\Core\\Email::class);\n                $sDesc = $oEmail->sendPricealarmToCustomer($oPricealarm->oxpricealarm__oxemail->value, $oPricealarm, null, true);\n\n                $iOldLang = $oLang->getTplLanguage();\n                $oLang->setTplLanguage($iLang);\n                $oLetter->oxpricealarm__oxlongdesc = new \\OxidEsales\\Eshop\\Core\\Field($sDesc, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n                $oLang->setTplLanguage($iOldLang);\n            }\n\n            $this->_aViewData[\"editor\"] = $this->generateTextEditor(\"100%\", 300, $oLetter, \"oxpricealarm__oxlongdesc\", \"details.css\");\n            $this->_aViewData[\"edit\"] = $oPricealarm;\n            $this->_aViewData[\"actshop\"] = $config->getShopId();\n        }\n\n        parent::render();\n\n        return \"pricealarm_main\";\n    }\n\n    /**\n     * Sending email to selected customer\n     */\n    public function send()\n    {\n        $blError = true;\n\n        // error\n        if (($sOxid = $this->getEditObjectId())) {\n            $oPricealarm = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\PriceAlarm::class);\n            $oPricealarm->load($sOxid);\n\n            $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n            $sMailBody = isset($aParams['oxpricealarm__oxlongdesc']) ? stripslashes($aParams['oxpricealarm__oxlongdesc']) : '';\n\n            $sRecipient = $oPricealarm->oxpricealarm__oxemail->value;\n\n            $oEmail = oxNew(\\OxidEsales\\Eshop\\Core\\Email::class);\n            $blSuccess = (int) $oEmail->sendPricealarmToCustomer($sRecipient, $oPricealarm, $sMailBody);\n\n            // setting result message\n            if ($blSuccess) {\n                $oPricealarm->oxpricealarm__oxsended->setValue(date(\"Y-m-d H:i:s\"));\n                $oPricealarm->save();\n                $blError = false;\n            }\n        }\n\n        if (!$blError) {\n            $this->_aViewData[\"mail_succ\"] = 1;\n        } else {\n            $this->_aViewData[\"mail_err\"] = 1;\n        }\n    }\n\n    /**\n     * Returns number of active price alarms.\n     *\n     * @return int\n     */\n    protected function getActivePriceAlarmsCount()\n    {\n        // #1140 R - price must be checked from the object.\n        $query = \"\n            SELECT oxarticles.oxid as oxid, oxpricealarm.oxprice as oxprice\n            FROM oxpricealarm, oxarticles\n            WHERE oxarticles.oxid = oxpricealarm.oxartid AND oxpricealarm.oxsended = '000-00-00 00:00:00'\";\n        $result = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->select($query);\n        $count = 0;\n\n        if ($result != false && $result->count() > 0) {\n            while (!$result->EOF) {\n                $article = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n                $article->load($result->fields['oxid']);\n                if ($article->getPrice()->getBruttoPrice() <= $result->fields['oxprice']) {\n                    $count++;\n                }\n                $result->fetchRow();\n            }\n        }\n\n        return $count;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/PriceAlarmSend.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * pricealarm sending manager.\n * Performs sending of pricealarm to selected iAllCnt groups.\n */\nclass PriceAlarmSend extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Default tab number\n     *\n     * @var int\n     */\n    protected $_iDefEdit = 1;\n\n    /**\n     * Executes parent method parent::render(), creates oxpricealarm object,\n     * sends pricealarm to iAllCnts of chosen groups and returns name of template\n     * file \"pricealarm_send\"/\"pricealarm_done\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        ini_set(\"session.gc_maxlifetime\", 36000);\n\n        $start = (int) Registry::getRequest()->getRequestEscapedParameter(\"iStart\");\n        $limit = $config->getConfigParam('iCntofMails');\n        $activeAlertsAmount = Registry::getRequest()->getRequestEscapedParameter(\"iAllCnt\");\n        if (!isset($activeAlertsAmount)) {\n            $activeAlertsAmount = $this->countActivePriceAlerts();\n        }\n\n        $this->sendPriceChangeNotifications($start, $limit);\n\n        // Advance mail pointer and set parameter\n        $start += $limit;\n\n        $this->_aViewData[\"iStart\"] = $start;\n        $this->_aViewData[\"iAllCnt\"] = $activeAlertsAmount;\n        $this->_aViewData[\"actlang\"] = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage();\n\n        if ($start < $activeAlertsAmount) {\n            $template = \"pricealarm_send\";\n        } else {\n            $template = \"pricealarm_done\";\n        }\n\n        return $template;\n    }\n\n    /**\n     * Overrides parent method to pass referred id.\n     *\n     * @param string $sId Class name\n     */\n    protected function setupNavigation($sId)\n    {\n        parent::setupNavigation('pricealarm_list');\n    }\n\n    /**\n     * Counts active price alerts and returns this number.\n     *\n     * @return int\n     */\n    protected function countActivePriceAlerts()\n    {\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $shopId = $config->getShopId();\n\n        $activeAlarmsQuery =\n            \"SELECT oxprice, oxartid FROM oxpricealarm\n                    WHERE oxsended = '000-00-00 00:00:00' AND oxshopid = :oxshopid\";\n        $result = $database->select($activeAlarmsQuery, [\n            'oxshopid' => $shopId\n        ]);\n        $count = 0;\n        while ($result != false && !$result->EOF) {\n            $alarmPrice = $result->fields['oxprice'];\n            $article = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            $article->load($result->fields['oxartid']);\n            if ($article->getPrice()->getBruttoPrice() <= $alarmPrice) {\n                $count++;\n            }\n            $result->fetchRow();\n        }\n\n        return $count;\n    }\n\n    /**\n     * Sends price alert notifications about changed article prices.\n     *\n     * @param int $start How much price alerts was already sent.\n     * @param int $limit How much price alerts to send.\n     */\n    protected function sendPriceChangeNotifications($start, $limit)\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $shopId = $config->getShopId();\n\n        $alarmsQuery =\n            \"SELECT oxid, oxemail, oxartid, oxprice FROM oxpricealarm\n            WHERE oxsended = '000-00-00 00:00:00' AND oxshopid = :oxshopid\";\n        $result = $database->selectLimit($alarmsQuery, $limit, $start, [\n            'oxshopid' => $shopId\n        ]);\n        while ($result != false && !$result->EOF) {\n            $article = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            $article->load($result->fields['oxartid']);\n            if ($article->getPrice()->getBruttoPrice() <= $result->fields['oxprice']) {\n                $this->sendeMail(\n                    $result->fields['oxemail'],\n                    $result->fields['oxartid'],\n                    $result->fields['oxid'],\n                    $result->fields['oxprice']\n                );\n            }\n            $result->fetchRow();\n        }\n    }\n\n    /**\n     * Creates and sends email with price alarm information.\n     *\n     * @param string $emailAddress Email address\n     * @param string $productID    Product id\n     * @param string $priceAlarmId Price alarm id\n     * @param string $bidPrice     Bid price\n     */\n    public function sendeMail($emailAddress, $productID, $priceAlarmId, $bidPrice)\n    {\n        $alarm = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\PriceAlarm::class);\n        $alarm->load($priceAlarmId);\n\n        $language = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n        $languageId = (int) $alarm->oxpricealarm__oxlang->value;\n\n        $oldLanguageId = $language->getTplLanguage();\n        $language->setTplLanguage($languageId);\n\n        $email = oxNew(\\OxidEsales\\Eshop\\Core\\Email::class);\n        $success = (int) $email->sendPricealarmToCustomer($emailAddress, $alarm);\n\n        $language->setTplLanguage($oldLanguageId);\n\n        if ($success) {\n            $alarm->oxpricealarm__oxsended = new \\OxidEsales\\Eshop\\Core\\Field(date(\"Y-m-d H:i:s\"));\n            $alarm->save();\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/SelectListController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin selectlist manager.\n * Returns template, that arranges two other templates (\"selectlist_list\"\n * and \"selectlist_main\") to frame.\n */\nclass SelectListController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'selectlist';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/SelectListList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin selectlist list manager.\n */\nclass SelectListList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxselectlist';\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'selectlist_list';\n\n    /**\n     * Default SQL sorting parameter (default null).\n     *\n     * @var string\n     */\n    protected $_sDefSortField = 'oxtitle';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/SelectListMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse stdClass;\n\nif (!defined('ERR_SUCCESS')) {\n    DEFINE(\"ERR_SUCCESS\", 1);\n}\nif (!defined('ERR_REQUIREDMISSING')) {\n    DEFINE(\"ERR_REQUIREDMISSING\", -1);\n}\nif (!defined('ERR_POSOUTOFBOUNDS')) {\n    DEFINE(\"ERR_POSOUTOFBOUNDS\", -2);\n}\n\n/**\n * Admin article main selectlist manager.\n * Performs collection and updatind (on user submit) main item information.\n */\nclass SelectListMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Keeps all act. fields to store\n     */\n    public $aFieldArray = null;\n\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $sOxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n\n        //create empty edit object\n        $this->_aViewData[\"edit\"] = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\SelectList::class);\n\n        if (isset($sOxId) && $sOxId != \"-1\") {\n            // generating category tree for select list\n            // A. hack - passing language by post as lists uses only language passed by POST/GET/SESSION\n            $_POST[\"language\"] = $this->_iEditLang;\n            $this->createCategoryTree(\"artcattree\", $sOxId);\n\n            // load object\n            $oAttr = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\SelectList::class);\n            $oAttr->loadInLang($this->_iEditLang, $sOxId);\n\n            $aFieldList = $oAttr->getFieldList();\n            if (is_array($aFieldList)) {\n                foreach ($aFieldList as $key => $oField) {\n                    if ($oField->priceUnit == '%') {\n                        $oField->price = $oField->fprice;\n                    }\n                }\n            }\n\n            $oOtherLang = $oAttr->getAvailableInLangs();\n            if (!isset($oOtherLang[$this->_iEditLang])) {\n                $oAttr->loadInLang(key($oOtherLang), $sOxId);\n            }\n            $this->_aViewData[\"edit\"] = $oAttr;\n\n            // Disable editing for derived items.\n            if ($oAttr->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n\n            // remove already created languages\n            $aLang = array_diff(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->getLanguageNames(), $oOtherLang);\n            if (count($aLang)) {\n                $this->_aViewData[\"posslang\"] = $aLang;\n            }\n\n            foreach ($oOtherLang as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $oLang;\n            }\n\n            $iErr = \\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable(\"iErrorCode\");\n\n            if (!$iErr) {\n                $iErr = ERR_SUCCESS;\n            }\n\n            $this->_aViewData[\"iErrorCode\"] = $iErr;\n            \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable(\"iErrorCode\", ERR_SUCCESS);\n        }\n        if (Registry::getRequest()->getRequestEscapedParameter(\"aoc\")) {\n            $oSelectlistMainAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\SelectListMainAjax::class);\n            $this->_aViewData['oxajax'] = $oSelectlistMainAjax->getColumns();\n\n            return \"popups/selectlist_main\";\n        }\n\n        return \"selectlist_main\";\n    }\n\n    /**\n     * Saves selection list parameters changes.\n     *\n     * @return mixed\n     */\n    public function save()\n    {\n        parent::save();\n\n        $sOxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        $oAttr = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\SelectList::class);\n\n        if ($sOxId != \"-1\") {\n            $oAttr->loadInLang($this->_iEditLang, $sOxId);\n        } else {\n            $aParams['oxselectlist__oxid'] = null;\n        }\n\n        //Disable editing for derived items\n        if ($oAttr->isDerived()) {\n            return;\n        }\n\n        //$aParams = $oAttr->ConvertNameArray2Idx( $aParams);\n        $oAttr->setLanguage(0);\n        $oAttr->assign($aParams);\n\n        //#708\n        if (!is_array($this->aFieldArray)) {\n            $this->aFieldArray = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->assignValuesFromText($oAttr->oxselectlist__oxvaldesc->getRawValue());\n        }\n        // build value\n        $oAttr->oxselectlist__oxvaldesc = new \\OxidEsales\\Eshop\\Core\\Field(\"\", \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        foreach ($this->aFieldArray as $oField) {\n            $oAttr->oxselectlist__oxvaldesc->setValue($oAttr->oxselectlist__oxvaldesc->getRawValue() . $oField->name, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n            if (isset($oField->price) && $oField->price) {\n                $oAttr->oxselectlist__oxvaldesc->setValue($oAttr->oxselectlist__oxvaldesc->getRawValue() . \"!P!\" . trim(str_replace(\",\", \".\", $oField->price)), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n                if ($oField->priceUnit == '%') {\n                    $oAttr->oxselectlist__oxvaldesc->setValue($oAttr->oxselectlist__oxvaldesc->getRawValue() . '%', \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n                }\n            }\n            $oAttr->oxselectlist__oxvaldesc->setValue($oAttr->oxselectlist__oxvaldesc->getRawValue() . \"__@@\", \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        }\n\n        $oAttr->setLanguage($this->_iEditLang);\n        $oAttr->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oAttr->getId());\n    }\n\n    /**\n     * Saves selection list parameters changes in different language (eg. english).\n     *\n     * @return null\n     */\n    public function saveinnlang()\n    {\n        $sOxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        $oObj = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\SelectList::class);\n\n        if ($sOxId != \"-1\") {\n            $oObj->loadInLang($this->_iEditLang, $sOxId);\n        } else {\n            $aParams['oxselectlist__oxid'] = null;\n        }\n\n        //Disable editing for derived items\n        if ($oObj->isDerived()) {\n            return;\n        }\n\n        parent::save();\n\n        //$aParams = $oObj->ConvertNameArray2Idx( $aParams);\n        $oObj->setLanguage(0);\n        $oObj->assign($aParams);\n\n        // apply new language\n        $oObj->setLanguage(Registry::getRequest()->getRequestEscapedParameter(\"new_lang\"));\n        $oObj->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oObj->getId());\n    }\n\n    /**\n     * Deletes field from field array and stores object\n     *\n     * @return null\n     */\n    public function delFields()\n    {\n        $oSelectlist = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\SelectList::class);\n        if ($oSelectlist->loadInLang($this->_iEditLang, $this->getEditObjectId())) {\n            // Disable editing for derived items.\n            if ($oSelectlist->isDerived()) {\n                return;\n            }\n\n            $aDelFields = Registry::getRequest()->getRequestEscapedParameter(\"aFields\");\n            $this->aFieldArray = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->assignValuesFromText($oSelectlist->oxselectlist__oxvaldesc->getRawValue());\n\n            if (is_array($aDelFields) && count($aDelFields)) {\n                foreach ($aDelFields as $sDelField) {\n                    $sDel = $this->parseFieldName($sDelField);\n                    foreach ($this->aFieldArray as $sKey => $oField) {\n                        if ($oField->name == $sDel) {\n                            unset($this->aFieldArray[$sKey]);\n                            break;\n                        }\n                    }\n                }\n                $this->save();\n            }\n        }\n    }\n\n    /**\n     * Adds a field to field array and stores object\n     *\n     * @return null\n     */\n    public function addField()\n    {\n        $oSelectlist = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\SelectList::class);\n        if ($oSelectlist->loadInLang($this->_iEditLang, $this->getEditObjectId())) {\n            //Disable editing for derived items.\n            if ($oSelectlist->isDerived()) {\n                return;\n            }\n\n            $sAddField = Registry::getRequest()->getRequestEscapedParameter(\"sAddField\");\n            if (empty($sAddField)) {\n                \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable(\"iErrorCode\", ERR_REQUIREDMISSING);\n\n                return;\n            }\n\n            $this->aFieldArray = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->assignValuesFromText($oSelectlist->oxselectlist__oxvaldesc->getRawValue());\n\n            $oField = new stdClass();\n            $oField->name = $sAddField;\n            $oField->price = Registry::getRequest()->getRequestEscapedParameter(\"sAddFieldPriceMod\");\n            $oField->priceUnit = Registry::getRequest()->getRequestEscapedParameter(\"sAddFieldPriceModUnit\");\n\n            $this->aFieldArray[] = $oField;\n            if ($iPos = Registry::getRequest()->getRequestEscapedParameter(\"sAddFieldPos\")) {\n                if ($this->rearrangeFields($oField, $iPos - 1)) {\n                    return;\n                }\n            }\n\n            $this->save();\n        }\n    }\n\n    /**\n     * Modifies field from field array's first elem. and stores object\n     *\n     * @return null\n     */\n    public function changeField()\n    {\n        $sAddField = Registry::getRequest()->getRequestEscapedParameter(\"sAddField\");\n        if (empty($sAddField)) {\n            \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable(\"iErrorCode\", ERR_REQUIREDMISSING);\n\n            return;\n        }\n\n        $aChangeFields = Registry::getRequest()->getRequestEscapedParameter(\"aFields\");\n        if (is_array($aChangeFields) && count($aChangeFields)) {\n            $oSelectlist = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\SelectList::class);\n            if ($oSelectlist->loadInLang($this->_iEditLang, $this->getEditObjectId())) {\n                $this->aFieldArray = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->assignValuesFromText($oSelectlist->oxselectlist__oxvaldesc->getRawValue());\n                $sChangeFieldName = $this->parseFieldName($aChangeFields[0]);\n\n                foreach ($this->aFieldArray as $sKey => $oField) {\n                    if ($oField->name == $sChangeFieldName) {\n                        $this->aFieldArray[$sKey]->name = $sAddField;\n                        $this->aFieldArray[$sKey]->price = Registry::getRequest()->getRequestEscapedParameter(\"sAddFieldPriceMod\");\n                        $this->aFieldArray[$sKey]->priceUnit = Registry::getRequest()->getRequestEscapedParameter(\"sAddFieldPriceModUnit\");\n                        if ($iPos = Registry::getRequest()->getRequestEscapedParameter(\"sAddFieldPos\")) {\n                            if ($this->rearrangeFields($this->aFieldArray[$sKey], $iPos - 1)) {\n                                return;\n                            }\n                        }\n                        break;\n                    }\n                }\n                $this->save();\n            }\n        }\n    }\n\n    /**\n     * Resorts fields list and moves $oField to $iPos,\n     * uses $this->aFieldArray for fields storage.\n     *\n     * @param object  $oField field to be moved\n     * @param integer $iPos   new pos of the field\n     *\n     * @return bool - true if failed.\n     */\n    protected function rearrangeFields($oField, $iPos)\n    {\n        if (!isset($this->aFieldArray) || !is_array($this->aFieldArray)) {\n            return true;\n        }\n\n        $iFieldCount = count($this->aFieldArray);\n        if ($iPos < 0 || $iPos >= $iFieldCount) {\n            \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable(\"iErrorCode\", ERR_POSOUTOFBOUNDS);\n\n            return true;\n        }\n\n        $iCurrentPos = -1;\n        for ($i = 0; $i < $iFieldCount; $i++) {\n            if ($this->aFieldArray[$i] == $oField) {\n                $iCurrentPos = $i;\n                break;\n            }\n        }\n\n        if ($iCurrentPos == -1) {\n            return true;\n        }\n\n        if ($iCurrentPos == $iPos) {\n            return false;\n        }\n\n        $sField = $this->aFieldArray[$iCurrentPos];\n        if ($iCurrentPos < $iPos) {\n            for ($i = $iCurrentPos; $i < $iPos; $i++) {\n                $this->aFieldArray[$i] = $this->aFieldArray[$i + 1];\n            }\n            $this->aFieldArray[$iPos] = $sField;\n\n            return false;\n        } else {\n            for ($i = $iCurrentPos; $i > $iPos; $i--) {\n                $this->aFieldArray[$i] = $this->aFieldArray[$i - 1];\n            }\n            $this->aFieldArray[$iPos] = $sField;\n\n            return false;\n        }\n    }\n\n    /**\n     * Parses field name from given string\n     * String format is: \"someNr__@@someName__@@someTxt\"\n     *\n     * @param string $sInput given string\n     *\n     * @return string - name\n     */\n    public function parseFieldName($sInput)\n    {\n        $aInput = explode('__@@', $sInput, 3);\n\n        return $aInput[1];\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/SelectListMainAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse Exception;\n\n/**\n * Class manages article select lists configuration\n */\nclass SelectListMainAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * If true extended column selection will be build\n     *\n     * @var bool\n     */\n    protected $_blAllowExtColumns = true;\n\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,         visible, multilanguage, ident\n        ['oxartnum', 'oxarticles', 1, 0, 0],\n        ['oxtitle', 'oxarticles', 1, 1, 0],\n        ['oxean', 'oxarticles', 0, 0, 0],\n        ['oxmpn', 'oxarticles', 0, 0, 0],\n        ['oxprice', 'oxarticles', 0, 0, 0],\n        ['oxstock', 'oxarticles', 0, 0, 0],\n        ['oxid', 'oxarticles', 0, 0, 1]\n    ],\n                                 'container2' => [\n                                     ['oxartnum', 'oxarticles', 1, 0, 0],\n                                     ['oxtitle', 'oxarticles', 1, 1, 0],\n                                     ['oxean', 'oxarticles', 0, 0, 0],\n                                     ['oxmpn', 'oxarticles', 0, 0, 0],\n                                     ['oxprice', 'oxarticles', 0, 0, 0],\n                                     ['oxstock', 'oxarticles', 0, 0, 0],\n                                     ['oxid', 'oxobject2selectlist', 0, 0, 1],\n                                     ['oxid', 'oxarticles', 0, 0, 1]\n                                 ],\n                                 'container3' => [\n                                     ['oxtitle', 'oxselectlist', 1, 1, 0],\n                                     ['oxsort', 'oxobject2selectlist', 1, 0, 0],\n                                     ['oxident', 'oxselectlist', 0, 0, 0],\n                                     ['oxvaldesc', 'oxselectlist', 0, 0, 0],\n                                     ['oxid', 'oxselectlist', 0, 0, 1]\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        // looking for table/view\n        $sArtTable = $this->getViewName('oxarticles');\n        $sO2CView = $this->getViewName('oxobject2category');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sSelId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchSelId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sSelId) {\n            // performance\n            $sQAdd = \" from $sArtTable where 1 \";\n            $sQAdd .= $myConfig->getConfigParam('blVariantsSelection') ? '' : \" and $sArtTable.oxparentid = '' \";\n        } else {\n            // selected category ?\n            if ($sSynchSelId && $sSelId != $sSynchSelId) {\n                $sQAdd = \" from $sO2CView as oxobject2category left join $sArtTable on \"\n                    . $myConfig->getConfigParam('blVariantsSelection')\n                        ? \" ( $sArtTable.oxid=oxobject2category.oxobjectid or\"\n                            . \" $sArtTable.oxparentid=oxobject2category.oxobjectid ) \"\n                        : \" $sArtTable.oxid=oxobject2category.oxobjectid \"\n                    . \" where oxobject2category.oxcatnid = \" . $oDb->quote($sSelId);\n            } else {\n                $sQAdd = \" from $sArtTable left join oxobject2selectlist on\"\n                    . \" $sArtTable.oxid=oxobject2selectlist.oxobjectid \"\n                    . \" where oxobject2selectlist.oxselnid = \" . $oDb->quote($sSelId);\n            }\n        }\n\n        if ($sSynchSelId && $sSynchSelId != $sSelId) {\n            // performance\n            $sQAdd .= \" and $sArtTable.oxid not in ( select oxobject2selectlist.oxobjectid from oxobject2selectlist \"\n                . \" where oxobject2selectlist.oxselnid = \" . $oDb->quote($sSynchSelId) . \" ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes article from Selection list\n     */\n    public function removeArtFromSel()\n    {\n        $aChosenArt = $this->getActionIds('oxobject2selectlist.oxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = parent::addFilter(\"delete oxobject2selectlist.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif (is_array($aChosenArt)) {\n            $sQ = \"delete from oxobject2selectlist where oxobject2selectlist.oxid in (\"\n                . implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aChosenArt))\n                . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adds article to Selection list\n     *\n     * @throws Exception\n     */\n    public function addArtToSel()\n    {\n        $aAddArticle = $this->getActionIds('oxarticles.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sArtTable = $this->getViewName('oxarticles');\n            $aAddArticle = $this->getAll(parent::addFilter(\"select $sArtTable.oxid \" . $this->getQuery()));\n        }\n\n        if ($soxId && $soxId != \"-1\" && is_array($aAddArticle)) {\n            // We force reading from master to prevent issues with slow replications or open transactions\n            // (see ESDEV-3804 and ESDEV-3822).\n            $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster();\n            foreach ($aAddArticle as $sAdd) {\n                $oNewGroup = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                $oNewGroup->init(\"oxobject2selectlist\");\n                $oNewGroup->oxobject2selectlist__oxobjectid = new Field($sAdd);\n                $oNewGroup->oxobject2selectlist__oxselnid = new Field($soxId);\n                $oNewGroup->oxobject2selectlist__oxsort = new Field(\n                    (int) $database->getOne(\n                        \"select max(oxsort) + 1 from oxobject2selectlist where oxobjectid = :oxobjectid\",\n                        [\n                            'oxobjectid' => $sAdd\n                        ]\n                    )\n                );\n                $oNewGroup->save();\n\n                $this->onArticleAddToSelectionList($sAdd);\n            }\n        }\n    }\n\n    /**\n     * Method is used for overriding.\n     *\n     * @param string $articleId\n     */\n    protected function onArticleAddToSelectionList($articleId)\n    {\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/SelectListOrderAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages article select lists sorting\n */\nclass SelectListOrderAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [\n        ['oxtitle', 'oxselectlist', 1, 1, 0],\n        ['oxsort', 'oxobject2selectlist', 1, 0, 0],\n        ['oxident', 'oxselectlist', 0, 0, 0],\n        ['oxvaldesc', 'oxselectlist', 0, 0, 0],\n        ['oxid', 'oxobject2selectlist', 0, 0, 1]\n    ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $sSelTable = $this->getViewName('oxselectlist');\n        $sArtId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n\n        return \" from $sSelTable left join oxobject2selectlist on oxobject2selectlist.oxselnid = $sSelTable.oxid \" .\n                 \"where oxobjectid = \" . DatabaseProvider::getDb()->quote($sArtId) . \" \";\n    }\n\n    /**\n     * Returns SQL query addon for sorting\n     *\n     * @return string\n     */\n    protected function getSorting()\n    {\n        return 'order by oxobject2selectlist.oxsort ';\n    }\n\n    /**\n     * Applies sorting for selection lists\n     */\n    public function setSorting()\n    {\n        $sSelId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSelect = \"select * from oxobject2selectlist where oxobjectid = :oxobjectid order by oxsort\";\n\n        $oList = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n        $oList->init(\"oxbase\", \"oxobject2selectlist\");\n        $oList->selectString($sSelect, [\n            'oxobjectid' => $sSelId\n        ]);\n\n        // fixing indexes\n        $iSelCnt = 0;\n        $aIdx2Id = [];\n        foreach ($oList as $sKey => $oSel) {\n            if ($oSel->oxobject2selectlist__oxsort->value != $iSelCnt) {\n                $oSel->oxobject2selectlist__oxsort->setValue($iSelCnt);\n\n                // saving new index\n                $oSel->save();\n            }\n            $aIdx2Id[$iSelCnt] = $sKey;\n            $iSelCnt++;\n        }\n\n        //\n        if (($iKey = array_search(Registry::getRequest()->getRequestEscapedParameter('sortoxid'), $aIdx2Id)) !== false) {\n            $iDir = (Registry::getRequest()->getRequestEscapedParameter('direction') == 'up') ? ($iKey - 1) : ($iKey + 1);\n            if (isset($aIdx2Id[$iDir])) {\n                // exchanging indexes\n                $oDir1 = $oList->offsetGet($aIdx2Id[$iDir]);\n                $oDir2 = $oList->offsetGet($aIdx2Id[$iKey]);\n\n                $iCopy = $oDir1->oxobject2selectlist__oxsort->value;\n                $oDir1->oxobject2selectlist__oxsort->setValue($oDir2->oxobject2selectlist__oxsort->value);\n                $oDir2->oxobject2selectlist__oxsort->setValue($iCopy);\n\n                $oDir1->save();\n                $oDir2->save();\n            }\n        }\n\n        $sQAdd = $this->getQuery();\n\n        $sQ = 'select ' . $this->getQueryCols() . $sQAdd;\n        $sCountQ = 'select count( * ) ' . $sQAdd;\n\n        $this->outputResponse($this->getData($sCountQ, $sQ));\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ShopConfiguration.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse Exception;\nuse OxidEsales\\Eshop\\Application\\Model\\Category;\nuse OxidEsales\\Eshop\\Application\\Model\\Shop;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\DisplayError;\nuse OxidEsales\\Eshop\\Core\\NoJsValidator;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form\\ContactFormBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FieldConfigurationInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ModuleConfigurationDaoBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setting\\Event\\SettingChangedEvent;\n\n/**\n * Admin shop config manager.\n * Collects shop config information, updates it on user submit, etc.\n * Admin Menu: Main Menu -> Core Settings -> General.\n */\nclass ShopConfiguration extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    protected $_sThisTemplate = 'shop_config';\n    protected $_aSkipMultiline = ['aHomeCountry'];\n    protected $_aParseFloat = ['iMinOrderPrice'];\n\n    protected $_aConfParams = [\n        \"bool\"   => 'confbools',\n        \"str\"    => 'confstrs',\n        \"arr\"    => 'confarrs',\n        \"aarr\"   => 'confaarrs',\n        \"select\" => 'confselects',\n        \"num\"    => 'confnum',\n    ];\n\n    /**\n     * Executes parent method parent::render(), passes shop configuration parameters\n     * to template engine and returns name of template file \"shop_config\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        $config = Registry::getConfig();\n\n        parent::render();\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $this->_aViewData[\"edit\"] = $shop = $this->getEditShop($soxId);\n\n            try {\n                // category choosen as default\n                $this->_aViewData[\"defcat\"] = null;\n                if ($shop->oxshops__oxdefcat->value) {\n                    $category = oxNew(Category::class);\n                    if ($category->load($shop->oxshops__oxdefcat->value)) {\n                        $this->_aViewData[\"defcat\"] = $category;\n                    }\n                }\n            } catch (Exception $exception) {\n                // on most cases this means that views are broken, so just\n                // outputting notice and keeping functionality flow ..\n                $this->_aViewData[\"updateViews\"] = 1;\n            }\n\n            $aoc = Registry::getRequest()->getRequestEscapedParameter(\"aoc\");\n            if ($aoc == 1) {\n                $shopDefaultCategoryAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ShopDefaultCategoryAjax::class);\n                $this->_aViewData['oxajax'] = $shopDefaultCategoryAjax->getColumns();\n\n                return \"popups/shop_default_category\";\n            }\n        }\n\n        $dbVariables = $this->loadConfVars($soxId, $this->getModuleForConfigVars());\n        $confVars = $dbVariables['vars'];\n        $confVars['str']['sVersion'] = $config->getConfigParam('sVersion');\n\n        $this->_aViewData[\"var_constraints\"] = $dbVariables['constraints'];\n        $this->_aViewData[\"var_grouping\"] = $dbVariables['grouping'];\n        foreach ($this->_aConfParams as $type => $param) {\n            $this->_aViewData[$param] = $confVars[$type];\n        }\n\n        // #251A passing country list\n        $countryList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\CountryList::class);\n        $countryList->loadActiveCountries(Registry::getLang()->getObjectTplLanguage());\n        if (isset($confVars['arr'][\"aHomeCountry\"]) && count($confVars['arr'][\"aHomeCountry\"]) && count($countryList)) {\n            foreach ($countryList as $sCountryId => $oCountry) {\n                if (in_array($oCountry->oxcountry__oxid->value, $confVars['arr'][\"aHomeCountry\"])) {\n                    $countryList[$sCountryId]->selected = \"1\";\n                }\n            }\n        }\n\n        $this->_aViewData[\"countrylist\"] = $countryList;\n\n        // checking if cUrl is enabled\n        $this->_aViewData[\"blCurlIsActive\"] = (!function_exists('curl_init')) ? false : true;\n\n        $contactFormConfiguration = ContainerFacade::get(ContactFormBridgeInterface::class)\n            ->getContactFormConfiguration();\n\n        /** @var FieldConfigurationInterface $fieldConfiguration */\n        foreach ($contactFormConfiguration->getFieldConfigurations() as $fieldConfiguration) {\n            $this->_aViewData['contactFormFieldConfigurations'][] = [\n                'name' => $fieldConfiguration->getName(),\n                'label' => $fieldConfiguration->getLabel(),\n                'isRequired' => $fieldConfiguration->isRequired(),\n            ];\n        }\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * return theme filter for config variables\n     *\n     * @return string\n     */\n    protected function getModuleForConfigVars()\n    {\n        return '';\n    }\n\n    /**\n     * Saves shop configuration variables\n     */\n    public function saveConfVars()\n    {\n        $config = Registry::getConfig();\n\n        $this->resetContentCache();\n\n        $configValidator = oxNew(NoJsValidator::class);\n        foreach ($this->_aConfParams as $existingConfigType => $existingConfigName) {\n            $requestValue = Registry::getRequest()->getRequestParameter($existingConfigName);\n            if (is_array($requestValue)) {\n                foreach ($requestValue as $configName => $newConfigValue) {\n                    $oldValue = $config->getConfigParam($configName);\n                    if ($newConfigValue !== $oldValue) {\n                        $sValueToValidate = is_array($newConfigValue) ? join(', ', $newConfigValue) : $newConfigValue;\n                        if (!$configValidator->isValid($sValueToValidate)) {\n                            $error = oxNew(DisplayError::class);\n                            $error->setFormatParameters(htmlspecialchars($sValueToValidate));\n                            $error->setMessage(\"SHOP_CONFIG_ERROR_INVALID_VALUE\");\n                            Registry::getUtilsView()->addErrorToDisplay($error);\n                            continue;\n                        }\n                        $this->saveSetting($configName, $existingConfigType, $newConfigValue);\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * Saves changed shop configuration parameters.\n     */\n    public function save()\n    {\n        // saving config params\n        $this->saveConfVars();\n\n        //saving additional fields (\"oxshops__oxdefcat\"\") that goes directly to shop (not config)\n        /** @var Shop $shop */\n        $shop = oxNew(Shop::class);\n        if ($shop->load($this->getEditObjectId())) {\n            $shop->assign(Registry::getRequest()->getRequestEscapedParameter(\"editval\"));\n            $shop->save();\n        }\n    }\n\n    /**\n     * Load and parse config vars from db.\n     * Return value is a map:\n     *      'vars'        => config variable values as array[type][name] = value\n     *      'constraints' => constraints list as array[name] = constraint\n     *      'grouping'    => grouping info as array[name] = grouping\n     *\n     * @param int    $shopId Shop id\n     * @param string $moduleId module to load (empty string is for base values)\n     *\n     * @return array\n     */\n    public function loadConfVars($shopId, $moduleId)\n    {\n        $config = Registry::getConfig();\n        $configurationVariables = [\n            \"bool\"   => [],\n            \"str\"    => [],\n            \"arr\"    => [],\n            \"aarr\"   => [],\n            \"select\" => [],\n        ];\n        $constraints = [];\n        $groupings = [];\n        $rs = DatabaseProvider::getDb()->select(\n            \"select cfg.oxvarname,\n                    cfg.oxvartype,\n                    cfg.oxvarvalue,\n                    disp.oxvarconstraint,\n                    disp.oxgrouping\n                from oxconfig as cfg\n                    left join oxconfigdisplay as disp\n                        on cfg.oxmodule=disp.oxcfgmodule and cfg.oxvarname=disp.oxcfgvarname\n                where cfg.oxshopid = :oxshopid\n                    and cfg.oxmodule = :oxmodule\n                order by disp.oxpos, cfg.oxvarname\",\n            [\n                'oxshopid' => $shopId,\n                'oxmodule' => $moduleId\n            ]\n        );\n\n        if ($rs != false && $rs->count() > 0) {\n            while (!$rs->EOF) {\n                list($name, $type, $value, $constraint, $grouping) = array_values($rs->fields);\n                $configurationVariables[$type][$name] = $this->unserializeConfVar($type, $name, $value);\n                $constraints[$name] = $this->parseConstraint($type, $constraint);\n                if ($grouping) {\n                    if (!isset($groupings[$grouping])) {\n                        $groupings[$grouping] = [$name => $type];\n                    } else {\n                        $groupings[$grouping][$name] = $type;\n                    }\n                }\n                $rs->fetchRow();\n            }\n        }\n\n        return [\n            'vars'        => $configurationVariables,\n            'constraints' => $constraints,\n            'grouping'    => $groupings,\n        ];\n    }\n\n    /**\n     * If allow to configure information sending to OXID.\n     * For PE and EE users it is always turned on.\n     *\n     * @return bool\n     */\n    public function informationSendingToOxidConfigurable()\n    {\n        return !Registry::getConfig()->getEdition()->isCommunityEdition();\n    }\n\n    /**\n     * parse constraint from type and serialized values\n     *\n     * @param string $type       variable type\n     * @param string $constraint serialized constraint\n     *\n     * @return mixed\n     */\n    protected function parseConstraint($type, $constraint)\n    {\n        switch ($type) {\n            case \"select\":\n                return array_map('trim', explode('|', $constraint));\n                break;\n        }\n        return null;\n    }\n\n    /**\n     * serialize constraint from type and value\n     *\n     * @param string $type       variable type\n     * @param mixed  $constraint constraint value\n     *\n     * @return string\n     */\n    protected function serializeConstraint($type, $constraint)\n    {\n        switch ($type) {\n            case \"select\":\n                return implode('|', array_map('trim', $constraint));\n                break;\n        }\n        return '';\n    }\n\n    /**\n     * Unserialize config var depending on it's type\n     *\n     * @param string $type  var type\n     * @param string $name  var name\n     * @param string $value var value\n     *\n     * @return mixed\n     */\n    public function unserializeConfVar($type, $name, $value)\n    {\n        $str = Str::getStr();\n        $data = null;\n\n        switch ($type) {\n            case \"bool\":\n                $data = ($value == \"true\" || $value == \"1\");\n                break;\n\n            case \"str\":\n            case \"select\":\n            case \"num\":\n            case \"int\":\n                $data = $str->htmlentities($value);\n                if (in_array($name, $this->_aParseFloat)) {\n                    $data = str_replace(',', '.', $data);\n                }\n                break;\n\n            case \"arr\":\n                if (in_array($name, $this->_aSkipMultiline)) {\n                    $data = unserialize($value);\n                } else {\n                    $data = $str->htmlentities($this->arrayToMultiline(unserialize($value)));\n                }\n                break;\n\n            case \"aarr\":\n                if (in_array($name, $this->_aSkipMultiline)) {\n                    $data = unserialize($value);\n                } else {\n                    $data = $str->htmlentities($this->aarrayToMultiline(unserialize($value)));\n                }\n                break;\n        }\n\n        return $data;\n    }\n\n    /**\n     * Prepares data for storing to database.\n     * Example: $sType='aarr', $sName='someName', $mValue='key1=>val1\\nkey2=>val2'\n     *\n     * @param string $type  var type\n     * @param string $name  var name\n     * @param mixed  $value var value\n     *\n     * @return string\n     */\n    public function serializeConfVar($type, $name, $value)\n    {\n        $data = $value;\n\n        switch ($type) {\n            case \"bool\":\n                break;\n\n            case \"str\":\n            case \"select\":\n            case \"int\":\n                if (in_array($name, $this->_aParseFloat)) {\n                    $data = str_replace(',', '.', $data);\n                }\n                break;\n\n            case \"arr\":\n                if (!is_array($value)) {\n                    $data = $this->multilineToArray($value);\n                }\n                break;\n\n            case \"aarr\":\n                $data = $this->multilineToAarray($value);\n                break;\n        }\n\n        return $data;\n    }\n\n    /**\n     * Converts simple array to multiline text. Returns this text.\n     *\n     * @param array $input Array with text\n     *\n     * @return string\n     */\n    protected function arrayToMultiline($input)\n    {\n        return implode(\"\\n\", (array) $input);\n    }\n\n    /**\n     * Converts Multiline text to simple array. Returns this array.\n     *\n     * @param string $multiline Multiline text\n     *\n     * @return array\n     */\n    protected function multilineToArray($multiline)\n    {\n        $array = explode(\"\\n\", $multiline);\n        if (is_array($array)) {\n            foreach ($array as $key => $value) {\n                $array[$key] = trim($value);\n                if ($array[$key] == \"\") {\n                    unset($array[$key]);\n                }\n            }\n\n            return $array;\n        }\n    }\n\n    /**\n     * Converts associative array to multiline text. Returns this text.\n     *\n     * @param array $input Array to convert\n     *\n     * @return string\n     */\n    protected function aarrayToMultiline($input)\n    {\n        if (is_array($input)) {\n            $multiline = '';\n            foreach ($input as $key => $value) {\n                if ($multiline) {\n                    $multiline .= \"\\n\";\n                }\n\n                if (is_string($value)) {\n                    $multiline .= $key . \" => \" . $value;\n                } else {\n                    Registry::getLogger()->warning(\n                        \"The value in ShopConfiguration::aarrayToMultiline() is not a string. The nested values are not supported.\",\n                        [$value]\n                    );\n                }\n            }\n\n            return $multiline;\n        }\n    }\n\n    /**\n     * Converts Multiline text to associative array. Returns this array.\n     *\n     * @param string $multiline Multiline text\n     *\n     * @return array\n     */\n    protected function multilineToAarray($multiline)\n    {\n        $string = Str::getStr();\n        $array = [];\n        $lines = explode(\"\\n\", $multiline);\n        foreach ($lines as $line) {\n            $line = trim($line);\n            if ($line != \"\" && $string->preg_match(\"/(.+)=>(.+)/\", $line, $regs)) {\n                $key = trim($regs[1]);\n                $value = trim($regs[2]);\n                if ($key != \"\" && $value != \"\") {\n                    $array[$key] = $value;\n                }\n            }\n        }\n\n        return $array;\n    }\n\n    /**\n     * Returns active/editable object id\n     *\n     * @return string\n     */\n    public function getEditObjectId()\n    {\n        $editId = parent::getEditObjectId();\n        if (!$editId) {\n            return Registry::getConfig()->getShopId();\n        }\n\n        return $editId;\n    }\n\n    /**\n     * @param string $configName\n     * @param string $existingConfigType\n     * @param mixed $configValue\n     */\n    private function saveSetting(string $configName, string $existingConfigType, $configValue): void\n    {\n        $shopId = (int)$this->getEditObjectId();\n        $module = $this->getModuleForConfigVars();\n        $preparedConfigValue = $this->serializeConfVar($existingConfigType, $configName, $configValue);\n        if (strpos($module, 'module:') !== false) {\n            $moduleId = explode(':', $module)[1];\n            $moduleConfigurationBridge = ContainerFacade::get(ModuleConfigurationDaoBridgeInterface::class);\n            $moduleConfiguration = $moduleConfigurationBridge->get($moduleId);\n\n            if ($moduleConfiguration->hasModuleSetting($configName)) {\n                $setting = $moduleConfiguration->getModuleSetting($configName);\n                $setting->setValue($preparedConfigValue);\n\n                $moduleConfigurationBridge->save($moduleConfiguration);\n\n                ContainerFacade::dispatch(new SettingChangedEvent($configName, $shopId, $moduleId));\n            } else {\n                Registry::getLogger()->warning(\n                    \"Module \\\"$moduleId\\\" setting \\\"$configName\\\" is missing in metadata.php or configuration file.\"\n                );\n                Registry::getConfig()->saveShopConfVar($existingConfigType, $configName, $preparedConfigValue, $shopId, $module);\n            }\n        } else {\n            Registry::getConfig()->saveShopConfVar($existingConfigType, $configName, $preparedConfigValue, $shopId, $module);\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ShopController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\ShopIdCalculator;\n\n/**\n * Admin shop manager.\n * Returns template, that arranges two other templates (\"shop_list\"\n * and \"shop_main\") to frame.\n * Admin Menu: Main Menu -> Core Settings.\n */\nclass ShopController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    const CURRENT_TEMPLATE = 'shop';\n\n    /**\n     * Executes parent method parent::render() and returns name of template\n     * file \"shop\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n        $this->_aViewData['currentadminshop'] = ShopIdCalculator::BASE_SHOP_ID;\n\n        return static::CURRENT_TEMPLATE;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ShopCountries.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n//shop location countries - used when loading dynamic content from oxid servers\n$aLanguages = [\n\n    'en' => 'English',\n    'de' => 'Deutsch',\n\n];\n\n$aLocationCountries['en'] = [\n\n    'de' => 'Germany, Austria, Switzerland',\n    'en' => 'Any other',\n\n];\n\n$aLocationCountries['de'] = [\n\n    'de' => 'Deutschland, &Ouml;sterreich, Schweiz',\n    'en' => 'Andere Region',\n\n];\n\n$aCountries['en'] = [\n\n    \"a7c40f6320aeb2ec2.72885259\" => \"Austria\",\n    \"a7c40f63272a57296.32117580\" => \"France\",\n    \"a7c40f631fc920687.20179984\" => \"Germany\",\n    \"a7c40f632a0804ab5.18804076\" => \"United Kingdom\",\n    \"8f241f11096877ac0.98748826\" => \"United States\",\n    \"a7c40f6321c6f6109.43859248\" => \"Switzerland\",\n    \"8f241f11095306451.36998225\" => \"Afghanistan\",\n    \"8f241f110953265a5.25286134\" => \"Albania\",\n    \"8f241f1109533b943.50287900\" => \"Algeria\",\n    \"8f241f1109534f8c7.80349931\" => \"American Samoa\",\n    \"8f241f11095363464.89657222\" => \"Andorra\",\n    \"8f241f11095377d33.28678901\" => \"Angola\",\n    \"8f241f11095392e41.74397491\" => \"Anguilla\",\n    \"8f241f110953a8d10.29474848\" => \"Antarctica\",\n    \"8f241f110953be8f2.56248134\" => \"Antigua and Barbuda\",\n    \"8f241f110953d2fb0.54260547\" => \"Argentina\",\n    \"8f241f110953e7993.88180360\" => \"Armenia\",\n    \"8f241f110953facc6.31621036\" => \"Aruba\",\n    \"8f241f11095410f38.37165361\" => \"Australia\",\n    \"8f241f1109543cf47.17877015\" => \"Azerbaijan\",\n    \"8f241f11095451379.72078871\" => \"Bahamas\",\n    \"8f241f110954662e3.27051654\" => \"Bahrain\",\n    \"8f241f1109547ae49.60154431\" => \"Bangladesh\",\n    \"8f241f11095497083.21181725\" => \"Barbados\",\n    \"8f241f110954ac5b9.63105203\" => \"Belarus\",\n    \"a7c40f632e04633c9.47194042\" => \"Belgium\",\n    \"8f241f110954d3621.45362515\" => \"Belize\",\n    \"8f241f110954ea065.41455848\" => \"Benin\",\n    \"8f241f110954fee13.50011948\" => \"Bermuda\",\n    \"8f241f11095513ca0.75349731\" => \"Bhutan\",\n    \"8f241f1109552aee2.91004965\" => \"Bolivia\",\n    \"8f241f1109553f902.06960438\" => \"Bosnia and Herzegovina\",\n    \"8f241f11095554834.54199483\" => \"Botswana\",\n    \"8f241f1109556dd57.84292282\" => \"Bouvet Island\",\n    \"8f241f11095592407.89986143\" => \"Brazil\",\n    \"8f241f110955a7644.68859180\" => \"British Indian Ocean Territory\",\n    \"8f241f110955bde61.63256042\" => \"Brunei Darussalam\",\n    \"8f241f110955d3260.55487539\" => \"Bulgaria\",\n    \"8f241f110955ea7c8.36762654\" => \"Burkina Faso\",\n    \"8f241f110956004d5.11534182\" => \"Burundi\",\n    \"8f241f110956175f9.81682035\" => \"Cambodia\",\n    \"8f241f11095632828.20263574\" => \"Cameroon\",\n    \"8f241f11095649d18.02676059\" => \"Canada\",\n    \"8f241f1109565e671.48876354\" => \"Cape Verde\",\n    \"8f241f11095673248.50405852\" => \"Cayman Islands\",\n    \"8f241f1109568a509.03566030\" => \"Central African Republic\",\n    \"8f241f1109569d4c2.42800039\" => \"Chad\",\n    \"8f241f110956b3ea7.11168270\" => \"Chile\",\n    \"8f241f110956c8860.37981845\" => \"China\",\n    \"8f241f110956df6b2.52283428\" => \"Christmas Island\",\n    \"8f241f110956f54b4.26327849\" => \"Cocos (Keeling) Islands\",\n    \"8f241f1109570a1e3.69772638\" => \"Colombia\",\n    \"8f241f1109571f018.46251535\" => \"Comoros\",\n    \"8f241f11095732184.72771986\" => \"Congo\",\n    \"8f241f1109575d708.20084199\" => \"Congo, The Democratic Republic Of The\",\n    \"8f241f11095746a92.94878441\" => \"Cook Islands\",\n    \"8f241f1109575d708.20084150\" => \"Costa Rica\",\n    \"8f241f11095771f76.87904122\" => \"Cote d'Ivoire\",\n    \"8f241f11095789a04.65154246\" => \"Croatia\",\n    \"8f241f1109579ef49.91803242\" => \"Cuba\",\n    \"8f241f110957b6896.52725150\" => \"Cyprus\",\n    \"8f241f110957cb457.97820918\" => \"Czech Republic\",\n    \"8f241f110957e6ef8.56458418\" => \"Denmark\",\n    \"8f241f110957fd356.02918645\" => \"Djibouti\",\n    \"8f241f11095811ea5.84717844\" => \"Dominica\",\n    \"8f241f11095825bf2.61063355\" => \"Dominican Republic\",\n    \"8f241f1109584d512.06663789\" => \"Ecuador\",\n    \"8f241f11095861fb7.55278256\" => \"Egypt\",\n    \"8f241f110958736a9.06061237\" => \"El Salvador\",\n    \"8f241f1109588d077.74284490\" => \"Equatorial Guinea\",\n    \"8f241f110958a2216.38324531\" => \"Eritrea\",\n    \"8f241f110958b69e4.93886171\" => \"Estonia\",\n    \"8f241f110958caf67.08982313\" => \"Ethiopia\",\n    \"8f241f110958e2cc3.90770249\" => \"Falkland Islands (Malvinas)\",\n    \"8f241f110958f7ba4.96908065\" => \"Faroe Islands\",\n    \"8f241f1109590d226.07938729\" => \"Fiji\",\n    \"a7c40f63293c19d65.37472814\" => \"Finland\",\n    \"8f241f1109594fcb1.79441780\" => \"French Guiana\",\n    \"8f241f110959636f5.71476354\" => \"French Polynesia\",\n    \"8f241f110959784a3.34264829\" => \"French Southern Territories\",\n    \"8f241f11095994cb6.59353392\" => \"Gabon\",\n    \"8f241f110959ace77.17379319\" => \"Gambia\",\n    \"8f241f110959c2341.01830199\" => \"Georgia\",\n    \"8f241f110959e96b3.05752152\" => \"Ghana\",\n    \"8f241f110959fdde0.68919405\" => \"Gibraltar\",\n    \"a7c40f633114e8fc6.25257477\" => \"Greece\",\n    \"8f241f11095a29f47.04102343\" => \"Greenland\",\n    \"8f241f11095a3f195.88886789\" => \"Grenada\",\n    \"8f241f11095a52578.45413493\" => \"Guadeloupe\",\n    \"8f241f11095a717b3.68126681\" => \"Guam\",\n    \"8f241f11095a870a5.42235635\" => \"Guatemala\",\n    \"56d308a822c18e106.3ba59099\" => \"Guernsey\",\n    \"8f241f11095a9bf82.19989557\" => \"Guinea\",\n    \"8f241f11095ab2b56.83049280\" => \"Guinea-Bissau\",\n    \"8f241f11095ac9d30.56640429\" => \"Guyana\",\n    \"8f241f11095aebb06.34405179\" => \"Haiti\",\n    \"8f241f11095aff2c3.98054755\" => \"Heard Island And Mcdonald Islands\",\n    \"8f241f110968ebc30.63792746\" => \"Holy See (Vatican City State)\",\n    \"8f241f11095b13f57.56022305\" => \"Honduras\",\n    \"8f241f11095b29021.49657118\" => \"Hong Kong\",\n    \"8f241f11095b3e016.98213173\" => \"Hungary\",\n    \"8f241f11095b55846.26192602\" => \"Iceland\",\n    \"8f241f11095b6bb86.01364904\" => \"India\",\n    \"8f241f11095b80526.59927631\" => \"Indonesia\",\n    \"8f241f11095b94476.05195832\" => \"Iran\",\n    \"8f241f11095bad5b2.42645724\" => \"Iraq\",\n    \"a7c40f632be4237c2.48517912\" => \"Ireland\",\n    \"8f241f11096982354.73448999\" => \"Isle Of Man\",\n    \"8f241f11095bd65e1.59459683\" => \"Israel\",\n    \"a7c40f6323c4bfb36.59919433\" => \"Italy\",\n    \"8f241f11095bfe834.63390185\" => \"Jamaica\",\n    \"8f241f11095c11d43.73419747\" => \"Japan\",\n    \"8f241f11096944468.61956599\" => \"Jersey\",\n    \"8f241f11095c2b304.75906962\" => \"Jordan\",\n    \"8f241f11095c3e2d1.36714463\" => \"Kazakhstan\",\n    \"8f241f11095c5b8e8.66333679\" => \"Kenya\",\n    \"8f241f11095c6e184.21450618\" => \"Kiribati\",\n    \"8f241f11095cb1546.46652174\" => \"Kuwait\",\n    \"8f241f11095cc7ef5.28043767\" => \"Kyrgyzstan\",\n    \"8f241f11095cdccd5.96388808\" => \"Laos\",\n    \"8f241f11095cf2ea6.73925511\" => \"Latvia\",\n    \"8f241f11095d07d87.58986129\" => \"Lebanon\",\n    \"8f241f11095d1c9b2.21548132\" => \"Lesotho\",\n    \"8f241f11095d2fd28.91858908\" => \"Liberia\",\n    \"8f241f11095d46188.64679605\" => \"Libya\",\n    \"a7c40f6322d842ae3.83331920\" => \"Liechtenstein\",\n    \"8f241f11095d6ffa8.86593236\" => \"Lithuania\",\n    \"a7c40f63264309e05.58576680\" => \"Luxembourg\",\n    \"8f241f11095d9c1b2.13577033\" => \"Macao\",\n    \"8f241f11095db2291.58912887\" => \"Macedonia\",\n    \"8f241f11095dccf17.06266806\" => \"Madagascar\",\n    \"8f241f11095de2119.60795833\" => \"Malawi\",\n    \"8f241f11095df78a8.44559506\" => \"Malaysia\",\n    \"8f241f11095e0c6c9.43746477\" => \"Maldives\",\n    \"8f241f11095e24006.17141715\" => \"Mali\",\n    \"8f241f11095e36eb3.69050509\" => \"Malta\",\n    \"8f241f11095e4e338.26817244\" => \"Marshall Islands\",\n    \"8f241f11095e631e1.29476484\" => \"Martinique\",\n    \"8f241f11095e7bff9.09518271\" => \"Mauritania\",\n    \"8f241f11095e90a81.01156393\" => \"Mauritius\",\n    \"8f241f11095ea6249.81474246\" => \"Mayotte\",\n    \"8f241f11095ebf3a6.86388577\" => \"Mexico\",\n    \"8f241f11095ed4902.49276197\" => \"Micronesia, Federated States Of\",\n    \"8f241f11095ee9923.85175653\" => \"Moldova\",\n    \"8f241f11095f00d65.30318330\" => \"Monaco\",\n    \"8f241f11095f160c9.41059441\" => \"Mongolia\",\n    \"56d308a822c18e106.3ba59048\" => \"Montenegro\",\n    \"8f241f11095f314f5.05830324\" => \"Montserrat\",\n    \"8f241f11096006828.49285591\" => \"Morocco\",\n    \"8f241f1109601b419.55269691\" => \"Mozambique\",\n    \"8f241f11096030af5.65449043\" => \"Myanmar\",\n    \"8f241f11096046575.31382060\" => \"Namibia\",\n    \"8f241f1109605b1f4.20574895\" => \"Nauru\",\n    \"8f241f1109607a9e7.03486450\" => \"Nepal\",\n    \"a7c40f632cdd63c52.64272623\" => \"Netherlands\",\n    \"8f241f110960aeb64.09757010\" => \"Netherlands Antilles\",\n    \"8f241f110960c3e97.21901471\" => \"New Caledonia\",\n    \"8f241f110960d8e58.96466103\" => \"New Zealand\",\n    \"8f241f110960ec345.71805056\" => \"Nicaragua\",\n    \"8f241f11096101a79.70513227\" => \"Niger\",\n    \"8f241f11096116744.92008092\" => \"Nigeria\",\n    \"8f241f1109612dc68.63806992\" => \"Niue\",\n    \"8f241f110961442c2.82573898\" => \"Norfolk Island\",\n    \"8f241f11095c87284.37982544\" => \"North Korea\",\n    \"8f241f11096162678.71164081\" => \"Northern Mariana Islands\",\n    \"8f241f11096176795.61257067\" => \"Norway\",\n    \"8f241f1109618d825.87661926\" => \"Oman\",\n    \"2db455824e4a19cc7.14731328\" => \"Other country\",\n    \"8f241f110961a2401.59039740\" => \"Pakistan\",\n    \"8f241f110961b7729.14290490\" => \"Palau\",\n    \"8f241f110968ebc30.63792799\" => \"Palestinian Territory, Occupied\",\n    \"8f241f110961cc384.18166560\" => \"Panama\",\n    \"8f241f110961e3538.78435307\" => \"Papua New Guinea\",\n    \"8f241f110961f9d61.52794273\" => \"Paraguay\",\n    \"8f241f1109620b245.16261506\" => \"Peru\",\n    \"8f241f1109621faf8.40135556\" => \"Philippines\",\n    \"8f241f11096234d62.44125992\" => \"Pitcairn\",\n    \"8f241f1109624d3f8.50953605\" => \"Poland\",\n    \"a7c40f632f65bd8e2.84963272\" => \"Portugal\",\n    \"8f241f11096279a22.50582479\" => \"Puerto Rico\",\n    \"8f241f1109628f903.51478291\" => \"Qatar\",\n    \"8f241f110962a3ec5.65857240\" => \"Réunion\",\n    \"8f241f110962c3007.60363573\" => \"Romania\",\n    \"8f241f110962e40e6.75062153\" => \"Russian Federation\",\n    \"8f241f110962f8615.93666560\" => \"Rwanda\",\n    \"8f241f110968a7cc9.56710199\" => \"Saint Barthélemy\",\n    \"8f241f1109654dca4.99466434\" => \"Saint Helena\",\n    \"8f241f110963177a7.49289900\" => \"Saint Kitts and Nevis\",\n    \"8f241f1109632fab4.68646740\" => \"Saint Lucia\",\n    \"a7c40f632f65bd8e2.84963299\" => \"Saint Martin\",\n    \"8f241f1109656cde9.10816078\" => \"Saint Pierre and Miquelon\",\n    \"8f241f110963443c3.29598809\" => \"Saint Vincent and The Grenadines\",\n    \"8f241f11096359986.06476221\" => \"Samoa\",\n    \"8f241f11096375757.44126946\" => \"San Marino\",\n    \"8f241f1109639b8c4.57484984\" => \"Sao Tome and Principe\",\n    \"8f241f110963b9b20.41500709\" => \"Saudi Arabia\",\n    \"8f241f110963d9962.36307144\" => \"Senegal\",\n    \"8f241f110963f98d8.68428379\" => \"Serbia\",\n    \"8f241f11096418496.77253079\" => \"Seychelles\",\n    \"8f241f11096436968.69551351\" => \"Sierra Leone\",\n    \"8f241f11096456a48.79608805\" => \"Singapore\",\n    \"8f241f1109647a265.29938154\" => \"Slovakia\",\n    \"8f241f11096497149.85116254\" => \"Slovenia\",\n    \"8f241f110964b7bf9.49501835\" => \"Solomon Islands\",\n    \"8f241f110964d5f29.11398308\" => \"Somalia\",\n    \"8f241f110964f2623.74976876\" => \"South Africa\",\n    \"8f241f1109533b943.50287999\" => \"South Georgia and The South Sandwich Islands\",\n    \"8f241f11095c9de64.01275726\" => \"South Korea\",\n    \"a7c40f633038cd578.22975442\" => \"Spain\",\n    \"8f241f11096531330.03198083\" => \"Sri Lanka\",\n    \"8f241f1109658cbe5.08293991\" => \"Sudan\",\n    \"8f241f110965c7347.75108681\" => \"Suriname\",\n    \"8f241f110965eb7b7.26149742\" => \"Svalbard and Jan Mayen\",\n    \"8f241f1109660c113.62780718\" => \"Swaziland\",\n    \"a7c40f632848c5217.53322339\" => \"Sweden\",\n    \"8f241f1109666b7f3.81435898\" => \"Syria\",\n    \"8f241f11096687ec7.58824735\" => \"Taiwan, Province of China\",\n    \"8f241f110966a54d1.43798997\" => \"Tajikistan\",\n    \"8f241f110966c3a75.68297960\" => \"Tanzania\",\n    \"8f241f11096707e08.60512709\" => \"Thailand\",\n    \"8f241f11095839323.86755169\" => \"Timor-Leste\",\n    \"8f241f110967241e1.34925220\" => \"Togo\",\n    \"8f241f11096742565.72138875\" => \"Tokelau\",\n    \"8f241f11096762b31.03069244\" => \"Tonga\",\n    \"8f241f1109677ed23.84886671\" => \"Trinidad and Tobago\",\n    \"8f241f1109679d988.46004322\" => \"Tunisia\",\n    \"8f241f110967bba40.88233204\" => \"Turkey\",\n    \"8f241f110967d8f65.52699796\" => \"Turkmenistan\",\n    \"8f241f110967f73f8.13141492\" => \"Turks and Caicos Islands\",\n    \"8f241f1109680ec30.97426963\" => \"Tuvalu\",\n    \"8f241f11096823019.47846368\" => \"Uganda\",\n    \"8f241f110968391d2.37199812\" => \"Ukraine\",\n    \"8f241f1109684bf15.63071279\" => \"United Arab Emirates\",\n    \"8f241f11096894977.41239553\" => \"United States Minor Outlying Islands\",\n    \"8f241f110968a7cc9.56710143\" => \"Uruguay\",\n    \"8f241f110968bec45.44161857\" => \"Uzbekistan\",\n    \"8f241f110968d3f03.13630334\" => \"Vanuatu\",\n    \"8f241f11096902d92.14742486\" => \"Venezuela\",\n    \"8f241f11096919d00.92534927\" => \"Vietnam\",\n    \"8f241f1109692fc04.15216034\" => \"Virgin Islands, British\",\n    \"8f241f11096944468.61956573\" => \"Virgin Islands, U.S.\",\n    \"8f241f110969598c8.76966113\" => \"Wallis and Futuna\",\n    \"8f241f1109696e4e9.33006292\" => \"Western Sahara\",\n    \"8f241f11096982354.73448958\" => \"Yemen\",\n    \"8f241f110969c34a2.42564730\" => \"Zambia\",\n    \"8f241f110969da699.04185888\" => \"Zimbabwe\",\n    \"a7c40f632a0804ab5.18804099\" => \"Åland Islands\",\n];\n\n$aCountries['de'] = [\n\n    \"a7c40f6320aeb2ec2.72885259\" => \"Österreich\",\n    \"a7c40f63272a57296.32117580\" => \"Frankreich\",\n    \"a7c40f631fc920687.20179984\" => \"Deutschland\",\n    \"a7c40f6321c6f6109.43859248\" => \"Schweiz\",\n    \"a7c40f632a0804ab5.18804076\" => \"Vereinigtes Königreich\",\n    \"8f241f11096877ac0.98748826\" => \"Vereinigte Staaten von Amerika\",\n    \"8f241f11095306451.36998225\" => \"Afghanistan\",\n    \"8f241f110953265a5.25286134\" => \"Albanien\",\n    \"8f241f1109533b943.50287900\" => \"Algerien\",\n    \"8f241f1109534f8c7.80349931\" => \"Amerikanisch Samoa\",\n    \"8f241f11096944468.61956573\" => \"Amerikanische Jungferninseln\",\n    \"2db455824e4a19cc7.14731328\" => \"Anderes Land\",\n    \"8f241f11095363464.89657222\" => \"Andorra\",\n    \"8f241f11095377d33.28678901\" => \"Angola\",\n    \"8f241f11095392e41.74397491\" => \"Anguilla\",\n    \"8f241f110953a8d10.29474848\" => \"Antarktis\",\n    \"8f241f110953be8f2.56248134\" => \"Antigua und Barbuda\",\n    \"8f241f110953d2fb0.54260547\" => \"Argentinien\",\n    \"8f241f110953e7993.88180360\" => \"Armenien\",\n    \"8f241f110953facc6.31621036\" => \"Aruba\",\n    \"8f241f1109543cf47.17877015\" => \"Aserbaidschan\",\n    \"8f241f11095410f38.37165361\" => \"Australien\",\n    \"8f241f11095451379.72078871\" => \"Bahamas\",\n    \"8f241f110954662e3.27051654\" => \"Bahrain\",\n    \"8f241f1109547ae49.60154431\" => \"Bangladesch\",\n    \"8f241f11095497083.21181725\" => \"Barbados\",\n    \"a7c40f632e04633c9.47194042\" => \"Belgien\",\n    \"8f241f110954d3621.45362515\" => \"Belize\",\n    \"8f241f110954ea065.41455848\" => \"Benin\",\n    \"8f241f110954fee13.50011948\" => \"Bermuda\",\n    \"8f241f11095513ca0.75349731\" => \"Bhutan\",\n    \"8f241f1109552aee2.91004965\" => \"Bolivien\",\n    \"8f241f1109553f902.06960438\" => \"Bosnien und Herzegowina\",\n    \"8f241f11095554834.54199483\" => \"Botsuana\",\n    \"8f241f1109556dd57.84292282\" => \"Bouvetinsel\",\n    \"8f241f11095592407.89986143\" => \"Brasilien\",\n    \"8f241f1109692fc04.15216034\" => \"Britische Jungferninseln\",\n    \"8f241f110955a7644.68859180\" => \"Britisches Territorium im Indischen Ozean\",\n    \"8f241f110955bde61.63256042\" => \"Brunei Darussalam\",\n    \"8f241f110955d3260.55487539\" => \"Bulgarien\",\n    \"8f241f110955ea7c8.36762654\" => \"Burkina Faso\",\n    \"8f241f110956004d5.11534182\" => \"Burundi\",\n    \"8f241f110956b3ea7.11168270\" => \"Chile\",\n    \"8f241f110956c8860.37981845\" => \"China\",\n    \"8f241f11095746a92.94878441\" => \"Cookinseln\",\n    \"8f241f1109575d708.20084150\" => \"Costa Rica\",\n    \"8f241f11095771f76.87904122\" => \"Elfenbeinküste\",\n    \"8f241f11095811ea5.84717844\" => \"Dominica\",\n    \"8f241f11095825bf2.61063355\" => \"Dominikanische Republik\",\n    \"8f241f110957fd356.02918645\" => \"Dschibuti\",\n    \"8f241f110957e6ef8.56458418\" => \"Dänemark\",\n    \"8f241f1109584d512.06663789\" => \"Ecuador\",\n    \"8f241f110958736a9.06061237\" => \"El Salvador\",\n    \"8f241f110958a2216.38324531\" => \"Eritrea\",\n    \"8f241f110958b69e4.93886171\" => \"Estland\",\n    \"8f241f110958e2cc3.90770249\" => \"Falklandinseln (Malwinen)\",\n    \"8f241f1109590d226.07938729\" => \"Fidschi\",\n    \"a7c40f63293c19d65.37472814\" => \"Finnland\",\n    \"8f241f1109594fcb1.79441780\" => \"Französisch Guiana\",\n    \"8f241f110959636f5.71476354\" => \"Französisch-Polynesien\",\n    \"8f241f110959784a3.34264829\" => \"Französische Südgebiete\",\n    \"8f241f110958f7ba4.96908065\" => \"Färöer\",\n    \"8f241f11095994cb6.59353392\" => \"Gabun\",\n    \"8f241f110959ace77.17379319\" => \"Gambia\",\n    \"8f241f110959c2341.01830199\" => \"Georgien\",\n    \"8f241f110959e96b3.05752152\" => \"Ghana\",\n    \"8f241f110959fdde0.68919405\" => \"Gibraltar\",\n    \"8f241f11095a3f195.88886789\" => \"Grenada\",\n    \"a7c40f633114e8fc6.25257477\" => \"Griechenland\",\n    \"8f241f11095a29f47.04102343\" => \"Grönland\",\n    \"8f241f11095a52578.45413493\" => \"Guadeloupe\",\n    \"8f241f11095a717b3.68126681\" => \"Guam\",\n    \"8f241f11095a870a5.42235635\" => \"Guatemala\",\n    \"56d308a822c18e106.3ba59099\" => \"Guernsey\",\n    \"8f241f11095a9bf82.19989557\" => \"Guinea\",\n    \"8f241f11095ab2b56.83049280\" => \"Guinea-Bissau\",\n    \"8f241f11095ac9d30.56640429\" => \"Guyana\",\n    \"8f241f11095aebb06.34405179\" => \"Haiti\",\n    \"8f241f11095aff2c3.98054755\" => \"Heard Insel und McDonald Inseln\",\n    \"8f241f110968ebc30.63792746\" => \"Heiliger Stuhl (Vatikanstadt)\",\n    \"8f241f11095b13f57.56022305\" => \"Honduras\",\n    \"8f241f11095b29021.49657118\" => \"Hong Kong\",\n    \"8f241f11095b6bb86.01364904\" => \"Indien\",\n    \"8f241f11095b80526.59927631\" => \"Indonesien\",\n    \"8f241f11095bad5b2.42645724\" => \"Irak\",\n    \"8f241f11095b94476.05195832\" => \"Iran\",\n    \"a7c40f632be4237c2.48517912\" => \"Irland\",\n    \"8f241f11095b55846.26192602\" => \"Island\",\n    \"8f241f11096982354.73448999\" => \"Isle of Man\",\n    \"8f241f11095bd65e1.59459683\" => \"Israel\",\n    \"a7c40f6323c4bfb36.59919433\" => \"Italien\",\n    \"8f241f11095bfe834.63390185\" => \"Jamaika\",\n    \"8f241f11095c11d43.73419747\" => \"Japan\",\n    \"8f241f11096982354.73448958\" => \"Jemen\",\n    \"8f241f11096944468.61956599\" => \"Jersey\",\n    \"8f241f11095c2b304.75906962\" => \"Jordanien\",\n    \"8f241f11095673248.50405852\" => \"Kaimaninseln\",\n    \"8f241f110956175f9.81682035\" => \"Kambodscha\",\n    \"8f241f11095632828.20263574\" => \"Kamerun\",\n    \"8f241f11095649d18.02676059\" => \"Kanada\",\n    \"8f241f1109565e671.48876354\" => \"Kap Verde\",\n    \"8f241f11095c3e2d1.36714463\" => \"Kasachstan\",\n    \"8f241f1109628f903.51478291\" => \"Katar\",\n    \"8f241f11095c5b8e8.66333679\" => \"Kenia\",\n    \"8f241f11095cc7ef5.28043767\" => \"Kirgisistan\",\n    \"8f241f11095c6e184.21450618\" => \"Kiribati\",\n    \"8f241f110956f54b4.26327849\" => \"Kokosinseln (Keelinginseln)\",\n    \"8f241f1109570a1e3.69772638\" => \"Kolumbien\",\n    \"8f241f1109571f018.46251535\" => \"Komoren\",\n    \"8f241f11095732184.72771986\" => \"Kongo\",\n    \"8f241f1109575d708.20084199\" => \"Kongo, Dem. Rep.\",\n    \"8f241f11095789a04.65154246\" => \"Kroatien\",\n    \"8f241f1109579ef49.91803242\" => \"Kuba\",\n    \"8f241f11095cb1546.46652174\" => \"Kuwait\",\n    \"8f241f11095cdccd5.96388808\" => \"Laos\",\n    \"8f241f11095d1c9b2.21548132\" => \"Lesotho\",\n    \"8f241f11095cf2ea6.73925511\" => \"Lettland\",\n    \"8f241f11095d07d87.58986129\" => \"Libanon\",\n    \"8f241f11095d2fd28.91858908\" => \"Liberia\",\n    \"8f241f11095d46188.64679605\" => \"Libyen\",\n    \"a7c40f6322d842ae3.83331920\" => \"Liechtenstein\",\n    \"8f241f11095d6ffa8.86593236\" => \"Litauen\",\n    \"a7c40f63264309e05.58576680\" => \"Luxemburg\",\n    \"8f241f11095d9c1b2.13577033\" => \"Macao\",\n    \"8f241f11095dccf17.06266806\" => \"Madagaskar\",\n    \"8f241f11095de2119.60795833\" => \"Malawi\",\n    \"8f241f11095df78a8.44559506\" => \"Malaysia\",\n    \"8f241f11095e0c6c9.43746477\" => \"Malediven\",\n    \"8f241f11095e24006.17141715\" => \"Mali\",\n    \"8f241f11095e36eb3.69050509\" => \"Malta\",\n    \"8f241f11096006828.49285591\" => \"Marokko\",\n    \"8f241f11095e4e338.26817244\" => \"Marshallinseln\",\n    \"8f241f11095e631e1.29476484\" => \"Martinique\",\n    \"8f241f11095e7bff9.09518271\" => \"Mauretanien\",\n    \"8f241f11095e90a81.01156393\" => \"Mauritius\",\n    \"8f241f11095ea6249.81474246\" => \"Mayotte\",\n    \"8f241f11095db2291.58912887\" => \"Mazedonien\",\n    \"8f241f11095ebf3a6.86388577\" => \"Mexiko\",\n    \"8f241f11095ed4902.49276197\" => \"Mikronesien, Föderierte Staaten von\",\n    \"8f241f11095ee9923.85175653\" => \"Moldawien\",\n    \"8f241f11095f00d65.30318330\" => \"Monaco\",\n    \"8f241f11095f160c9.41059441\" => \"Mongolei\",\n    \"56d308a822c18e106.3ba59048\" => \"Montenegro\",\n    \"8f241f11095f314f5.05830324\" => \"Montserrat\",\n    \"8f241f1109601b419.55269691\" => \"Mosambik\",\n    \"8f241f11096030af5.65449043\" => \"Myanmar\",\n    \"8f241f11096046575.31382060\" => \"Namibia\",\n    \"8f241f1109605b1f4.20574895\" => \"Nauru\",\n    \"8f241f1109607a9e7.03486450\" => \"Nepal\",\n    \"8f241f110960c3e97.21901471\" => \"Neukaledonien\",\n    \"8f241f110960d8e58.96466103\" => \"Neuseeland\",\n    \"8f241f110960ec345.71805056\" => \"Nicaragua\",\n    \"a7c40f632cdd63c52.64272623\" => \"Niederlande\",\n    \"8f241f110960aeb64.09757010\" => \"Niederländische Antillen\",\n    \"8f241f11096101a79.70513227\" => \"Niger\",\n    \"8f241f11096116744.92008092\" => \"Nigeria\",\n    \"8f241f1109612dc68.63806992\" => \"Niue\",\n    \"8f241f11095c87284.37982544\" => \"Nordkorea\",\n    \"8f241f110961442c2.82573898\" => \"Norfolkinsel\",\n    \"8f241f11096176795.61257067\" => \"Norwegen\",\n    \"8f241f11096162678.71164081\" => \"Nördliche Marianen\",\n    \"8f241f1109618d825.87661926\" => \"Oman\",\n    \"8f241f110961a2401.59039740\" => \"Pakistan\",\n    \"8f241f110961b7729.14290490\" => \"Palau\",\n    \"8f241f110968ebc30.63792799\" => \"Palästinische Gebiete\",\n    \"8f241f110961cc384.18166560\" => \"Panama\",\n    \"8f241f110961e3538.78435307\" => \"Papua-Neuguinea\",\n    \"8f241f110961f9d61.52794273\" => \"Paraguay\",\n    \"8f241f1109620b245.16261506\" => \"Peru\",\n    \"8f241f1109621faf8.40135556\" => \"Philippinen\",\n    \"8f241f11096234d62.44125992\" => \"Pitcairn\",\n    \"8f241f1109624d3f8.50953605\" => \"Polen\",\n    \"a7c40f632f65bd8e2.84963272\" => \"Portugal\",\n    \"8f241f11096279a22.50582479\" => \"Puerto Rico\",\n    \"8f241f11096687ec7.58824735\" => \"Republik China (Taiwan)\",\n    \"8f241f110962a3ec5.65857240\" => \"Réunion\",\n    \"8f241f110962f8615.93666560\" => \"Ruanda\",\n    \"8f241f110962c3007.60363573\" => \"Rumänien\",\n    \"8f241f110962e40e6.75062153\" => \"Russische Föderation\",\n    \"8f241f1109654dca4.99466434\" => \"Saint Helena\",\n    \"8f241f1109656cde9.10816078\" => \"Saint Pierre und Miquelon\",\n    \"8f241f110968a7cc9.56710199\" => \"Saint-Barthélemy\",\n    \"a7c40f632f65bd8e2.84963299\" => \"Saint-Martin\",\n    \"8f241f110964b7bf9.49501835\" => \"Salomonen\",\n    \"8f241f110969c34a2.42564730\" => \"Sambia\",\n    \"8f241f11096359986.06476221\" => \"Samoa\",\n    \"8f241f11096375757.44126946\" => \"San Marino\",\n    \"8f241f1109639b8c4.57484984\" => \"Sao Tome und Principe\",\n    \"8f241f110963b9b20.41500709\" => \"Saudi-Arabien\",\n    \"a7c40f632848c5217.53322339\" => \"Schweden\",\n    \"8f241f110963d9962.36307144\" => \"Senegal\",\n    \"8f241f110963f98d8.68428379\" => \"Serbien\",\n    \"8f241f11096418496.77253079\" => \"Seychellen\",\n    \"8f241f11096436968.69551351\" => \"Sierra Leone\",\n    \"8f241f110969da699.04185888\" => \"Simbabwe\",\n    \"8f241f11096456a48.79608805\" => \"Singapur\",\n    \"8f241f1109647a265.29938154\" => \"Slowakei\",\n    \"8f241f11096497149.85116254\" => \"Slowenien\",\n    \"8f241f110964d5f29.11398308\" => \"Somalia\",\n    \"a7c40f633038cd578.22975442\" => \"Spanien\",\n    \"8f241f11096531330.03198083\" => \"Sri Lanka\",\n    \"8f241f110963177a7.49289900\" => \"St. Kitts und Nevis\",\n    \"8f241f1109632fab4.68646740\" => \"St. Lucia\",\n    \"8f241f110963443c3.29598809\" => \"St. Vincent und die Grenadinen\",\n    \"8f241f1109658cbe5.08293991\" => \"Sudan\",\n    \"8f241f110965c7347.75108681\" => \"Suriname\",\n    \"8f241f110965eb7b7.26149742\" => \"Svalbard und Jan Mayen\",\n    \"8f241f1109660c113.62780718\" => \"Swasiland\",\n    \"8f241f110964f2623.74976876\" => \"Südafrika\",\n    \"8f241f1109533b943.50287999\" => \"Südgeorgien und die Südlichen Sandwichinseln\",\n    \"8f241f11095c9de64.01275726\" => \"Südkorea\",\n    \"8f241f1109666b7f3.81435898\" => \"Syrien\",\n    \"8f241f110966a54d1.43798997\" => \"Tadschikistan\",\n    \"8f241f110966c3a75.68297960\" => \"Tansania\",\n    \"8f241f11096707e08.60512709\" => \"Thailand\",\n    \"8f241f11095839323.86755169\" => \"Timor-Leste\",\n    \"8f241f110967241e1.34925220\" => \"Togo\",\n    \"8f241f11096742565.72138875\" => \"Tokelau\",\n    \"8f241f11096762b31.03069244\" => \"Tonga\",\n    \"8f241f1109677ed23.84886671\" => \"Trinidad und Tobago\",\n    \"8f241f1109569d4c2.42800039\" => \"Tschad\",\n    \"8f241f110957cb457.97820918\" => \"Tschechische Republik\",\n    \"8f241f1109679d988.46004322\" => \"Tunesien\",\n    \"8f241f110967d8f65.52699796\" => \"Turkmenistan\",\n    \"8f241f110967f73f8.13141492\" => \"Turks- und Caicosinseln\",\n    \"8f241f1109680ec30.97426963\" => \"Tuvalu\",\n    \"8f241f110967bba40.88233204\" => \"Türkei\",\n    \"8f241f11096823019.47846368\" => \"Uganda\",\n    \"8f241f110968391d2.37199812\" => \"Ukraine\",\n    \"8f241f11095b3e016.98213173\" => \"Ungarn\",\n    \"8f241f11096894977.41239553\" => \"United States Minor Outlying Islands\",\n    \"8f241f110968a7cc9.56710143\" => \"Uruguay\",\n    \"8f241f110968bec45.44161857\" => \"Usbekistan\",\n    \"8f241f110968d3f03.13630334\" => \"Vanuatu\",\n    \"8f241f11096902d92.14742486\" => \"Venezuela\",\n    \"8f241f1109684bf15.63071279\" => \"Vereinigte Arabische Emirate\",\n    \"8f241f11096919d00.92534927\" => \"Vietnam\",\n    \"8f241f110969598c8.76966113\" => \"Wallis und Futuna\",\n    \"8f241f110956df6b2.52283428\" => \"Weihnachtsinsel\",\n    \"8f241f110954ac5b9.63105203\" => \"Weißrussland\",\n    \"8f241f1109696e4e9.33006292\" => \"Westsahara\",\n    \"8f241f1109568a509.03566030\" => \"Zentralafrikanische Republik\",\n    \"8f241f110957b6896.52725150\" => \"Zypern\",\n    \"a7c40f632a0804ab5.18804099\" => \"Åland Inseln\",\n    \"8f241f11095861fb7.55278256\" => \"Ägypten\",\n    \"8f241f1109588d077.74284490\" => \"Äquatorialguinea\",\n    \"8f241f110958caf67.08982313\" => \"Äthiopien\",\n];\n"
  },
  {
    "path": "source/Application/Controller/Admin/ShopDefaultCategoryAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class controls article assignment to attributes\n */\nclass ShopDefaultCategoryAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,         visible, multilanguage, ident\n        ['oxtitle', 'oxcategories', 1, 1, 0],\n        ['oxdesc', 'oxcategories', 1, 1, 0],\n        ['oxid', 'oxcategories', 0, 0, 0],\n        ['oxid', 'oxcategories', 0, 0, 1]\n    ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $oCat = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n        $oCat->setLanguage(Registry::getRequest()->getRequestEscapedParameter('editlanguage'));\n\n        $sCategoriesTable = $oCat->getViewName();\n\n        return \" from $sCategoriesTable where \" . $oCat->getSqlActiveSnippet();\n    }\n\n    /**\n     * Removing article from corssselling list\n     */\n    public function unassignCat()\n    {\n        $sShopId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $oShop = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Shop::class);\n        if ($oShop->load($sShopId)) {\n            $oShop->oxshops__oxdefcat = new \\OxidEsales\\Eshop\\Core\\Field('');\n            $oShop->save();\n        }\n    }\n\n    /**\n     * Adding article to corssselling list\n     */\n    public function assignCat()\n    {\n        $sChosenCat = Registry::getRequest()->getRequestEscapedParameter('oxcatid');\n        $sShopId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $oShop = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Shop::class);\n        if ($oShop->load($sShopId)) {\n            $oShop->oxshops__oxdefcat = new \\OxidEsales\\Eshop\\Core\\Field($sChosenCat);\n            $oShop->save();\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ShopLicense.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Shop;\nuse OxidEsales\\Eshop\\Core\\Curl;\nuse OxidEsales\\Eshop\\Core\\Exception\\SystemComponentException;\nuse OxidEsales\\Eshop\\Core\\OnlineCaller;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\ShopVersion;\nuse Throwable;\n\n/**\n * Admin shop license setting manager.\n * Collects shop license settings, updates it on user submit, etc.\n * Admin Menu: Main Menu -> Core Settings -> License.\n */\nclass ShopLicense extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ShopConfiguration\n{\n    /** @var string Current class template */\n    protected $_sThisTemplate = 'shop_license';\n\n    /** @var string Current shop version links for edition. */\n    private string $versionCheckLink = 'https://admin.oxid-esales.com/CE/onlinecheck.php';\n\n    /** @inheritdoc */\n    public function render()\n    {\n        if (Registry::getConfig()->isDemoShop()) {\n            /** @var SystemComponentException $oSystemComponentException */\n            $oSystemComponentException = oxNew(SystemComponentException::class, 'license');\n            throw $oSystemComponentException;\n        }\n\n        parent::render();\n\n        $soxId = $this->_aViewData['oxid'] = $this->getEditObjectId();\n        if ($soxId != '-1') {\n            // load object\n            $oShop = oxNew(Shop::class);\n            $oShop->load($soxId);\n            $this->_aViewData['edit'] = $oShop;\n        }\n\n        $this->_aViewData['version'] = ShopVersion::getVersion();\n\n        $this->_aViewData['aCurVersionInfo'] = $this->fetchCurVersionInfo($this->versionCheckLink);\n\n        if (!$this->canUpdate()) {\n            $this->_aViewData['readonly'] = true;\n        }\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Checks if the license key update is allowed.\n     *\n     * @return bool\n     */\n    protected function canUpdate(): bool\n    {\n        $myConfig = Registry::getConfig();\n\n        $blIsMallAdmin = Registry::getSession()->getVariable('malladmin');\n        if (!$blIsMallAdmin) {\n            return false;\n        }\n        if ($myConfig->isDemoShop()) {\n            return false;\n        }\n\n        return true;\n    }\n\n    /**\n     * Fetch current shop version information from url\n     *\n     * @param string $sUrl current version info fetching url by edition\n     *\n     * @return string\n     */\n    protected function fetchCurVersionInfo($sUrl): string\n    {\n        try {\n            $response = $this->requestVersionInfo($sUrl);\n        } catch (Throwable $e) {\n            /** Exception is not logged! */\n            $this->handleConnectionError($e);\n            return '';\n        }\n\n        $response = $this->filterVersionCheckerResponse($response);\n        $newestShopVersion = $this->parseResponseForTheNewestShopVersion($response);\n\n        return $newestShopVersion && $this->shopIsOutdated($newestShopVersion)\n            ? $this->insertLinkToShopUpdateDocumentation($response)\n            : $response;\n    }\n\n    private function requestVersionInfo(string $url): string\n    {\n        $curl = oxNew(Curl::class);\n        $curl->setMethod('POST');\n        $curl->setUrl(sprintf('%s/%s', $url, $this->getLanguageAbbreviation()));\n        $curl->setParameters(['myversion' => ShopVersion::getVersion()]);\n        $curl->setOption(\n            Curl::CONNECT_TIMEOUT_OPTION,\n            OnlineCaller::CURL_CONNECT_TIMEOUT\n        );\n        $curl->setOption(\n            Curl::EXECUTION_TIMEOUT_OPTION,\n            OnlineCaller::CURL_EXECUTION_TIMEOUT\n        );\n        return $curl->execute();\n    }\n\n    private function getLanguageAbbreviation(): string\n    {\n        $language = Registry::getLang();\n        return $language->getLanguageAbbr($language->getTplLanguage());\n    }\n\n    private function handleConnectionError(Throwable $e): void\n    {\n        $this->displayErrorMessage($e->getMessage());\n    }\n\n    private function displayErrorMessage(string $message): void\n    {\n        Registry::getUtilsView()->addErrorToDisplay(\n            sprintf(\n                '%s! %s.',\n                Registry::getLang()->translateString('ADMIN_SETTINGS_LICENSE_VERSION_FETCH_INFO_ERROR'),\n                sprintf(Registry::getLang()->translateString('CURL_EXECUTE_ERROR'), $message)\n            )\n        );\n    }\n\n    private function filterVersionCheckerResponse(string $response): string\n    {\n        $response = strip_tags(trim($response), '<br><b>');\n\n        return str_replace(['<br/>', '<br />'], '<br>', $response);\n    }\n\n    private function parseResponseForTheNewestShopVersion(string $response): string\n    {\n        preg_match_all('/[1-9]{1,3}\\.\\d{1,3}\\.\\d{1,3}/', $response, $matches);\n\n        return $matches[0][1] ?? '';\n    }\n\n    private function shopIsOutdated(string $newestShopVersion): bool\n    {\n        return version_compare(ShopVersion::getVersion(), $newestShopVersion, '<');\n    }\n\n    private function insertLinkToShopUpdateDocumentation(string $response): string\n    {\n        $lines = explode('<br>', $response);\n        $lineWithUpdateText = array_key_last($lines) - 1;\n        $documentationUrl = Registry::getLang()->translateString('VERSION_UPDATE_LINK');\n\n        $lines[$lineWithUpdateText] =\n            \"<a id='linkToUpdate' href='$documentationUrl' target='_blank'>{$lines[$lineWithUpdateText]}</a>\";\n\n        return implode('<br>', $lines);\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ShopList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse oxRegistry;\n\n/**\n * Admin shop list manager.\n * Performs collection and managing (such as filtering or deleting) function.\n * Admin Menu: Main Menu -> Core Settings.\n */\nclass ShopList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /** New Shop indicator. */\n    const NEW_SHOP_ID = '-1';\n\n    /**\n     * Forces main frame update is set TRUE\n     *\n     * @var bool\n     */\n    protected $_blUpdateMain = false;\n\n    /**\n     * Default SQL sorting parameter (default null).\n     *\n     * @var string\n     */\n    protected $_sDefSortField = 'oxname';\n\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxshop';\n\n    /**\n     * Navigation frame reload marker\n     *\n     * @var bool\n     */\n    protected $_blUpdateNav = null;\n\n    /**\n     * Executes parent method parent::render() and returns name of template\n     * file \"shop_list\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        parent::render();\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != self::NEW_SHOP_ID) {\n            // load object\n            $oShop = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Shop::class);\n            if (!$oShop->load($soxId)) {\n                $soxId = $myConfig->getBaseShopId();\n                $oShop->load($soxId);\n            }\n            $this->_aViewData['editshop'] = $oShop;\n        }\n\n        // default page number 1\n        $this->_aViewData['default_edit'] = 'shop_main';\n        $this->_aViewData['updatemain'] = $this->_blUpdateMain;\n\n        $this->updateNavigation();\n\n        if (isset($this->_aViewData['updatenav']) && $this->_aViewData['updatenav']) {\n            //skipping requirements checking when reloading nav frame\n            \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable(\"navReload\", true);\n        }\n\n        //making sure we really change shops on low level\n        if ($soxId && $soxId != self::NEW_SHOP_ID) {\n            $myConfig->setShopId($soxId);\n            \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable('currentadminshop', $soxId);\n        }\n\n        return 'shop_list';\n    }\n\n    /**\n     * Sets SQL WHERE condition. Returns array of conditions.\n     *\n     * @return array\n     */\n    public function buildWhere()\n    {\n        // we override this to add our shop if we are not malladmin\n        $this->_aWhere = parent::buildWhere();\n        if (!\\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable('malladmin')) {\n            // we only allow to see our shop\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $this->_aWhere[$tableViewNameGenerator->getViewName(\"oxshops\") . \".oxid\"]\n                = \\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable(\"actshop\");\n        }\n\n        return $this->_aWhere;\n    }\n\n    /**\n     * Set to view data if update navigation menu.\n     */\n    protected function updateNavigation()\n    {\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ShopMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Shop;\nuse OxidEsales\\Eshop\\Application\\Model\\User;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Exception\\StandardException;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\n\n/**\n * Admin article main shop manager.\n * Performs collection and updatind (on user submit) main item information.\n * Admin Menu: Main Menu -> Core Settings -> Main.\n */\nclass ShopMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** Identifies new shop. */\n    const NEW_SHOP_ID = \"-1\";\n\n    /**\n     * Shop field set size, limited to 64bit by MySQL\n     *\n     * @var int\n     */\n    const SHOP_FIELD_SET_SIZE = 64;\n\n    /**\n     * Controller render method, which returns the name of the template file.\n     *\n     * @return string\n     */\n    public function render()\n    {\n        $config = Registry::getConfig();\n        parent::render();\n\n        $shopId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n\n        $templateName = $this->renderNewShop();\n\n        if ($templateName) {\n            return $templateName;\n        }\n\n        $user = $this->getUser();\n        $shopId = $this->updateShopIdByUser($user, $shopId, true);\n\n        if (isset($shopId) && $shopId != self::NEW_SHOP_ID) {\n            $shop = oxNew(Shop::class);\n            $subjLang = Registry::getRequest()->getRequestEscapedParameter(\"subjlang\");\n            if (!isset($subjLang)) {\n                $subjLang = $this->_iEditLang;\n            }\n\n            if ($subjLang && $subjLang > 0) {\n                $this->_aViewData[\"subjlang\"] = $subjLang;\n            }\n\n            $shop->loadInLang($subjLang, $shopId);\n\n            $this->_aViewData[\"edit\"] = $shop;\n            Registry::getSession()->setVariable(\"shp\", $shopId);\n        }\n\n        $this->checkParent($shop);\n\n        $this->_aViewData['IsOXDemoShop'] = $config->isDemoShop();\n        if (!isset($this->_aViewData['updatenav'])) {\n            $this->_aViewData['updatenav'] = Registry::getRequest()->getRequestEscapedParameter('updatenav');\n        }\n\n        return \"shop_main\";\n    }\n\n    /**\n     * Saves changed main shop configuration parameters.\n     *\n     * @return null\n     */\n    public function save()\n    {\n        parent::save();\n\n        $config = Registry::getConfig();\n        $shopId = $this->getEditObjectId();\n\n        $parameters = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        $user = $this->getUser();\n        $shopId = $this->updateShopIdByUser($user, $shopId, false);\n\n        //  #918 S\n        // checkbox handling\n        $parameters['oxshops__oxactive'] = (isset($parameters['oxshops__oxactive']) && $parameters['oxshops__oxactive'] == true) ? 1 : 0;\n        $parameters['oxshops__oxproductive'] = (isset($parameters['oxshops__oxproductive']) && $parameters['oxshops__oxproductive'] == true) ? 1 : 0;\n\n        $subjLang = Registry::getRequest()->getRequestEscapedParameter(\"subjlang\");\n        $shopLanguageId = ($subjLang && $subjLang > 0) ? $subjLang : 0;\n\n        $shop = oxNew(Shop::class);\n        if ($shopId != self::NEW_SHOP_ID) {\n            $shop->loadInLang($shopLanguageId, $shopId);\n        } else {\n            $parameters = $this->updateParameters($parameters);\n        }\n\n        if (isset($parameters['oxshops__oxsmtp']) && $parameters['oxshops__oxsmtp']) {\n            $parameters['oxshops__oxsmtp'] = trim($parameters['oxshops__oxsmtp']);\n        }\n\n        $shop->setLanguage(0);\n        $shop->assign($parameters);\n        $shop->setLanguage($shopLanguageId);\n\n        if (($newSMPTPass = Registry::getRequest()->getRequestEscapedParameter(\"oxsmtppwd\"))) {\n            $shop->oxshops__oxsmtppwd->setValue($newSMPTPass == '-' ? \"\" : $newSMPTPass);\n        }\n\n        $canCreateShop = $this->canCreateShop($shopId, $shop, $config);\n        if (!$canCreateShop) {\n            return;\n        }\n\n        try {\n            $shop->save();\n        } catch (StandardException $e) {\n            $this->checkExceptionType($e);\n            return;\n        }\n\n        $this->_aViewData[\"updatelist\"] = \"1\";\n\n        $this->updateShopInformation($config, $shop, $shopId);\n\n        Registry::getSession()->setVariable(\"actshop\", $shopId);\n    }\n\n    /**\n     * Returns array of config variables which cannot be copied\n     *\n     * @return array\n     */\n    protected function getNonCopyConfigVars(): array\n    {\n        $nonCopyVars = [\n            'aSerials',\n            'IMS',\n            'IMD',\n            'IMA',\n            'sBackTag',\n            'sUtilModule',\n        ];\n        $multiShopTables = ContainerFacade::getParameter('oxid_esales.multi_shop_tables');\n        foreach ($multiShopTables as $multiShopTable) {\n            $nonCopyVars[] = 'blMallInherit_' . strtolower($multiShopTable);\n        }\n\n        return $nonCopyVars;\n    }\n\n    /**\n     * Copies base shop config variables to current\n     *\n     * @param Shop $shop new shop object\n     */\n    protected function copyConfigVars($shop)\n    {\n        $config = Registry::getConfig();\n        $utilsObject = Registry::getUtilsObject();\n        $db = DatabaseProvider::getDb();\n\n        $nonCopyVars = $this->getNonCopyConfigVars();\n\n        $selectShopConfigurationQuery =\n            \"select oxvarname, oxvartype, oxvarvalue, oxmodule\n            from oxconfig where oxshopid = '1'\";\n\n        $shopConfiguration = $db->select($selectShopConfigurationQuery);\n        if ($shopConfiguration != false && $shopConfiguration->count() > 0) {\n            while (!$shopConfiguration->EOF) {\n                $configName = $shopConfiguration->fields['oxvarname'];\n                if (!in_array($configName, $nonCopyVars)) {\n                    $newId = $utilsObject->generateUID();\n                    $insertNewConfigQuery =\n                        \"insert into oxconfig (oxid, oxshopid, oxvarname, oxvartype, oxvarvalue, oxmodule)\n                         values (:oxid, :oxshopid, :oxvarname, :oxvartype, :value, :oxmodule)\";\n                    $db->execute($insertNewConfigQuery, [\n                        'oxid' => $newId,\n                        'oxshopid' => $shop->getId(),\n                        'oxvarname' => $shopConfiguration->fields['oxvarname'],\n                        'oxvartype' => $shopConfiguration->fields['oxvartype'],\n                        'value' => $shopConfiguration->fields['oxvarvalue'],\n                        'oxmodule' => $shopConfiguration->fields['oxmodule'],\n                    ]);\n                }\n                $shopConfiguration->fetchRow();\n            }\n        }\n\n        $inheritAll = $shop->getFieldData('oxisinherited') ? 'true' : 'false';\n        foreach (ContainerFacade::getParameter('oxid_esales.multi_shop_tables') as $multiShopTable) {\n            $config->saveShopConfVar(\n                'bool',\n                'blMallInherit_' . strtolower($multiShopTable),\n                $inheritAll,\n                $shop->getId()\n            );\n        }\n    }\n\n    /**\n     * Return template name for new shop if it is different from standard.\n     *\n     * @return string\n     */\n    protected function renderNewShop()\n    {\n        return '';\n    }\n\n    /**\n     * Check user rights and change userId if it needs.\n     *\n     * @param User $user\n     * @param string $shopId\n     * @param bool $updateViewData Update view data when shop ID changes.\n     *\n     * @return string\n     */\n    protected function updateShopIdByUser($user, $shopId, $updateViewData = false)\n    {\n        return $shopId;\n    }\n\n    /**\n     * Load Shop parent and set result to _aViewData.\n     *\n     * @param Shop $shop\n     */\n    protected function checkParent($shop)\n    {\n    }\n\n    /**\n     * Unset not used Shop parameters.\n     *\n     * @param array $parameters\n     *\n     * @return array\n     */\n    protected function updateParameters($parameters)\n    {\n        $parameters['oxshops__oxid'] = null;\n\n        return $parameters;\n    }\n\n    /**\n     * Check for exception type and set it to _aViewData.\n     *\n     * @param StandardException $exception\n     */\n    protected function checkExceptionType($exception)\n    {\n    }\n\n    /**\n     * Check if Shop can be created.\n     *\n     * @param string $shopId\n     * @param Shop $shop\n     *\n     * @return bool\n     */\n    protected function canCreateShop($shopId, $shop)\n    {\n        return true;\n    }\n\n    /**\n     * Update shop information in DB and oxConfig.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Config $config\n     * @param Shop $shop\n     * @param string $shopId\n     */\n    protected function updateShopInformation($config, $shop, $shopId)\n    {\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ShopPerformance.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin shop system setting manager.\n * Collects shop system settings, updates it on user submit, etc.\n * Admin Menu: Main Menu -> Core Settings -> System.\n */\nclass ShopPerformance extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ShopConfiguration\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'shop_performance';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ShopRdfa.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Admin shop system RDFa manager.\n * Collects shop system settings, updates it on user submit, etc.\n * Admin Menu: Main Menu -> Core Settings -> RDFa.\n */\nclass ShopRdfa extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ShopConfiguration\n{\n    /**\n     * Template name\n     *\n     * @var array\n     */\n    protected $_sThisTemplate = 'shop_rdfa';\n\n    /**\n     * Predefined customer types\n     *\n     * @var array\n     */\n    protected $_aCustomers = [\"Enduser\"           => 0,\n                                   \"Reseller\"          => 0,\n                                   \"Business\"          => 0,\n                                   \"PublicInstitution\" => 0];\n\n    /**\n     * Gets list of content pages which could be used for embedding\n     * business entity, price specification, and delivery specification data\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\ContentList\n     */\n    public function getContentList()\n    {\n        $oContentList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ContentList::class);\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sTable = $tableViewNameGenerator->getViewName(\"oxcontents\", $this->_iEditLang);\n        $oContentList->selectString(\n            \"SELECT * \n             FROM {$sTable} \n             WHERE OXACTIVE = 1 AND OXTYPE = 0\n                AND OXLOADID IN ('oxagb', 'oxdeliveryinfo', 'oximpressum', 'oxrightofwithdrawal')\n                AND OXSHOPID = :OXSHOPID\n             ORDER BY OXLOADID ASC\",\n            ['OXSHOPID' => Registry::getRequest()->getRequestEscapedParameter(\"oxid\")]\n        );\n\n        return $oContentList;\n    }\n\n    /**\n     * Handles and returns customer array\n     *\n     * @return array\n     */\n    public function getCustomers()\n    {\n        $aCustomersConf = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopConfVar(\"aRDFaCustomers\");\n        if (isset($aCustomersConf)) {\n            foreach ($this->_aCustomers as $sCustomer => $iValue) {\n                $aCustomers[$sCustomer] = (in_array($sCustomer, $aCustomersConf)) ? 1 : 0;\n            }\n        } else {\n            $aCustomers = [];\n        }\n\n        return $aCustomers;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ShopSeo.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin shop system setting manager.\n * Collects shop system settings, updates it on user submit, etc.\n * Admin Menu: Main Menu -> Core Settings -> System.\n */\nclass ShopSeo extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ShopConfiguration\n{\n    /**\n     * Active seo url id\n     */\n    protected $_sActSeoObject = null;\n\n    /**\n     * Executes parent method parent::render() and returns name of template\n     * file \"shop_system\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        $this->_aViewData['subjlang'] = $this->_iEditLang;\n\n        // loading shop\n        $oShop = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Shop::class);\n        $oShop->loadInLang($this->_iEditLang, $this->_aViewData['edit']->getId());\n        $this->_aViewData['edit'] = $oShop;\n\n        // loading static seo urls\n        $sQ = \"select oxstdurl, oxobjectid from oxseo where oxtype='static' and oxshopid = :oxshopid\"\n            . \" group by oxobjectid order by oxstdurl\";\n\n        $oList = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n        $oList->init('oxbase', 'oxseo');\n        $oList->selectString($sQ, [\n            'oxshopid' => $oShop->getId()\n        ]);\n\n        $this->_aViewData['aStaticUrls'] = $oList;\n\n        // loading active url info\n        $this->loadActiveUrl($oShop->getId());\n\n        return \"shop_seo\";\n    }\n\n    /**\n     * Loads and sets active url info to view\n     *\n     * @param int $iShopId active shop id\n     */\n    protected function loadActiveUrl($iShopId)\n    {\n        $sActObject = null;\n        if ($this->_sActSeoObject) {\n            $sActObject = $this->_sActSeoObject;\n        } elseif (is_array($aStatUrl = Registry::getRequest()->getRequestEscapedParameter('aStaticUrl'))) {\n            $sActObject = $aStatUrl['oxseo__oxobjectid'];\n        }\n\n        if ($sActObject && $sActObject != '-1') {\n            $this->_aViewData['sActSeoObject'] = $sActObject;\n\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $sQ = \"select oxseourl, oxlang from oxseo where oxobjectid = :oxobjectid and oxshopid = :oxshopid\";\n            $oRs = $oDb->select($sQ, [\n                'oxobjectid' => $sActObject,\n                'oxshopid' => $iShopId\n            ]);\n            if ($oRs != false && $oRs->count() > 0) {\n                while (!$oRs->EOF) {\n                    $aSeoUrls[$oRs->fields['oxlang']] = [$sActObject, $oRs->fields['oxseourl']];\n                    $oRs->fetchRow();\n                }\n                $this->_aViewData['aSeoUrls'] = $aSeoUrls;\n            }\n        }\n    }\n\n    /**\n     * Saves changed shop configuration parameters.\n     */\n    public function save()\n    {\n        // saving config params\n        $this->saveConfVars();\n\n        $oShop = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Shop::class);\n        if ($oShop->loadInLang($this->_iEditLang, $this->getEditObjectId())) {\n            //assigning values\n            $oShop->setLanguage(0);\n            $oShop->assign(Registry::getRequest()->getRequestEscapedParameter('editval'));\n            $oShop->setLanguage($this->_iEditLang);\n            $oShop->save();\n\n            // saving static url changes\n            if (is_array($aStaticUrl = Registry::getRequest()->getRequestEscapedParameter('aStaticUrl'))) {\n                $this->_sActSeoObject = Registry::getSeoEncoder()->encodeStaticUrls(\n                    $this->processUrls($aStaticUrl),\n                    $oShop->getId(),\n                    $this->_iEditLang\n                );\n            }\n        }\n    }\n\n    /**\n     * Goes through urls array and prepares them for saving to db\n     *\n     * @param array $aUrls urls to process\n     *\n     * @return array\n     */\n    protected function processUrls($aUrls)\n    {\n        if (isset($aUrls['oxseo__oxstdurl']) && $aUrls['oxseo__oxstdurl']) {\n            $aUrls['oxseo__oxstdurl'] = $this->cleanupUrl($aUrls['oxseo__oxstdurl']);\n        }\n\n        if (isset($aUrls['oxseo__oxseourl']) && is_array($aUrls['oxseo__oxseourl'])) {\n            foreach ($aUrls['oxseo__oxseourl'] as $iPos => $sUrl) {\n                $aUrls['oxseo__oxseourl'][$iPos] = $this->cleanupUrl($sUrl);\n            }\n        }\n\n        return $aUrls;\n    }\n\n    /**\n     * processes urls by fixing \"&amp;\", \"&\"\n     *\n     * @param string $sUrl processable url\n     *\n     * @return string\n     */\n    protected function cleanupUrl($sUrl)\n    {\n        // replacing &amp; to & or removing double &&\n        while ((stripos($sUrl, '&amp;') !== false) || (stripos($sUrl, '&&') !== false)) {\n            $sUrl = str_replace('&amp;', '&', $sUrl);\n            $sUrl = str_replace('&&', '&', $sUrl);\n        }\n\n        // converting & to &amp;\n        return str_replace('&', '&amp;', $sUrl);\n    }\n\n    /**\n     * Resetting SEO ids\n     */\n    public function dropSeoIds()\n    {\n        $this->resetSeoData(Registry::getConfig()->getShopId());\n    }\n\n    /**\n     * Deletes static url.\n     */\n    public function deleteStaticUrl()\n    {\n        $aStaticUrl = Registry::getRequest()->getRequestEscapedParameter('aStaticUrl');\n        if (is_array($aStaticUrl)) {\n            $sObjectid = $aStaticUrl['oxseo__oxobjectid'];\n            if ($sObjectid && $sObjectid != '-1') {\n                $this->deleteStaticUrlFromDb($sObjectid);\n            }\n        }\n    }\n\n    /**\n     * Deletes static url from DB.\n     *\n     * @param string $staticUrlId\n     */\n    protected function deleteStaticUrlFromDb($staticUrlId)\n    {\n        // active shop id\n        $shopId = $this->getEditObjectId();\n        $db = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $db->execute(\"delete from oxseo where oxtype='static' and oxobjectid = :oxobjectid and oxshopid = :oxshopid\", [\n            'oxobjectid' => $staticUrlId,\n            'oxshopid' => $shopId\n        ]);\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ShopSystem.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin shop system setting manager.\n * Collects shop system settings, updates it on user submit, etc.\n * Admin Menu: Main Menu -> Core Settings -> System.\n */\nclass ShopSystem extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ShopConfiguration\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'shop_system';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/SystemInfoController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererInterface;\n\n/**\n * Admin systeminfo manager.\n * Returns template \"systeminfo\" and phphinfo() result to frame.\n */\nclass SystemInfoController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Executes parent method parent::render(), prints shop and\n     * PHP configuration information.\n     *\n     * @return null\n     */\n    public function render()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        parent::render();\n\n        $oAuthUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n        $oAuthUser->loadAdminUser();\n        $blisMallAdmin = $oAuthUser->oxuser__oxrights->value == \"malladmin\";\n\n        if ($blisMallAdmin && !$myConfig->isDemoShop()) {\n            $aClassVars = get_object_vars($myConfig);\n            $aSystemInfo = [];\n            $aSystemInfo['pkg.info'] = $myConfig->getPackageInfo();\n            foreach ($aClassVars as $name => $value) {\n                if (gettype($value) == \"object\") {\n                    continue;\n                }\n\n                if (!$this->isClassVariableVisible($name)) {\n                    continue;\n                }\n\n                $value = var_export($value, true);\n                $value = str_replace(\"\\n\", \"<br>\", $value);\n                $aSystemInfo[$name] = $value;\n            }\n            $context = [\n                \"oViewConf\" => $this->_aViewData[\"oViewConf\"],\n                \"oView\" => $this->_aViewData[\"oView\"],\n                \"shop\" => $this->_aViewData[\"shop\"] ?? 1,\n                \"isdemo\" => $myConfig->isDemoShop(),\n                \"aSystemInfo\" => $aSystemInfo\n            ];\n\n            ob_start();\n            echo $this->getRenderer()->renderTemplate(\"systeminfo\", $context);\n            echo(\"<br><br>\");\n\n            phpinfo();\n            $sMessage = ob_get_clean();\n\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->showMessageAndExit($sMessage);\n        } else {\n            return \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->showMessageAndExit(\"Access denied !\");\n        }\n    }\n\n    /**\n     * @internal\n     *\n     * @return TemplateRendererInterface\n     */\n    private function getRenderer()\n    {\n        return ContainerFacade::get(TemplateRendererBridgeInterface::class)\n            ->getTemplateRenderer();\n    }\n\n    /**\n     * Checks if class var can be shown in systeminfo.\n     *\n     * @param string $varName\n     * @return bool\n     */\n    protected function isClassVariableVisible($varName)\n    {\n        return !in_array($varName, [\n            'oDB',\n            'dbUser',\n            'dbPwd',\n            'oSerial',\n            'aSerials',\n            'sSerialNr'\n        ]);\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/SystemRequirements.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Collects System information.\n * Admin Menu: Service -> System Requirements.\n */\nclass SystemRequirements extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'sysreq';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/SystemRequirementsList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Collects System information.\n * Admin Menu: Service -> System Requirements.\n */\nclass SystemRequirementsList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'sysreq_list';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/SystemRequirementsMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Collects System information.\n * Admin Menu: Service -> System Requirements -> Main.\n */\nclass SystemRequirementsMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $oSysReq = oxNew(\\OxidEsales\\Eshop\\Core\\SystemRequirements::class);\n\n        $this->_aViewData['aInfo'] = $oSysReq->getSystemInfo();\n        $this->_aViewData['aCollations'] = $oSysReq->checkCollation();\n\n        return \"sysreq_main\";\n    }\n\n    /**\n     * Returns module state\n     *\n     * @param int $iModuleState state integer value\n     *\n     * @return string\n     */\n    public function getModuleClass($iModuleState)\n    {\n        switch ($iModuleState) {\n            case 2:\n                $sClass = 'pass';\n                break;\n            case 1:\n                $sClass = 'pmin';\n                break;\n            case -1:\n                $sClass = 'null';\n                break;\n            default:\n                $sClass = 'fail';\n                break;\n        }\n        return $sClass;\n    }\n\n    /**\n     * Returns hint URL\n     *\n     * @param string $sIdent Module ident\n     *\n     * @return string\n     */\n    public function getReqInfoUrl($sIdent)\n    {\n        $oSysReq = oxNew(\\OxidEsales\\Eshop\\Core\\SystemRequirements::class);\n\n        return $oSysReq->getReqInfoUrl($sIdent);\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ThemeConfiguration.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse oxAdminDetails;\n\nclass ThemeConfiguration extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ShopConfiguration\n{\n    protected $_sTheme = null;\n\n    /** @inheritdoc */\n    public function render()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $sTheme = $this->_sTheme = $this->getEditObjectId();\n        $sShopId = $myConfig->getShopId();\n\n        if (!isset($sTheme)) {\n            $sTheme = $this->_sTheme = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('sTheme');\n        }\n\n        $oTheme = oxNew(\\OxidEsales\\Eshop\\Core\\Theme::class);\n        if ($oTheme->load($sTheme)) {\n            $this->_aViewData[\"oTheme\"] = $oTheme;\n\n            try {\n                $aDbVariables = $this->loadConfVars($sShopId, $this->getModuleForConfigVars());\n                $this->_aViewData[\"var_constraints\"] = $aDbVariables['constraints'];\n                $this->_aViewData[\"var_grouping\"] = $aDbVariables['grouping'];\n                foreach ($this->_aConfParams as $sType => $sParam) {\n                    $this->_aViewData[$sParam] = $aDbVariables['vars'][$sType] ?? null;\n                }\n            } catch (\\OxidEsales\\Eshop\\Core\\Exception\\StandardException $exception) {\n                Registry::getUtilsView()->addErrorToDisplay($exception);\n                Registry::getLogger()->error($exception->getMessage(), [$exception]);\n            }\n        } else {\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay(oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\StandardException::class, 'EXCEPTION_THEME_NOT_LOADED'));\n        }\n\n        return 'theme_config';\n    }\n\n    /**\n     * return theme filter for config variables\n     *\n     * @return string\n     */\n    protected function getModuleForConfigVars()\n    {\n        if ($this->_sTheme === null) {\n            $this->_sTheme = $this->getEditObjectId();\n        }\n\n        return \\OxidEsales\\Eshop\\Core\\Config::OXMODULE_THEME_PREFIX . $this->_sTheme;\n    }\n\n    /**\n     * Saves shop configuration variables\n     */\n    public function saveConfVars()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        oxAdminDetails::save();\n\n        $sShopId = $myConfig->getShopId();\n\n        $sModule = $this->getModuleForConfigVars();\n\n        foreach ($this->_aConfParams as $sType => $sParam) {\n            $aConfVars = Registry::getRequest()->getRequestEscapedParameter($sParam);\n            if (is_array($aConfVars)) {\n                foreach ($aConfVars as $sName => $sValue) {\n                    $myConfig->saveShopConfVar(\n                        $sType,\n                        $sName,\n                        $this->serializeConfVar($sType, $sName, $sValue),\n                        $sShopId,\n                        $sModule\n                    );\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ThemeController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin theme manager.\n * Returns template, that arranges two other templates (\"theme_list\"\n * and \"theme_main\") to frame.\n * Admin Menu: Main Menu -> Theme.\n */\nclass ThemeController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Executes parent method parent::render() and returns name of template\n     * file \"theme\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        return \"theme\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ThemeList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin actionss manager.\n * Sets list template, list object class ('oxactions') and default sorting\n * field ('oxactions.oxtitle').\n * Admin Menu: Manage Products -> Actions.\n */\nclass ThemeList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Calls parent::render() and returns name of template to render\n     *\n     * @return string\n     */\n    public function render()\n    {\n        $oTheme = oxNew(\\OxidEsales\\Eshop\\Core\\Theme::class);\n\n        parent::render();\n\n        // assign our list\n        $this->_aViewData['mylist'] = $oTheme->getList();\n\n        return 'theme_list';\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ThemeMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse oxRegistry;\nuse oxTheme;\nuse oxException;\n\nclass ThemeMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        $soxId = $this->getEditObjectId();\n\n        $oTheme = oxNew(\\OxidEsales\\Eshop\\Core\\Theme::class);\n\n        if (!$soxId) {\n            $soxId = $oTheme->getActiveThemeId();\n        }\n\n        if ($oTheme->load($soxId)) {\n            $this->_aViewData[\"oTheme\"] = $oTheme;\n        } else {\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay(oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\StandardException::class, 'EXCEPTION_THEME_NOT_LOADED'));\n        }\n\n        parent::render();\n\n        if ($this->themeInConfigFile()) {\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay('EXCEPTION_THEME_SHOULD_BE_ONLY_IN_DATABASE');\n        }\n\n        return 'theme_main';\n    }\n\n    /**\n     * Check if theme config is in config file.\n     *\n     * @return bool\n     */\n    public function themeInConfigFile()\n    {\n        $blThemeSet = isset(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->sTheme);\n        $blCustomThemeSet = isset(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->sCustomTheme);\n\n        return ($blThemeSet || $blCustomThemeSet);\n    }\n\n\n    /**\n     * Set theme\n     *\n     * @return null\n     */\n    public function setTheme()\n    {\n        $sTheme = $this->getEditObjectId();\n        /** @var \\OxidEsales\\Eshop\\Core\\Theme $oTheme */\n        $oTheme = oxNew(\\OxidEsales\\Eshop\\Core\\Theme::class);\n        if (!$oTheme->load($sTheme)) {\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay(oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\StandardException::class, 'EXCEPTION_THEME_NOT_LOADED'));\n\n            return;\n        }\n        try {\n            $oTheme->activate();\n            $this->resetContentCache();\n        } catch (\\OxidEsales\\Eshop\\Core\\Exception\\StandardException $exception) {\n            Registry::getUtilsView()->addErrorToDisplay($exception);\n            Registry::getLogger()->error($exception->getMessage(), [$exception]);\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ToolsController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse oxRegistry;\n\n/**\n * Admin systeminfo manager.\n * Returns template, that arranges two other templates (\"delivery_list\"\n * and \"delivery_main\") to frame.\n */\nclass ToolsController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Executes parent method parent::render(), prints shop and\n     * PHP configuration information.\n     *\n     * @return string\n     */\n    public function render()\n    {\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->isDemoShop()) {\n            return \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->showMessageAndExit(\"Access denied !\");\n        }\n\n        parent::render();\n\n        return \"tools\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ToolsList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse Exception;\nuse OxidEsales\\Eshop\\Core\\Str;\n\n/**\n * Admin systeminfo manager.\n * Returns template, that arranges two other templates (\"tools_list\"\n * and \"tools_main\") to frame.\n */\nclass ToolsList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Current class template name\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'tools_list';\n\n    /**\n     * Performs full view update\n     */\n    public function updateViews()\n    {\n        //preventing edit for anyone except malladmin\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable(\"malladmin\")) {\n            $oMetaData = oxNew(\\OxidEsales\\Eshop\\Core\\DbMetaDataHandler::class);\n            $this->_aViewData[\"blViewSuccess\"] = $oMetaData->updateViews();\n        }\n    }\n\n    /**\n     * Method performs user passed SQL query\n     */\n    public function performsql()\n    {\n        $oAuthUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n        $oAuthUser->loadAdminUser();\n        if ($oAuthUser->oxuser__oxrights->value === \"malladmin\") {\n            $sUpdateSQL = Registry::getRequest()->getRequestEscapedParameter(\"updatesql\");\n            $sUpdateSQLFile = $this->processFiles();\n\n            if ($sUpdateSQLFile && strlen($sUpdateSQLFile) > 0) {\n                if (isset($sUpdateSQL) && strlen($sUpdateSQL)) {\n                    $sUpdateSQL .= \";\\r\\n\" . $sUpdateSQLFile;\n                } else {\n                    $sUpdateSQL = $sUpdateSQLFile;\n                }\n            }\n\n            $sUpdateSQL = trim(stripslashes($sUpdateSQL));\n            $oStr = Str::getStr();\n            $iLen = $oStr->strlen($sUpdateSQL);\n            if ($this->prepareSQL($sUpdateSQL, $iLen)) {\n                $aQueries = $this->aSQLs;\n                $this->_aViewData[\"aQueries\"] = [];\n                $aPassedQueries = [];\n                $aQAffectedRows = [];\n                $aQErrorMessages = [];\n                $aQErrorNumbers = [];\n\n                if (!empty($aQueries) && is_array($aQueries)) {\n                    $blStop = false;\n                    $oDB = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n                    $iQueriesCounter = 0;\n                    for ($i = 0; $i < count($aQueries); $i++) {\n                        $sUpdateSQL = $aQueries[$i];\n                        $sUpdateSQL = trim($sUpdateSQL);\n\n                        if ($oStr->strlen($sUpdateSQL) > 0) {\n                            $aPassedQueries[$iQueriesCounter] = nl2br($oStr->htmlentities($sUpdateSQL));\n                            if ($oStr->strlen($aPassedQueries[$iQueriesCounter]) > 200) {\n                                $aPassedQueries[$iQueriesCounter] = $oStr->substr($aPassedQueries[$iQueriesCounter], 0, 200) . \"...\";\n                            }\n\n                            while ($sUpdateSQL[$oStr->strlen($sUpdateSQL) - 1] == \";\") {\n                                $sUpdateSQL = $oStr->substr($sUpdateSQL, 0, ($oStr->strlen($sUpdateSQL) - 1));\n                            }\n\n                            $aQAffectedRows [$iQueriesCounter] = null;\n                            $aQErrorMessages[$iQueriesCounter] = null;\n                            $aQErrorNumbers [$iQueriesCounter] = null;\n\n                            try {\n                                $aQAffectedRows[$iQueriesCounter] = $oDB->execute($sUpdateSQL);\n                            } catch (Exception $exception) {\n                                // Report errors\n                                $aQErrorMessages[$iQueriesCounter] = $oStr->htmlentities($exception->getMessage());\n                                $aQErrorNumbers[$iQueriesCounter] = $oStr->htmlentities($exception->getCode());\n                                // Trigger breaking the loop\n                                $blStop = true;\n                            }\n\n                            $iQueriesCounter++;\n\n                            // stopping on first error..\n                            if ($blStop) {\n                                break;\n                            }\n                        }\n                    }\n                }\n                $this->_aViewData[\"aQueries\"] = $aPassedQueries;\n                $this->_aViewData[\"aAffectedRows\"] = $aQAffectedRows;\n                $this->_aViewData[\"aErrorMessages\"] = $aQErrorMessages;\n                $this->_aViewData[\"aErrorNumbers\"] = $aQErrorNumbers;\n            }\n            $this->_iDefEdit = 1;\n        }\n    }\n\n    /**\n     * Processes files containing SQL queries\n     *\n     * @return mixed\n     */\n    protected function processFiles()\n    {\n        if (isset($_FILES['myfile']['name'])) {\n            // process all files\n            foreach ($_FILES['myfile']['name'] as $key => $value) {\n                $aSource = $_FILES['myfile']['tmp_name'];\n                $sSource = $aSource[$key];\n                $value = strtolower($value);\n                // add type to name\n                $aFilename = explode(\".\", $value);\n\n                //hack?\n\n                $aBadFiles = [\"php\", 'php4', 'php5', \"jsp\", \"cgi\", \"cmf\", \"exe\"];\n\n                if (in_array($aFilename[1], $aBadFiles)) {\n                    \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->showMessageAndExit(\"File didn't pass our allowed files filter.\");\n                }\n\n                //reading SQL dump file\n                if (filesize($sSource) > 0) {\n                    $rHandle = fopen($sSource, \"r\");\n                    $sContents = fread($rHandle, filesize($sSource));\n                    fclose($rHandle);\n\n                    //reading only one SQL dump file\n                    return $sContents;\n                }\n\n                return;\n            }\n        }\n\n        return;\n    }\n\n    /**\n     * Method parses givent SQL queries string and returns array on success\n     *\n     * @param string  $sSQL    SQL queries\n     * @param integer $iSQLlen query lenght\n     *\n     * @return mixed\n     */\n    protected function prepareSQL($sSQL, $iSQLlen)\n    {\n        $sStrStart = \"\";\n        $blString = false;\n        $oStr = Str::getStr();\n\n        //removing \"mysqldump\" application comments\n        while ($oStr->preg_match(\"/^\\-\\-.*\\n/\", $sSQL)) {\n            $sSQL = trim($oStr->preg_replace(\"/^\\-\\-.*\\n/\", \"\", $sSQL));\n        }\n        while ($oStr->preg_match(\"/\\n\\-\\-.*\\n/\", $sSQL)) {\n            $sSQL = trim($oStr->preg_replace(\"/\\n\\-\\-.*\\n/\", \"\\n\", $sSQL));\n        }\n\n        for ($iPos = 0; $iPos < $iSQLlen; ++$iPos) {\n            $sChar = $sSQL[$iPos];\n            if ($blString) {\n                while (true) {\n                    $iPos = $oStr->strpos($sSQL, $sStrStart, $iPos);\n                    //we are at the end of string ?\n                    if (!$iPos) {\n                        $this->aSQLs[] = $sSQL;\n\n                        return true;\n                    } elseif ($sStrStart == '`' || $sSQL[$iPos - 1] != '\\\\') {\n                        //found some query separators\n                        $blString = false;\n                        $sStrStart = \"\";\n                        break;\n                    } else {\n                        $iNext = 2;\n                        $blBackslash = false;\n                        while ($iPos - $iNext > 0 && $sSQL[$iPos - $iNext] == '\\\\') {\n                            $blBackslash = !$blBackslash;\n                            $iNext++;\n                        }\n                        if ($blBackslash) {\n                            $blString = false;\n                            $sStrStart = \"\";\n                            break;\n                        } else {\n                            $iPos++;\n                        }\n                    }\n                }\n            } elseif ($sChar == \";\") {\n                // delimiter found, appending query array\n                $this->aSQLs[] = $oStr->substr($sSQL, 0, $iPos);\n                $sSQL = ltrim($oStr->substr($sSQL, min($iPos + 1, $iSQLlen)));\n                $iSQLlen = $oStr->strlen($sSQL);\n                if ($iSQLlen) {\n                    $iPos = -1;\n                } else {\n                    return true;\n                }\n            } elseif (($sChar == '\"') || ($sChar == '\\'') || ($sChar == '`')) {\n                $blString = true;\n                $sStrStart = $sChar;\n            } elseif ($sChar == \"#\" || ($sChar == ' ' && $iPos > 1 && $sSQL[$iPos - 2] . $sSQL[$iPos - 1] == '--')) {\n                // removing # commented query code\n                $iCommStart = (($sSQL[$iPos] == \"#\") ? $iPos : $iPos - 2);\n                $iCommEnd = ($oStr->strpos(' ' . $sSQL, \"\\012\", $iPos + 2))\n                    ? $oStr->strpos(' ' . $sSQL, \"\\012\", $iPos + 2)\n                    : $oStr->strpos(' ' . $sSQL, \"\\015\", $iPos + 2);\n                if (!$iCommEnd) {\n                    if ($iCommStart > 0) {\n                        $this->aSQLs[] = trim($oStr->substr($sSQL, 0, $iCommStart));\n                    }\n\n                    return true;\n                } else {\n                    $sSQL = $oStr->substr($sSQL, 0, $iCommStart) . ltrim($oStr->substr($sSQL, $iCommEnd));\n                    $iSQLlen = $oStr->strlen($sSQL);\n                    $iPos--;\n                }\n            } elseif (32358 < 32270 && ($sChar == '!' && $iPos > 1 && $sSQL[$iPos - 2] . $sSQL[$iPos - 1] == '/*')) {\n                // removing comments like /**/\n                $sSQL[$iPos] = ' ';\n            }\n        }\n\n        if (!empty($sSQL) && $oStr->preg_match(\"/[^[:space:]]+/\", $sSQL)) {\n            $this->aSQLs[] = $sSQL;\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/ToolsMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Model\\User;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\n\n/**\n * CVS export manager.\n * Performs export function according to user chosen categories.\n * Admin Menu: Maine Menu -> Im/Export -> Export.\n */\nclass ToolsMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        if (Registry::getConfig()->isDemoShop()) {\n            Registry::getUtils()->showMessageAndExit(\"Access denied !\");\n        }\n\n        parent::render();\n\n        $oAuthUser = oxNew(User::class);\n        $oAuthUser->loadAdminUser();\n        $this->_aViewData[\"blIsMallAdmin\"] = $oAuthUser->oxuser__oxrights->value == \"malladmin\";\n\n        $this->_aViewData['showViewUpdate'] = ContainerFacade::getParameter('oxid_esales.show_update_views_button');\n\n        return \"tools_main\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/UserAddress.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin user address setting manager.\n * Collects user address settings, updates it on user submit, etc.\n * Admin Menu: User Administration -> Users -> Addresses.\n */\nclass UserAddress extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * If true, means that address was deleted\n     *\n     * @var bool\n     */\n    protected $_blDelete = false;\n\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n            $oUser->load($soxId);\n\n            // load adress\n            $sAddressIdParameter = Registry::getRequest()->getRequestEscapedParameter(\"oxaddressid\");\n            $soxAddressId = isset($this->sSavedOxid) ? $this->sSavedOxid : $sAddressIdParameter;\n            if ($soxAddressId != \"-1\" && isset($soxAddressId)) {\n                $oAdress = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Address::class);\n                $oAdress->load($soxAddressId);\n                $this->_aViewData[\"edit\"] = $oAdress;\n            }\n\n            $this->_aViewData[\"oxaddressid\"] = $soxAddressId;\n\n            // generate selected\n            $oAddressList = $oUser->getUserAddresses();\n            foreach ($oAddressList as $oAddress) {\n                if ($oAddress->oxaddress__oxid->value == $soxAddressId) {\n                    $oAddress->selected = 1;\n                    break;\n                }\n            }\n\n            $this->_aViewData[\"edituser\"] = $oUser;\n        }\n\n        $oCountryList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\CountryList::class);\n        $oCountryList->loadActiveCountries(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->getObjectTplLanguage());\n\n        $this->_aViewData[\"countrylist\"] = $oCountryList;\n\n        if (!$this->allowAdminEdit($soxId)) {\n            $this->_aViewData['readonly'] = true;\n        }\n\n        return \"user_address\";\n    }\n\n    /**\n     * Saves user addressing information.\n     */\n    public function save()\n    {\n        parent::save();\n\n        if ($this->allowAdminEdit($this->getEditObjectId())) {\n            $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n            $oAdress = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Address::class);\n            if (isset($aParams['oxaddress__oxid']) && $aParams['oxaddress__oxid'] == \"-1\") {\n                $aParams['oxaddress__oxid'] = null;\n            } else {\n                $oAdress->load($aParams['oxaddress__oxid']);\n            }\n\n            $oAdress->assign($aParams);\n            $oAdress->save();\n\n            $this->sSavedOxid = $oAdress->getId();\n        }\n    }\n\n    /**\n     * Deletes user addressing information.\n     */\n    public function delAddress()\n    {\n        $this->_blDelete = false;\n        if ($this->allowAdminEdit($this->getEditObjectId())) {\n            $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n            if (isset($aParams['oxaddress__oxid']) && $aParams['oxaddress__oxid'] != \"-1\") {\n                $oAdress = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Address::class);\n                $this->_blDelete = $oAdress->delete($aParams['oxaddress__oxid']);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/UserArticle.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin user articles setting manager.\n * Collects user articles settings, updates it on user submit, etc.\n * Admin Menu: User Administration -> Users -> Articles.\n */\nclass UserArticle extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Executes parent method parent::render(), creates oxlist object and returns name\n     * of template file \"user_article\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->getEditObjectId();\n        if ($soxId && $soxId != '-1') {\n            // load object\n            $oArticlelist = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\OrderArticleList::class);\n            $oArticlelist->loadOrderArticlesForUser($soxId);\n\n            $this->_aViewData['oArticlelist'] = $oArticlelist;\n        }\n\n        return 'user_article';\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/UserExtend.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin user extended settings manager.\n * Collects user extended settings, updates it on user submit, etc.\n * Admin Menu: User Administration -> Users -> Extended.\n */\nclass UserExtend extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Executes parent method parent::render(), creates oxuser object and\n     * returns name of template file \"user_extend\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n            $oUser->load($soxId);\n\n            //show country in active language\n            $oCountry = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Country::class);\n            $oCountry->loadInLang(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->getObjectTplLanguage(), $oUser->oxuser__oxcountryid->value);\n            $oUser->oxuser__oxcountry = new \\OxidEsales\\Eshop\\Core\\Field($oCountry->oxcountry__oxtitle->value);\n\n            $this->_aViewData[\"edit\"] = $oUser;\n        }\n\n        if (!$this->allowAdminEdit($soxId)) {\n            $this->_aViewData['readonly'] = true;\n        }\n\n        return \"user_extend\";\n    }\n\n    /**\n     * Saves user extended information.\n     *\n     * @return mixed\n     */\n    public function save()\n    {\n        parent::save();\n\n        $soxId = $this->getEditObjectId();\n\n        if (!$this->allowAdminEdit($soxId)) {\n            return false;\n        }\n\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        $oUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n        if ($soxId != \"-1\") {\n            $oUser->load($soxId);\n        } else {\n            $aParams['oxuser__oxid'] = null;\n        }\n\n        // checkbox handling\n        $aParams['oxuser__oxactive'] = $oUser->oxuser__oxactive->value;\n\n        $blNewsParams = Registry::getRequest()->getRequestEscapedParameter(\"editnews\");\n        if (isset($blNewsParams)) {\n            $oNewsSubscription = $oUser->getNewsSubscription();\n            $oNewsSubscription->setOptInStatus((int) $blNewsParams);\n            $oNewsSubscription->setOptInEmailStatus((int) Registry::getRequest()->getRequestEscapedParameter(\"emailfailed\"));\n        }\n\n        $oUser->assign($aParams);\n        $oUser->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oUser->getId());\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/UserGroupController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin usergroup manager.\n * Returns template, that arranges two other templates (\"usergroup_list\"\n * and \"usergroup_main\") to frame.\n * Admin Menu: User Administration -> User Groups.\n */\nclass UserGroupController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'usergroup';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/UserGroupList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin usergroup list manager.\n * Performs collection and managing (such as filtering or deleting) function.\n * Admin Menu: User Administration -> User Groups.\n */\nclass UserGroupList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxgroups';\n\n    /**\n     * Default SQL sorting parameter (default null).\n     *\n     * @var string\n     */\n    protected $_sDefSortField = \"oxtitle\";\n\n    /**\n     * Executes parent method parent::render() and returns name of template\n     * file \"usergroup_list\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        return \"usergroup_list\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/UserGroupMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse stdClass;\n\n/**\n * Admin article main usergroup manager.\n * Performs collection and updatind (on user submit) main item information.\n * Admin Menu: User Administration -> User Groups -> Main.\n */\nclass UserGroupMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oGroup = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Groups::class);\n            $oGroup->loadInLang($this->_iEditLang, $soxId);\n\n            $oOtherLang = $oGroup->getAvailableInLangs();\n            if (!isset($oOtherLang[$this->_iEditLang])) {\n                $oGroup->loadInLang(key($oOtherLang), $soxId);\n            }\n\n            $this->_aViewData[\"edit\"] = $oGroup;\n\n            // remove already created languages\n            $aLang = array_diff(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->getLanguageNames(), $oOtherLang);\n\n            if (count($aLang)) {\n                $this->_aViewData[\"posslang\"] = $aLang;\n            }\n\n            foreach ($oOtherLang as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $oLang;\n            }\n        }\n        if (Registry::getRequest()->getRequestEscapedParameter(\"aoc\")) {\n            $oUsergroupMainAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\UserGroupMainAjax::class);\n            $this->_aViewData['oxajax'] = $oUsergroupMainAjax->getColumns();\n\n            return \"popups/usergroup_main\";\n        }\n\n        return \"usergroup_main\";\n    }\n\n    /**\n     * Saves changed usergroup parameters.\n     */\n    public function save()\n    {\n        parent::save();\n\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n        // checkbox handling\n        if (!isset($aParams['oxgroups__oxactive'])) {\n            $aParams['oxgroups__oxactive'] = 0;\n        }\n\n        $oGroup = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Groups::class);\n        if ($soxId != \"-1\") {\n            $oGroup->load($soxId);\n        } else {\n            $aParams['oxgroups__oxid'] = null;\n        }\n\n        $oGroup->setLanguage(0);\n        $oGroup->assign($aParams);\n        $oGroup->setLanguage($this->_iEditLang);\n        $oGroup->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oGroup->getId());\n    }\n\n    /**\n     * Saves changed selected group parameters in different language.\n     */\n    public function saveinnlang()\n    {\n        $this->save();\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/UserGroupMainAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages users assignment to groups\n */\nclass UserGroupMainAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,  visible, multilanguage, ident\n        ['oxusername', 'oxuser', 1, 0, 0],\n        ['oxlname', 'oxuser', 0, 0, 0],\n        ['oxfname', 'oxuser', 0, 0, 0],\n        ['oxstreet', 'oxuser', 0, 0, 0],\n        ['oxstreetnr', 'oxuser', 0, 0, 0],\n        ['oxcity', 'oxuser', 0, 0, 0],\n        ['oxzip', 'oxuser', 0, 0, 0],\n        ['oxfon', 'oxuser', 0, 0, 0],\n        ['oxbirthdate', 'oxuser', 0, 0, 0],\n        ['oxid', 'oxuser', 0, 0, 1],\n    ],\n                                 'container2' => [\n                                     ['oxusername', 'oxuser', 1, 0, 0],\n                                     ['oxlname', 'oxuser', 0, 0, 0],\n                                     ['oxfname', 'oxuser', 0, 0, 0],\n                                     ['oxstreet', 'oxuser', 0, 0, 0],\n                                     ['oxstreetnr', 'oxuser', 0, 0, 0],\n                                     ['oxcity', 'oxuser', 0, 0, 0],\n                                     ['oxzip', 'oxuser', 0, 0, 0],\n                                     ['oxfon', 'oxuser', 0, 0, 0],\n                                     ['oxbirthdate', 'oxuser', 0, 0, 0],\n                                     ['oxid', 'oxobject2group', 0, 0, 1],\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        // looking for table/view\n        $sUserTable = $this->getViewName('oxuser');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sRoleId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchRoleId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sRoleId) {\n            $sQAdd = \" from $sUserTable where 1 \";\n        } else {\n            $sQAdd = \" from $sUserTable, oxobject2group where $sUserTable.oxid=oxobject2group.oxobjectid and \";\n            $sQAdd .= \" oxobject2group.oxgroupsid = \" . $oDb->quote($sRoleId);\n        }\n\n        if ($sSynchRoleId && $sSynchRoleId != $sRoleId) {\n            $sQAdd .= \" and $sUserTable.oxid not in ( select $sUserTable.oxid from $sUserTable, oxobject2group where $sUserTable.oxid=oxobject2group.oxobjectid and \";\n            $sQAdd .= \" oxobject2group.oxgroupsid = \" . $oDb->quote($sSynchRoleId);\n            if (!$myConfig->getConfigParam('blMallUsers')) {\n                $sQAdd .= \" and $sUserTable.oxshopid = '\" . $myConfig->getShopId() . \"' \";\n            }\n            $sQAdd .= \" ) \";\n        }\n\n        if (!$myConfig->getConfigParam('blMallUsers')) {\n            $sQAdd .= \" and $sUserTable.oxshopid = '\" . $myConfig->getShopId() . \"' \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes User from group\n     */\n    public function removeUserFromUGroup()\n    {\n        $aRemoveGroups = $this->getActionIds('oxobject2group.oxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxobject2group.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif ($aRemoveGroups && is_array($aRemoveGroups)) {\n            $sQ = \"delete from oxobject2group where oxobject2group.oxid in (\" . implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aRemoveGroups)) . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adds User to group\n     */\n    public function addUserToUGroup()\n    {\n        $aAddUsers = $this->getActionIds('oxuser.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sUserTable = $this->getViewName('oxuser');\n            $aAddUsers = $this->getAll($this->addFilter(\"select $sUserTable.oxid \" . $this->getQuery()));\n        }\n        if ($soxId && $soxId != \"-1\" && is_array($aAddUsers)) {\n            foreach ($aAddUsers as $sAdduser) {\n                $oNewGroup = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Object2Group::class);\n                $oNewGroup->oxobject2group__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sAdduser);\n                $oNewGroup->oxobject2group__oxgroupsid = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                $oNewGroup->save();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/UserList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse oxRegistry;\nuse oxUser;\n\n/**\n * Admin user list manager.\n * Performs collection and managing (such as filtering or deleting) function.\n * Admin Menu: User Administration -> Users.\n */\nclass UserList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxuser';\n\n    /**\n     * Default SQL sorting parameter (default null).\n     *\n     * @var string\n     */\n    protected $_sDefSortField = \"oxusername\";\n\n    /**\n     * Type of list.\n     *\n     * @var string\n     */\n    protected $_sListType = 'oxuserlist';\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'user_list';\n\n    /**\n     * Executes parent::render(), sets blacklist and preventdelete flag\n     *\n     * @return null\n     */\n    public function render()\n    {\n        foreach ($this->getItemList() as $itemId => $user) {\n            /** @var \\OxidEsales\\Eshop\\Application\\Model\\User $user */\n            if ($user->inGroup(\"oxidblacklist\") || $user->inGroup(\"oxidblocked\")) {\n                $user->blacklist = \"1\";\n            }\n            $user->blPreventDelete = false;\n            if (!$this->allowAdminEdit($itemId)) {\n                $user->blPreventDelete = true;\n            }\n        }\n\n        return parent::render();\n    }\n\n    /**\n     * Admin user is allowed to be deleted only by mall admin\n     *\n     * @return null\n     */\n    public function deleteEntry()\n    {\n        if ($this->allowAdminEdit($this->getEditObjectId())) {\n            $this->_oList = null;\n\n            return parent::deleteEntry();\n        }\n    }\n\n    /**\n     * Prepares SQL where query according SQL condition array and attaches it to SQL end.\n     * For each search value if german umlauts exist, adds them\n     * and replaced by spec. char to query\n     *\n     * @param array  $whereQuery SQL condition array\n     * @param string $fullQuery  SQL query string\n     *\n     * @return string\n     */\n    public function prepareWhereQuery($whereQuery, $fullQuery)\n    {\n        $nameWhere = null;\n        if (isset($whereQuery['oxuser.oxlname']) && ($name = $whereQuery['oxuser.oxlname'])) {\n            // check if this is search string (contains % sign at begining and end of string)\n            $isSearchValue = $this->isSearchValue($name);\n            $name = $this->processFilter($name);\n            $nameWhere['oxuser.oxfname'] = $nameWhere['oxuser.oxlname'] = $name;\n\n            unset($whereQuery['oxuser.oxlname']);\n        }\n        $query = parent::prepareWhereQuery($whereQuery, $fullQuery);\n\n        if ($nameWhere) {\n            $values = explode(' ', $name);\n            $query .= ' and (';\n            $queryBoolAction = '';\n            $utilsString = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsString();\n\n            foreach ($nameWhere as $fieldName => $fieldValue) {\n                //for each search field using AND action\n                foreach ($values as $value) {\n                    $query .= \" {$queryBoolAction} {$fieldName} \";\n\n                    //for search in same field for different values using AND\n                    $queryBoolAction = ' or ';\n\n                    $query .= $this->buildFilter($value, $isSearchValue);\n\n                    // trying to search spec chars in search value\n                    // if found, add cleaned search value to search sql\n                    $uml = $utilsString->prepareStrForSearch($value);\n                    if ($uml) {\n                        $query .= \" or {$fieldName} \";\n                        $query .= $this->buildFilter($uml, $isSearchValue);\n                    }\n                }\n            }\n\n            // end for AND action\n            $query .= ' ) ';\n        }\n\n        return $query;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/UserMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Model\\User;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse stdClass;\nuse Exception;\n\n/**\n * Admin article main user manager.\n * Performs collection and updating (on user submit) main item information.\n * Admin Menu: User Administration -> Users -> Main.\n */\nclass UserMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    private $_sSaveError = null;\n\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        // malladmin stuff\n        $oAuthUser = oxNew(User::class);\n        $oAuthUser->loadAdminUser();\n        $blisMallAdmin = $oAuthUser->oxuser__oxrights->value == \"malladmin\";\n\n        // User rights\n        $aUserRights = [];\n        $oLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n        $iTplLang = $oLang->getTplLanguage();\n\n        $iPos = count($aUserRights);\n        $aUserRights[$iPos] = new stdClass();\n        $aUserRights[$iPos]->name = $oLang->translateString(\"user\", $iTplLang);\n        $aUserRights[$iPos]->id = \"user\";\n\n        if ($blisMallAdmin) {\n            $iPos = count($aUserRights);\n            $aUserRights[$iPos] = new stdClass();\n            $aUserRights[$iPos]->id = \"malladmin\";\n            $aUserRights[$iPos]->name = $oLang->translateString(\"Admin\", $iTplLang);\n        }\n\n        $aUserRights = $this->calculateAdditionalRights($aUserRights);\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oUser = oxNew(User::class);\n            $oUser->load($soxId);\n            $this->_aViewData[\"edit\"] = $oUser;\n\n            if (!($oUser->oxuser__oxrights->value == \"malladmin\" && !$blisMallAdmin)) {\n                // generate selected right\n                reset($aUserRights);\n                foreach ($aUserRights as $val) {\n                    if ($val->id == $oUser->oxuser__oxrights->value) {\n                        $val->selected = 1;\n                        break;\n                    }\n                }\n            }\n        }\n\n        // passing country list\n        $oCountryList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\CountryList::class);\n        $oCountryList->loadActiveCountries($oLang->getObjectTplLanguage());\n\n        $this->_aViewData[\"countrylist\"] = $oCountryList;\n\n        $this->_aViewData[\"rights\"] = $aUserRights;\n\n        if ($this->_sSaveError) {\n            $this->_aViewData[\"sSaveError\"] = $this->_sSaveError;\n        }\n\n        if (!$this->allowAdminEdit($soxId)) {\n            $this->_aViewData['readonly'] = true;\n        }\n        if (Registry::getRequest()->getRequestEscapedParameter(\"aoc\")) {\n            $oUserMainAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\UserMainAjax::class);\n            $this->_aViewData['oxajax'] = $oUserMainAjax->getColumns();\n\n            return \"popups/user_main\";\n        }\n\n        return \"user_main\";\n    }\n\n    /**\n     * Saves main user parameters.\n     *\n     * @return mixed\n     */\n    public function save()\n    {\n        parent::save();\n\n        $soxId = $this->getEditObjectId();\n        if ($this->allowAdminEdit($soxId)) {\n            $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n            if (!isset($aParams['oxuser__oxactive'])) {\n                $aParams['oxuser__oxactive'] = 0;\n            }\n\n            $oUser = oxNew(User::class);\n            if ($soxId != \"-1\") {\n                $oUser->load($soxId);\n            } else {\n                $aParams['oxuser__oxid'] = null;\n            }\n\n            if (($sNewPass = Registry::getRequest()->getRequestEscapedParameter(\"newPassword\"))) {\n                $oUser->setPassword($sNewPass);\n            }\n\n            if (\n                isset($aParams['oxuser__oxusername'])\n                && ($aParams['oxuser__oxusername'] !== $oUser->getRawFieldData('oxusername'))\n                && $oUser->isEmailInUse($aParams['oxuser__oxusername'])\n            ) {\n                $this->_sSaveError = 'EXCEPTION_USER_USEREXISTS';\n\n                return;\n            }\n\n            $oUser->assign($aParams);\n\n            if ($soxId == \"-1\") {\n                $this->onUserCreation($oUser);\n            }\n\n            $oUser->oxuser__oxbirthdate->fldtype = 'char';\n\n            try {\n                $oUser->save();\n\n                $this->setEditObjectId($oUser->getId());\n            } catch (Exception $oExcp) {\n                $this->_sSaveError = $oExcp->getMessage();\n            }\n        }\n    }\n\n    /**\n     * If we need to add more rights / modify current rights by any conditions.\n     *\n     * @param array $userRights\n     *\n     * @return array\n     */\n    protected function calculateAdditionalRights($userRights)\n    {\n        return $userRights;\n    }\n\n    /**\n     * Additional actions on user creation.\n     *\n     * @param User $user\n     *\n     * @return User\n     */\n    protected function onUserCreation($user)\n    {\n        return $user;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/UserMainAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages user assignment to groups\n */\nclass UserMainAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,  visible, multilanguage, ident\n        ['oxtitle', 'oxgroups', 1, 0, 0],\n        ['oxid', 'oxgroups', 0, 0, 0],\n        ['oxid', 'oxgroups', 0, 0, 1],\n    ],\n                                 'container2' => [\n                                     ['oxtitle', 'oxgroups', 1, 0, 0],\n                                     ['oxid', 'oxgroups', 0, 0, 0],\n                                     ['oxid', 'oxobject2group', 0, 0, 1],\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetch\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        // looking for table/view\n        $sGroupTable = $this->getViewName('oxgroups');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sDeldId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchDelId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sDeldId) {\n            $sQAdd = \" from $sGroupTable where 1 \";\n        } else {\n            $sQAdd = \" from $sGroupTable left join oxobject2group on oxobject2group.oxgroupsid=$sGroupTable.oxid \";\n            $sQAdd .= \" where oxobject2group.oxobjectid = \" . $oDb->quote($sDeldId);\n        }\n\n        if ($sSynchDelId && $sSynchDelId != $sDeldId) {\n            $sQAdd .= \" and $sGroupTable.oxid not in ( select $sGroupTable.oxid from $sGroupTable left join oxobject2group on oxobject2group.oxgroupsid=$sGroupTable.oxid \";\n            $sQAdd .= \" where oxobject2group.oxobjectid = \" . $oDb->quote($sSynchDelId) . \" ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes user from selected user group(s).\n     */\n    public function removeUserFromGroup()\n    {\n        $aRemoveGroups = $this->getActionIds('oxobject2group.oxid');\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxobject2group.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif ($aRemoveGroups && is_array($aRemoveGroups)) {\n            $sQ = \"delete from oxobject2group where oxobject2group.oxid in (\" . implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aRemoveGroups)) . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adds user to selected user group(s).\n     */\n    public function addUserToGroup()\n    {\n        $aAddGroups = $this->getActionIds('oxgroups.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sGroupTable = $this->getViewName('oxgroups');\n            $aAddGroups = $this->getAll($this->addFilter(\"select $sGroupTable.oxid \" . $this->getQuery()));\n        }\n        if ($soxId && $soxId != \"-1\" && is_array($aAddGroups)) {\n            foreach ($aAddGroups as $sAddgroup) {\n                $oNewGroup = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Object2Group::class);\n                $oNewGroup->oxobject2group__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                $oNewGroup->oxobject2group__oxgroupsid = new \\OxidEsales\\Eshop\\Core\\Field($sAddgroup);\n                $oNewGroup->save();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/UserOverview.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Class for extending\n */\nclass UserOverview extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n            $oUser->load($soxId);\n            $this->_aViewData[\"edit\"] = $oUser;\n        }\n\n        return \"user_overview\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/UserPayment.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin user payment settings manager.\n * Collects user payment settings, updates it on user submit, etc.\n * Admin Menu: User Administration -> Users -> Payment.\n */\nclass UserPayment extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * (default false).\n     *\n     * @var bool\n     */\n    protected $_blDelete = false;\n\n    /**\n     * Selected user\n     *\n     * @var object\n     */\n    protected $_oActiveUser = null;\n\n    /**\n     * Selected user payment\n     *\n     * @var string\n     */\n    protected $_sPaymentId = null;\n\n    /**\n     * List of all payments\n     *\n     * @var object\n     */\n    protected $_oPaymentTypes = null;\n\n    /**\n     * Selected user payment\n     *\n     * @var object\n     */\n    protected $_oUserPayment = null;\n\n    /**\n     * List of all user payments\n     *\n     * @var object\n     */\n    protected $_oUserPayments = null;\n\n    /**\n     * Executes parent method parent::render(), creates oxlist and oxuser objects\n     * and returns the name of the template file.\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n        $this->_aViewData[\"edit\"] = $this->getSelUserPayment();\n        $this->_aViewData[\"oxpaymentid\"] = $this->getPaymentId();\n        $this->_aViewData[\"paymenttypes\"] = $this->getPaymentTypes();\n        $this->_aViewData[\"edituser\"] = $this->getUser();\n        $this->_aViewData[\"userpayments\"] = $this->getUserPayments();\n        $sOxId = $this->getEditObjectId();\n\n        if (!$this->allowAdminEdit($sOxId)) {\n            $this->_aViewData['readonly'] = true;\n        }\n\n        return \"user_payment\";\n    }\n\n    /**\n     * Saves user payment settings.\n     */\n    public function save()\n    {\n        parent::save();\n\n        $soxId = $this->getEditObjectId();\n        if ($this->allowAdminEdit($soxId)) {\n            $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n            $aDynvalues = Registry::getRequest()->getRequestEscapedParameter(\"dynvalue\");\n\n            if (isset($aDynvalues)) {\n                // store the dynvalues\n                $aParams['oxuserpayments__oxvalue'] = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->assignValuesToText($aDynvalues);\n            }\n\n            if ($aParams['oxuserpayments__oxid'] == \"-1\") {\n                $aParams['oxuserpayments__oxid'] = null;\n            }\n\n            $oAdress = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\UserPayment::class);\n            $oAdress->assign($aParams);\n            $oAdress->save();\n        }\n    }\n\n    /**\n     * Deletes selected user payment information.\n     */\n    public function delPayment()\n    {\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n        $soxId = $this->getEditObjectId();\n        if ($this->allowAdminEdit($soxId)) {\n            if ($aParams['oxuserpayments__oxid'] != \"-1\") {\n                $oAdress = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\UserPayment::class);\n                if ($oAdress->load($aParams['oxuserpayments__oxid'])) {\n                    $this->_blDelete = (bool) $oAdress->delete();\n                }\n            }\n        }\n    }\n\n    /**\n     * Returns selected user\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\User|false\n     */\n    public function getUser()\n    {\n        if ($this->_oActiveUser == null) {\n            $this->_oActiveUser = false;\n            $sOxId = $this->getEditObjectId();\n            if (isset($sOxId) && $sOxId != \"-1\") {\n                // load object\n                $this->_oActiveUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n                $this->_oActiveUser->load($sOxId);\n            }\n        }\n\n        return $this->_oActiveUser;\n    }\n\n    /**\n     * Returns selected Payment Id\n     *\n     * @return object\n     */\n    public function getPaymentId()\n    {\n        if ($this->_sPaymentId == null) {\n            $this->_sPaymentId = Registry::getRequest()->getRequestEscapedParameter(\"oxpaymentid\");\n            if (!$this->_sPaymentId || $this->_blDelete) {\n                if ($oUser = $this->getUser()) {\n                    $oUserPayments = $oUser->getUserPayments();\n                    if (isset($oUserPayments[0])) {\n                        $this->_sPaymentId = $oUserPayments[0]->oxuserpayments__oxid->value;\n                    }\n                }\n            }\n            if (!$this->_sPaymentId) {\n                $this->_sPaymentId = \"-1\";\n            }\n        }\n\n        return $this->_sPaymentId;\n    }\n\n    /**\n     * Returns selected Payment Id\n     *\n     * @return object\n     */\n    public function getPaymentTypes()\n    {\n        if ($this->_oPaymentTypes == null) {\n            // all paymenttypes\n            $this->_oPaymentTypes = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n            $this->_oPaymentTypes->init(\"oxpayment\");\n            $oListObject = $this->_oPaymentTypes->getBaseObject();\n            $oListObject->setLanguage(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->getObjectTplLanguage());\n            $this->_oPaymentTypes->getList();\n        }\n\n        return $this->_oPaymentTypes;\n    }\n\n    /**\n     * Returns selected Payment\n     *\n     * @return object\n     */\n    public function getSelUserPayment()\n    {\n        if ($this->_oUserPayment == null) {\n            $this->_oUserPayment = false;\n            $sPaymentId = $this->getPaymentId();\n            if ($sPaymentId != \"-1\" && isset($sPaymentId)) {\n                $this->_oUserPayment = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\UserPayment::class);\n                $this->_oUserPayment->load($sPaymentId);\n                $sTemplate = $this->_oUserPayment->oxuserpayments__oxvalue->value;\n\n                // generate selected paymenttype\n                $oPaymentTypes = $this->getPaymentTypes();\n                foreach ($oPaymentTypes as $oPayment) {\n                    if ($oPayment->oxpayments__oxid->value == $this->_oUserPayment->oxuserpayments__oxpaymentsid->value) {\n                        $oPayment->selected = 1;\n                        // if there are no values assigned we set default from paymenttype\n                        if (!$sTemplate) {\n                            $sTemplate = $oPayment->oxpayments__oxvaldesc->value;\n                        }\n                        break;\n                    }\n                }\n                $this->_oUserPayment->setDynValues(\\OxidEsales\\Eshop\\Core\\Registry::getUtils()->assignValuesFromText($sTemplate));\n            }\n        }\n\n        return $this->_oUserPayment;\n    }\n\n    /**\n     * Returns selected Payment Id\n     *\n     * @return object\n     */\n    public function getUserPayments()\n    {\n        if ($this->_oUserPayments == null) {\n            $this->_oUserPayments = false;\n            if ($oUser = $this->getUser()) {\n                $sTplLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getObjectTplLanguage();\n                $sPaymentId = $this->getPaymentId();\n                $this->_oUserPayments = $oUser->getUserPayments();\n                // generate selected\n                foreach ($this->_oUserPayments as $oUserPayment) {\n                    $oPayment = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Payment::class);\n                    $oPayment->setLanguage($sTplLang);\n                    $oPayment->load($oUserPayment->oxuserpayments__oxpaymentsid->value);\n                    $oUserPayment->oxpayments__oxdesc = clone $oPayment->oxpayments__oxdesc;\n                    if ($oUserPayment->oxuserpayments__oxid->value == $sPaymentId) {\n                        $oUserPayment->selected = 1;\n                        break;\n                    }\n                }\n            }\n        }\n\n        return $this->_oUserPayments;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/UserRemark.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin user history settings manager.\n * Collects user history settings, updates it on user submit, etc.\n * Admin Menu: User Administration -> Users -> History.\n */\nclass UserRemark extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->getEditObjectId();\n        $sRemoxId = Registry::getRequest()->getRequestEscapedParameter(\"rem_oxid\");\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n            $oUser->load($soxId);\n            $this->_aViewData[\"edit\"] = $oUser;\n\n            // all remark\n            $oRems = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n            $oRems->init(\"oxremark\");\n            $sSelect = \"select * from oxremark where oxparentid = :oxparentid order by oxcreate desc\";\n            $oRems->selectString($sSelect, [\n                'oxparentid' => $oUser->getId()\n            ]);\n            foreach ($oRems as $key => $val) {\n                if ($val->oxremark__oxid->value == $sRemoxId) {\n                    $val->selected = 1;\n                    $oRems[$key] = $val;\n                    break;\n                }\n            }\n\n            $this->_aViewData[\"allremark\"] = $oRems;\n\n            if (isset($sRemoxId)) {\n                $oRemark = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Remark::class);\n                $oRemark->load($sRemoxId);\n                $this->_aViewData[\"remarktext\"] = $oRemark->oxremark__oxtext->value;\n                $this->_aViewData[\"remarkheader\"] = $oRemark->oxremark__oxheader->value;\n            }\n        }\n\n        return \"user_remark\";\n    }\n\n    /**\n     * Saves user history text changes.\n     */\n    public function save()\n    {\n        parent::save();\n\n        $oRemark = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Remark::class);\n\n        // try to load if exists\n        $oRemark->load(Registry::getRequest()->getRequestEscapedParameter(\"rem_oxid\"));\n\n        $oRemark->oxremark__oxtext = new \\OxidEsales\\Eshop\\Core\\Field(Registry::getRequest()->getRequestEscapedParameter(\"remarktext\"));\n        $oRemark->oxremark__oxheader = new \\OxidEsales\\Eshop\\Core\\Field(Registry::getRequest()->getRequestEscapedParameter(\"remarkheader\"));\n        $oRemark->oxremark__oxparentid = new \\OxidEsales\\Eshop\\Core\\Field($this->getEditObjectId());\n        $oRemark->oxremark__oxtype = new \\OxidEsales\\Eshop\\Core\\Field(\"r\");\n        $oRemark->save();\n    }\n\n    /**\n     * Deletes user actions history record.\n     */\n    public function delete()\n    {\n        $oRemark = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Remark::class);\n        $oRemark->delete(Registry::getRequest()->getRequestEscapedParameter(\"rem_oxid\"));\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/VendorController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Returns template, that arranges two other templates (\"vendor_list\"\n * and \"vendor_main\") to frame.\n * Admin Menu: Settings -> Vendors\n */\nclass VendorController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'vendor';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/VendorList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin vendor list manager.\n */\nclass VendorList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'vendor_list';\n\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxvendor';\n\n    /**\n     * Default SQL sorting parameter (default null).\n     *\n     * @var string\n     */\n    protected $_sDefSortField = 'oxtitle';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/VendorMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse stdClass;\n\n/**\n * Admin vendor main screen.\n * Performs collection and updating (on user submit) main item information.\n */\nclass VendorMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /**\n     * Executes parent method parent::render(),\n     * and returns name of template file\n     * \"vendor_main\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oVendor = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Vendor::class);\n            $oVendor->loadInLang($this->_iEditLang, $soxId);\n\n            $oOtherLang = $oVendor->getAvailableInLangs();\n            if (!isset($oOtherLang[$this->_iEditLang])) {\n                $oVendor->loadInLang(key($oOtherLang), $soxId);\n            }\n            $this->_aViewData[\"edit\"] = $oVendor;\n\n            // category tree\n            $this->createCategoryTree(\"artcattree\");\n\n            //Disable editing for derived articles\n            if ($oVendor->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n\n            // remove already created languages\n            $aLang = array_diff(Registry::getLang()->getLanguageNames(), $oOtherLang);\n            if (count($aLang)) {\n                $this->_aViewData[\"posslang\"] = $aLang;\n            }\n\n            foreach ($oOtherLang as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $oLang;\n            }\n        }\n\n        if ($this->getViewConfig()->isAltImageServerConfigured()) {\n            $this->_aViewData[\"imageUrl\"] = ContainerFacade::getParameter('oxid_esales.alternative_image_url');\n        }\n\n        if (Registry::getRequest()->getRequestEscapedParameter(\"aoc\")) {\n            $oVendorMainAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\VendorMainAjax::class);\n            $this->_aViewData['oxajax'] = $oVendorMainAjax->getColumns();\n\n            return \"popups/vendor_main\";\n        }\n\n        return \"vendor_main\";\n    }\n\n    /**\n     * Saves selection list parameters changes.\n     *\n     * @return mixed\n     */\n    public function save()\n    {\n        parent::save();\n\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        if (!isset($aParams['oxvendor__oxactive'])) {\n            $aParams['oxvendor__oxactive'] = 0;\n        }\n\n        $oVendor = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Vendor::class);\n        if ($soxId != \"-1\") {\n            $oVendor->loadInLang($this->_iEditLang, $soxId);\n        } else {\n            $aParams['oxvendor__oxid'] = null;\n        }\n\n        //Disable editing for derived articles\n        if ($oVendor->isDerived()) {\n            return;\n        }\n\n        $oVendor->setLanguage(0);\n        $oVendor->assign($aParams);\n        $oVendor->setLanguage($this->_iEditLang);\n        $oVendor = Registry::getUtilsFile()->processFiles($oVendor);\n        $oVendor->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oVendor->getId());\n    }\n\n    /**\n     * Saves selection list parameters changes in different language (eg. english).\n     *\n     * @return mixed\n     */\n    public function saveinnlang()\n    {\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        if (!isset($aParams['oxvendor__oxactive'])) {\n            $aParams['oxvendor__oxactive'] = 0;\n        }\n\n        $oVendor = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Vendor::class);\n\n        if ($soxId != \"-1\") {\n            $oVendor->loadInLang($this->_iEditLang, $soxId);\n        } else {\n            $aParams['oxvendor__oxid'] = null;\n        }\n\n        //Disable editing for derived articles\n        if ($oVendor->isDerived()) {\n            return;\n        }\n\n        $oVendor->setLanguage(0);\n        $oVendor->assign($aParams);\n        $oVendor->setLanguage($this->_iEditLang);\n        $oVendor = Registry::getUtilsFile()->processFiles($oVendor);\n        $oVendor->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oVendor->getId());\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/VendorMainAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages vendor assignment to articles\n */\nclass VendorMainAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * If true extended column selection will be build\n     *\n     * @var bool\n     */\n    protected $_blAllowExtColumns = true;\n\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,       visible, multilanguage, ident\n        ['oxartnum', 'oxarticles', 1, 0, 0],\n        ['oxtitle', 'oxarticles', 1, 1, 0],\n        ['oxean', 'oxarticles', 1, 0, 0],\n        ['oxmpn', 'oxarticles', 0, 0, 0],\n        ['oxprice', 'oxarticles', 0, 0, 0],\n        ['oxstock', 'oxarticles', 0, 0, 0],\n        ['oxid', 'oxarticles', 0, 0, 1]\n    ],\n                                 'container2' => [\n                                     ['oxartnum', 'oxarticles', 1, 0, 0],\n                                     ['oxtitle', 'oxarticles', 1, 1, 0],\n                                     ['oxean', 'oxarticles', 1, 0, 0],\n                                     ['oxmpn', 'oxarticles', 0, 0, 0],\n                                     ['oxprice', 'oxarticles', 0, 0, 0],\n                                     ['oxstock', 'oxarticles', 0, 0, 0],\n                                     ['oxid', 'oxarticles', 0, 0, 1]\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        // looking for table/view\n        $sArtTable = $this->getViewName('oxarticles');\n        $sO2CView = $this->getViewName('oxobject2category');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $oConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $sVendorId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchVendorId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // vendor selected or not ?\n        if (!$sVendorId) {\n            $sQAdd = ' from ' . $sArtTable . ' where ' . $sArtTable . '.oxshopid=\"' . $oConfig->getShopId() . '\" and 1 ';\n            $sQAdd .= $oConfig->getConfigParam('blVariantsSelection') ? '' : \" and $sArtTable.oxparentid = '' and $sArtTable.oxvendorid != \" . $oDb->quote($sSynchVendorId);\n        } else {\n            // selected category ?\n            if ($sSynchVendorId && $sSynchVendorId != $sVendorId) {\n                $sQAdd = \" from $sO2CView left join $sArtTable on \";\n                $sQAdd .= $oConfig->getConfigParam('blVariantsSelection') ? \" ( $sArtTable.oxid = $sO2CView.oxobjectid or $sArtTable.oxparentid = oxobject2category.oxobjectid )\" : \" $sArtTable.oxid = $sO2CView.oxobjectid \";\n                $sQAdd .= 'where ' . $sArtTable . '.oxshopid=\"' . $oConfig->getShopId() . '\" and ' . $sO2CView . '.oxcatnid = ' . $oDb->quote($sVendorId) . ' and ' . $sArtTable . '.oxvendorid != ' . $oDb->quote($sSynchVendorId);\n            } else {\n                $sQAdd = \" from $sArtTable where $sArtTable.oxvendorid = \" . $oDb->quote($sVendorId);\n            }\n\n            $sQAdd .= $oConfig->getConfigParam('blVariantsSelection') ? '' : \" and $sArtTable.oxparentid = '' \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Adds filter SQL to current query\n     *\n     * @param string $sQ query to add filter condition\n     *\n     * @return string\n     */\n    protected function addFilter($sQ)\n    {\n        $sArtTable = $this->getViewName('oxarticles');\n        $sQ = parent::addFilter($sQ);\n\n        // display variants or not ?\n        $sQ .= \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blVariantsSelection') ? ' group by ' . $sArtTable . '.oxid ' : '';\n\n        return $sQ;\n    }\n\n    /**\n     * Removes article from Vendor\n     */\n    public function removeVendor()\n    {\n        $oConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $aRemoveArt = $this->getActionIds('oxarticles.oxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sArtTable = $this->getViewName('oxarticles');\n            $aRemoveArt = $this->getAll($this->addFilter(\"select $sArtTable.oxid \" . $this->getQuery()));\n        }\n\n        if (is_array($aRemoveArt)) {\n            $sSelect = \"update oxarticles set oxvendorid = null where \"\n                . $this->onVendorActionArticleUpdateConditions($aRemoveArt);\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sSelect);\n\n            $this->resetCounter(\"vendorArticle\", Registry::getRequest()->getRequestEscapedParameter('oxid'));\n\n            $this->onVendorAction(Registry::getRequest()->getRequestEscapedParameter('oxid'));\n        }\n    }\n\n    /**\n     * Adds article to Vendor config\n     */\n    public function addVendor()\n    {\n        $oConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $aAddArticle = $this->getActionIds('oxarticles.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sArtTable = $this->getViewName('oxarticles');\n            $aAddArticle = $this->getAll($this->addFilter(\"select $sArtTable.oxid \" . $this->getQuery()));\n        }\n\n        if ($soxId && $soxId != \"-1\" && is_array($aAddArticle)) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $sSelect = \"update oxarticles set oxvendorid = \" . $oDb->quote($soxId) . \" where \"\n                . $this->onVendorActionArticleUpdateConditions($aAddArticle);\n\n            $oDb->Execute($sSelect);\n            $this->resetCounter(\"vendorArticle\", $soxId);\n\n            $this->onVendorAction($soxId);\n        }\n    }\n\n    /**\n     * Condition for updating oxarticles on add / remove vendor actions.\n     *\n     * @param array $articleIds\n     *\n     * @return string\n     */\n    protected function onVendorActionArticleUpdateConditions($articleIds)\n    {\n        return 'oxid in (' . implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($articleIds)) . ')';\n    }\n\n    /**\n     * Additional actions on vendor add/remove.\n     *\n     * @param string $vendorOxid\n     */\n    protected function onVendorAction($vendorOxid)\n    {\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/VendorSeo.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Vendor seo config class\n */\nclass VendorSeo extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ObjectSeo\n{\n    /**\n     * Updating showsuffix field\n     *\n     * @return null\n     */\n    public function save()\n    {\n        $oVendor = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n        $oVendor->init('oxvendor');\n        if ($oVendor->load($this->getEditObjectId())) {\n            $sShowSuffixField = 'oxvendor__oxshowsuffix';\n            $blShowSuffixParameter = Registry::getRequest()->getRequestEscapedParameter('blShowSuffix');\n            $oVendor->$sShowSuffixField = new \\OxidEsales\\Eshop\\Core\\Field((int) $blShowSuffixParameter);\n            $oVendor->save();\n        }\n\n        return parent::save();\n    }\n\n    /**\n     * Returns current object type seo encoder object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderVendor\n     */\n    protected function getEncoder()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderVendor::class);\n    }\n\n    /**\n     * This SEO object supports suffixes so return TRUE\n     *\n     * @return bool\n     */\n    public function isSuffixSupported()\n    {\n        return true;\n    }\n\n    /**\n     * Returns true if SEO object id has suffix enabled\n     *\n     * @return bool\n     */\n    public function isEntrySuffixed()\n    {\n        $oVendor = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Vendor::class);\n        if ($oVendor->load($this->getEditObjectId())) {\n            return (bool) $oVendor->oxvendor__oxshowsuffix->value;\n        }\n    }\n\n    /**\n     * Returns url type\n     *\n     * @return string\n     */\n    protected function getType()\n    {\n        return 'oxvendor';\n    }\n\n    /**\n     * Returns seo uri\n     *\n     * @return string\n     */\n    public function getEntryUri()\n    {\n        $oVendor = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Vendor::class);\n        if ($oVendor->load($this->getEditObjectId())) {\n            return $this->getEncoder()->getVendorUri($oVendor, $this->getEditLang());\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/VoucherSerieController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin voucherserie manager.\n * Returns template, that arranges two other templates (\"voucherserie_list\"\n * and \"voucherserie_main\") to frame.\n * Admin Menu: Shop Settings -> Vouchers.\n */\nclass VoucherSerieController extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'voucherserie';\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/VoucherSerieExport.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse Symfony\\Component\\Filesystem\\Path;\n\n/**\n * General export class.\n */\nclass VoucherSerieExport extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\VoucherSerieMain\n{\n    /**\n     * Export class name\n     *\n     * @var string\n     */\n    public $sClassDo = \"voucherserie_export\";\n\n    /**\n     * Export file extension\n     *\n     * @var string\n     */\n    public $sExportFileType = \"csv\";\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = \"voucherserie_export\";\n\n    /**\n     * Number of records to export per tick\n     *\n     * @var int\n     */\n    public $iExportPerTick = 1000;\n\n    /**\n     * Calls parent costructor and initializes $this->_sFilePath parameter\n     */\n    public function __construct()\n    {\n        parent::__construct();\n\n        // export file name\n        $this->sExportFileName = $this->getExportFileName();\n\n        // set generic frame template\n        $this->_sFilePath = $this->getExportFilePath();\n    }\n\n    /**\n     * Returns export file download url\n     *\n     * @return string\n     */\n    public function getDownloadUrl()\n    {\n        $myConfig = Registry::getConfig();\n\n        ContainerFacade::getParameter('oxid_esales.shop_admin_url');\n        $url = ContainerFacade::getParameter('oxid_esales.shop_admin_url') ?:\n            ContainerFacade::getParameter('oxid_esales.shop_url') . $myConfig->getConfigParam('sAdminDir');\n\n        $url = Registry::getUtilsUrl()->processUrl($url . '/index.php');\n\n        return $url . '&amp;cl=' . $this->sClassDo . '&amp;fnc=download';\n    }\n\n    /**\n     * Return export file name\n     *\n     * @return string\n     */\n    protected function getExportFileName()\n    {\n        $sSessionFileName = Registry::getSession()->getVariable(\"sExportFileName\");\n        if (!$sSessionFileName) {\n            $session = Registry::getSession();\n            $sSessionFileName = md5($session->getId() . Registry::getUtilsObject()->generateUId());\n            Registry::getSession()->setVariable(\"sExportFileName\", $sSessionFileName);\n        }\n\n        return $sSessionFileName;\n    }\n\n    /**\n     * Return export file path\n     *\n     * @return string\n     */\n    protected function getExportFilePath()\n    {\n        return Path::join(\n            ContainerFacade::getParameter('oxid_esales.shop_source_directory'),\n            'export',\n            $this->getExportFileName()\n        );\n    }\n\n    /**\n     * Performs Voucherserie export to export file.\n     */\n    public function download()\n    {\n        $oUtils = Registry::getUtils();\n        $oUtils->setHeader(\"Pragma: public\");\n        $oUtils->setHeader(\"Cache-Control: must-revalidate, post-check=0, pre-check=0\");\n        $oUtils->setHeader(\"Expires: 0\");\n        $oUtils->setHeader(\"Content-Disposition: attachment; filename=vouchers.csv\");\n        $oUtils->setHeader(\"Content-Type: application/csv\");\n        $sFile = $this->getExportFilePath();\n        if (file_exists($sFile) && is_readable($sFile)) {\n            readfile($sFile);\n        }\n        $oUtils->showMessageAndExit(\"\");\n    }\n\n    /**\n     * Does Export\n     */\n    public function run()\n    {\n        $blContinue = true;\n\n        $this->fpFile = @fopen($this->_sFilePath, \"a\");\n        if (!isset($this->fpFile) || !$this->fpFile) {\n            // we do have an error !\n            $this->stop(ERR_FILEIO);\n        } else {\n            // file is open\n            $iStart = Registry::getRequest()->getRequestEscapedParameter(\"iStart\");\n            if (!$iStart) {\n                ftruncate($this->fpFile, 0);\n            }\n\n            if (($iExportedItems = $this->exportVouchers($iStart)) === false) {\n                // end reached\n                $this->stop(ERR_SUCCESS);\n                $blContinue = false;\n            }\n\n            if ($blContinue) {\n                // make ticker continue\n                $this->_aViewData['refresh'] = 0;\n                $this->_aViewData['iStart'] = $iStart + $iExportedItems;\n                $this->_aViewData['iExpItems'] = $iStart + $iExportedItems;\n            }\n            fclose($this->fpFile);\n        }\n    }\n\n    /**\n     * Writes voucher number information to export file and returns number of written records info\n     *\n     * @param int $iStart start exporting from\n     *\n     * @return int\n     */\n    public function exportVouchers($iStart)\n    {\n        $voucherSerie = $this->getVoucherSerie();\n        if (!$voucherSerie) {\n            return false;\n        }\n        $resultSet = DatabaseProvider::getDb()\n            ->selectLimit(\n                'select oxvouchernr from oxvouchers where oxvoucherserieid = :oxvoucherserieid',\n                $this->iExportPerTick,\n                $iStart,\n                ['oxvoucherserieid' => $voucherSerie->getId()]\n            );\n        if ($resultSet->EOF) {\n            return false;\n        }\n        $exportedVouchersCount = 0;\n        // writing header text\n        if ($iStart == 0) {\n            $this->write(\n                Registry::getLang()->translateString(\n                    'VOUCHERSERIE_MAIN_VOUCHERSTATISTICS',\n                    Registry::getLang()->getTplLanguage(),\n                    true\n                )\n            );\n        }\n        // writing vouchers..\n        while (!$resultSet->EOF) {\n            $this->write(current($resultSet->fields));\n            $exportedVouchersCount++;\n            $resultSet->fetchRow();\n        }\n\n        return $exportedVouchersCount;\n    }\n\n    /**\n     * writes one line into open export file\n     *\n     * @param string $sLine exported line\n     */\n    public function write($sLine)\n    {\n        if ($sLine) {\n            fwrite($this->fpFile, $sLine . \"\\n\");\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/VoucherSerieGenerate.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Voucher Serie generator class\n */\nclass VoucherSerieGenerate extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\VoucherSerieMain\n{\n    /**\n     * Voucher generator class name\n     *\n     * @var string\n     */\n    public $sClassDo = \"voucherserie_generate\";\n\n    /**\n     * Number of vouchers to generate per tick\n     *\n     * @var int\n     */\n    public $iGeneratePerTick = 100;\n\n    /**\n     * Current class template name\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = \"voucherserie_generate\";\n\n    /**\n     * Voucher serie object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\VoucherSerie\n     */\n    protected $_oVoucherSerie = null;\n\n    /**\n     * Generated vouchers count\n     *\n     * @var int\n     */\n    protected $_iGenerated = false;\n\n    /**\n     * Generates vouchers by offset iCnt\n     *\n     * @param integer $cnt voucher offset\n     *\n     * @return bool\n     */\n    public function nextTick($cnt)\n    {\n        if ($iGeneratedItems = $this->generateVoucher($cnt)) {\n            return $iGeneratedItems;\n        }\n\n        return false;\n    }\n\n    /**\n     * Generates and saves vouchers. Returns number of saved records\n     *\n     * @param int $iCnt voucher counter offset\n     *\n     * @return int saved record count\n     */\n    public function generateVoucher($iCnt)\n    {\n        $iAmount = abs((int) \\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable(\"voucherAmount\"));\n\n        // creating new vouchers\n        if ($iCnt < $iAmount && ($oVoucherSerie = $this->getVoucherSerie())) {\n            if (!$this->_iGenerated) {\n                $this->_iGenerated = $iCnt;\n            }\n\n            $blRandomNr = (bool) \\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable(\"randomVoucherNr\");\n            $sVoucherNr = $blRandomNr ? \\OxidEsales\\Eshop\\Core\\Registry::getUtilsObject()->generateUID() : \\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable(\"voucherNr\");\n\n            $oNewVoucher = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Voucher::class);\n            $oNewVoucher->oxvouchers__oxvoucherserieid = new \\OxidEsales\\Eshop\\Core\\Field($oVoucherSerie->getId());\n            $oNewVoucher->oxvouchers__oxvouchernr = new \\OxidEsales\\Eshop\\Core\\Field($sVoucherNr);\n            $oNewVoucher->save();\n\n            $this->_iGenerated++;\n        }\n\n        return $this->_iGenerated;\n    }\n\n    /**\n     * Runs voucher generation\n     */\n    public function run()\n    {\n        $blContinue = true;\n        $iExportedItems = 0;\n\n        // file is open\n        $iStart = Registry::getRequest()->getRequestEscapedParameter(\"iStart\");\n\n        for ($i = $iStart; $i < $iStart + $this->iGeneratePerTick; $i++) {\n            if (($iExportedItems = $this->nextTick($i)) === false) {\n                // end reached\n                $this->stop(ERR_SUCCESS);\n                $blContinue = false;\n                break;\n            }\n        }\n\n        if ($blContinue) {\n            // make ticker continue\n            $this->_aViewData['refresh'] = 0;\n            $this->_aViewData['iStart'] = $i;\n            $this->_aViewData['iExpItems'] = $iExportedItems;\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/VoucherSerieGroups.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin voucherserie groups manager.\n * Collects and manages information about user groups, added to one or another\n * serie of vouchers.\n * Admin Menu: Shop Settings -> Vouchers -> Groups.\n */\nclass VoucherSerieGroups extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oVoucherSerie = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\VoucherSerie::class);\n            $oVoucherSerie->load($soxId);\n            $oVoucherSerie->setUserGroups();\n            $this->_aViewData[\"edit\"] = $oVoucherSerie;\n\n            //Disable editing for derived items\n            if ($oVoucherSerie->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n        }\n        if (Registry::getRequest()->getRequestEscapedParameter(\"aoc\")) {\n            $oVoucherSerieGroupsAjax = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\Admin\\VoucherSerieGroupsAjax::class);\n            $this->_aViewData['oxajax'] = $oVoucherSerieGroupsAjax->getColumns();\n\n            return \"popups/voucherserie_groups\";\n        }\n\n        return \"voucherserie_groups\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/VoucherSerieGroupsAjax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class manages voucher assignment to user groups\n */\nclass VoucherSerieGroupsAjax extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax\n{\n    /**\n     * Columns array\n     *\n     * @var array\n     */\n    protected $_aColumns = ['container1' => [ // field , table,  visible, multilanguage, ident\n        ['oxtitle', 'oxgroups', 1, 0, 0],\n        ['oxid', 'oxgroups', 0, 0, 0],\n        ['oxid', 'oxgroups', 0, 0, 1],\n    ],\n                                 'container2' => [\n                                     ['oxtitle', 'oxgroups', 1, 0, 0],\n                                     ['oxid', 'oxgroups', 0, 0, 0],\n                                     ['oxid', 'oxobject2group', 0, 0, 1],\n                                 ]\n    ];\n\n    /**\n     * Returns SQL query for data to fetc\n     *\n     * @return string\n     */\n    protected function getQuery()\n    {\n        // looking for table/view\n        $sGroupTable = $this->getViewName('oxgroups');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sVoucherId = Registry::getRequest()->getRequestEscapedParameter('oxid');\n        $sSynchVoucherId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        // category selected or not ?\n        if (!$sVoucherId) {\n            $sQAdd = \" from $sGroupTable where 1 \";\n        } else {\n            $sQAdd = \" from $sGroupTable, oxobject2group where \";\n            $sQAdd .= \" oxobject2group.oxobjectid = \" . $oDb->quote($sVoucherId) . \" and $sGroupTable.oxid = oxobject2group.oxgroupsid \";\n        }\n\n        if ($sSynchVoucherId && $sSynchVoucherId != $sVoucherId) {\n            $sQAdd .= \" and $sGroupTable.oxid not in ( select $sGroupTable.oxid from $sGroupTable, oxobject2group where \";\n            $sQAdd .= \" oxobject2group.oxobjectid = \" . $oDb->quote($sSynchVoucherId) . \" and $sGroupTable.oxid = oxobject2group.oxgroupsid ) \";\n        }\n\n        return $sQAdd;\n    }\n\n    /**\n     * Removes selected user group(s) from Voucher serie list.\n     */\n    public function removeGroupFromVoucher()\n    {\n        $aRemoveGroups = $this->getActionIds('oxobject2group.oxid');\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sQ = $this->addFilter(\"delete oxobject2group.* \" . $this->getQuery());\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        } elseif ($aRemoveGroups && is_array($aRemoveGroups)) {\n            $sQ = \"delete from oxobject2group where oxobject2group.oxid in (\" . implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aRemoveGroups)) . \") \";\n            \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->Execute($sQ);\n        }\n    }\n\n    /**\n     * Adds selected user group(s) to Voucher serie list.\n     */\n    public function addGroupToVoucher()\n    {\n        $aChosenCat = $this->getActionIds('oxgroups.oxid');\n        $soxId = Registry::getRequest()->getRequestEscapedParameter('synchoxid');\n\n        if (Registry::getRequest()->getRequestEscapedParameter('all')) {\n            $sGroupTable = $this->getViewName('oxgroups');\n            $aChosenCat = $this->getAll($this->addFilter(\"select $sGroupTable.oxid \" . $this->getQuery()));\n        }\n        if ($soxId && $soxId != \"-1\" && is_array($aChosenCat)) {\n            foreach ($aChosenCat as $sChosenCat) {\n                $oNewGroup = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Object2Group::class);\n                $oNewGroup->oxobject2group__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($soxId);\n                $oNewGroup->oxobject2group__oxgroupsid = new \\OxidEsales\\Eshop\\Core\\Field($sChosenCat);\n                $oNewGroup->save();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/VoucherSerieList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin voucherserie list manager.\n * Collects voucherserie base information (serie no., discount, valid from, etc.),\n * there is ability to filter them by deiscount, serie no. or delete them.\n * Admin Menu: Shop Settings -> Vouchers.\n */\nclass VoucherSerieList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxvoucherserie';\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'voucherserie_list';\n\n    /**\n     * Deletes selected Voucherserie.\n     */\n    public function deleteEntry()\n    {\n        // first we remove vouchers\n        $oVoucherSerie = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\VoucherSerie::class);\n        $oVoucherSerie->load($this->getEditObjectId());\n        $oVoucherSerie->deleteVoucherList();\n\n        parent::deleteEntry();\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/VoucherSerieMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Admin article main voucherserie manager.\n * There is possibility to change voucherserie name, description, valid terms\n * and etc.\n * Admin Menu: Shop Settings -> Vouchers -> Main.\n */\nclass VoucherSerieMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\DynamicExportBaseController\n{\n    /**\n     * Export class name\n     *\n     * @var string\n     */\n    public $sClassDo = \"voucherSerie_generate\";\n\n    /**\n     * Voucher serie object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\VoucherSerie\n     */\n    protected $_oVoucherSerie = null;\n\n    /**\n     * Current class template name\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = \"voucherserie_main\";\n\n    /**\n     * View id, use old class name for compatibility reasons.\n     *\n     * @var string\n     */\n    protected $viewId = 'voucherserie_main';\n\n    /**\n     * Executes parent method parent::render(), creates VoucherSerie object\n     * and returns the name of the template file.\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oVoucherSerie = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\VoucherSerie::class);\n            $oVoucherSerie->load($soxId);\n            $this->_aViewData[\"edit\"] = $oVoucherSerie;\n\n            //Disable editing for derived items\n            if ($oVoucherSerie->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n        }\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Saves main Voucherserie parameters changes.\n     *\n     * @return mixed\n     */\n    public function save()\n    {\n        parent::save();\n\n        // Parameter Processing\n        $soxId = $this->getEditObjectId();\n        $aSerieParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        // Voucher Serie Processing\n        $oVoucherSerie = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\VoucherSerie::class);\n        // if serie already exist use it\n        if ($soxId != \"-1\") {\n            $oVoucherSerie->load($soxId);\n        } else {\n            $aSerieParams[\"oxvoucherseries__oxid\"] = null;\n        }\n\n        //Disable editing for derived items\n        if ($oVoucherSerie->isDerived()) {\n            return;\n        }\n\n        $aSerieParams[\"oxvoucherseries__oxdiscount\"] = abs((float) $aSerieParams[\"oxvoucherseries__oxdiscount\"]);\n\n        $oVoucherSerie->assign($aSerieParams);\n        $oVoucherSerie->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oVoucherSerie->getId());\n    }\n\n    /**\n     * Returns voucher status information array\n     *\n     * @return array\n     */\n    public function getStatus()\n    {\n        if ($oSerie = $this->getVoucherSerie()) {\n            return $oSerie->countVouchers();\n        }\n    }\n\n    /**\n     * Overriding parent function, doing nothing..\n     */\n    public function prepareExport()\n    {\n    }\n\n\n    /**\n     * Returns voucher serie object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\VoucherSerie\n     */\n    protected function getVoucherSerie()\n    {\n        if ($this->_oVoucherSerie == null) {\n            $oVoucherSerie = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\VoucherSerie::class);\n            $sId = Registry::getRequest()->getRequestEscapedParameter(\"voucherid\");\n            if ($oVoucherSerie->load($sId ? $sId : \\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable(\"voucherid\"))) {\n                $this->_oVoucherSerie = $oVoucherSerie;\n            }\n        }\n\n        return $this->_oVoucherSerie;\n    }\n\n    /**\n     * Prepares Export\n     *\n     * @return null\n     */\n    public function start()\n    {\n        $sVoucherNr = trim(Registry::getRequest()->getRequestEscapedParameter(\"voucherNr\"));\n        $bRandomNr = Registry::getRequest()->getRequestEscapedParameter(\"randomVoucherNr\");\n        $controllerId = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getRequestControllerId();\n\n        if ($controllerId == 'voucherserie_generate' && !$bRandomNr && empty($sVoucherNr)) {\n            return;\n        }\n\n        $this->_aViewData['refresh'] = 0;\n        $this->_aViewData['iStart'] = 0;\n        $iEnd = $this->prepareExport();\n        \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable(\"iEnd\", $iEnd);\n        $this->_aViewData['iEnd'] = $iEnd;\n\n        // saving export info\n        \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable(\"voucherid\", Registry::getRequest()->getRequestEscapedParameter(\"voucherid\"));\n        \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable(\"voucherAmount\", abs((int) Registry::getRequest()->getRequestEscapedParameter(\"voucherAmount\")));\n        \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable(\"randomVoucherNr\", $bRandomNr);\n        \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable(\"voucherNr\", $sVoucherNr);\n    }\n\n    /**\n     * Current view ID getter helps to identify navigation position\n     * fix for 0003701, passing dynexportbase::getViewId\n     *\n     * @return string\n     */\n    public function getViewId()\n    {\n        return \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController::getViewId();\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/WrappingList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\n/**\n * Admin wrapping list manager.\n * Performs collection and managing (such as filtering or deleting) function.\n * Admin Menu: User Administration -> Users.\n */\nclass WrappingList extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController\n{\n    /**\n     * Name of chosen object class (default null).\n     *\n     * @var string\n     */\n    protected $_sListClass = 'oxwrapping';\n\n    /**\n     * Default SQL sorting parameter (default null).\n     *\n     * @var string\n     */\n    protected $_sDefSortField = 'oxname';\n\n    /**\n     * Executes parent method parent::render() and returns name of template\n     * file \"user_list\".\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        return \"wrapping_list\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/Admin/WrappingMain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse stdClass;\n\n/**\n * Admin wrapping main manager.\n * Performs collection and updatind (on user submit) main item information.\n * Admin Menu: System Administration -> Wrapping -> Main.\n */\nclass WrappingMain extends \\OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        $soxId = $this->_aViewData[\"oxid\"] = $this->getEditObjectId();\n        if (isset($soxId) && $soxId != \"-1\") {\n            // load object\n            $oWrapping = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Wrapping::class);\n            $oWrapping->loadInLang($this->_iEditLang, $soxId);\n\n            $oOtherLang = $oWrapping->getAvailableInLangs();\n            if (!isset($oOtherLang[$this->_iEditLang])) {\n                $oWrapping->loadInLang(key($oOtherLang), $soxId);\n            }\n            $this->_aViewData[\"edit\"] = $oWrapping;\n\n            //Disable editing for derived articles\n            if ($oWrapping->isDerived()) {\n                $this->_aViewData['readonly'] = true;\n            }\n\n            // remove already created languages\n            $aLang = array_diff(Registry::getLang()->getLanguageNames(), $oOtherLang);\n            if (count($aLang)) {\n                $this->_aViewData[\"posslang\"] = $aLang;\n            }\n\n            foreach ($oOtherLang as $id => $language) {\n                $oLang = new stdClass();\n                $oLang->sLangDesc = $language;\n                $oLang->selected = ($id == $this->_iEditLang);\n                $this->_aViewData[\"otherlang\"][$id] = clone $oLang;\n            }\n        }\n\n        if ($this->getViewConfig()->isAltImageServerConfigured()) {\n            $this->_aViewData[\"imageUrl\"] = ContainerFacade::getParameter('oxid_esales.alternative_image_url');\n        }\n\n        return \"wrapping_main\";\n    }\n\n    /**\n     * Saves main wrapping parameters.\n     *\n     * @return null\n     */\n    public function save()\n    {\n        parent::save();\n\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        if (!$this->validateRequestImages()) {\n            Registry::getUtilsView()->addErrorToDisplay('ERROR_MESSAGE_WRONG_IMAGE_FILE_TYPE');\n            return;\n        }\n\n        // checkbox handling\n        if (!isset($aParams['oxwrapping__oxactive'])) {\n            $aParams['oxwrapping__oxactive'] = 0;\n        }\n\n        $oWrapping = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Wrapping::class);\n\n        if ($soxId != \"-1\") {\n            $oWrapping->loadInLang($this->_iEditLang, $soxId);\n            // #1173M - not all pic are deleted, after article is removed\n            Registry::getUtilsPic()->overwritePic($oWrapping, 'oxwrapping', 'oxpic', 'WP', '0', $aParams, Registry::getConfig()->getPictureDir(false));\n        } else {\n            $aParams['oxwrapping__oxid'] = null;\n            //$aParams = $oWrapping->ConvertNameArray2Idx( $aParams);\n        }\n\n        //Disable editing for derived articles\n        if ($oWrapping->isDerived()) {\n            return;\n        }\n\n        $oWrapping->setLanguage(0);\n        $oWrapping->assign($aParams);\n        $oWrapping->setLanguage($this->_iEditLang);\n\n        $oWrapping = Registry::getUtilsFile()->processFiles($oWrapping);\n        $oWrapping->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oWrapping->getId());\n    }\n\n    /**\n     * Saves main wrapping parameters.\n     *\n     * @return null\n     */\n    public function saveinnlang()\n    {\n        $soxId = $this->getEditObjectId();\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n\n        // checkbox handling\n        if (!isset($aParams['oxwrapping__oxactive'])) {\n            $aParams['oxwrapping__oxactive'] = 0;\n        }\n\n        $oWrapping = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Wrapping::class);\n\n        if ($soxId != \"-1\") {\n            $oWrapping->load($soxId);\n        } else {\n            $aParams['oxwrapping__oxid'] = null;\n            //$aParams = $oWrapping->ConvertNameArray2Idx( $aParams);\n        }\n\n        //Disable editing for derived articles\n        if ($oWrapping->isDerived()) {\n            return;\n        }\n\n        $oWrapping->setLanguage(0);\n        $oWrapping->assign($aParams);\n        $oWrapping->setLanguage($this->_iEditLang);\n\n        $oWrapping = Registry::getUtilsFile()->processFiles($oWrapping);\n        $oWrapping->save();\n\n        // set oxid if inserted\n        $this->setEditObjectId($oWrapping->getId());\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/ArticleDetailsController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Category;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Email\\EmailValidatorServiceBridgeInterface;\n\n/**\n * Article details information page.\n * Collects detailed article information, possible variants, such information\n * as crosselling, similarlist, picture gallery list, etc.\n * OXID eShop -> (Any chosen product).\n */\nclass ArticleDetailsController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Current class default template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/details/details';\n\n    /**\n     * Current product parent article object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected $_oParentProd = null;\n\n    /**\n     * Parent article name\n     *\n     * @var string\n     */\n    protected $_sParentName = null;\n\n    /**\n     * Parent article url\n     *\n     * @var string\n     */\n    protected $_sParentUrl = null;\n\n    /**\n     * Picture gallery\n     *\n     * @var array\n     */\n    protected $_aPicGallery = null;\n\n    /**\n     * Select lists\n     *\n     * @var array\n     */\n    protected $_aSelectLists = null;\n\n    /**\n     * Reviews of current article\n     *\n     * @var array\n     */\n    protected $_aReviews = null;\n\n    /**\n     * CrossSelling article list\n     *\n     * @var object\n     */\n    protected $_oCrossSelling = null;\n\n    /**\n     * Similar products article list\n     *\n     * @var object\n     */\n    protected $_oSimilarProducts = null;\n\n    /**\n     * Accessories of current article\n     *\n     * @var object\n     */\n    protected $_oAccessoires = null;\n\n    /**\n     * List of customer also bought these products\n     *\n     * @var object\n     */\n    protected $_aAlsoBoughtArts = null;\n\n    /**\n     * Search title\n     *\n     * @var string\n     */\n    protected $_sSearchTitle = null;\n\n    /**\n     * Marker if active product was fully initialized before returning it\n     * (see details::getProduct())\n     *\n     * @var bool\n     */\n    protected $_blIsInitialized = false;\n\n    /**\n     * Current view link type\n     *\n     * @var int\n     */\n    protected $_iLinkType = null;\n\n    /**\n     * Bid price.\n     *\n     * @var string\n     */\n    protected $_sBidPrice = null;\n\n    /**\n     * Price alarm status.\n     *\n     * @var integer\n     */\n    protected $_iPriceAlarmStatus = null;\n\n    /**\n     * Search parameter for Html\n     *\n     * @var string\n     */\n    protected $_sSearchParamForHtml = null;\n\n    /**\n     * Array of id to form recommendation list.\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @var array\n     */\n    protected $_aSimilarRecommListIds = null;\n\n\n    /**\n     * Marked which defines if current view is sortable or not\n     *\n     * @var bool\n     */\n    protected $_blShowSorting = true;\n\n    /**\n     * Returns current product parent article object if it is available\n     *\n     * @param string $parentId parent product id\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected function getParentProduct($parentId)\n    {\n        if ($parentId && $this->_oParentProd === null) {\n            $this->_oParentProd = false;\n            $article = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            if (($article->load($parentId))) {\n                $this->processProduct($article);\n                $this->_oParentProd = $article;\n            }\n        }\n\n        return $this->_oParentProd;\n    }\n\n    /**\n     * In case list type is \"search\" returns search parameters which will be added to product details link\n     *\n     * @return string|null\n     */\n    protected function getAddDynUrlParams()\n    {\n        if ($this->getListType() == \"search\") {\n            return $this->getDynUrlParams();\n        }\n    }\n\n    /**\n     * Returns array of params => values which are used in hidden forms and as additional url params.\n     * NOTICE: this method SHOULD return raw (non encoded into entities) parameters, because values\n     * are processed by htmlentities() to avoid security and broken templates problems\n     * This exact fix is added for article details to parse variant selection properly for widgets.\n     *\n     * @return array\n     */\n    public function getNavigationParams()\n    {\n        $parameters = parent::getNavigationParams();\n\n        $variantSelectionListId = Registry::getRequest()->getRequestEscapedParameter('varselid');\n        $selectListParameters = Registry::getRequest()->getRequestEscapedParameter('sel');\n        if (!$variantSelectionListId && !$selectListParameters) {\n            return $parameters;\n        }\n\n        if (is_array($variantSelectionListId)) {\n            foreach ($variantSelectionListId as $key => $value) {\n                $parameters[\"varselid[$key]\"] = $value;\n            }\n        }\n\n        if (is_array($selectListParameters)) {\n            foreach ($selectListParameters as $key => $value) {\n                $parameters[\"sel[$key]\"] = $value;\n            }\n        }\n\n        return $parameters;\n    }\n\n\n    /**\n     * Processes product by setting link type and in case list type is search adds search parameters to details link\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $article Product to process\n     */\n    protected function processProduct($article)\n    {\n        $article->setLinkType($this->getLinkType());\n        if ($dynamicParameters = $this->getAddDynUrlParams()) {\n            $article->appendLink($dynamicParameters);\n        }\n    }\n\n    /**\n     * Generates current view id.\n     *\n     * @return string\n     */\n    protected function generateViewId()\n    {\n        return parent::generateViewId() . '|' . Registry::getRequest()->getRequestEscapedParameter('anid') . '|';\n    }\n\n    /**\n     * If possible loads additional article info (\\OxidEsales\\Eshop\\Application\\Model\\Article::getCrossSelling(),\n     * \\OxidEsales\\Eshop\\Application\\Model\\Article::getAccessoires(), \\OxidEsales\\Eshop\\Application\\Model\\Article::getReviews(), \\OxidEsales\\Eshop\\Application\\Model\\Article::GetSimilarProducts(),\n     * \\OxidEsales\\Eshop\\Application\\Model\\Article::GetCustomerAlsoBoughtThisProducts()), forms variants details\n     * navigation URLs\n     * loads select lists (\\OxidEsales\\Eshop\\Application\\Model\\Article::GetSelectLists()), prepares HTML meta data\n     * (details::_convertForMetaTags()). Returns name of template file\n     * details::_sThisTemplate\n     *\n     * @return  string  $this->_sThisTemplate   current template file name\n     */\n    public function render()\n    {\n        $article = $this->getProduct();\n\n        if ($article->oxarticles__oxtemplate->value) {\n            $this->_sThisTemplate = $article->oxarticles__oxtemplate->value;\n        }\n\n        if ($templateName = Registry::getRequest()->getRequestEscapedParameter('tpl')) {\n            $this->_sThisTemplate = 'custom/' . basename($templateName);\n        }\n\n        parent::render();\n\n        $renderPartial = Registry::getRequest()->getRequestEscapedParameter('renderPartial');\n        $this->addTplParam('renderPartial', $renderPartial);\n\n        switch ($renderPartial) {\n            case \"productInfo\":\n                return 'page/details/ajax/fullproductinfo';\n                break;\n            case \"detailsMain\":\n                return 'page/details/ajax/productmain';\n                break;\n            default:\n                // can not be removed, as it is used for breadcrumb loading\n                $locator = oxNew('oxLocator', $this->getListType());\n                $locator->setLocatorData($article, $this);\n\n                return $this->_sThisTemplate;\n        }\n    }\n\n    /**\n     * Returns current view meta data\n     * If $meta parameter comes empty, sets to it article title and description.\n     * It happens if current view has no meta data defined in oxcontent table\n     *\n     * @param string $meta           User defined description, description content or empty value\n     * @param int    $length         Max length of result, -1 for no truncation\n     * @param bool   $descriptionTag If true - performs additional duplicate cleaning\n     *\n     * @return string\n     */\n    protected function prepareMetaDescription($meta, $length = 200, $descriptionTag = false)\n    {\n        if (!$meta) {\n            $article = $this->getProduct();\n\n            $meta = $article->getLongDescription()->value;\n            if ($meta == '') {\n                $meta = $article->oxarticles__oxshortdesc->value;\n            }\n            $meta = $article->oxarticles__oxtitle->value . ' - ' . $meta;\n        }\n\n        return parent::prepareMetaDescription($meta, $length, $descriptionTag);\n    }\n\n    /**\n     * Returns current view keywords seperated by comma\n     * If $keywords parameter comes empty, sets to it article title and description.\n     * It happens if current view has no meta data defined in oxcontent table\n     *\n     * @param string $keywords              User defined keywords, keywords content or empty value\n     * @param bool   $removeDuplicatedWords Remove duplicated words\n     *\n     * @return string\n     */\n    protected function prepareMetaKeyword($keywords, $removeDuplicatedWords = true)\n    {\n        if (!$keywords) {\n            $article = $this->getProduct();\n            $keywords = trim($this->getTitle());\n\n            if ($categoryTree = $this->getCategoryTree()) {\n                foreach ($categoryTree->getPath() as $category) {\n                    $keywords .= \", \" . trim($category->oxcategories__oxtitle->value);\n                }\n            }\n\n            // Adding search keys info\n            if ($searchKeys = trim($article->oxarticles__oxsearchkeys->value)) {\n                $keywords .= \", \" . $searchKeys;\n            }\n\n            $keywords = parent::prepareMetaKeyword($keywords, $removeDuplicatedWords);\n        }\n\n        return $keywords;\n    }\n\n    /**\n     * Saves user ratings and review text (oxReview object)\n     *\n     * @return null\n     */\n    public function saveReview()\n    {\n        if (!Registry::getSession()->checkSessionChallenge()) {\n            return;\n        }\n\n        if (\n            $this->canAcceptFormData() &&\n            ($user = $this->getUser()) && ($article = $this->getProduct())\n        ) {\n            $articleRating = Registry::getRequest()->getRequestEscapedParameter('artrating');\n            if ($articleRating !== null) {\n                $articleRating = (int) $articleRating;\n            }\n\n            //save rating\n            if ($articleRating !== null && $articleRating >= 1 && $articleRating <= 5) {\n                $rating = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Rating::class);\n                if ($rating->allowRating($user->getId(), 'oxarticle', $article->getId())) {\n                    $rating->oxratings__oxuserid = new Field($user->getId());\n                    $rating->oxratings__oxtype = new Field('oxarticle');\n                    $rating->oxratings__oxobjectid = new Field($article->getId());\n                    $rating->oxratings__oxrating = new Field($articleRating);\n                    $rating->save();\n                    $article->addToRatingAverage($articleRating);\n                }\n            }\n\n            if (($reviewText = trim((string) Registry::getRequest()->getRequestParameter('rvw_txt')))) {\n                $review = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Review::class);\n                $review->oxreviews__oxobjectid = new Field($article->getId());\n                $review->oxreviews__oxtype = new Field('oxarticle');\n                $review->oxreviews__oxtext = new Field($reviewText, Field::T_RAW);\n                $review->oxreviews__oxlang = new Field(Registry::getLang()->getBaseLanguage());\n                $review->oxreviews__oxuserid = new Field($user->getId());\n                $review->oxreviews__oxrating = new Field(($articleRating !== null) ? $articleRating : 0);\n                $review->save();\n            }\n        }\n    }\n\n    /**\n     * Adds article to selected recommendation list\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @return null\n     */\n    public function addToRecomm()\n    {\n        if (!Registry::getSession()->checkSessionChallenge()) {\n            return;\n        }\n\n        if (!$this->getViewConfig()->getShowListmania()) {\n            return;\n        }\n\n        $recommendationText = trim((string) Registry::getRequest()->getRequestEscapedParameter('recomm_txt'));\n        $recommendationListId = Registry::getRequest()->getRequestEscapedParameter('recomm');\n        $articleId = $this->getProduct()->getId();\n\n        if ($articleId) {\n            $recommendationList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\RecommendationList::class);\n            $recommendationList->load($recommendationListId);\n            $recommendationList->addArticle($articleId, $recommendationText);\n        }\n    }\n\n    /**\n     * Returns active product id to load its seo meta info\n     *\n     * @return string\n     */\n    protected function getSeoObjectId()\n    {\n        if ($article = $this->getProduct()) {\n            return $article->getId();\n        }\n    }\n\n    /**\n     * Returns current product\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    public function getProduct()\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        if ($this->_oProduct === null) {\n            //this option is only for lists and we must reset value\n            //as blLoadVariants = false affect \"ab price\" functionality\n            $config->setConfigParam('blLoadVariants', true);\n\n            $articleId = Registry::getRequest()->getRequestEscapedParameter('anid');\n\n            // object is not yet loaded\n            $this->_oProduct = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n\n            if (!$this->_oProduct->load($articleId)) {\n                unset($_GET, $_POST);\n                $config->dropLastActiveView();\n                error_404_handler($_SERVER['REQUEST_URI']);\n            }\n\n            $variantSelectionId = Registry::getRequest()->getRequestEscapedParameter(\"varselid\");\n            $variantSelections = $this->_oProduct->getVariantSelections($variantSelectionId);\n            if ($variantSelections && $variantSelections['oActiveVariant'] && $variantSelections['blPerfectFit']) {\n                $this->_oProduct = $variantSelections['oActiveVariant'];\n            }\n        }\n\n        // additional checks\n        if (!$this->_blIsInitialized) {\n            $this->additionalChecksForArticle();\n        }\n\n        return $this->_oProduct;\n    }\n\n    /**\n     * Runs additional checks for article.\n     */\n    protected function additionalChecksForArticle()\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $utils = \\OxidEsales\\Eshop\\Core\\Registry::getUtils();\n\n        $shouldContinue = true;\n        if (!$this->_oProduct->isVisible()) {\n            $shouldContinue = false;\n        } elseif ($this->_oProduct->oxarticles__oxparentid->value) {\n            $parentArticle = $this->getParentProduct($this->_oProduct->oxarticles__oxparentid->value);\n            if (!$parentArticle || !$parentArticle->isVisible()) {\n                $shouldContinue = false;\n            }\n        }\n\n        if (!$shouldContinue) {\n            $utils->redirect($config->getShopHomeUrl());\n            $utils->showMessageAndExit('');\n        }\n\n        $this->processProduct($this->_oProduct);\n        $this->_blIsInitialized = true;\n    }\n\n    /**\n     * Returns current view link type\n     *\n     * @return int\n     */\n    public function getLinkType()\n    {\n        if ($this->_iLinkType === null) {\n            $listType = Registry::getRequest()->getRequestEscapedParameter('listtype');\n            if ('vendor' == $listType) {\n                $this->_iLinkType = OXARTICLE_LINKTYPE_VENDOR;\n            } elseif ('manufacturer' == $listType) {\n                $this->_iLinkType = OXARTICLE_LINKTYPE_MANUFACTURER;\n                // @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n            } elseif ('recommlist' == $listType) {\n                $this->_iLinkType = OXARTICLE_LINKTYPE_RECOMM;\n                // END deprecated\n            } else {\n                $this->_iLinkType = OXARTICLE_LINKTYPE_CATEGORY;\n\n                // price category has own type..\n                $activeCategory = $this->getActiveCategory();\n                if ($activeCategory && $activeCategory->isPriceCategory()) {\n                    $this->_iLinkType = OXARTICLE_LINKTYPE_PRICECATEGORY;\n                }\n            }\n        }\n\n        return $this->_iLinkType;\n    }\n\n    /**\n     * Template variable getter. Returns if draw parent url\n     *\n     * @return bool\n     */\n    public function drawParentUrl()\n    {\n        return $this->getProduct()->isVariant();\n    }\n\n    /**\n     * Template variable getter. Returns picture gallery of current article\n     *\n     * @return array\n     */\n    public function getPictureGallery()\n    {\n        if ($this->_aPicGallery === null) {\n            //get picture gallery\n            $this->_aPicGallery = $this->getPicturesProduct()->getPictureGallery();\n        }\n\n        return $this->_aPicGallery;\n    }\n\n\n    /**\n     * Template variable getter. Returns active picture\n     *\n     * @return object\n     */\n    public function getActPicture()\n    {\n        return $this->getPictureGallery()['activeMedia']?->getDetailUrl();\n    }\n\n\n    /**\n     * Template variable getter. Returns selectLists of current article\n     *\n     * @return array\n     */\n    public function getSelectLists()\n    {\n        if ($this->_aSelectLists === null) {\n            $this->_aSelectLists = false;\n            if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('bl_perfLoadSelectLists')) {\n                $this->_aSelectLists = $this->getProduct()->getSelectLists();\n            }\n        }\n\n        return $this->_aSelectLists;\n    }\n\n    /**\n     * Template variable getter. Returns reviews of current article\n     *\n     * @return array\n     */\n    public function getReviews()\n    {\n        if ($this->_aReviews === null) {\n            $this->_aReviews = false;\n            if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('bl_perfLoadReviews')) {\n                $this->_aReviews = $this->getProduct()->getReviews();\n            }\n        }\n\n        return $this->_aReviews;\n    }\n\n    /**\n     * Template variable getter. Returns cross selling\n     *\n     * @return object\n     */\n    public function getCrossSelling()\n    {\n        if ($this->_oCrossSelling === null) {\n            $this->_oCrossSelling = false;\n            if ($article = $this->getProduct()) {\n                $this->_oCrossSelling = $article->getCrossSelling();\n            }\n        }\n\n        return $this->_oCrossSelling;\n    }\n\n    /**\n     * Template variable getter. Returns similar article list\n     *\n     * @return object\n     */\n    public function getSimilarProducts()\n    {\n        if ($this->_oSimilarProducts === null) {\n            $this->_oSimilarProducts = false;\n            if ($article = $this->getProduct()) {\n                $this->_oSimilarProducts = $article->getSimilarProducts();\n            }\n        }\n\n        return $this->_oSimilarProducts;\n    }\n\n    /**\n     * Return array of id to form recommend list.\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @return array\n     */\n    public function getSimilarRecommListIds()\n    {\n        if ($this->_aSimilarRecommListIds === null) {\n            $this->_aSimilarRecommListIds = false;\n\n            if ($article = $this->getProduct()) {\n                $this->_aSimilarRecommListIds = [$article->getId()];\n            }\n        }\n\n        return $this->_aSimilarRecommListIds;\n    }\n\n    /**\n     * Template variable getter. Returns accessories of article\n     *\n     * @return object\n     */\n    public function getAccessoires()\n    {\n        if ($this->_oAccessoires === null) {\n            $this->_oAccessoires = false;\n            if ($article = $this->getProduct()) {\n                $this->_oAccessoires = $article->getAccessoires();\n            }\n        }\n\n        return $this->_oAccessoires;\n    }\n\n    /**\n     * Template variable getter. Returns list of customer also bought these products\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\ArticleList|false\n     */\n    public function getAlsoBoughtTheseProducts()\n    {\n        if ($this->_aAlsoBoughtArts === null) {\n            $this->_aAlsoBoughtArts = false;\n            if ($article = $this->getProduct()) {\n                $this->_aAlsoBoughtArts = $article->getCustomerAlsoBoughtThisProducts();\n            }\n        }\n\n        return $this->_aAlsoBoughtArts;\n    }\n\n    /**\n     * Template variable getter. Returns if price alarm is enabled\n     *\n     * @return bool\n     */\n    public function isPriceAlarm()\n    {\n        return $this->getProduct()->isPriceAlarm();\n    }\n\n    /**\n     * returns object, associated with current view.\n     * (the object that is shown in frontend)\n     *\n     * @param int $languageId language id\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected function getSubject($languageId)\n    {\n        return $this->getProduct();\n    }\n\n    /**\n     * Returns search title. It will be set in oxLocator\n     *\n     * @return string\n     */\n    public function getSearchTitle()\n    {\n        return $this->_sSearchTitle;\n    }\n\n    /**\n     * Returns search title setter\n     *\n     * @param string $title search title\n     */\n    public function setSearchTitle($title)\n    {\n        $this->_sSearchTitle = $title;\n    }\n\n    /**\n     * Active category path setter.\n     *\n     * @param string $activeCategoryPath Category tree path\n     */\n    public function setCatTreePath($activeCategoryPath)\n    {\n        $this->_sCatTreePath = $activeCategoryPath;\n    }\n\n    /**\n     * If product details are accessed by vendor url\n     * view must not be indexable\n     *\n     * @return int\n     */\n    public function noIndex()\n    {\n        $listType = Registry::getRequest()->getRequestEscapedParameter('listtype');\n        if ($listType && ('vendor' == $listType || 'manufacturer' == $listType)) {\n            return $this->_iViewIndexState = VIEW_INDEXSTATE_NOINDEXFOLLOW;\n        }\n\n        return parent::noIndex();\n    }\n\n    /**\n     * Returns current view title. Default is null\n     *\n     * @return null\n     */\n    public function getTitle()\n    {\n        if ($article = $this->getProduct()) {\n            $articleTitle = $article->oxarticles__oxtitle->value;\n            $variantSelectionId = $article->oxarticles__oxvarselect->value;\n\n            $variantSelectionValue = $variantSelectionId ? ' ' . $variantSelectionId : '';\n\n            return $articleTitle . $variantSelectionValue;\n        }\n    }\n\n    /**\n     * Returns view canonical url\n     *\n     * @return string\n     */\n    public function getCanonicalUrl()\n    {\n        if (($article = $this->getProduct())) {\n            if ($article->oxarticles__oxparentid->value) {\n                $article = $this->getParentProduct($article->oxarticles__oxparentid->value);\n            }\n\n            $utilsUrl = Registry::getUtilsUrl();\n            if (Registry::getUtils()->seoIsActive()) {\n                $url = $utilsUrl->prepareCanonicalUrl($article->getBaseSeoLink($article->getLanguage(), true));\n            } else {\n                $url = $utilsUrl->prepareCanonicalUrl($article->getBaseStdLink($article->getLanguage()));\n            }\n\n            return $url;\n        }\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        if ('search' == $this->getListType()) {\n            $paths = $this->getSearchBreadCrumb();\n            // @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n        } elseif ('recommlist' == $this->getListType()) {\n            $paths = $this->_getRecommendationListBredCrumb();\n            // END deprecated\n        } elseif ('vendor' == $this->getListType()) {\n            $paths = $this->getVendorBreadCrumb();\n        } else {\n            $paths = $this->getCategoryBreadCrumb();\n        }\n\n        return $paths;\n    }\n\n    /**\n     * Validates email address.\n     * If email address is OK - creates price alarm object and saves it (oxPriceAlarm::save()).\n     * If email is wrong - returns false.\n     * Sends price alarm notification mail to shop owner.\n     *\n     * @return null\n     */\n    public function addMe()\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $utils = \\OxidEsales\\Eshop\\Core\\Registry::getUtils();\n\n        $parameters = Registry::getRequest()->getRequestEscapedParameter('pa');\n        $emailValidator = ContainerFacade::get(EmailValidatorServiceBridgeInterface::class);\n        if (!isset($parameters['email']) || !$emailValidator->isEmailValid($parameters['email'])) {\n            $this->_iPriceAlarmStatus = 0;\n            return;\n        }\n\n        $parameters['aid'] = $this->getProduct()->getId();\n        $activeCurrency = $config->getActShopCurrencyObject();\n        // convert currency to default\n        $price = $utils->currency2Float($parameters['price']);\n\n        $priceAlarm = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\PriceAlarm::class);\n        $priceAlarm->oxpricealarm__oxuserid = new Field(Registry::getSession()->getVariable('usr'));\n        $priceAlarm->oxpricealarm__oxemail = new Field($parameters['email']);\n        $priceAlarm->oxpricealarm__oxartid = new Field($parameters['aid']);\n        $priceAlarm->oxpricealarm__oxprice = new Field($utils->fRound($price, $activeCurrency));\n        $priceAlarm->oxpricealarm__oxshopid = new Field($config->getShopId());\n        $priceAlarm->oxpricealarm__oxcurrency = new Field($activeCurrency->name);\n\n        $priceAlarm->oxpricealarm__oxlang = new Field(Registry::getLang()->getBaseLanguage());\n\n        $priceAlarm->save();\n\n        // Send Email\n        $email = oxNew(\\OxidEsales\\Eshop\\Core\\Email::class);\n        $this->_iPriceAlarmStatus = (int) $email->sendPricealarmNotification($parameters, $priceAlarm);\n    }\n\n    /**\n     * Return price alarm status (if it was send)\n     *\n     * @return integer\n     */\n    public function getPriceAlarmStatus()\n    {\n        return $this->_iPriceAlarmStatus;\n    }\n\n    /**\n     * Template variable getter. Returns bid price\n     *\n     * @return string\n     */\n    public function getBidPrice()\n    {\n        if ($this->_sBidPrice === null) {\n            $this->_sBidPrice = false;\n\n            $parameters = Registry::getRequest()->getRequestEscapedParameter('pa');\n            $activeCurrency = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActShopCurrencyObject();\n            $price = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->currency2Float($parameters['price']);\n            $this->_sBidPrice = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->formatCurrency($price, $activeCurrency);\n        }\n\n        return $this->_sBidPrice;\n    }\n\n    /**\n     * Returns variant selection\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\VariantSelectList\n     */\n    public function getVariantSelections()\n    {\n        $article = $this->getProduct();\n        $variantSelectionListId = Registry::getRequest()->getRequestEscapedParameter(\"varselid\");\n        if (($articleParent = $this->getParentProduct($article->oxarticles__oxparentid->value))) {\n            return $articleParent->getVariantSelections($variantSelectionListId, $article->getId());\n        }\n\n        return $article->getVariantSelections($variantSelectionListId);\n    }\n\n    /**\n     * Returns pictures product object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    public function getPicturesProduct()\n    {\n        $variantSelections = $this->getVariantSelections();\n        if ($variantSelections && $variantSelections['oActiveVariant'] && !$variantSelections['blPerfectFit']) {\n            return $variantSelections['oActiveVariant'];\n        }\n\n        return $this->getProduct();\n    }\n\n    /**\n     * Template variable getter. Returns search parameter for Html\n     *\n     * @return string\n     */\n    public function getSearchParamForHtml()\n    {\n        if ($this->_sSearchParamForHtml === null) {\n            $this->_sSearchParamForHtml = Registry::getRequest()->getRequestEscapedParameter('searchparam');\n        }\n\n        return $this->_sSearchParamForHtml;\n    }\n\n    /**\n     * Returns if page has rdfa\n     *\n     * @return bool\n     */\n    public function showRdfa()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blRDFaEmbedding');\n    }\n\n    /**\n     * Sets normalized rating\n     *\n     * @return array\n     */\n    public function getRDFaNormalizedRating()\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $minRating = $config->getConfigParam(\"iRDFaMinRating\");\n        $maxRating = $config->getConfigParam(\"iRDFaMaxRating\");\n\n        $article = $this->getProduct();\n        $count = $article->oxarticles__oxratingcnt->value;\n        if (isset($minRating) && isset($maxRating) && $maxRating != '' && $minRating != '' && $count > 0) {\n            $normalizedRating = [];\n            $value = ((4 * ($article->oxarticles__oxrating->value - $minRating) / ($maxRating - $minRating))) + 1;\n            $normalizedRating[\"count\"] = $count;\n            $normalizedRating[\"value\"] = round($value, 2);\n\n            return $normalizedRating;\n        }\n\n        return false;\n    }\n\n    /**\n     * Sets and returns validity period of given object\n     *\n     * @param string $configVariableName object name\n     *\n     * @return array\n     */\n    public function getRDFaValidityPeriod($configVariableName)\n    {\n        if ($configVariableName) {\n            $validity = [];\n            $days = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam($configVariableName);\n            $from = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime();\n\n            $through = $from + ($days * 24 * 60 * 60);\n            $validity[\"from\"] = date('Y-m-d\\TH:i:s', $from) . \"Z\";\n            $validity[\"through\"] = date('Y-m-d\\TH:i:s', $through) . \"Z\";\n\n            return $validity;\n        }\n\n        return false;\n    }\n\n    /**\n     * Gets business function of the gr:Offering\n     *\n     * @return string\n     */\n    public function getRDFaBusinessFnc()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam(\"sRDFaBusinessFnc\");\n    }\n\n    /**\n     * Gets the types of customers for which the given gr:Offering is valid\n     *\n     * @return array\n     */\n    public function getRDFaCustomers()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam(\"aRDFaCustomers\");\n    }\n\n    /**\n     * Gets information whether prices include vat\n     *\n     * @return int\n     */\n    public function getRDFaVAT()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam(\"iRDFaVAT\");\n    }\n\n    /**\n     * Gets a generic description of product condition\n     *\n     * @return string\n     */\n    public function getRDFaGenericCondition()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam(\"iRDFaCondition\");\n    }\n\n    /**\n     * Returns bundle product\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article|false\n     */\n    public function getBundleArticle()\n    {\n        $article = $this->getProduct();\n        if ($article && $article->oxarticles__oxbundleid->value) {\n            $bundle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            $bundle->load($article->oxarticles__oxbundleid->value);\n\n            return $bundle;\n        }\n\n        return false;\n    }\n\n    /**\n     * Gets accepted payment methods\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\PaymentList\n     */\n    public function getRDFaPaymentMethods()\n    {\n        $price = $this->getProduct()->getPrice()->getBruttoPrice();\n        $paymentList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\PaymentList::class);\n        $paymentList->loadRDFaPaymentList($price);\n\n        return $paymentList;\n    }\n\n    /**\n     * Returns delivery methods with assigned delivery sets.\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\DeliverySetList\n     */\n    public function getRDFaDeliverySetMethods()\n    {\n        $deliverySetList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\DeliverySetList::class);\n        $deliverySetList->loadRDFaDeliverySetList();\n\n        return $deliverySetList;\n    }\n\n    /**\n     * Template variable getter. Returns delivery list for current product\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\DeliveryList\n     */\n    public function getProductsDeliveryList()\n    {\n        $article = $this->getProduct();\n        $deliveryList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\DeliveryList::class);\n        $deliveryList->loadDeliveryListForProduct($article);\n\n        return $deliveryList;\n    }\n\n    /**\n     * Gets content id of delivery information page\n     *\n     * @return string\n     */\n    public function getRDFaDeliveryChargeSpecLoc()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam(\"sRDFaDeliveryChargeSpecLoc\");\n    }\n\n    /**\n     * Gets content id of payments\n     *\n     * @return string\n     */\n    public function getRDFaPaymentChargeSpecLoc()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam(\"sRDFaPaymentChargeSpecLoc\");\n    }\n\n    /**\n     * Gets content id of company info page (About Us)\n     *\n     * @return string\n     */\n    public function getRDFaBusinessEntityLoc()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam(\"sRDFaBusinessEntityLoc\");\n    }\n\n    /**\n     * Returns if to show products left stock\n     *\n     * @return string\n     */\n    public function showRDFaProductStock()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam(\"blShowRDFaProductStock\");\n    }\n\n\n    /**\n     * Template variable getter. Returns if review module is on\n     *\n     * @return bool\n     */\n    public function isReviewActive()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('bl_perfLoadReviews');\n    }\n\n    /**\n     * Returns default category sorting for selected category\n     *\n     * @return array\n     */\n    public function getDefaultSorting()\n    {\n        $sorting = parent::getDefaultSorting();\n        $activeCategory = $this->getActiveCategory();\n\n        if ($this->getListType() != 'search' && $activeCategory && $activeCategory instanceof Category) {\n            if ($categorySorting = $activeCategory->getDefaultSorting()) {\n                $sortingDirection = ($activeCategory->getDefaultSortingMode()) ? \"desc\" : \"asc\";\n                $sorting = ['sortby' => $categorySorting, 'sortdir' => $sortingDirection];\n            }\n        }\n\n        return $sorting;\n    }\n\n    /**\n     * Returns sorting parameters separated by \"|\"\n     *\n     * @return string\n     */\n    public function getSortingParameters()\n    {\n        $sorting = $this->getSorting($this->getSortIdent());\n        if (!is_array($sorting)) {\n            return null;\n        }\n\n        return implode('|', $sorting);\n    }\n\n    /**\n     * Vendor bread crumb\n     *\n     * @return array\n     */\n    protected function getVendorBreadCrumb()\n    {\n        $paths = [];\n        $vendorPath = [];\n\n        $vendor = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Vendor::class);\n        $vendor->load('root');\n\n        $vendorPath['link'] = $vendor->getLink();\n        $vendorPath['title'] = $vendor->oxvendor__oxtitle->value;\n        $paths[] = $vendorPath;\n\n        $vendor = $this->getActVendor();\n        if ($vendor instanceof \\OxidEsales\\Eshop\\Application\\Model\\Vendor) {\n            $vendorPath['link'] = $vendor->getLink();\n            $vendorPath['title'] = $vendor->oxvendor__oxtitle->value;\n            $paths[] = $vendorPath;\n        }\n\n        return $paths;\n    }\n\n    /**\n     * Recommendation list bread crumb\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @return array\n     */\n    protected function _getRecommendationListBredCrumb() // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore\n    {\n        $paths = [];\n        $recommListPath = [];\n        $baseLanguageId = Registry::getLang()->getBaseLanguage();\n        $recommListPath['title'] = Registry::getLang()->translateString('LISTMANIA', $baseLanguageId, false);\n        $paths[] = $recommListPath;\n\n        return $paths;\n    }\n\n    /**\n     * Search bread crumb\n     *\n     * @return array\n     */\n    protected function getSearchBreadCrumb()\n    {\n        $paths = [];\n        $searchPath = [];\n\n        $baseLanguageId = Registry::getLang()->getBaseLanguage();\n        $translatedString = Registry::getLang()->translateString('SEARCH_RESULT', $baseLanguageId, false);\n        $selfLink = $this->getViewConfig()->getSelfLink();\n        $sessionToken = Registry::getSession()->getVariable('sess_stoken');\n\n        $searchPath['title'] = sprintf($translatedString, $this->getSearchParamForHtml());\n        $searchPath['link'] = $selfLink . 'stoken=' . $sessionToken . \"&amp;cl=search&amp;\" .\n                              \"searchparam=\" . $this->getSearchParamForHtml();\n\n        $paths[] = $searchPath;\n\n        return $paths;\n    }\n\n    /**\n     * Category bread crumb\n     *\n     * @return array\n     */\n    protected function getCategoryBreadCrumb()\n    {\n        $paths = [];\n\n        $categoryTree = $this->getCatTreePath();\n\n        if ($categoryTree) {\n            foreach ($categoryTree as $category) {\n                /** @var Category $category */\n                $categoryPath = [];\n\n                $categoryPath['link'] = $category->getLink();\n                $categoryPath['title'] = $category->oxcategories__oxtitle->value;\n\n                $paths[] = $categoryPath;\n            }\n        }\n\n        return $paths;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/ArticleListController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Category;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererInterface;\n\n/**\n * List of articles for a selected product group.\n * Collects list of articles, according to it generates links for list gallery,\n * meta tags (for search engines). Result - \"list\" template.\n * OXID eShop -> (Any selected shop product category).\n */\nclass ArticleListController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Count of all articles in list.\n     *\n     * @var integer\n     */\n    protected $_iAllArtCnt = 0;\n\n    /**\n     * Number of possible pages.\n     *\n     * @var integer\n     */\n    protected $_iCntPages = 0;\n\n    /**\n     * Current class default template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/list/list';\n\n    /**\n     * New layout list template\n     *\n     * @deprecated will be removed in v8.0\n     *\n     * @var string\n     */\n    protected $_sThisMoreTemplate = 'page/list/morecategories';\n\n    /**\n     * Category path string\n     *\n     * @var string\n     */\n    protected $_sCatPathString = null;\n\n    /**\n     * Marked which defines if current view is sortable or not\n     *\n     * @var bool\n     */\n    protected $_blShowSorting = true;\n\n    /**\n     * Category attributes.\n     *\n     * @var array\n     */\n    protected $_aAttributes = null;\n\n    /**\n     * Category article list\n     *\n     * @var array\n     */\n    protected $_aCatArtList = null;\n\n    /**\n     * If category has subcategories\n     *\n     * @var bool\n     */\n    protected $_blHasVisibleSubCats = null;\n\n    /**\n     * List of category's subcategories\n     *\n     * @var array\n     */\n    protected $_aSubCatList = null;\n\n    /**\n     * Page navigation\n     *\n     * @var object\n     */\n    protected $_oPageNavigation = null;\n\n    /**\n     * Active object is category.\n     *\n     * @var bool\n     */\n    protected $_blIsCat = null;\n\n    /**\n     * Recomendation list\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @var object\n     */\n    protected $_oRecommList = null;\n\n    /**\n     * Category title\n     *\n     * @var string\n     */\n    protected $_sCatTitle = null;\n\n    /**\n     * Sign if to load and show bargain action\n     *\n     * @var bool\n     */\n    protected $_blBargainAction = false;\n\n    /**\n     * Array of id to form recommendation list.\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @var array\n     */\n    protected $_aSimilarRecommListIds = null;\n\n    /**\n     * Generates (if not generated yet) and returns view ID (for\n     * template engine caching).\n     *\n     * @return string   $this->_sViewId view id\n     */\n    protected function generateViewId()\n    {\n        $categoryId = Registry::getRequest()->getRequestEscapedParameter('cnid');\n        $activePage = $this->getActPage();\n        $articlesPerPage = Registry::getSession()->getVariable('_artperpage');\n        $listDisplayType = $this->getArticleListDisplayType();\n        $parentViewId = parent::generateViewId();\n\n        return md5(\n            $parentViewId . '|' . $categoryId . '|' . $activePage . '|' . $articlesPerPage . '|' . $listDisplayType\n        );\n    }\n\n    /**\n     * Executes parent::render(), loads active category, prepares article\n     * list sorting rules. According to category type loads list of\n     * articles - regular (oxArticleList::LoadCategoryArticles()) or price\n     * dependent (oxArticleList::LoadPriceArticles()). Generates page navigation data\n     * such as previous/next window URL, number of available pages, generates\n     * meta tags info (\\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::_convertForMetaTags()) and returns\n     * name of template to render. Also checks if actual pages count does not exceed real\n     * articles page count. If yes - calls error_404_handler().\n     *\n     * @return  string  $this->_sThisTemplate   current template file name\n     */\n    public function render()\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $category = $this->getCategoryToRender();\n\n        $isCategoryActive = $category && (bool) $category->oxcategories__oxactive->value;\n        if (!$isCategoryActive) {\n            Registry::getUtils()->redirect($config->getShopURL() . 'index.php', true, 302);\n        }\n        //checking if actual pages count does not exceed real articles page count\n        $this->getArticleList();\n        if ($this->_blIsCat) {\n            $this->checkRequestedPage();\n        }\n\n        parent::render();\n\n        // processing list articles\n        $this->processListArticles();\n\n        return $this->getTemplateName();\n    }\n\n    /**\n     * Returns category, which should be rendered.\n     * In case of 'more categories' page is viewed, sets 'more categories' template,\n     * sets empty category as active category and returns it.\n     *\n     * @return Category\n     */\n    protected function getCategoryToRender()\n    {\n        $this->_blIsCat = false;\n\n        // A. checking for fake \"more\" category\n        // @deprecated oxmore feature will be removed in v8.0\n        if ('oxmore' == Registry::getRequest()->getRequestEscapedParameter('cnid')) {\n            // overriding some standard value and parameters\n            $this->_sThisTemplate = $this->_sThisMoreTemplate;\n            $category = oxNew(Category::class);\n            $category->oxcategories__oxactive = new Field(1, Field::T_RAW);\n            $this->setActiveCategory($category);\n        // END deprecated\n        } elseif (($category = $this->getActiveCategory())) {\n            $this->_blIsCat = true;\n            $this->_blBargainAction = true;\n        }\n\n        return $category;\n    }\n\n    /**\n     * Checks if requested page is valid and:\n     * - redirecting to first page in case requested page does not exist\n     * or\n     * - displays 404 error if category has no products\n     */\n    protected function checkRequestedPage()\n    {\n        $pageCount = $this->getPageCount();\n        $currentPageNumber = $this->getActPage();\n        // redirecting to first page in case requested page does not exist\n        if ($pageCount && (($pageCount - 1) < $currentPageNumber)) {\n            Registry::getUtils()->redirect($this->getActiveCategory()->getLink(), false);\n        }\n        if (!$pageCount && $currentPageNumber) {\n            // display error if category has no products, but page number is entered\n            $this->_iActPage = 0;\n            error_404_handler($this->getActiveCategory()->getLink());\n        }\n    }\n\n    /**\n     * Iterates through list articles and performs list view specific tasks:\n     *  - sets type of link which needs to be generated (Manufacturer link)\n     */\n    protected function processListArticles()\n    {\n        if ($articleList = $this->getArticleList()) {\n            $linkType = $this->getProductLinkType();\n            $dynamicParameters = $this->getAddUrlParams();\n            $seoParameters = $this->getAddSeoUrlParams();\n\n            foreach ($articleList as $article) {\n                /** @var \\OxidEsales\\Eshop\\Application\\Model\\Article $article */\n                $article->setLinkType($linkType);\n\n                if ($dynamicParameters) {\n                    $article->appendStdLink($dynamicParameters);\n                }\n\n                if ($seoParameters) {\n                    $article->appendLink($seoParameters);\n                }\n            }\n        }\n    }\n\n    /**\n     * Returns additional URL parameters which must be added to list products dynamic urls\n     *\n     * @return string\n     */\n    public function getAddUrlParams()\n    {\n        $dynamicParameters = parent::getAddUrlParams();\n        if (!Registry::getUtils()->seoIsActive()) {\n            $pageNumber = (int) Registry::getRequest()->getRequestEscapedParameter('pgNr');\n            if ($pageNumber > 0) {\n                $dynamicParameters .= ($dynamicParameters ? '&amp;' : '') . \"pgNr={$pageNumber}\";\n            }\n        }\n\n        return $dynamicParameters;\n    }\n\n    /**\n     * Returns additional URL parameters which must be added to list products seo urls\n     *\n     * @return string\n     */\n    public function getAddSeoUrlParams()\n    {\n        return '';\n    }\n\n    /**\n     * Returns product link type:\n     *  - OXARTICLE_LINKTYPE_PRICECATEGORY - when active category is price category\n     *  - OXARTICLE_LINKTYPE_CATEGORY - when active category is regular category\n     *\n     * @return int\n     */\n    protected function getProductLinkType()\n    {\n        $categoryType = OXARTICLE_LINKTYPE_CATEGORY;\n        if (($category = $this->getActiveCategory()) && $category->isPriceCategory()) {\n            $categoryType = OXARTICLE_LINKTYPE_PRICECATEGORY;\n        }\n\n        return $categoryType;\n    }\n\n    /**\n     * Stores chosen category filter into session.\n     *\n     * Session variables:\n     * <b>session_attrfilter</b>\n     */\n    public function executefilter()\n    {\n        $baseLanguageId = Registry::getLang()->getBaseLanguage();\n        // store this into session\n        $attributeFilter = Registry::getRequest()->getRequestParameter('attrfilter');\n        $activeCategory = Registry::getRequest()->getRequestEscapedParameter('cnid');\n\n        if (!empty($attributeFilter)) {\n            $sessionFilter = Registry::getSession()->getVariable('session_attrfilter');\n            //fix for #2904 - if language will be changed attributes of this category will be deleted from session\n            //and new filters for active language set.\n            $sessionFilter[$activeCategory] = null;\n            $sessionFilter[$activeCategory][$baseLanguageId] = $attributeFilter;\n            Registry::getSession()->setVariable('session_attrfilter', $sessionFilter);\n        }\n    }\n\n    /**\n     * Reset filter.\n     */\n    public function resetFilter()\n    {\n        $activeCategory = Registry::getRequest()->getRequestEscapedParameter('cnid');\n        $sessionFilter = Registry::getSession()->getVariable('session_attrfilter');\n\n        unset($sessionFilter[$activeCategory]);\n        Registry::getSession()->setVariable('session_attrfilter', $sessionFilter);\n    }\n\n    /**\n     * Loads and returns article list of active category.\n     *\n     * @param Category $category category object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\ArticleList\n     */\n    protected function loadArticles($category)\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $numberOfCategoryArticles = (int) $config->getConfigParam('iNrofCatArticles');\n        $numberOfCategoryArticles = $numberOfCategoryArticles ? $numberOfCategoryArticles : 1;\n\n        // load only articles which we show on screen\n        $articleList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n        $articleList->setSqlLimit($numberOfCategoryArticles * $this->getRequestPageNr(), $numberOfCategoryArticles);\n        $articleList->setCustomSorting($this->getSortingSql($this->getSortIdent()));\n\n        if ($category->isPriceCategory()) {\n            $priceFrom = $category->oxcategories__oxpricefrom->value;\n            $priceTo = $category->oxcategories__oxpriceto->value;\n\n            $this->_iAllArtCnt = $articleList->loadPriceArticles($priceFrom, $priceTo, $category);\n        } else {\n            $sessionFilter = Registry::getSession()->getVariable('session_attrfilter');\n\n            $activeCategoryId = $category->getId();\n            $this->_iAllArtCnt = $articleList->loadCategoryArticles($activeCategoryId, $sessionFilter);\n        }\n\n        $this->_iCntPages = ceil($this->_iAllArtCnt / $numberOfCategoryArticles);\n\n        return $articleList;\n    }\n\n    /**\n     * Get actual page number.\n     *\n     * @return int\n     */\n    public function getActPage()\n    {\n        //Fake oxmore category has no subpages so we can set the page number to zero\n        // @deprecated oxmore feature will be removed in v8.0\n        if ('oxmore' == Registry::getRequest()->getRequestEscapedParameter('cnid')) {\n            return 0;\n        }\n        // END deprecated\n\n        return $this->getRequestPageNr();\n    }\n\n    /**\n     * Calls parent::getActPage();\n     *\n     * @todo this function is a temporary solution and should be rmeoved as\n     * soon product list loading is refactored\n     *\n     * @return int\n     */\n    protected function getRequestPageNr()\n    {\n        return parent::getActPage();\n    }\n\n    /**\n     * Get list display type\n     *\n     * @return null|string\n     */\n    protected function getArticleListDisplayType()\n    {\n        $listDisplayType = Registry::getSession()->getVariable('ldtype');\n\n        if (is_null($listDisplayType)) {\n            $listDisplayType = Registry::getConfig()->getConfigParam('sDefaultListDisplayType');\n        }\n\n        return $listDisplayType;\n    }\n\n    /**\n     * Returns active product id to load its seo meta info\n     *\n     * @return string\n     */\n    protected function getSeoObjectId()\n    {\n        if (($category = $this->getActiveCategory())) {\n            return $category->getId();\n        }\n    }\n\n    /**\n     * Returns string built from category titles\n     *\n     * @return string\n     */\n    protected function getCatPathString()\n    {\n        if ($this->_sCatPathString === null) {\n            // marking as already set\n            $this->_sCatPathString = false;\n\n            //fetching category path\n            if (is_array($categoryTreePath = $this->getCatTreePath())) {\n                $stringModifier = Str::getStr();\n                $this->_sCatPathString = '';\n                foreach ($categoryTreePath as $category) {\n                    if ($this->_sCatPathString) {\n                        $this->_sCatPathString .= ', ';\n                    }\n                    $this->_sCatPathString .= $stringModifier->strtolower($category->oxcategories__oxtitle->value);\n                }\n            }\n        }\n\n        return $this->_sCatPathString;\n    }\n\n    /**\n     * Returns current view meta description data.\n     *\n     * @param string $meta           Category path.\n     * @param int    $length         Max length of result, -1 for no truncation.\n     * @param bool   $descriptionTag If true - performs additional duplicate cleaning.\n     *\n     * @return  string\n     */\n    protected function prepareMetaDescription($meta, $length = 1024, $descriptionTag = false)\n    {\n        $description = '';\n        // appending parent title\n        if ($activeCategory = $this->getActiveCategory()) {\n            if (($parentCategory = $activeCategory->getParentCategory())) {\n                $description .= \" {$parentCategory->oxcategories__oxtitle->value} -\";\n            }\n\n            // adding category title\n            $description .= \" {$activeCategory->oxcategories__oxtitle->value}.\";\n        }\n\n        // and final component ..\n        //changed for #2776\n        if (($suffix = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActiveShop()->oxshops__oxtitleprefix->value)) {\n            $description .= \" {$suffix}\";\n        }\n\n        // making safe for output\n        $str = Str::getStr();\n        $description = $str->html_entity_decode($description);\n        $description = $str->strip_tags($description);\n        $description = $str->cleanStr($description);\n        $description = $str->htmlspecialchars($description);\n\n        return trim($description);\n    }\n\n    /**\n     * Template variable getter. Returns meta description\n     *\n     * @return string\n     */\n    public function getMetaDescription()\n    {\n        $meta = parent::getMetaDescription();\n\n        if ($titlePageSuffix = $this->getTitlePageSuffix()) {\n            if ($meta) {\n                $meta .= \", \";\n            }\n            $meta .= $titlePageSuffix;\n        }\n\n        return $meta;\n    }\n\n    /**\n     * Meta tags - description and keywords - generator for search\n     * engines. Uses string passed by parameters, cleans HTML tags,\n     * string duplicates, special chars. Also removes strings defined\n     * in $config->aSkipTags (Admin area).\n     *\n     * @param string $meta           Category path\n     * @param int    $length         Max length of result, -1 for no truncation\n     * @param bool   $descriptionTag If true - performs additional duplicate cleaning\n     *\n     * @return  string\n     */\n    protected function collectMetaDescription($meta, $length = 1024, $descriptionTag = false)\n    {\n        //formatting description tag\n        $category = $this->getActiveCategory();\n\n        $additionalText = ($category instanceof Category) ? $this->collectCategoryMetaDescription($category) : '';\n\n        if (!$additionalText) {\n            $additionalText = $this->collectProductMetaDescription();\n        }\n        if (!$meta) {\n            $meta = trim($this->getCatPathString());\n        }\n        $meta = ($meta) ? \"{$meta} - {$additionalText}\" : $additionalText;\n        return parent::prepareMetaDescription($meta, $length, $descriptionTag);\n    }\n\n    private function collectCategoryMetaDescription(Category $category): string\n    {\n        if (isset($category->oxcategories__oxlongdesc) && $category->oxcategories__oxlongdesc instanceof Field) {\n            $activeLanguageId = Registry::getLang()->getTplLanguage();\n            $oxid = (string) $category->getId() . (string) $category->getLanguage();\n            return trim($this->getRenderer()->renderFragment(\n                $category->oxcategories__oxlongdesc->getRawValue(),\n                \"ox:{$oxid}{$activeLanguageId}\",\n                $this->getViewData()\n            ));\n        }\n        return '';\n    }\n\n    private function getRenderer(): TemplateRendererInterface\n    {\n        return ContainerFacade::get(TemplateRendererBridgeInterface::class)\n            ->getTemplateRenderer();\n    }\n\n    private function collectProductMetaDescription(): string\n    {\n        $articleList = $this->getArticleList();\n        if ($articleList && $articleList->count()) {\n            $articleTitles = [];\n            foreach ($articleList as $article) {\n                $articleTitles[] = $article->oxarticles__oxtitle->value;\n            }\n            return implode(', ', $articleTitles);\n        }\n        return '';\n    }\n\n    /**\n     * Returns current view keywords separated by comma\n     *\n     * @param string $keywords              Data to use as keywords\n     * @param bool   $removeDuplicatedWords Remove duplicated words\n     *\n     * @return string\n     */\n    protected function prepareMetaKeyword($keywords, $removeDuplicatedWords = true)\n    {\n        $keywords = '';\n        if (($activeCategory = $this->getActiveCategory())) {\n            $keywordsList = [];\n\n            if ($categoryTree = $this->getCategoryTree()) {\n                foreach ($categoryTree->getPath() as $category) {\n                    $keywordsList[] = trim($category->oxcategories__oxtitle->value);\n                }\n            }\n\n            $subCategories = $activeCategory->getSubCats();\n            if (is_array($subCategories)) {\n                foreach ($subCategories as $subCategory) {\n                    $keywordsList[] = $subCategory->oxcategories__oxtitle->value;\n                }\n            }\n\n            if (count($keywordsList) > 0) {\n                $keywords = implode(\", \", $keywordsList);\n            }\n        }\n\n        $keywords = parent::prepareMetaDescription($keywords, -1, $removeDuplicatedWords);\n\n        return trim($keywords);\n    }\n\n    /**\n     * Creates a string of keyword filtered by the function prepareMetaDescription and without any duplicates\n     * additional the admin defined strings are removed\n     *\n     * @param string $keywords category path\n     *\n     * @return string\n     */\n    protected function collectMetaKeyword($keywords)\n    {\n        $maxTextLength = 60;\n        $text = '';\n\n        if (count($articleList = $this->getArticleList())) {\n            $stringModifier = Str::getStr();\n            foreach ($articleList as $article) {\n                /** @var \\OxidEsales\\Eshop\\Application\\Model\\Article $article */\n                $description = $stringModifier->strip_tags(\n                    trim($stringModifier->strtolower($article->getLongDescription()->value))\n                );\n\n                //removing dots from string (they are not cleaned up during general string cleanup)\n                $description = $stringModifier->preg_replace(\"/\\./\", \" \", $description);\n\n                if ($stringModifier->strlen($description) > $maxTextLength) {\n                    $midText = $stringModifier->substr($description, 0, $maxTextLength);\n                    $description = $stringModifier->substr(\n                        $midText,\n                        0,\n                        ($stringModifier->strlen($midText) - $stringModifier->strpos(strrev($midText), ' '))\n                    );\n                }\n                if ($text) {\n                    $text .= ', ';\n                }\n                $text .= $description;\n            }\n        }\n\n        if (!$keywords) {\n            $keywords = $this->getCatPathString();\n        }\n\n        if ($keywords) {\n            $text = \"{$keywords}, {$text}\";\n        }\n\n        return parent::prepareMetaKeyword($text);\n    }\n\n    /**\n     * Assigns Template name ($this->_sThisTemplate) for article list\n     * preview. Name of template can be defined in admin or passed by\n     * URL (\"tpl\" variable).\n     *\n     * @return string\n     */\n    public function getTemplateName()\n    {\n        if ($templateName = Registry::getRequest()->getRequestEscapedParameter('tpl')) {\n            $this->_sThisTemplate = 'custom/' . basename($templateName);\n        } elseif (($category = $this->getActiveCategory()) && $category->getFieldData('oxtemplate')) {\n            $this->_sThisTemplate = $category->oxcategories__oxtemplate->value;\n        }\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Adds page number parameter to current Url and returns formatted url\n     *\n     * @param string $url         Url to append page numbers\n     * @param int    $currentPage Current page number\n     * @param int    $languageId  Requested language\n     *\n     * @return string\n     */\n    protected function addPageNrParam($url, $currentPage, $languageId = null)\n    {\n        if (Registry::getUtils()->seoIsActive() && ($category = $this->getActiveCategory())) {\n            if ($currentPage) {\n                // only if page number > 0\n                $url = $category->getBaseSeoLink($languageId, $currentPage);\n            }\n        } else {\n            $url = parent::addPageNrParam($url, $currentPage, $languageId);\n        }\n\n        return $url;\n    }\n\n    /**\n     * Returns true if we have category\n     *\n     * @return bool\n     */\n    protected function isActCategory()\n    {\n        return $this->_blIsCat;\n    }\n\n    /**\n     * Generates Url for page navigation\n     *\n     * @return string\n     */\n    public function generatePageNavigationUrl()\n    {\n        if ((Registry::getUtils()->seoIsActive() && ($category = $this->getActiveCategory()))) {\n            return $category->getLink();\n        }\n\n        return parent::generatePageNavigationUrl();\n    }\n\n    /**\n     * Returns default category sorting for selected category\n     *\n     * @return array\n     */\n    public function getDefaultSorting()\n    {\n        $sorting = parent::getDefaultSorting();\n\n        $category = $this->getActiveCategory();\n        if ($category && $category instanceof Category) {\n            if ($defaultSorting = $category->getDefaultSorting()) {\n                $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n                $articleViewName = $tableViewNameGenerator->getViewName('oxarticles');\n                $sortBy = $articleViewName . '.' . $defaultSorting;\n                $sortDirection = ($category->getDefaultSortingMode()) ? \"desc\" : \"asc\";\n                $sorting = ['sortby' => $sortBy, 'sortdir' => $sortDirection];\n            }\n        }\n\n        return $sorting;\n    }\n\n\n    /**\n     * Returns title suffix used in template\n     *\n     * @return string\n     */\n    public function getTitleSuffix()\n    {\n        if ($this->getActiveCategory()->oxcategories__oxshowsuffix->value) {\n            return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActiveShop()->oxshops__oxtitlesuffix->value;\n        }\n    }\n\n    /**\n     * Returns title page suffix used in template\n     *\n     * @return string\n     */\n    public function getTitlePageSuffix()\n    {\n        if (($activePage = $this->getActPage())) {\n            return Registry::getLang()->translateString('PAGE') . \" \" . ($activePage + 1);\n        }\n    }\n\n    /**\n     * Returns object, associated with current view.\n     * (the object that is shown in frontend)\n     *\n     * @param int $languageId Language id\n     *\n     * @return object\n     */\n    protected function getSubject($languageId)\n    {\n        return $this->getActiveCategory();\n    }\n\n    /**\n     * Template variable getter. Returns array of attribute values\n     * we do have here in this category\n     *\n     * @return array\n     */\n    public function getAttributes()\n    {\n        $this->_aAttributes = false;\n\n        if (($category = $this->getActiveCategory())) {\n            $attributes = $category->getAttributes();\n            if (count($attributes)) {\n                $this->_aAttributes = $attributes;\n            }\n        }\n\n        return $this->_aAttributes;\n    }\n\n    /**\n     * Template variable getter. Returns category's article list\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\ArticleList|null\n     */\n    public function getArticleList()\n    {\n        if ($this->_aArticleList === null) {\n            if ($category = $this->getActiveCategory()) {\n                $articleList = $this->loadArticles($category);\n                if (count($articleList)) {\n                    $this->_aArticleList = $articleList;\n                }\n            }\n        }\n\n        return $this->_aArticleList;\n    }\n\n    /**\n     * Article count getter\n     *\n     * @return int\n     */\n    public function getArticleCount()\n    {\n        return $this->_iAllArtCnt;\n    }\n\n    /**\n     * Return array of id to form recommend list.\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @return array\n     */\n    public function getSimilarRecommListIds()\n    {\n        if ($this->_aSimilarRecommListIds === null) {\n            $this->_aSimilarRecommListIds = false;\n\n            if ($categoryArticlesList = $this->getArticleList()) {\n                $this->_aSimilarRecommListIds = $categoryArticlesList->arrayKeys();\n            }\n        }\n\n        return $this->_aSimilarRecommListIds;\n    }\n\n    /**\n     * Template variable getter. Returns category path\n     *\n     * @return array\n     */\n    public function getCatTreePath()\n    {\n        if ($this->_sCatTreePath === null) {\n            $this->_sCatTreePath = false;\n            // category path\n            if ($categoryTree = $this->getCategoryTree()) {\n                $this->_sCatTreePath = $categoryTree->getPath();\n            }\n        }\n\n        return $this->_sCatTreePath;\n    }\n\n    /**\n     * Template variable getter. Returns category path array\n     *\n     * @return array\n     */\n    public function getTreePath()\n    {\n        if ($categoryTree = $this->getCategoryTree()) {\n            return $categoryTree->getPath();\n        }\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $paths = [];\n\n        // @deprecated oxmore feature will be removed in v8.0\n        if ('oxmore' == Registry::getRequest()->getRequestEscapedParameter('cnid')) {\n            $path = [];\n            $path['title'] = Registry::getLang()->translateString(\n                'CATEGORY_OVERVIEW',\n                Registry::getLang()->getBaseLanguage(),\n                false\n            );\n            $path['link'] = $this->getLink();\n\n            $paths[] = $path;\n\n            return $paths;\n        }\n        // END deprecated\n\n        if (($categoryTree = $this->getCategoryTree()) && ($categoryPaths = $categoryTree->getPath())) {\n            foreach ($categoryPaths as $category) {\n                /** @var Category $category */\n                $categoryPath = [];\n\n                $categoryPath['link'] = $category->getLink();\n                $categoryPath['title'] = $category->oxcategories__oxtitle->value;\n\n                $paths[] = $categoryPath;\n            }\n        }\n\n        return $paths;\n    }\n\n    /**\n     * Template variable getter. Returns true if category has active\n     * subcategories.\n     *\n     * @return bool\n     */\n    public function hasVisibleSubCats()\n    {\n        if ($this->_blHasVisibleSubCats === null) {\n            $this->_blHasVisibleSubCats = false;\n            if ($activeCategory = $this->getActiveCategory()) {\n                $this->_blHasVisibleSubCats = $activeCategory->getHasVisibleSubCats();\n            }\n        }\n\n        return $this->_blHasVisibleSubCats;\n    }\n\n    /**\n     * Template variable getter. Returns list of subcategories.\n     *\n     * @return array\n     */\n    public function getSubCatList()\n    {\n        if ($this->_aSubCatList === null) {\n            $this->_aSubCatList = [];\n            if ($activeCategory = $this->getActiveCategory()) {\n                $this->_aSubCatList = $activeCategory->getSubCats();\n            }\n        }\n\n        return $this->_aSubCatList;\n    }\n\n    /**\n     * Template variable getter. Returns page navigation\n     *\n     * @return object\n     */\n    public function getPageNavigation()\n    {\n        if ($this->_oPageNavigation === null) {\n            $this->_oPageNavigation = $this->generatePageNavigation();\n        }\n\n        return $this->_oPageNavigation;\n    }\n\n    /**\n     * Template variable getter. Returns category title.\n     *\n     * @return string\n     */\n    public function getTitle()\n    {\n        if ($this->_sCatTitle === null) {\n            $this->_sCatTitle = false;\n            // @deprecated oxmore feature will be removed in v8.0\n            if ($this->getCategoryId() == 'oxmore') {\n                $language = Registry::getLang();\n                $baseLanguageId = $language->getBaseLanguage();\n\n                $this->_sCatTitle = $language->translateString('CATEGORY_OVERVIEW', $baseLanguageId, false);\n            // END deprecated\n            } elseif (($category = $this->getActiveCategory())) {\n                $this->_sCatTitle = $category->oxcategories__oxtitle->value;\n            }\n        }\n\n        return $this->_sCatTitle;\n    }\n\n    /**\n     * Template variable getter. Returns bargain article list\n     *\n     * @return array\n     */\n    public function getBargainArticleList()\n    {\n        if ($this->_aBargainArticleList === null) {\n            $this->_aBargainArticleList = [];\n            if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('bl_perfLoadAktion') && $this->isActCategory()) {\n                $articleList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n                $articleList->loadActionArticles('OXBARGAIN');\n                if ($articleList->count()) {\n                    $this->_aBargainArticleList = $articleList;\n                }\n            }\n        }\n\n        return $this->_aBargainArticleList;\n    }\n\n    /**\n     * Template variable getter. Returns active search\n     *\n     * @return Category\n     */\n    public function getActiveCategory()\n    {\n        if ($this->_oActCategory === null) {\n            $this->_oActCategory = false;\n            $category = oxNew(Category::class);\n            if ($category->load($this->getCategoryId())) {\n                $this->_oActCategory = $category;\n            }\n        }\n\n        return $this->_oActCategory;\n    }\n\n    /**\n     * Returns view canonical url\n     *\n     * @return string\n     */\n    public function getCanonicalUrl()\n    {\n        if (($category = $this->getActiveCategory())) {\n            $utilsUrl = Registry::getUtilsUrl();\n            if (Registry::getUtils()->seoIsActive()) {\n                $url = $utilsUrl->prepareCanonicalUrl(\n                    $category->getBaseSeoLink($category->getLanguage(), $this->getActPage())\n                );\n            } else {\n                $url = $utilsUrl->prepareCanonicalUrl(\n                    $category->getBaseStdLink($category->getLanguage(), $this->getActPage())\n                );\n            }\n\n            return $url;\n        }\n    }\n\n    /**\n     * Returns config parameters blShowListDisplayType value\n     *\n     * @return boolean\n     */\n    public function canSelectDisplayType()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blShowListDisplayType');\n    }\n\n    /**\n     * Get list articles pages count\n     *\n     * @return int\n     */\n    public function getPageCount()\n    {\n        return $this->_iCntPages;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/BasketController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Wrapping;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Application\\Model\\BasketContentMarkGenerator;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse Psr\\Log\\LoggerInterface;\n\n/**\n * Current session shopping cart (basket item list).\n * Contains with user selected articles (with detail information), list of\n * similar products, top offer articles.\n * OXID eShop -> SHOPPING CART.\n */\nclass BasketController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/checkout/basket';\n\n    /**\n     * Order step marker\n     *\n     * @var bool\n     */\n    protected $_blIsOrderStep = true;\n\n    /**\n     * all basket articles\n     *\n     * @var object\n     */\n    protected $_oBasketArticles = null;\n\n    /**\n     * Similar List\n     *\n     * @var object\n     */\n    protected $_oSimilarList = null;\n\n    /**\n     * Recomm List\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @var object\n     */\n    protected $_oRecommList = null;\n\n    /**\n     * First basket product object. It is used to load\n     * recommendation list info and similar product list\n     *\n     * @var \\OxidEsales\\EshopCommunity\\Application\\Model\\Article\n     */\n    protected $_oFirstBasketProduct = null;\n\n    /**\n     * Current view search engine indexing state\n     *\n     * @var int\n     */\n    protected $_iViewIndexState = VIEW_INDEXSTATE_NOINDEXNOFOLLOW;\n\n    /**\n     * Wrapping objects list\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    protected $_oWrappings = null;\n\n    /**\n     * Card objects list\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    protected $_oCards = null;\n\n    /**\n     * Array of id to form recommendation list.\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @var array\n     */\n    protected $_aSimilarRecommListIds = null;\n\n    /**\n     * Executes parent::render(), creates list with basket articles\n     * Returns name of template file basket::_sThisTemplate (for Search\n     * engines return \"content\" template to avoid fake orders etc).\n     *\n     * @return  string   $this->_sThisTemplate  current template file name\n     */\n    public function render()\n    {\n        $config = Registry::getConfig();\n\n        if (Registry::getConfig()->getConfigParam('blPsBasketReservationEnabled')) {\n            $session = Registry::getSession();\n            $session->getBasketReservations()->renewExpiration();\n        }\n\n        $this->_aViewData[\"allowUnevenAmounts\"] = $config->getConfigParam('blAllowUnevenAmounts');\n\n        parent::render();\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Return the current articles from the basket\n     *\n     * @return object|bool\n     */\n    public function getBasketArticles()\n    {\n        if ($this->_oBasketArticles === null) {\n            $this->_oBasketArticles = false;\n\n            // passing basket articles\n            $session = Registry::getSession();\n            if ($oBasket = $session->getBasket()) {\n                $this->_oBasketArticles = $oBasket->getBasketArticles();\n            }\n        }\n\n        return $this->_oBasketArticles;\n    }\n\n    /**\n     * return the basket articles\n     *\n     * @return object|bool\n     */\n    public function getFirstBasketProduct()\n    {\n        if ($this->_oFirstBasketProduct === null) {\n            $this->_oFirstBasketProduct = false;\n\n            $aBasketArticles = $this->getBasketArticles();\n            if (is_array($aBasketArticles) && $oProduct = reset($aBasketArticles)) {\n                $this->_oFirstBasketProduct = $oProduct;\n            }\n        }\n\n        return $this->_oFirstBasketProduct;\n    }\n\n    /**\n     * return the similar articles\n     *\n     * @return object|bool\n     */\n    public function getBasketSimilarList()\n    {\n        if ($this->_oSimilarList === null) {\n            $this->_oSimilarList = false;\n\n            // similar product info\n            if ($oProduct = $this->getFirstBasketProduct()) {\n                $this->_oSimilarList = $oProduct->getSimilarProducts();\n            }\n        }\n\n        return $this->_oSimilarList;\n    }\n\n    /**\n     * Return array of id to form recommend list.\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @return array\n     */\n    public function getSimilarRecommListIds()\n    {\n        if ($this->_aSimilarRecommListIds === null) {\n            $this->_aSimilarRecommListIds = false;\n\n            if ($oProduct = $this->getFirstBasketProduct()) {\n                $this->_aSimilarRecommListIds = [$oProduct->getId()];\n            }\n        }\n\n        return $this->_aSimilarRecommListIds;\n    }\n\n    /**\n     * return the Link back to shop\n     *\n     * @return bool\n     */\n    public function showBackToShop()\n    {\n        $iNewBasketItemMessage = Registry::getConfig()->getConfigParam('iNewBasketItemMessage');\n        $sBackToShop = Registry::getSession()->getVariable('_backtoshop');\n\n        return ($iNewBasketItemMessage == 3 && $sBackToShop);\n    }\n\n    /**\n     * Assigns voucher to current basket\n     *\n     * @return null\n     */\n    public function addVoucher()\n    {\n        $session = Registry::getSession();\n        if (!$session->checkSessionChallenge()) {\n            ContainerFacade::get(LoggerInterface::class)\n                ->warning('EXCEPTION_NON_MATCHING_CSRF_TOKEN');\n            Registry::getUtilsView()->addErrorToDisplay('ERROR_MESSAGE_NON_MATCHING_CSRF_TOKEN');\n            return;\n        }\n\n        if (!$this->getViewConfig()->getShowVouchers()) {\n            return;\n        }\n\n        $oBasket = $session->getBasket();\n        try {\n            $oBasket->addVoucher(Registry::getRequest()->getRequestEscapedParameter('voucherNr'));\n        } catch (\\OxidEsales\\Eshop\\Core\\Exception\\VoucherException $oEx) {\n            // problems adding voucher\n            Registry::getUtilsView()->addErrorToDisplay($oEx, false, true);\n        }\n    }\n\n    /**\n     * Removes voucher from basket (calls \\OxidEsales\\Eshop\\Application\\Model\\Basket::removeVoucher())\n     *\n     * @return null\n     */\n    public function removeVoucher()\n    {\n        $session = Registry::getSession();\n        if (!$session->checkSessionChallenge()) {\n            ContainerFacade::get(LoggerInterface::class)\n                ->warning('EXCEPTION_NON_MATCHING_CSRF_TOKEN');\n            Registry::getUtilsView()->addErrorToDisplay('ERROR_MESSAGE_NON_MATCHING_CSRF_TOKEN');\n            return;\n        }\n\n        if (!$this->getViewConfig()->getShowVouchers()) {\n            return;\n        }\n\n        $oBasket = $session->getBasket();\n        $oBasket->removeVoucher(Registry::getRequest()->getRequestEscapedParameter('voucherId'));\n    }\n\n    /**\n     * Redirects user back to previous part of shop (list, details, ...) from basket.\n     * Used with option \"Display Message when Product is added to Cart\" set to \"Open Basket\"\n     * ($myConfig->iNewBasketItemMessage == 3)\n     *\n     * @return string   $sBackLink  back link\n     */\n    public function backToShop()\n    {\n        if (Registry::getConfig()->getConfigParam('iNewBasketItemMessage') == 3) {\n            $oSession = Registry::getSession();\n            if ($sBackLink = $oSession->getVariable('_backtoshop')) {\n                $oSession->deleteVariable('_backtoshop');\n\n                return $sBackLink;\n            }\n        }\n    }\n\n    /**\n     * Returns a name of the view variable containing the error/exception messages\n     *\n     * @return null\n     */\n    public function getErrorDestination()\n    {\n        return 'basket';\n    }\n\n    /**\n     * Returns wrapping options availability state (TRUE/FALSE)\n     *\n     * @return bool\n     */\n    public function isWrapping()\n    {\n        if (!$this->getViewConfig()->getShowGiftWrapping()) {\n            return false;\n        }\n        if (!isset($this->_iWrapCnt)) {\n            $wrapping = oxNew(Wrapping::class);\n            $this->_iWrapCnt = $wrapping->getWrappingCount('WRAP') + $wrapping->getWrappingCount('CARD');\n        }\n\n        return (bool) $this->_iWrapCnt;\n    }\n\n    /**\n     * Return basket wrappings list if available\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    public function getWrappingList()\n    {\n        if ($this->_oWrappings === null) {\n            $this->_oWrappings = new \\OxidEsales\\Eshop\\Core\\Model\\ListModel();\n\n            // load wrapping papers\n            if ($this->getViewConfig()->getShowGiftWrapping()) {\n                $this->_oWrappings = oxNew(Wrapping::class)->getWrappingList('WRAP');\n            }\n        }\n\n        return $this->_oWrappings;\n    }\n\n    /**\n     * Returns greeting cards list if available\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    public function getCardList()\n    {\n        if ($this->_oCards === null) {\n            $this->_oCards = new \\OxidEsales\\Eshop\\Core\\Model\\ListModel();\n\n            // load gift cards\n            if ($this->getViewConfig()->getShowGiftWrapping()) {\n                $this->_oCards = oxNew(Wrapping::class)->getWrappingList('CARD');\n            }\n        }\n\n        return $this->_oCards;\n    }\n\n    /**\n     * Updates wrapping data in session basket object\n     * (\\OxidEsales\\Eshop\\Core\\Session::getBasket()) - adds wrapping info to\n     * each article in basket (if possible). Plus adds\n     * gift message and chosen card ( takes from GET/POST/session;\n     * oBasket::giftmessage, oBasket::chosencard). Then sets\n     * basket back to session (\\OxidEsales\\Eshop\\Core\\Session::setBasket()).\n     */\n    public function changeWrapping()\n    {\n        $session = Registry::getSession();\n        if (!$session->checkSessionChallenge()) {\n            ContainerFacade::get(LoggerInterface::class)\n                ->warning('EXCEPTION_NON_MATCHING_CSRF_TOKEN');\n            Registry::getUtilsView()->addErrorToDisplay('ERROR_MESSAGE_NON_MATCHING_CSRF_TOKEN');\n            return;\n        }\n\n        if ($this->getViewConfig()->getShowGiftWrapping()) {\n            $oBasket = $session->getBasket();\n\n            $this->setWrappingInfo($oBasket, Registry::getRequest()->getRequestEscapedParameter('wrapping'));\n\n            $oBasket->setCardMessage(Registry::getRequest()->getRequestEscapedParameter('giftmessage'));\n            $oBasket->setCardId(Registry::getRequest()->getRequestEscapedParameter('chosencard'));\n            $oBasket->onUpdate();\n        }\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n\n        $iBaseLanguage = Registry::getLang()->getBaseLanguage();\n        $aPath['title'] = Registry::getLang()->translateString('CART', $iBaseLanguage, false);\n        $aPath['link']  = $this->getLink();\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n\n    /**\n     * Method returns object with explanation marks for articles in basket.\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\BasketContentMarkGenerator\n     */\n    public function getBasketContentMarkGenerator()\n    {\n        $session = Registry::getSession();\n\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\BasketContentMarkGenerator $oBasketContentMarkGenerator */\n        return oxNew(BasketContentMarkGenerator::class, $session->getBasket());\n    }\n\n    /**\n     * Sets basket wrapping\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket\n     * @param array                                      $aWrapping\n     */\n    protected function setWrappingInfo($oBasket, $aWrapping)\n    {\n        if (is_array($aWrapping) && count($aWrapping)) {\n            foreach ($oBasket->getContents() as $sKey => $oBasketItem) {\n                if (isset($aWrapping[$sKey])) {\n                    $oBasketItem->setWrapping($aWrapping[$sKey]);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/ClearCookiesController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents\\AllCookiesRemovedEvent;\n\n/**\n * CMS - loads pages and displays it\n */\nclass ClearCookiesController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Current view template\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/info/clearcookies';\n\n    /**\n     * Executes parent::render(), passes template variables to\n     * template engine and generates content. Returns the name\n     * of template to render content::_sThisTemplate\n     *\n     * @return  string  $this->_sThisTemplate   current template file name\n     */\n    public function render()\n    {\n        parent::render();\n\n        $this->removeCookies();\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Clears all cookies\n     */\n    protected function removeCookies()\n    {\n        $oUtilsServer = Registry::getUtilsServer();\n        if (isset($_SERVER['HTTP_COOKIE'])) {\n            $aCookies = explode(';', $_SERVER['HTTP_COOKIE']);\n            foreach ($aCookies as $sCookie) {\n                $sRawCookie = explode('=', $sCookie);\n                $oUtilsServer->setOxCookie(trim($sRawCookie[0]), '', time() - 10000, '/');\n            }\n        }\n        $oUtilsServer->setOxCookie('language', '', time() - 10000, '/');\n        $oUtilsServer->setOxCookie('displayedCookiesNotification', '', time() - 10000, '/');\n\n        ContainerFacade::dispatch(new AllCookiesRemovedEvent());\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n\n        $iBaseLanguage = Registry::getLang()->getBaseLanguage();\n        $aPath['title'] = Registry::getLang()->translateString('INFO_ABOUT_COOKIES', $iBaseLanguage, false);\n        $aPath['link'] = $this->getLink();\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/CompareController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Comparing Products.\n * Takes a few products and show attribute values to compare them.\n */\nclass CompareController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Number of possible compare pages.\n     *\n     * @var integer\n     */\n    protected $_iCntPages = 1;\n\n    /**\n     * Number of user's orders.\n     *\n     * @var integer\n     */\n    protected $_iOrderCnt = null;\n\n    /**\n     * Number of articles per page.\n     *\n     * @var integer\n     */\n    protected $_iArticlesPerPage = 3;\n\n    /**\n     * Number of user's orders.\n     *\n     * @var integer\n     */\n    protected $_iCompItemsCnt = null;\n\n    /**\n     * Items which are currently to show in comparison.\n     *\n     * @var array\n     */\n    protected $_aCompItems = null;\n\n    /**\n     * Article list in comparison.\n     *\n     * @var object\n     */\n    protected $_oArtList = null;\n\n    /**\n     * Article attribute list in comparison.\n     *\n     * @var object\n     */\n    protected $_oAttributeList = null;\n\n    /**\n     * Recomendation list\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @var object\n     */\n    protected $_oRecommList = null;\n\n    /**\n     * Page navigation\n     *\n     * @var object\n     */\n    protected $_oPageNavigation = null;\n\n    /**\n     * Sign if to load and show bargain action\n     *\n     * @var bool\n     */\n    protected $_blBargainAction = true;\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/compare/compare';\n\n    /**\n     * Array of id to form recommendation list.\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @var array\n     */\n    protected $_aSimilarRecommListIds = null;\n\n    /**\n     * moves current article to the left in compare items array\n     */\n    public function moveLeft() //#777C\n    {\n        $sArticleId = Registry::getRequest()->getRequestEscapedParameter('aid');\n        if ($sArticleId && ($aItems = $this->getCompareItems())) {\n            $sPrevArticleId = null;\n\n            $blFound = false;\n            foreach ($aItems as $sOxid => $sVal) {\n                if ($sOxid == $sArticleId) {\n                    $blFound = true;\n                }\n                if (!$blFound) {\n                    $sPrevArticleId = $sOxid;\n                }\n            }\n\n            if ($sPrevArticleId) {\n                $aNewItems = [];\n                foreach ($aItems as $sOxid => $sVal) {\n                    if ($sOxid == $sPrevArticleId) {\n                        $aNewItems[$sArticleId] = true;\n                    } elseif ($sOxid == $sArticleId) {\n                        $aNewItems[$sPrevArticleId] = true;\n                    } else {\n                        $aNewItems[$sOxid] = true;\n                    }\n                }\n\n                $this->setCompareItems($aNewItems);\n            }\n        }\n    }\n\n    /**\n     * moves current article to the right in compare items array\n     */\n    public function moveRight() //#777C\n    {\n        $sArticleId = Registry::getRequest()->getRequestEscapedParameter('aid');\n        if ($sArticleId && ($aItems = $this->getCompareItems())) {\n            $sNextArticleId = 0;\n\n            $blFound = false;\n            foreach ($aItems as $sOxid => $sVal) {\n                if ($blFound) {\n                    $sNextArticleId = $sOxid;\n                    $blFound = false;\n                }\n                if ($sOxid == $sArticleId) {\n                    $blFound = true;\n                }\n            }\n\n            if ($sNextArticleId) {\n                $aNewItems = [];\n                foreach ($aItems as $sOxid => $sVal) {\n                    if ($sOxid == $sArticleId) {\n                        $aNewItems[$sNextArticleId] = true;\n                    } elseif ($sOxid == $sNextArticleId) {\n                        $aNewItems[$sArticleId] = true;\n                    } else {\n                        $aNewItems[$sOxid] = true;\n                    }\n                }\n                $this->setCompareItems($aNewItems);\n            }\n        }\n    }\n\n    /**\n     * changes default template for compare in popup\n     */\n    public function inPopup() // #777C\n    {\n        $this->_sThisTemplate = 'compare_popup';\n        $this->_iArticlesPerPage = -1;\n    }\n\n    /**\n     * Articlelist count in comparison setter\n     *\n     * @param integer $iCount compare items count\n     */\n    public function setCompareItemsCnt($iCount)\n    {\n        $this->_iCompItemsCnt = $iCount;\n    }\n\n    /**\n     * Template variable getter. Returns article list count in comparison\n     *\n     * @return integer\n     */\n    public function getCompareItemsCnt()\n    {\n        if ($this->_iCompItemsCnt === null) {\n            $this->_iCompItemsCnt = 0;\n            if ($aItems = $this->getCompareItems()) {\n                $this->_iCompItemsCnt = count($aItems);\n            }\n        }\n\n        return $this->_iCompItemsCnt;\n    }\n\n    /**\n     * Compare item $_aCompItems getter\n     *\n     * @return null\n     */\n    public function getCompareItems()\n    {\n        if ($this->_aCompItems === null) {\n            $aItems = Registry::getSession()->getVariable('aFiltcompproducts');\n            if (is_array($aItems) && count($aItems)) {\n                $this->_aCompItems = $aItems;\n            }\n        }\n\n        return $this->_aCompItems;\n    }\n\n    /**\n     * Compare item $_aCompItems setter\n     *\n     * @param array $aItems compare items i new order\n     */\n    public function setCompareItems($aItems)\n    {\n        $this->_aCompItems = $aItems;\n        Registry::getSession()->setVariable('aFiltcompproducts', $aItems);\n    }\n\n    /**\n     *  $_iArticlesPerPage setter\n     *\n     * @param int $iNumber article count in compare page\n     */\n    protected function setArticlesPerPage($iNumber)\n    {\n        $this->_iArticlesPerPage = $iNumber;\n    }\n\n    /**\n     *  turn off paging\n     */\n    public function setNoPaging()\n    {\n        $this->setArticlesPerPage(0);\n    }\n\n\n    /**\n     * Template variable getter. Returns comparison's article\n     * list in order per page\n     *\n     * @return object\n     */\n    public function getCompArtList()\n    {\n        if ($this->_oArtList === null) {\n            if (($aItems = $this->getCompareItems())) {\n                // counts how many pages\n                $oList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n                $oList->loadIds(array_keys($aItems));\n\n                // cut page articles\n                if ($this->_iArticlesPerPage > 0) {\n                    $this->_iCntPages = ceil($oList->count() / $this->_iArticlesPerPage);\n                    $aItems = $this->removeArticlesFromPage($aItems, $oList);\n                }\n\n                $this->_oArtList = $this->changeArtListOrder($aItems, $oList);\n            }\n        }\n\n        return $this->_oArtList;\n    }\n\n    /**\n     * Template variable getter. Returns attribute list\n     *\n     * @return object\n     */\n    public function getAttributeList()\n    {\n        if ($this->_oAttributeList === null) {\n            $this->_oAttributeList = false;\n            if ($oArtList = $this->getCompArtList()) {\n                $aProductIds = array_keys($oArtList);\n                foreach ($oArtList as $oArticle) {\n                    if ($oArticle->getParentId()) {\n                        $aProductIds[] = $oArticle->getParentId();\n                    }\n                }\n                $oAttributeList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\AttributeList::class);\n                $this->_oAttributeList = $oAttributeList->loadAttributesByIds($aProductIds);\n            }\n        }\n\n        return $this->_oAttributeList;\n    }\n\n    /**\n     * Return array of id to form recommend list.\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @return array\n     */\n    public function getSimilarRecommListIds()\n    {\n        if ($this->_aSimilarRecommListIds === null) {\n            $this->_aSimilarRecommListIds = false;\n\n            if ($oArtList = $this->getCompArtList()) {\n                $this->_aSimilarRecommListIds = array_keys($oArtList);\n            }\n        }\n\n        return $this->_aSimilarRecommListIds;\n    }\n\n    /**\n     * Template variable getter. Returns page navigation\n     *\n     * @return object\n     */\n    public function getPageNavigation()\n    {\n        if ($this->_oPageNavigation === null) {\n            $this->_oPageNavigation = false;\n            $this->_oPageNavigation = $this->generatePageNavigation();\n        }\n\n        return $this->_oPageNavigation;\n    }\n\n    /**\n     * Cuts page articles\n     *\n     * @param array  $aItems article array\n     * @param object $oList  article list array\n     *\n     * @return array $aNewItems\n     */\n    protected function removeArticlesFromPage($aItems, $oList)\n    {\n        //#1106S $aItems changed to $oList.\n        //2006-08-10 Alfonsas, compare arrows fixed, array position is very important here, preserve it.\n        $aListKeys = $oList->arrayKeys();\n        $aItemKeys = array_keys($aItems);\n        $aKeys = array_intersect($aItemKeys, $aListKeys);\n        $aNewItems = [];\n        $iActPage = $this->getActPage();\n        $maxPageIndex = $this->_iArticlesPerPage * $iActPage + $this->_iArticlesPerPage;\n        for ($i = $this->_iArticlesPerPage * $iActPage; $i < $maxPageIndex; $i++) {\n            if (!isset($aKeys[$i])) {\n                break;\n            }\n            $aNewItems[$aKeys[$i]] = & $aItems[$aKeys[$i]];\n        }\n\n        return $aNewItems;\n    }\n\n    /**\n     * Changes order of list elements\n     *\n     * @param array  $aItems article array\n     * @param object $oList  article list array\n     *\n     * @return array $oNewList\n     */\n    protected function changeArtListOrder($aItems, $oList)\n    {\n        // #777C changing order of list elements, according to $aItems\n        $oNewList = [];\n        $iCnt = 0;\n        $iActPage = $this->getActPage();\n        foreach ($aItems as $sOxid => $sVal) {\n            //#4391T, skipping non loaded products\n            if (!isset($oList[$sOxid])) {\n                continue;\n            }\n\n            $iCnt++;\n            $oNewList[$sOxid] = $oList[$sOxid];\n\n            // hide arrow if article is first in the list\n            $oNewList[$sOxid]->hidePrev = false;\n            if ($iActPage == 0 && $iCnt == 1) {\n                $oNewList[$sOxid]->hidePrev = true;\n            }\n\n            // hide arrow if article is last in the list\n            $oNewList[$sOxid]->hideNext = false;\n            if (($iActPage + 1) == $this->_iCntPages && $iCnt == count($aItems)) {\n                $oNewList[$sOxid]->hideNext = true;\n            }\n        }\n\n        return $oNewList;\n    }\n\n    /**\n     * changes default template for compare in popup\n     *\n     * @return null\n     */\n    public function getOrderCnt()\n    {\n        if ($this->_iOrderCnt === null) {\n            $this->_iOrderCnt = 0;\n            if ($oUser = $this->getUser()) {\n                $this->_iOrderCnt = $oUser->getOrderCount();\n            }\n        }\n\n        return $this->_iOrderCnt;\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n\n        $aPath['title'] = Registry::getLang()->translateString(\n            'MY_ACCOUNT',\n            Registry::getLang()->getBaseLanguage(),\n            false\n        );\n        $aPath['link'] = Registry::getSeoEncoder()->getStaticUrl($this->getViewConfig()->getSelfLink() . 'cl=account');\n        $aPaths[] = $aPath;\n\n        $aPath['title'] = Registry::getLang()->translateString(\n            'PRODUCT_COMPARISON',\n            Registry::getLang()->getBaseLanguage(),\n            false\n        );\n        $aPath['link'] = $this->getLink();\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/ContactController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Email;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormField;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form\\ContactFormBridgeInterface;\n\n/**\n * Contact window.\n * Arranges \"CONTACT\" window, by creating form for user opinion (etc.)\n * submission. After user correctly\n * fulfils all required fields all information is sent to shop owner by\n * email. OXID eShop -> CONTACT.\n */\nclass ContactController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Entered user data.\n     *\n     * @var array\n     */\n    protected $_aUserData = null;\n\n    /**\n     * Entered contact subject.\n     *\n     * @var string\n     */\n    protected $_sContactSubject = null;\n\n    /**\n     * Entered conatct message.\n     *\n     * @var string\n     */\n    protected $_sContactMessage = null;\n\n    /**\n     * Contact email send status.\n     *\n     * @var null|int\n     */\n    protected $_blContactSendStatus = null;\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/info/contact';\n\n    /**\n     * Current view search engine indexing state\n     *\n     * @var int\n     */\n    protected $_iViewIndexState = VIEW_INDEXSTATE_NOINDEXNOFOLLOW;\n\n    /**\n     * @return string\n     */\n    public function render()\n    {\n        $form = ContainerFacade::get(ContactFormBridgeInterface::class)\n            ->getContactForm();\n\n        /** @var FormField $formField */\n        foreach ($form->getFields() as $key => $formField) {\n            $this->_aViewData['contactFormFields'][$key] = [\n                'name' => $formField->getName(),\n                'label' => $formField->getLabel(),\n                'isRequired' => $formField->isRequired(),\n            ];\n        }\n\n        return parent::render();\n    }\n\n    /**\n     * Composes and sends user written message, returns false if some parameters\n     * are missing.\n     *\n     * @return bool\n     */\n    public function send()\n    {\n        $contactFormBridge = ContainerFacade::get(ContactFormBridgeInterface::class);\n\n        $form = $contactFormBridge->getContactForm();\n        $form->handleRequest($this->getMappedContactFormRequest());\n\n        if ($form->isValid()) {\n            $this->sendContactMail(\n                $form->email->getValue(),\n                $form->subject->getValue(),\n                $contactFormBridge->getContactFormMessage($form)\n            );\n        } else {\n            foreach ($form->getErrors() as $error) {\n                Registry::getUtilsView()->addErrorToDisplay($error);\n            }\n\n            return false;\n        }\n    }\n\n    /**\n     * Template variable getter. Returns entered user data\n     *\n     * @return object\n     */\n    public function getUserData()\n    {\n        if ($this->_aUserData === null) {\n            $this->_aUserData = Registry::getRequest()->getRequestEscapedParameter('editval');\n        }\n\n        return $this->_aUserData;\n    }\n\n    /**\n     * Template variable getter. Returns entered contact subject\n     *\n     * @return object\n     */\n    public function getContactSubject()\n    {\n        if ($this->_sContactSubject === null) {\n            $this->_sContactSubject = Registry::getRequest()->getRequestEscapedParameter('c_subject');\n        }\n\n        return $this->_sContactSubject;\n    }\n\n    /**\n     * Template variable getter. Returns entered message\n     *\n     * @return object\n     */\n    public function getContactMessage()\n    {\n        if ($this->_sContactMessage === null) {\n            $this->_sContactMessage = Registry::getRequest()->getRequestEscapedParameter('c_message');\n        }\n\n        return $this->_sContactMessage;\n    }\n\n    /**\n     * Template variable getter. Returns status if email was send succesfull\n     *\n     * @return null|int\n     */\n    public function getContactSendStatus()\n    {\n        return $this->_blContactSendStatus;\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $title = Registry::getLang()->translateString(\n            'CONTACT',\n            Registry::getLang()->getBaseLanguage(),\n            false\n        );\n\n        return [\n            [\n                'title' => $title,\n                'link'  => $this->getLink(),\n            ]\n        ];\n    }\n\n    /**\n     * Page title\n     *\n     * @return string\n     */\n    public function getTitle()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActiveShop()->oxshops__oxcompany->value;\n    }\n\n    /**\n     * @return array\n     */\n    private function getMappedContactFormRequest()\n    {\n        $request = Registry::getRequest();\n        $personData = $request->getRequestEscapedParameter('editval');\n\n        return [\n            'email'         => $personData['oxuser__oxusername'] ?? '',\n            'firstName'     => $personData['oxuser__oxfname'] ?? '',\n            'lastName'      => $personData['oxuser__oxlname'] ?? '',\n            'salutation'    => $personData['oxuser__oxsal'] ?? '',\n            'subject'       => $request->getRequestEscapedParameter('c_subject'),\n            'message'       => $request->getRequestEscapedParameter('c_message'),\n        ];\n    }\n\n    /**\n     * Send a contact mail.\n     *\n     * @param string $email\n     * @param string $subject\n     * @param string $message\n     */\n    private function sendContactMail($email, $subject, $message)\n    {\n        $mailer = oxNew(Email::class);\n\n        if ($mailer->sendContactMail($email, $subject, $message)) {\n            $this->_blContactSendStatus = 1;\n        } else {\n            Registry::getUtilsView()->addErrorToDisplay('ERROR_MESSAGE_CHECK_EMAIL');\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/ContentController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererInterface;\nuse function basename;\n\n/**\n * CMS - loads pages and displays it\n */\nclass ContentController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Content id.\n     *\n     * @var string\n     */\n    protected $_sContentId = null;\n\n    /**\n     * Content object\n     *\n     * @var object\n     */\n    protected $_oContent = null;\n\n    /**\n     * Current view template\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/info/content';\n\n    /**\n     * Current view plain template\n     *\n     * @var string\n     */\n    protected $_sThisPlainTemplate = 'page/info/content_plain';\n\n    /**\n     * Current view content category (if available)\n     */\n    protected $_oContentCat = null;\n\n    /**\n     * Ids of contents which can be accessed without any restrictions when private sales is ON\n     *\n     * @var array\n     */\n    protected $_aPsAllowedContents = [\"oxagb\", \"oxrightofwithdrawal\", \"oximpressum\"];\n\n    /**\n     * Current view content title\n     *\n     * @var string\n     */\n    protected $_sContentTitle = null;\n\n    /**\n     * Sign if to load and show bargain action\n     *\n     * @var bool\n     */\n    protected $_blBargainAction = true;\n\n    /**\n     * Business entity data template\n     *\n     * @var string\n     */\n    protected $_sBusinessTemplate = 'rdfa/content/inc/business_entity';\n\n    /**\n     * Delivery charge data template\n     *\n     * @var string\n     */\n    protected $_sDeliveryTemplate = 'rdfa/content/inc/delivery_charge';\n\n    /**\n     * Payment charge data template\n     *\n     * @var string\n     */\n    protected $_sPaymentTemplate = 'rdfa/content/inc/payment_charge';\n\n    /**\n     * An array including all ShopConfVars which are used to extend business\n     * entity data\n     *\n     * @var array\n     */\n    protected $_aBusinessEntityExtends = [\n        'sRDFaLogoUrl',\n        'sRDFaLongitude',\n        'sRDFaLatitude',\n        'sRDFaGLN',\n        'sRDFaNAICS',\n        'sRDFaISIC',\n        'sRDFaDUNS',\n    ];\n\n    /**\n     * Returns prefix ID used by template engine.\n     *\n     * @return string    $this->_sViewId\n     */\n    public function getViewId()\n    {\n        if (!isset($this->_sViewId)) {\n            $this->_sViewId = parent::getViewId() . '|' . Registry::getRequest()->getRequestEscapedParameter('oxcid');\n        }\n\n        return $this->_sViewId;\n    }\n\n    /** @inheritdoc  */\n    public function render()\n    {\n        parent::render();\n\n        $content = $this->getContent();\n        if ($content && $content->getLoadId()) {\n            $this->validateContentAccessPermissions($content->getLoadId());\n            $this->getViewConfig()->setViewConfigParam('oxloadid', $content->getLoadId());\n        }\n        $templateName = $this->getTplName();\n        if (!$templateName && (!$content || !$content->getId())) {\n            error_404_handler();\n        }\n        if ($this->showPlainTemplate()) {\n            $this->_sThisTemplate = $this->_sThisPlainTemplate;\n        } elseif ($templateName) {\n            $this->_sThisTemplate = $templateName;\n        }\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Checks if content can be shown\n     *\n     * @param string $sContentIdent ident of content to display\n     *\n     * @return bool\n     */\n    protected function canShowContent($sContentIdent)\n    {\n        return !(\n            $this->isEnabledPrivateSales() &&\n            !$this->getUser() && !in_array($sContentIdent, $this->_aPsAllowedContents)\n        );\n    }\n\n    /**\n     * Returns current view meta data\n     * If $sMeta parameter comes empty, sets to it current content title\n     *\n     * @param string $sMeta     category path\n     * @param int    $iLength   max length of result, -1 for no truncation\n     * @param bool   $blDescTag if true - performs additional duplicate cleaning\n     *\n     * @return string\n     */\n    protected function prepareMetaDescription($sMeta, $iLength = 200, $blDescTag = false)\n    {\n        if (!$sMeta) {\n            $sMeta = $this->getContent()->oxcontents__oxtitle->value;\n        }\n\n        return parent::prepareMetaDescription($sMeta, $iLength, $blDescTag);\n    }\n\n    /**\n     * Returns current view keywords seperated by comma\n     * If $sKeywords parameter comes empty, sets to it current content title\n     *\n     * @param string $sKeywords               data to use as keywords\n     * @param bool   $blRemoveDuplicatedWords remove duplicated words\n     *\n     * @return string\n     */\n    protected function prepareMetaKeyword($sKeywords, $blRemoveDuplicatedWords = true)\n    {\n        if (!$sKeywords) {\n            $sKeywords = $this->getContent()->oxcontents__oxtitle->value;\n        }\n\n        return parent::prepareMetaKeyword($sKeywords, $blRemoveDuplicatedWords);\n    }\n\n    /**\n     * If current content is assigned to category returns its object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Content\n     */\n    public function getContentCategory()\n    {\n        if ($this->_oContentCat === null) {\n            // setting default status ..\n            $this->_oContentCat = false;\n            if (($oContent = $this->getContent()) && $oContent->oxcontents__oxtype->value == 2) {\n                $this->_oContentCat = $oContent;\n            }\n        }\n\n        return $this->_oContentCat;\n    }\n\n    /**\n     * Returns true if user forces to display plain template or\n     * if private sales switched ON and user is not logged in\n     *\n     * @return bool\n     */\n    public function showPlainTemplate()\n    {\n        $blPlain = (bool) Registry::getRequest()->getRequestEscapedParameter('plain');\n        if ($blPlain === false) {\n            $oUser = $this->getUser();\n            if (\n                $this->isEnabledPrivateSales() &&\n                (!$oUser || ($oUser && !$oUser->isTermsAccepted()))\n            ) {\n                $blPlain = true;\n            }\n        }\n\n        return (bool) $blPlain;\n    }\n\n    /**\n     * Returns active content id to load its seo meta info\n     *\n     * @return string\n     */\n    protected function getSeoObjectId()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('oxcid');\n    }\n\n    /**\n     * Template variable getter. Returns active content id.\n     * If no content id specified, uses \"impressum\" content id\n     *\n     * @return object\n     */\n    public function getContentId()\n    {\n        if ($this->_sContentId === null) {\n            $sContentId = Registry::getRequest()->getRequestEscapedParameter('oxcid');\n            $sLoadId = Registry::getRequest()->getRequestEscapedParameter('oxloadid');\n\n            $this->_sContentId = false;\n            $oContent = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Content::class);\n\n            if ($sLoadId) {\n                $blRes = $oContent->loadByIdent($sLoadId);\n            } elseif ($sContentId) {\n                $blRes = $oContent->load($sContentId);\n            } else {\n                //get default content (impressum)\n                $blRes = $oContent->loadByIdent('oximpressum');\n            }\n\n            if ($blRes && $oContent->oxcontents__oxactive->value) {\n                $this->_sContentId = $oContent->oxcontents__oxid->value;\n                $this->_oContent = $oContent;\n            }\n        }\n\n        return $this->_sContentId;\n    }\n\n    /**\n     * Template variable getter. Returns active content\n     *\n     * @return object\n     */\n    public function getContent()\n    {\n        if ($this->_oContent === null) {\n            $this->_oContent = false;\n            if ($this->getContentId()) {\n                return $this->_oContent;\n            }\n        }\n\n        return $this->_oContent;\n    }\n\n    /**\n     * returns object, assosiated with current view.\n     * (the object that is shown in frontend)\n     *\n     * @param int $iLang language id\n     *\n     * @return object\n     */\n    protected function getSubject($iLang)\n    {\n        return $this->getContent();\n    }\n\n    /**\n     * Returns name of template\n     *\n     * @return string\n     */\n    protected function getTplName()\n    {\n        $requestedTemplate = Registry::getRequest()->getRequestEscapedParameter('tpl');\n        if (!$requestedTemplate) {\n            return null;\n        }\n        // security fix so that you can't access files from outside template dir\n        $baseName = basename($requestedTemplate);\n        return \"message/$baseName\";\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $oContent = $this->getContent();\n\n        $aPaths = [];\n        $aPath = [];\n\n        $aPath['title'] = $oContent->oxcontents__oxtitle->value;\n        $aPath['link'] = $this->getLink();\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n\n    /**\n     * Template variable getter. Returns tag title\n     *\n     * @return string\n     */\n    public function getTitle()\n    {\n        if ($this->_sContentTitle === null) {\n            $oContent = $this->getContent();\n            $this->_sContentTitle = $oContent->oxcontents__oxtitle->value;\n        }\n\n        return $this->_sContentTitle;\n    }\n\n    /**\n     * Returns if page has rdfa\n     *\n     * @return bool\n     */\n    public function showRdfa()\n    {\n        return Registry::getConfig()->getConfigParam('blRDFaEmbedding');\n    }\n\n    /**\n     * Returns template name wich content page to specify:\n     * business entity data, payment charge specifications or delivery charge\n     *\n     * @return array\n     */\n    public function getContentPageTpl()\n    {\n        $aTemplate = [];\n        $sContentId = $this->getContent()->oxcontents__oxloadid->value;\n        $myConfig = Registry::getConfig();\n        if ($sContentId == $myConfig->getConfigParam('sRDFaBusinessEntityLoc')) {\n            $aTemplate[] = $this->_sBusinessTemplate;\n        }\n        if ($sContentId == $myConfig->getConfigParam('sRDFaDeliveryChargeSpecLoc')) {\n            $aTemplate[] = $this->_sDeliveryTemplate;\n        }\n        if ($sContentId == $myConfig->getConfigParam('sRDFaPaymentChargeSpecLoc')) {\n            $aTemplate[] = $this->_sPaymentTemplate;\n        }\n\n        return $aTemplate;\n    }\n\n    /**\n     * Gets extended business entity data\n     *\n     * @return object\n     */\n    public function getBusinessEntityExtends()\n    {\n        $myConfig = Registry::getConfig();\n        $aExtends = [];\n\n        foreach ($this->_aBusinessEntityExtends as $sExtend) {\n            $aExtends[$sExtend] = $myConfig->getConfigParam($sExtend);\n        }\n\n        return $aExtends;\n    }\n\n    /**\n     * Returns an object including all payments which are not mapped to a\n     * predefined GoodRelations payment method. This object is used for\n     * defining new instances of gr:PaymentMethods at content pages.\n     *\n     * @return object\n     */\n    public function getNotMappedToRDFaPayments()\n    {\n        $oPayments = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\PaymentList::class);\n        $oPayments->loadNonRDFaPaymentList();\n\n        return $oPayments;\n    }\n\n    /**\n     * Returns an object including all delivery sets which are not mapped to a\n     * predefined GoodRelations delivery method. This object is used for\n     * defining new instances of gr:DeliveryMethods at content pages.\n     *\n     * @return object\n     */\n    public function getNotMappedToRDFaDeliverySets()\n    {\n        $oDelSets = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\DeliverySetList::class);\n        $oDelSets->loadNonRDFaDeliverySetList();\n\n        return $oDelSets;\n    }\n\n    /**\n     * Returns delivery methods with assigned deliverysets.\n     *\n     * @return object\n     */\n    public function getDeliveryChargeSpecs()\n    {\n        $aDeliveryChargeSpecs = [];\n        $oDeliveryChargeSpecs = $this->getDeliveryList();\n        foreach ($oDeliveryChargeSpecs as $oDeliveryChargeSpec) {\n            if ($oDeliveryChargeSpec->oxdelivery__oxaddsumtype->value == \"abs\") {\n                $oDelSets = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\DeliverySetList::class);\n                $oDelSets->loadRDFaDeliverySetList($oDeliveryChargeSpec->getId());\n                $oDeliveryChargeSpec->deliverysetmethods = $oDelSets;\n                $aDeliveryChargeSpecs[] = $oDeliveryChargeSpec;\n            }\n        }\n\n        return $aDeliveryChargeSpecs;\n    }\n\n    /**\n     * Template variable getter. Returns delivery list\n     *\n     * @return object\n     */\n    public function getDeliveryList()\n    {\n        if ($this->_oDelList === null) {\n            $this->_oDelList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\DeliveryList::class);\n            $this->_oDelList->getList();\n        }\n\n        return $this->_oDelList;\n    }\n\n    /**\n     * Returns rdfa VAT\n     *\n     * @return bool\n     */\n    public function getRdfaVAT()\n    {\n        return Registry::getConfig()->getConfigParam('iRDFaVAT');\n    }\n\n    /**\n     * Returns rdfa VAT\n     *\n     * @return bool\n     */\n    public function getRdfaPriceValidity()\n    {\n        $iDays = Registry::getConfig()->getConfigParam('iRDFaPriceValidity');\n        $iFrom = Registry::getUtilsDate()->getTime();\n        $iThrough = $iFrom + ($iDays * 24 * 60 * 60);\n        $oPriceValidity = [];\n        $oPriceValidity['validfrom'] = date('Y-m-d\\TH:i:s', $iFrom) . \"Z\";\n        $oPriceValidity['validthrough'] = date('Y-m-d\\TH:i:s', $iThrough) . \"Z\";\n\n        return $oPriceValidity;\n    }\n\n    /**\n     * Returns content parsed through renderer\n     *\n     * @return string\n     */\n    public function getParsedContent()\n    {\n        $activeLanguageId = Registry::getLang()->getTplLanguage();\n        return $this->getRenderer()->renderFragment(\n            $this->getContent()->oxcontents__oxcontent->value,\n            \"ox:{$this->getContent()->getId()}{$activeLanguageId}\",\n            $this->getViewData()\n        );\n    }\n\n    private function getRenderer(): TemplateRendererInterface\n    {\n        return ContainerFacade::get(TemplateRendererBridgeInterface::class)\n            ->getTemplateRenderer();\n    }\n\n    /**\n     * Returns view canonical url\n     *\n     * @return string\n     */\n    public function getCanonicalUrl()\n    {\n        $url = '';\n        if ($content = $this->getContent()) {\n            $utils = Registry::getUtilsUrl();\n            if (Registry::getUtils()->seoIsActive()) {\n                $url = $utils->prepareCanonicalUrl($content->getBaseSeoLink($content->getLanguage()));\n            } else {\n                $url = $utils->prepareCanonicalUrl($content->getBaseStdLink($content->getLanguage()));\n            }\n        }\n\n        return $url;\n    }\n\n    /**\n     * Terminates execution with exit() on no permissions\n     * @param string $contentId\n     * @return void\n     */\n    private function validateContentAccessPermissions(string $contentId): void\n    {\n        if (!$this->canShowContent($contentId)) {\n            Registry::getUtils()->redirect(Registry::getConfig()->getShopHomeUrl() . 'cl=account');\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/CreditsController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\n/**\n * Special page for Credits\n */\nclass CreditsController extends \\OxidEsales\\Eshop\\Application\\Controller\\ContentController\n{\n    /**\n     * Content id.\n     *\n     * @var string\n     */\n    protected $_sContentId = \"oxcredits\";\n\n    /**\n     * Returns active content id to load its seo meta info\n     *\n     * @return string\n     */\n    protected function getSeoObjectId()\n    {\n        return $this->getContentId();\n    }\n\n    /**\n     * Template variable getter. Returns active content\n     *\n     * @return object\n     */\n    public function getContent()\n    {\n        if ($this->_oContent === null) {\n            $this->_oContent = false;\n            $oContent = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Content::class);\n            if ($oContent->loadByIdent($this->getContentId())) {\n                $this->_oContent = $oContent;\n            }\n        }\n\n        return $this->_oContent;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/DownloadController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Article file download page.\n */\nclass DownloadController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Prevents from loading any component as this controller\n     * only returns file content if token is valid\n     */\n    public function init()\n    {\n        // empty for performance reasons\n    }\n\n    /**\n     * Checks if given token is valid, formats HTTP headers,\n     * and outputs file to buffer.\n     *\n     * If token is not valid, redirects to start page.\n     */\n    public function render()\n    {\n        $sFileOrderId = Registry::getRequest()->getRequestEscapedParameter('sorderfileid');\n\n        if ($sFileOrderId) {\n            $oArticleFile = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\File::class);\n            try {\n                /** @var \\OxidEsales\\Eshop\\Application\\Model\\OrderFile $oOrderFile */\n                $oOrderFile = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\OrderFile::class);\n                if ($oOrderFile->load($sFileOrderId)) {\n                    $sFileId = $oOrderFile->getFileId();\n                    $blLoadedAndExists = $oArticleFile->load($sFileId) && $oArticleFile->exist();\n                    if ($sFileId && $blLoadedAndExists && $oOrderFile->processOrderFile()) {\n                        $oArticleFile->download();\n                    } else {\n                        $sError = \"ERROR_MESSAGE_FILE_DOESNOT_EXIST\";\n                    }\n                }\n            } catch (\\OxidEsales\\Eshop\\Core\\Exception\\StandardException $oEx) {\n                $sError = \"ERROR_MESSAGE_FILE_DOWNLOAD_FAILED\";\n            }\n        } else {\n            $sError = \"ERROR_MESSAGE_WRONG_DOWNLOAD_LINK\";\n        }\n        if ($sError) {\n            $oEx = new \\OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay();\n            $oEx->setMessage($sError);\n            Registry::getUtilsView()->addErrorToDisplay($oEx, false);\n            Registry::getUtils()->redirect(Registry::getConfig()->getShopUrl() . 'index.php?cl=account_downloads');\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/ExceptionErrorController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\n/**\n * Displays exception errors\n */\nclass ExceptionErrorController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'message/exception';\n\n    /** @var array Remove loading of components on exception handling. */\n    protected $_aComponentNames = [];\n\n    /**\n     * Sets exception errros to template\n     */\n    public function displayExceptionError()\n    {\n        $aViewData = $this->getViewData();\n\n        //add all exceptions to display\n        $aErrors = $this->getErrors();\n\n        if (is_array($aErrors) && count($aErrors)) {\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->passAllErrorsToView($aViewData, $aErrors);\n        }\n\n        $this->addTplParam('Errors', $aViewData['Errors']);\n\n        // resetting errors from session\n        \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable('Errors', []);\n    }\n\n    /**\n     * return page errors array\n     *\n     * @return array\n     */\n    protected function getErrors()\n    {\n        $aErrors = \\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable('Errors');\n\n        if (null === $aErrors) {\n            $aErrors = [];\n        }\n\n        return $aErrors;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/ForgotPasswordController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Email;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Password reminder page.\n * Collects toparticle, bargain article list. There is a form with entry\n * field to enter login name (usually email). After user enters required\n * information and submits \"Request Password\" button mail is sent to users email.\n * OXID eShop -> MY ACCOUNT -> \"Forgot your password? - click here.\"\n */\nclass ForgotPasswordController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/account/forgotpwd';\n\n    /**\n     * Send forgot E-Mail.\n     *\n     * @var string\n     */\n    protected $_sForgotEmail;\n\n    /**\n     * Current view search engine indexing state\n     *\n     * @var int\n     */\n    protected $_iViewIndexState = VIEW_INDEXSTATE_NOINDEXNOFOLLOW;\n\n    /**\n     * Update link expiration status\n     *\n     * @var bool\n     *\n     * @deprecated property will be removed in next major\n     */\n    protected $_blUpdateLinkStatus;\n\n    /**\n     * Sign if to load and show bargain action\n     *\n     * @var bool\n     */\n    protected $_blBargainAction = true;\n\n    /**\n     * Executes Email::sendForgotPwdEmail() to send \"forgot password\" email to user\n     */\n    public function forgotPassword()\n    {\n        $this->_sForgotEmail = Registry::getRequest()->getRequestEscapedParameter('lgn_usr');\n        if ($this->_sForgotEmail) {\n            $result = oxNew(Email::class)->sendForgotPwdEmail($this->_sForgotEmail);\n            if ($result === -1) {\n                Registry::getUtilsView()->addErrorToDisplay('MESSAGE_NOT_ABLE_TO_SEND_EMAIL');\n                $this->_sForgotEmail = false;\n            }\n        }\n    }\n\n    /**\n     * Checks if password is fine and updates old one with new\n     * password. On success user is redirected to success page\n     *\n     * @return string\n     */\n    public function updatePassword()\n    {\n        $sNewPass = Registry::getRequest()->getRequestParameter('password_new');\n        $sConfPass = Registry::getRequest()->getRequestParameter('password_new_confirm');\n\n        $oUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n\n        /** @var \\OxidEsales\\Eshop\\Core\\InputValidator $oInputValidator */\n        $oInputValidator = Registry::getInputValidator();\n        if (($oExcp = $oInputValidator->checkPassword($oUser, $sNewPass, $sConfPass, true))) {\n            return Registry::getUtilsView()->addErrorToDisplay($oExcp->getMessage(), false, true);\n        }\n\n        // passwords are fine - updating and loggin user in\n        if ($oUser->loadUserByUpdateId($this->getUpdateId())) {\n            // setting new pass ..\n            $oUser->setPassword($sNewPass);\n\n            // resetting update pass params\n            $oUser->setUpdateKey(true);\n\n            // saving ..\n            $oUser->save();\n\n            // forcing user login\n            Registry::getSession()->setVariable('usr', $oUser->getId());\n\n            return 'forgotpwd?success=1';\n        } else {\n            // expired reminder\n            $oUtilsView = Registry::getUtilsView();\n\n            return $oUtilsView->addErrorToDisplay('ERROR_MESSAGE_PASSWORD_LINK_EXPIRED', false, true);\n        }\n    }\n\n    /**\n     * If user password update was successfull - setting success status\n     *\n     * @return bool\n     */\n    public function updateSuccess()\n    {\n        return (bool) Registry::getRequest()->getRequestEscapedParameter('success');\n    }\n\n    /**\n     * Notifies that password update form must be shown\n     *\n     * @return bool\n     */\n    public function showUpdateScreen()\n    {\n        return (bool) $this->getUpdateId();\n    }\n\n    /**\n     * Returns special id used for password update functionality\n     *\n     * @return string\n     */\n    public function getUpdateId()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('uid');\n    }\n\n    /**\n     * Returns password update link expiration status\n     *\n     * @return bool\n     */\n    public function isExpiredLink()\n    {\n        if (($sKey = $this->getUpdateId())) {\n            return oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class)->isExpiredUpdateId($sKey);\n        }\n\n        return false;\n    }\n\n    /**\n     * @return string\n     */\n    public function getForgotEmail()\n    {\n        return $this->_sForgotEmail;\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n\n        $iBaseLanguage = Registry::getLang()->getBaseLanguage();\n        $aPath['title'] = Registry::getLang()->translateString('FORGOT_PASSWORD', $iBaseLanguage, false);\n        $aPath['link'] = $this->getLink();\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n\n    /**\n     * Get password reminder page title\n     *\n     * @return string\n     */\n    public function getTitle()\n    {\n        $sTitle = 'FORGOT_PASSWORD';\n\n        if ($this->showUpdateScreen()) {\n            $sTitle = 'NEW_PASSWORD';\n        } elseif ($this->updateSuccess()) {\n            $sTitle = 'CHANGE_PASSWORD';\n        }\n\n        return Registry::getLang()->translateString($sTitle, Registry::getLang()->getBaseLanguage(), false);\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/FrontendController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Controller\\BaseController;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Price;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Request;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge\\UserReviewAndRatingBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Core\\SortingValidator;\nuse stdClass;\n\nuse function rawurlencode;\n\n// view indexing state for search engines:\ndefine('VIEW_INDEXSTATE_INDEX', 0); //  index without limitations\ndefine('VIEW_INDEXSTATE_NOINDEXNOFOLLOW', 1); //  no index / no follow\ndefine('VIEW_INDEXSTATE_NOINDEXFOLLOW', 2); //  no index / follow\n\n/**\n * Base view class.\n * Class is responsible for managing of components that must be\n * loaded and executed before any regular operation.\n */\n#[\\AllowDynamicProperties]\nclass FrontendController extends BaseController\n{\n    private const SEARCH_PARAM = 'searchparam';\n    private const SEARCH_CATEGORY_ID = 'searchcnid';\n    private const SEARCH_VENDOR = 'searchvendor';\n    private const SEARCH_MANUFACTURER = 'searchmanufacturer';\n    private const CATEGORY_ID = 'cnid';\n    private const MANUFACTURER_ID = 'mnid';\n    private const SEARCH_RECOMMENDATION = 'searchrecomm';\n    private const RECOMMENDATION_ID = 'recommid';\n    private const ACTIVE_PRODUCT_ID = 'anid';\n    private const LOAD_ID = 'oxloadid';\n    private const PAGE = 'page';\n    private const TEMPLATE = 'tpl';\n    private const PAGE_NUMBER = 'pgNr';\n    /**\n     * Characters which should be removed while preparing meta keywords\n     *\n     * @var string\n     */\n    protected $_sRemoveMetaChars = '.\\+*?[^]$(){}=!<>|:&';\n\n    /**\n     * Array of component objects.\n     *\n     * @var array of object\n     */\n    protected $_oaComponents = [];\n\n    /**\n     * Flag if current view is an order view\n     *\n     * @var bool\n     */\n    protected $_blIsOrderStep = false;\n\n    /**\n     * List type\n     *\n     * @var string\n     */\n    protected $_sListType = null;\n\n    /**\n     * Possible list display types\n     *\n     * @var array\n     */\n    protected $_aListDisplayTypes = ['grid', 'line', 'infogrid'];\n\n    /**\n     * List display type\n     *\n     * @var string\n     */\n    protected $_sListDisplayType = null;\n\n    /**\n     * List display type\n     *\n     * @var string\n     */\n    protected $_sCustomListDisplayType = null;\n\n    /**\n     * Active articles category object.\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Category\n     */\n    protected $_oActCategory = null;\n\n    /**\n     * Active Manufacturer object.\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Manufacturer\n     */\n    protected $_oActManufacturer = null;\n\n    /**\n     * Active vendor object.\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Vendor\n     */\n    protected $_oActVendor = null;\n\n    /**\n     * Active recommendation's list\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     */\n    protected $_oActiveRecommList = null;\n\n    /**\n     * Active search object - stdClass object which keeps navigation info\n     *\n     * @var stdClass\n     */\n    protected $_oActSearch = null;\n\n    /**\n     * Marked which defines if current view is sortable or not\n     *\n     * @var bool\n     */\n    protected $_blShowSorting = false;\n\n    /**\n     * Load currency option\n     *\n     * @var bool\n     */\n    protected $_blLoadCurrency = null;\n\n    /**\n     * Load Manufacturers option\n     *\n     * @var bool\n     */\n    protected $_blLoadManufacturerTree = null;\n\n    /**\n     * Don't show empty cats\n     *\n     * @var bool\n     */\n    protected $_blDontShowEmptyCats = null;\n\n    /**\n     * Load language option\n     *\n     * @var bool\n     */\n    protected $_blLoadLanguage = null;\n\n    /**\n     * Item count in category top navigation\n     *\n     * @var integer\n     */\n    protected $_iTopCatNavItmCnt = null;\n\n    /**\n     * List's \"order by\"\n     *\n     * @var string\n     */\n    protected $_sListOrderBy = null;\n\n    /**\n     * Order direction of list\n     *\n     * @var string\n     */\n    protected $_sListOrderDir = null;\n\n    /**\n     * Meta description\n     *\n     * @var string\n     */\n    protected $_sMetaDescription = null;\n\n    /**\n     * Meta keywords\n     *\n     * @var string\n     */\n    protected $_sMetaKeywords = null;\n\n    /**\n     * Start page meta description CMS ident\n     *\n     * @var string\n     */\n    protected $_sMetaDescriptionIdent = null;\n\n    /**\n     * Start page meta keywords CMS ident\n     *\n     * @var string\n     */\n    protected $_sMetaKeywordsIdent = null;\n\n    /**\n     * Additional params for url.\n     *\n     * @var string\n     */\n    protected $_sAdditionalParams = null;\n\n    /**\n     * Active currency object.\n     *\n     * @var object\n     */\n    protected $_oActCurrency = null;\n\n    /**\n     * Private sales on/off state\n     *\n     * @var bool\n     */\n    protected $_blEnabledPrivateSales = null;\n\n    /**\n     * Sign if any new component is added. On this case will be\n     * executed components stored in oxBaseView::_aComponentNames\n     * plus oxBaseView::_aComponentNames.\n     *\n     * @var bool\n     */\n    protected $_blCommonAdded = false;\n\n    /**\n     * Current view search engine indexing state:\n     *     VIEW_INDEXSTATE_INDEX - index without limitations\n     *     VIEW_INDEXSTATE_NOINDEXNOFOLLOW - no index / no follow\n     *     VIEW_INDEXSTATE_NOINDEXFOLLOW - no index / follow\n     */\n    protected $_iViewIndexState = VIEW_INDEXSTATE_INDEX;\n\n    /**\n     * If true, forces FrontendController::noIndex returns VIEW_INDEXSTATE_NOINDEXFOLLOW\n     * (FrontendController::$_iViewIndexState = VIEW_INDEXSTATE_NOINDEXFOLLOW; index / follow)\n     *\n     * @var bool\n     */\n    protected $_blForceNoIndex = false;\n\n    /**\n     * Number of products in compare list.\n     *\n     * @var integer\n     */\n    protected $_iCompItemsCnt = null;\n\n    /**\n     * Default content id\n     *\n     * @return string\n     */\n    protected $_sContentId = null;\n\n    /** @return \\OxidEsales\\Eshop\\Application\\Model\\Content Default content. */\n    protected $_oContent = null;\n\n    /** @var string View id. */\n    protected $_sViewResetID = null;\n\n    /** @var array Menu list. */\n    protected $_aMenueList = null;\n\n    /**\n     * Names of components (classes) that are initiated and executed\n     * before any other regular operation.\n     *\n     * @var array\n     */\n    protected $_aComponentNames = [\n        'oxcmp_user'       => 1, // 0 means dont init if cached\n        'oxcmp_lang'       => 0,\n        'oxcmp_cur'        => 1,\n        'oxcmp_shop'       => 1,\n        'oxcmp_categories' => 0,\n        'oxcmp_utils'      => 1,\n        'oxcmp_basket'     => 1\n    ];\n\n    /**\n     * Names of components (classes) that are initiated and executed\n     * before any other regular operation. User may modify this himself.\n     *\n     * @var array\n     */\n    protected $_aUserComponentNames = [];\n\n    /** @var \\OxidEsales\\Eshop\\Application\\Model\\Article Current view product object. */\n    protected $_oProduct = null;\n\n    /** @var int Number of current list page. */\n    protected $_iActPage = null;\n\n    /** @var array A list of articles. */\n    protected $_aArticleList = null;\n\n    /** @var \\OxidEsales\\Eshop\\Application\\Model\\ManufacturerList Manufacturer list object. */\n    protected $_oManufacturerTree = null;\n\n    /** @var \\OxidEsales\\Eshop\\Application\\Model\\CategoryList Category tree object. */\n    protected $_oCategoryTree = null;\n\n    /** @var array Top 5 article list. */\n    protected $_aTop5ArticleList = null;\n\n    /** @var array Bargain article list. */\n    protected $_aBargainArticleList = null;\n\n    /** @var integer If order price to low. */\n    protected $_blLowOrderPrice = null;\n\n    /** @var string Min order price. */\n    protected $_sMinOrderPrice = null;\n\n    /** @var string Real newsletter status. */\n    protected $_iNewsRealStatus = null;\n\n    /** @return array Url parameters which block redirection. */\n    protected $_aBlockRedirectParams = ['fnc', 'stoken', 'force_sid', 'force_admin_sid'];\n\n    /** @var \\OxidEsales\\Eshop\\Application\\Model\\Vendor Root vendor object. */\n    protected $_oRootVendor = null;\n\n    /** @var string Vendor id. */\n    protected $_sVendorId = null;\n\n    /** @var array Manufacturer list for search. */\n    protected $_aManufacturerlist = null;\n\n    /** @var \\OxidEsales\\Eshop\\Application\\Model\\Manufacturer Root manufacturer object. */\n    protected $_oRootManufacturer = null;\n\n    /** @var string Manufacturer id. */\n    protected $_sManufacturerId = null;\n\n    /** @var bool Has user newsletter subscribed. */\n    protected $_blNewsSubscribed = null;\n\n    /** @var \\OxidEsales\\Eshop\\Application\\Model\\Address Delivery address. */\n    protected $_oDelAddress = null;\n\n    /** @var array Category tree path. */\n    protected $_sCatTreePath = null;\n\n    /** @var array Loaded contents array (cache). */\n    protected $_aContents = [];\n\n    /** @var bool Sign if to load and show top5articles action. */\n    protected $_blTop5Action = false;\n\n    /** @var bool Sign if to load and show bargain action. */\n    protected $_blBargainAction = false;\n\n    /** @var array check all \"must-be-fields\" if they are completely. */\n    protected $_aMustFillFields = null;\n\n    /** @var bool If active root category was changed. */\n    protected $_blRootCatChanged = false;\n\n    /** @var array User address. */\n    protected $_aInvoiceAddress = null;\n\n    /** @var array User delivery address. */\n    protected $_aDeliveryAddress = null;\n\n    /** @var string Logged in user name. */\n    protected $_sActiveUsername = null;\n\n    /** @var boolean is VAT included in prices */\n    protected $_blIsVatIncluded = null;\n\n    /** @var array Components which needs to be initialized/rendered (depending on cache and its cache status). */\n    protected static $_aCollectedComponentNames = null;\n\n    /** @var array If active load components. By default active. */\n    protected $_blLoadComponents = true;\n\n    /** @var array Sorting columns list. */\n    protected $_aSortColumns = null;\n\n    /** @var StdClass Page navigation. */\n    protected $_oPageNavigation = null;\n\n    /** @var integer Number of possible pages. */\n    protected $_iCntPages = null;\n\n    /** @var string Form id. */\n    protected $_sFormId = null;\n\n    /** @var bool Whether session form id matches with request form id. */\n    protected $_blCanAcceptFormData = null;\n\n    /**\n     * Return true, if the review manager should be shown.\n     *\n     * @return bool\n     */\n    public function isUserAllowedToManageOwnReviews()\n    {\n        return (bool) Registry::getConfig()->getConfigParam('blAllowUsersToManageTheirReviews');\n    }\n\n    /**\n     * Get the total number of reviews for the active user.\n     *\n     * @return integer Number of reviews\n     */\n    public function getReviewAndRatingItemsCount()\n    {\n        $user = $this->getUser();\n        $count = 0;\n        if ($user) {\n            $count = ContainerFacade::get(UserReviewAndRatingBridgeInterface::class)\n                ->getReviewAndRatingListCount(\n                    $user->getId()\n                );\n        }\n\n        return $count;\n    }\n\n    /**\n     * Returns component names.\n     *\n     * At the moment it is not possible to override $_aCollectedComponentNames in oxUBase.\n     *\n     * @return array\n     */\n    protected function getComponentNames()\n    {\n        if (self::$_aCollectedComponentNames === null) {\n            self::$_aCollectedComponentNames = array_merge($this->_aComponentNames, $this->_aUserComponentNames);\n\n            if ($userComponentNames = ContainerFacade::getParameter('oxid_esales.cacheable_user_components')) {\n                self::$_aCollectedComponentNames = array_merge(self::$_aCollectedComponentNames, $userComponentNames);\n            }\n\n            if (Registry::getRequest()->getRequestEscapedParameter('_force_no_basket_cmp')) {\n                unset(self::$_aCollectedComponentNames['oxcmp_basket']);\n            }\n        }\n\n        reset(self::$_aCollectedComponentNames);\n\n        return self::$_aCollectedComponentNames;\n    }\n\n    /**\n     * In non admin mode checks if request was NOT processed by seo handler.\n     * If NOT, then tries to load alternative SEO url and if url is available -\n     * redirects to it. If no alternative path was found - 404 header is emitted\n     * and page is rendered\n     */\n    protected function processRequest()\n    {\n        $utils = Registry::getUtils();\n        $requestUrl = Registry::get(Request::class)->getRequestUrl();\n        // non admin, request is not empty and was not processed by seo engine\n        if (!isSearchEngineUrl() && $utils->seoIsActive() && $requestUrl) {\n            // fetching standard url and looking for it in seo table\n            if ($this->canRedirect() && ($redirectUrl = Registry::getSeoEncoder()->fetchSeoUrl($requestUrl))) {\n                $utils->redirect(Registry::getConfig()->getCurrentShopUrl() . $redirectUrl, false, 301);\n            } elseif (VIEW_INDEXSTATE_INDEX == $this->noIndex()) {\n                // forcing to set no index/follow meta\n                $this->forceNoIndex();\n\n                if (ContainerFacade::getParameter('oxid_esales.log_not_seo_urls')) {\n                    $shopId = Registry::getConfig()->getShopId();\n                    $languageId = Registry::getLang()->getBaseLanguage();\n                    $id = md5(strtolower($requestUrl) . $shopId . $languageId);\n\n                    // logging \"not found\" url\n                    $database = DatabaseProvider::getDb();\n                    $database->execute(\n                        \"replace oxseologs ( oxstdurl, oxident, oxshopid, oxlang ) values ( ?, ?, ?, ? ) \",\n                        [$requestUrl, $id, $shopId, $languageId]\n                    );\n                }\n            }\n        }\n    }\n\n    /**\n     * Calls self::_processRequest(), initializes components which needs to\n     * be loaded, sets current list type, calls parent::init()\n     */\n    public function init()\n    {\n        $this->processRequest();\n\n        // storing current view\n        $shouldInitialize = $this->shouldInitializeComponents();\n\n        // init all components if there are any\n        if ($this->_blLoadComponents) {\n            foreach ($this->getComponentNames() as $componentName => $isNotCacheable) {\n                // do not override initiated components\n                if (!isset($this->_oaComponents[$componentName])) {\n                    // component objects MUST be created to support user called functions\n                    $component = oxNew($componentName);\n                    $component->setParent($this);\n                    $component->setThisAction($componentName);\n                    $this->_oaComponents[$componentName] = $component;\n                }\n\n                // do we really need to initiate them ?\n                if ($shouldInitialize) {\n                    $this->_oaComponents[$componentName]->init();\n\n                    // executing only is view does not have action method\n                    if (!method_exists($this, (string)$this->getFncName())) {\n                        $this->_oaComponents[$componentName]->executeFunction($this->getFncName());\n                    }\n                }\n            }\n        }\n\n        parent::init();\n    }\n\n    /**\n     * Returns whether init() should initialize created components.\n     *\n     * @return bool\n     */\n    protected function shouldInitializeComponents()\n    {\n        return true;\n    }\n\n    /**\n     * If current view ID is not set - forms and returns view ID\n     * according to language and currency.\n     *\n     * @return string $this->_sViewId\n     */\n    public function getViewId()\n    {\n        if (isset($this->_sViewId)) {\n            return $this->_sViewId;\n        }\n\n        return $this->_sViewId = $this->generateViewId();\n    }\n\n    /**\n     * Generates current view id.\n     *\n     * @return string\n     */\n    protected function generateViewId()\n    {\n        $config = Registry::getConfig();\n        $viewId = $this->generateViewIdBase();\n\n        $viewId .= \"|\" . ((int) $this->_blForceNoIndex) . '|' . ((int) $this->isRootCatChanged());\n\n        // #0004798: SSL should be included in viewId\n        if ($config->isSsl()) {\n            $viewId .= \"|ssl\";\n        }\n\n        // #0002866: external global viewID addition\n        if (function_exists('customGetViewId')) {\n            $externalViewId = customGetViewId();\n\n            if ($externalViewId !== null) {\n                $viewId .= '|' . md5(serialize($externalViewId));\n            }\n        }\n\n        return $viewId;\n    }\n\n    /**\n     * Generates base for view id.\n     *\n     * @return string\n     */\n    protected function generateViewIdBase()\n    {\n        $languageId = Registry::getLang()->getBaseLanguage();\n        $currencyId = (int) Registry::getConfig()->getShopCurrency();\n\n        return \"ox|$languageId|$currencyId\";\n    }\n\n    /**\n     * Template variable getter. Returns true if sorting is on\n     *\n     * @return bool\n     */\n    public function showSorting()\n    {\n        return $this->_blShowSorting && Registry::getConfig()->getConfigParam('blShowSorting');\n    }\n\n    /**\n     * Set array of component objects\n     *\n     * @param array $components array of components objects\n     */\n    public function setComponents($components = null)\n    {\n        $this->_oaComponents = $components;\n    }\n\n    /**\n     * Get array of component objects\n     *\n     * @return array\n     */\n    public function getComponents()\n    {\n        return $this->_oaComponents;\n    }\n\n    /**\n     * Get component object\n     *\n     * @param string $name name of component object\n     *\n     * @return object\n     */\n    public function getComponent($name)\n    {\n        if (isset($name) && isset($this->_oaComponents[$name])) {\n            return $this->_oaComponents[$name];\n        }\n    }\n\n    /**\n     * Set flag if current view is an order view\n     *\n     * @param bool $isOrderStep flag if current view is an order view\n     */\n    public function setIsOrderStep($isOrderStep = null)\n    {\n        $this->_blIsOrderStep = $isOrderStep;\n    }\n\n    /**\n     * Get flag if current view is an order view\n     *\n     * @return bool\n     */\n    public function getIsOrderStep()\n    {\n        return $this->_blIsOrderStep;\n    }\n\n\n    /**\n     * Active category setter\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Category $category active category\n     */\n    public function setActiveCategory($category)\n    {\n        $this->_oActCategory = $category;\n    }\n\n    /**\n     * Returns active category\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Category|null\n     */\n    public function getActiveCategory()\n    {\n        return $this->_oActCategory;\n    }\n\n    /**\n     * Get list type\n     *\n     * @return string list type\n     */\n    public function getListType()\n    {\n        if ($this->_sListType == null) {\n            if ($listType = Registry::getRequest()->getRequestEscapedParameter('listtype')) {\n                $this->_sListType = $listType;\n            } elseif ($listType = Registry::getConfig()->getGlobalParameter('listtype')) {\n                $this->_sListType = $listType;\n            }\n        }\n\n        return $this->_sListType;\n    }\n\n    /**\n     * Returns list type\n     *\n     * @return string\n     */\n    public function getListDisplayType()\n    {\n        if ($this->_sListDisplayType == null) {\n            $this->_sListDisplayType = $this->getCustomListDisplayType();\n\n            if (!$this->_sListDisplayType) {\n                $this->_sListDisplayType = Registry::getConfig()->getConfigParam('sDefaultListDisplayType');\n            }\n\n            $this->_sListDisplayType = in_array((string) $this->_sListDisplayType, $this->_aListDisplayTypes) ?\n                $this->_sListDisplayType : 'infogrid';\n\n            // writing to session\n            if (Registry::getRequest()->getRequestEscapedParameter('ldtype')) {\n                Registry::getSession()->setVariable('ldtype', $this->_sListDisplayType);\n            }\n        }\n\n        return $this->_sListDisplayType;\n    }\n\n    /**\n     * Returns changed default list type\n     *\n     * @return string\n     */\n    public function getCustomListDisplayType()\n    {\n        if ($this->_sCustomListDisplayType == null) {\n            $this->_sCustomListDisplayType = Registry::getRequest()->getRequestEscapedParameter('ldtype');\n\n            if (!$this->_sCustomListDisplayType) {\n                $this->_sCustomListDisplayType = Registry::getSession()->getVariable('ldtype');\n            }\n        }\n\n        return $this->_sCustomListDisplayType;\n    }\n\n    /**\n     * List type setter\n     *\n     * @param string $type type of list\n     */\n    public function setListType($type)\n    {\n        $this->_sListType = $type;\n        Registry::getConfig()->setGlobalParameter('listtype', $type);\n    }\n\n    /**\n     * Returns currency switching option\n     *\n     * @return bool\n     */\n    public function loadCurrency()\n    {\n        if ($this->_blLoadCurrency == null) {\n            $this->_blLoadCurrency = false;\n            if ($loadCurrency = Registry::getConfig()->getConfigParam('bl_perfLoadCurrency')) {\n                $this->_blLoadCurrency = $loadCurrency;\n            }\n        }\n\n        return $this->_blLoadCurrency;\n    }\n\n    /**\n     * Returns true if empty categories are not loaded\n     *\n     * @return bool\n     */\n    public function dontShowEmptyCategories()\n    {\n        if ($this->_blDontShowEmptyCats == null) {\n            $this->_blDontShowEmptyCats = false;\n            if ($dontShowEmptyCats = Registry::getConfig()->getConfigParam('blDontShowEmptyCategories')) {\n                $this->_blDontShowEmptyCats = $dontShowEmptyCats;\n            }\n        }\n\n        return $this->_blDontShowEmptyCats;\n    }\n\n    /**\n     * Returns true if empty categories are not loaded\n     *\n     * @return bool\n     */\n    public function showCategoryArticlesCount()\n    {\n        return Registry::getConfig()->getConfigParam('bl_perfShowActionCatArticleCnt');\n    }\n\n    /**\n     * Returns if language should be loaded\n     *\n     * @return bool\n     */\n    public function isLanguageLoaded()\n    {\n        if ($this->_blLoadLanguage == null) {\n            $this->_blLoadLanguage = false;\n            if ($loadLanguage = Registry::getConfig()->getConfigParam('bl_perfLoadLanguages')) {\n                $this->_blLoadLanguage = $loadLanguage;\n            }\n        }\n\n        return $this->_blLoadLanguage;\n    }\n\n    /**\n     * Returns item count in top navigation of categories\n     *\n     * @return integer\n     */\n    public function getTopNavigationCatCnt()\n    {\n        if ($this->_iTopCatNavItmCnt == null) {\n            $topCategoryNavigationItemsCount = Registry::getConfig()->getConfigParam('iTopNaviCatCount');\n            $this->_iTopCatNavItmCnt = $topCategoryNavigationItemsCount ? $topCategoryNavigationItemsCount : 5;\n        }\n\n        return $this->_iTopCatNavItmCnt;\n    }\n\n    /**\n     * Returns sorted column parameter name\n     *\n     * @return string\n     */\n    public function getSortOrderByParameterName()\n    {\n        return 'listorderby';\n    }\n\n    /**\n     * Returns sorted column direction parameter name\n     *\n     * @return string\n     */\n    public function getSortOrderParameterName()\n    {\n        return 'listorder';\n    }\n\n\n    /**\n     * Returns page sort ident. It is used as ident in session variable aSorting[ident]\n     *\n     * @return string\n     */\n    public function getSortIdent()\n    {\n        return 'alist';\n    }\n\n    /**\n     * Returns default category sorting for selected category\n     *\n     * @return null\n     */\n    public function getDefaultSorting()\n    {\n        return null;\n    }\n\n    /**\n     * Returns default category sorting for selected category\n     *\n     * @return array\n     */\n    public function getUserSelectedSorting()\n    {\n        $request = Registry::get(\\OxidEsales\\Eshop\\Core\\Request::class);\n        $sortBy = $request->getRequestParameter($this->getSortOrderByParameterName());\n        $sortOrder = $request->getRequestParameter($this->getSortOrderParameterName());\n\n        if ((new SortingValidator())->isValid($sortBy, $sortOrder)) {\n            return ['sortby' => $sortBy, 'sortdir' => $sortOrder];\n        }\n    }\n\n    /**\n     * Returns sorting variable from session\n     *\n     * @param string $sortIdent sorting indent\n     *\n     * @return array\n     */\n    public function getSavedSorting($sortIdent)\n    {\n        $sorting = Registry::getSession()->getVariable('aSorting');\n        if (isset($sorting[$sortIdent])) {\n            return $sorting[$sortIdent];\n        }\n    }\n\n    /**\n     * Set sorting column name\n     *\n     * @param string $column - column name\n     */\n    public function setListOrderBy($column)\n    {\n        $this->_sListOrderBy = $column;\n    }\n\n    /**\n     * Set sorting directions\n     *\n     * @param string $direction - direction desc / asc\n     */\n    public function setListOrderDirection($direction)\n    {\n        $this->_sListOrderDir = $direction;\n    }\n\n    /**\n     * Template variable getter. Returns string after the list is ordered by\n     *\n     * @return array\n     */\n    public function getListOrderBy()\n    {\n        //if column is with table name split it\n        $columns = $this->_sListOrderBy ? explode('.', $this->_sListOrderBy) : [];\n        if (count($columns) > 1) {\n            return $columns[1];\n        }\n\n        return $this->_sListOrderBy;\n    }\n\n    /**\n     * Template variable getter. Returns list order direction\n     *\n     * @return array\n     */\n    public function getListOrderDirection()\n    {\n        return $this->_sListOrderDir;\n    }\n\n    /**\n     * Sets the view parameter \"meta_description\"\n     *\n     * @param string $description prepared string for description\n     *\n     * @return null\n     */\n    public function setMetaDescription($description)\n    {\n        return $this->_sMetaDescription = $description;\n    }\n\n    /**\n     * Sets the view parameter 'meta_keywords'\n     *\n     * @param string $keywords prepared string for meta keywords\n     *\n     * @return null\n     */\n    public function setMetaKeywords($keywords)\n    {\n        return $this->_sMetaKeywords = $keywords;\n    }\n\n    /**\n     * Fetches meta data (description or keywords) from seo table\n     *\n     * @param string $dataType data type \"oxkeywords\" or \"oxdescription\"\n     *\n     * @return string\n     */\n    protected function getMetaFromSeo($dataType)\n    {\n        $seoObjectId = $this->getSeoObjectId();\n        $baseLanguageId = Registry::getLang()->getBaseLanguage();\n        $shopId = Registry::getConfig()->getShopId();\n\n        if (\n            $seoObjectId && Registry::getUtils()->seoIsActive() &&\n            ($keywords = Registry::getSeoEncoder()->getMetaData($seoObjectId, $dataType, $shopId, $baseLanguageId))\n        ) {\n            return $keywords;\n        }\n    }\n\n    /**\n     * Fetches meta data (description or keywords) from content table\n     *\n     * @param string $metaIdent meta content ident\n     *\n     * @return string\n     */\n    protected function getMetaFromContent($metaIdent)\n    {\n        if ($metaIdent) {\n            $content = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Content::class);\n            if (\n                $content->loadByIdent($metaIdent) &&\n                $content->oxcontents__oxactive->value\n            ) {\n                return Str::getStr()->strip_tags($content->oxcontents__oxcontent->value);\n            }\n        }\n    }\n\n    /**\n     * Template variable getter. Returns meta keywords\n     *\n     * @return string\n     */\n    public function getMetaKeywords()\n    {\n        if ($this->_sMetaKeywords === null) {\n            $this->_sMetaKeywords = false;\n\n            // set special meta keywords ?\n            if (($keywords = $this->getMetaFromSeo('oxkeywords'))) {\n                $this->_sMetaKeywords = $keywords;\n            } elseif (($keywords = $this->getMetaFromContent($this->_sMetaKeywordsIdent))) {\n                $this->_sMetaKeywords = $this->prepareMetaKeyword($keywords, false);\n            } else {\n                $this->_sMetaKeywords = $this->prepareMetaKeyword(false, true);\n            }\n        }\n\n        return $this->_sMetaKeywords;\n    }\n\n    /**\n     * Template variable getter. Returns meta description\n     *\n     * @return string\n     */\n    public function getMetaDescription()\n    {\n        if ($this->_sMetaDescription === null) {\n            $this->_sMetaDescription = false;\n\n            // set special meta description ?\n            if (($description = $this->getMetaFromSeo('oxdescription'))) {\n                $this->_sMetaDescription = $description;\n            } elseif (($description = $this->getMetaFromContent($this->_sMetaDescriptionIdent))) {\n                $this->_sMetaDescription = $this->prepareMetaDescription($description);\n            } else {\n                $this->_sMetaDescription = $this->prepareMetaDescription(false);\n            }\n        }\n\n        return $this->_sMetaDescription;\n    }\n\n    /**\n     * Get active currency\n     *\n     * @return object\n     */\n    public function getActCurrency()\n    {\n        return $this->_oActCurrency;\n    }\n\n    /**\n     * Active currency setter\n     *\n     * @param object $currency Currency object\n     */\n    public function setActCurrency($currency)\n    {\n        $this->_oActCurrency = $currency;\n    }\n\n    /**\n     * Template variable getter. Returns comparison article list count.\n     *\n     * @return integer\n     */\n    public function getCompareItemCount()\n    {\n        if ($this->_iCompItemsCnt === null) {\n            $items = Registry::getSession()->getVariable('aFiltcompproducts');\n            $this->_iCompItemsCnt = is_array($items) ? count($items) : 0;\n        }\n\n        return $this->_iCompItemsCnt;\n    }\n\n    /**\n     * Forces output no index meta data for current view\n     */\n    protected function forceNoIndex()\n    {\n        $this->_blForceNoIndex = true;\n    }\n\n    /**\n     * Marks that current view is marked as no index, no follow and\n     * article details links must contain no follow tags\n     *\n     * @return int\n     */\n    public function noIndex()\n    {\n        if ($this->_blForceNoIndex) {\n            $this->_iViewIndexState = VIEW_INDEXSTATE_NOINDEXFOLLOW;\n        } elseif (Registry::getRequest()->getRequestEscapedParameter('cur')) {\n            $this->_iViewIndexState = VIEW_INDEXSTATE_NOINDEXNOFOLLOW;\n        } elseif (0 < Registry::getRequest()->getRequestEscapedParameter(self::PAGE_NUMBER)) {\n            $this->_iViewIndexState = VIEW_INDEXSTATE_NOINDEXFOLLOW;\n        } else {\n            switch (Registry::getRequest()->getRequestEscapedParameter('fnc')) {\n                case 'tocomparelist':\n                case 'tobasket':\n                    $this->_iViewIndexState = VIEW_INDEXSTATE_NOINDEXNOFOLLOW;\n                    break;\n            }\n        }\n\n        return $this->_iViewIndexState;\n    }\n\n    /**\n     * Template variable getter. Returns header menu list\n     *\n     * @return array\n     */\n    public function getMenueList()\n    {\n        return $this->_aMenueList;\n    }\n\n    /**\n     * Header menu list setter\n     *\n     * @param array $menu menu list\n     */\n    public function setMenueList($menu)\n    {\n        $this->_aMenueList = $menu;\n    }\n\n    /**\n     * Sets number of articles per page to config value\n     */\n    protected function setNrOfArtPerPage()\n    {\n        $config = Registry::getConfig();\n\n        //setting default values to avoid possible errors showing article list\n        $numberOfCategoryArticles = $config->getConfigParam('iNrofCatArticles');\n\n        $numberOfCategoryArticles = ($numberOfCategoryArticles) ? $numberOfCategoryArticles : 10;\n\n        // checking if all needed data is set\n        switch ($this->getListDisplayType()) {\n            case 'grid':\n                $numbersOfCategoryArticles = $config->getConfigParam('aNrofCatArticlesInGrid');\n                break;\n            case 'line':\n            case 'infogrid':\n            default:\n                $numbersOfCategoryArticles = $config->getConfigParam('aNrofCatArticles');\n        }\n\n        if (!is_array($numbersOfCategoryArticles) || !isset($numbersOfCategoryArticles[0])) {\n            $numbersOfCategoryArticles = [$numberOfCategoryArticles];\n            $config->setConfigParam('aNrofCatArticles', $numbersOfCategoryArticles);\n        } else {\n            $numberOfCategoryArticles = $numbersOfCategoryArticles[0];\n        }\n\n        $viewConfig = $this->getViewConfig();\n        //value from user input\n        $session = Registry::getSession();\n        if (($articlesPerPage = (int) Registry::getRequest()->getRequestEscapedParameter('_artperpage'))) {\n            // M45 Possibility to push any \"Show articles per page\" number parameter\n            $numberOfCategoryArticles = (in_array($articlesPerPage, $numbersOfCategoryArticles))\n                ? $articlesPerPage\n                : $numberOfCategoryArticles;\n            $viewConfig->setViewConfigParam('iartPerPage', $numberOfCategoryArticles);\n            $session->setVariable('_artperpage', $numberOfCategoryArticles);\n        } elseif (($sessArtPerPage = $session->getVariable('_artperpage')) && is_numeric($sessArtPerPage)) {\n            // M45 Possibility to push any \"Show articles per page\" number parameter\n            $numberOfCategoryArticles = (in_array($sessArtPerPage, $numbersOfCategoryArticles))\n                ? $sessArtPerPage\n                : $numberOfCategoryArticles;\n            $viewConfig->setViewConfigParam('iartPerPage', $numberOfCategoryArticles);\n            $session->setVariable('_artperpage', $numberOfCategoryArticles);\n        } else {\n            $viewConfig->setViewConfigParam('iartPerPage', $numberOfCategoryArticles);\n        }\n\n        //setting number of articles per page to config value\n        $config->setConfigParam('iNrofCatArticles', $numberOfCategoryArticles);\n    }\n\n    /**\n     * Override this function to return object it which is used to identify its seo meta info\n     */\n    protected function getSeoObjectId()\n    {\n    }\n\n    /**\n     * Returns current view meta description data\n     *\n     * @param string $meta                  Category path\n     * @param int    $length                Max length of result, -1 for no truncation\n     * @param bool   $removeDuplicatedWords If true - performs additional duplicate cleaning\n     *\n     * @return  string  $string    converted string\n     */\n    protected function prepareMetaDescription($meta, $length = 1024, $removeDuplicatedWords = false)\n    {\n        if ($meta) {\n            $stringModifier = Str::getStr();\n            if ($length != -1) {\n                /* *\n                 * performance - we do not need a huge amount of initial text.\n                 * assume that effective text may be double longer than $length\n                 * and simple truncate it\n                 */\n                $doubleLength = ($length * 2);\n                $meta = $stringModifier->substr($meta, 0, $doubleLength);\n            }\n\n            // decoding html entities\n            $meta = $stringModifier->html_entity_decode($meta);\n            // stripping HTML tags\n            $meta = $stringModifier->strip_tags($meta);\n\n            // removing some special chars\n            $meta = $stringModifier->cleanStr($meta);\n\n            // removing duplicate words\n            if ($removeDuplicatedWords) {\n                $meta = $this->removeDuplicatedWords($meta, Registry::getConfig()->getConfigParam('aSkipTags'));\n            }\n\n            // some special cases\n            $meta = str_replace(' ,', ',', $meta);\n            $pattern = [\"/,[\\s+\\-*]*,/\", \"/\\s+,/\"];\n            $meta = $stringModifier->preg_replace($pattern, ',', $meta);\n            $meta = Registry::getUtilsString()->minimizeTruncateString($meta, $length);\n            $meta = $stringModifier->htmlspecialchars($meta);\n\n            return trim($meta);\n        }\n    }\n\n    /**\n     * Returns current view keywords separated by comma\n     *\n     * @param string $keywords              Data to use as keywords\n     * @param bool   $removeDuplicatedWords If true - performs additional duplicate cleaning\n     *\n     * @return string of keywords separated by comma\n     */\n    protected function prepareMetaKeyword($keywords, $removeDuplicatedWords = true)\n    {\n        $string = $this->prepareMetaDescription($keywords, -1, false);\n\n        if ($removeDuplicatedWords) {\n            $string = $this->removeDuplicatedWords($string, Registry::getConfig()->getConfigParam('aSkipTags'));\n        }\n\n        return trim($string);\n    }\n\n    /**\n     * Removes duplicated words (not case sensitive)\n     *\n     * @param mixed $input    array of string or string\n     * @param array $skipTags in admin defined strings\n     *\n     * @return string of words separated by comma\n     */\n    protected function removeDuplicatedWords($input, $skipTags = [])\n    {\n        $stringModifier = Str::getStr();\n        if (is_array($input)) {\n            $input = implode(\" \", $input);\n        }\n\n        // removing some usually met characters..\n        $input = $stringModifier->preg_replace(\"/[\" . preg_quote($this->_sRemoveMetaChars, \"/\") . \"]/\", \" \", $input);\n\n        // splitting by word\n        $strings = $stringModifier->preg_split(\"/[\\s,]+/\", $input);\n\n        if ($count = count($skipTags)) {\n            for ($num = 0; $num < $count; $num++) {\n                $skipTags[$num] = $stringModifier->strtolower($skipTags[$num]);\n            }\n        }\n        $count = count($strings);\n        for ($num = 0; $num < $count; $num++) {\n            $strings[$num] = $stringModifier->strtolower($strings[$num]);\n            // removing in admin defined strings\n            if (!$strings[$num] || in_array($strings[$num], $skipTags)) {\n                unset($strings[$num]);\n            }\n        }\n\n        // duplicates\n        return implode(', ', array_unique($strings));\n    }\n\n    /**\n     * Returns array of params => values which are used in hidden forms and as additional url params.\n     * NOTICE: this method SHOULD return raw (non encoded into entities) parameters, because values\n     * are processed by htmlentities() to avoid security and broken templates problems\n     *\n     * @return array\n     */\n    public function getNavigationParams()\n    {\n        $config = Registry::getConfig();\n        $params[self::CATEGORY_ID] = $this->getCategoryId();\n        $params[self::MANUFACTURER_ID] = Registry::getRequest()->getRequestEscapedParameter(self::MANUFACTURER_ID);\n\n        $params['listtype'] = $this->getListType();\n        $params['ldtype'] = $this->getCustomListDisplayType();\n        $params['actcontrol'] = $this->getClassKey();\n\n        // @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n        $params[self::RECOMMENDATION_ID] = Registry::getRequest()->getRequestEscapedParameter(self::RECOMMENDATION_ID);\n\n        $params[self::SEARCH_RECOMMENDATION] = Registry::getRequest()\n            ->getRequestEscapedParameter(self::SEARCH_RECOMMENDATION);\n        // END deprecated\n        $params[self::SEARCH_PARAM] = Registry::getRequest()->getRequestEscapedParameter(self::SEARCH_PARAM);\n\n        $params[self::SEARCH_VENDOR] = Registry::getRequest()->getRequestEscapedParameter(self::SEARCH_VENDOR);\n        $params[self::SEARCH_CATEGORY_ID] = Registry::getRequest()\n            ->getRequestEscapedParameter(self::SEARCH_CATEGORY_ID);\n        $params[self::SEARCH_MANUFACTURER] = Registry::getRequest()\n            ->getRequestEscapedParameter(self::SEARCH_MANUFACTURER);\n\n        $params = array_merge($params, $this->getViewConfig()->getAdditionalNavigationParameters());\n\n        return $params;\n    }\n\n    /**\n     * Sets sorting item config\n     *\n     * @param string $sortIdent sortable item id\n     * @param string $sortBy    sort field\n     * @param string $sortDir   sort direction (optional)\n     */\n    public function setItemSorting($sortIdent, $sortBy, $sortDir = null)\n    {\n        $sorting = Registry::getSession()->getVariable('aSorting');\n        $sorting[$sortIdent]['sortby'] = $sortBy;\n        $sorting[$sortIdent]['sortdir'] = $sortDir ? $sortDir : null;\n\n        Registry::getSession()->setVariable('aSorting', $sorting);\n    }\n\n    /**\n     * Returns sorting config for current item\n     *\n     * @param string $sortIdent sortable item id\n     *\n     * @return array\n     */\n    public function getSorting($sortIdent)\n    {\n        $sorting = null;\n\n        if ($sorting = $this->getUserSelectedSorting()) {\n            $this->setItemSorting($sortIdent, $sorting['sortby'], $sorting['sortdir']);\n        } elseif (!$sorting = $this->getSavedSorting($sortIdent)) {\n            $sorting = $this->getDefaultSorting();\n        }\n\n        if ($sorting) {\n            $this->setListOrderBy($sorting['sortby']);\n            $this->setListOrderDirection($sorting['sortdir']);\n        }\n\n        return $sorting;\n    }\n\n    /**\n     * Returns part of SQL query with sorting params\n     *\n     * @param string $ident sortable item id\n     *\n     * @return string\n     */\n    public function getSortingSql($ident)\n    {\n        $sorting = $this->getSorting($ident);\n        if (is_array($sorting)) {\n            $sortDir = isset($sorting['sortdir']) ? $sorting['sortdir'] : '';\n            if ($this->isAllowedSortingOrder($sortDir)) {\n                $sortBy = DatabaseProvider::getDb()->quoteIdentifier($sorting['sortby']);\n                return trim($sortBy . ' ' . $sortDir);\n            }\n        }\n    }\n\n    /**\n     * Returns title suffix used in template\n     *\n     * @return string\n     */\n    public function getTitleSuffix()\n    {\n        return Registry::getConfig()->getActiveShop()->oxshops__oxtitlesuffix->value;\n    }\n\n    /**\n     * Returns title page suffix used in template in lists\n     */\n    public function getTitlePageSuffix()\n    {\n    }\n\n    /**\n     * Returns title prefix used in template\n     *\n     * @return string\n     */\n    public function getTitlePrefix()\n    {\n        return Registry::getConfig()->getActiveShop()->oxshops__oxtitleprefix->value;\n    }\n\n\n    /**\n     * Returns full page title\n     *\n     * @return string\n     */\n    public function getPageTitle()\n    {\n        $titleParts = [];\n        $titleParts[] = $this->getTitlePrefix();\n        $titleParts[] = $this->getTitle();\n        $titleParts[] = $this->getTitleSuffix();\n        $titleParts[] = $this->getTitlePageSuffix();\n\n        $titleParts = array_filter($titleParts);\n        $title = implode(' | ', $titleParts);\n\n        return $this->replaceDoubleQuotesWithHTMLCharacters($title);\n    }\n\n\n    /**\n     * returns object, associated with current view.\n     * (the object that is shown in frontend)\n     *\n     * @param int $languageId language id\n     *\n     * @return object\n     */\n    protected function getSubject($languageId)\n    {\n        return null;\n    }\n\n    /**\n     * returns additional url params for dynamic url building\n     *\n     * @return string\n     */\n    public function getDynUrlParams()\n    {\n        $result = '';\n        $listType = $this->getListType();\n\n        switch ($listType) {\n            default:\n                $result .= $this->getViewConfig()->getDynUrlParameters($listType);\n                break;\n            case 'search':\n                $result .= \"&amp;listtype={$listType}\";\n                $result .= $this->appendUnescapedEncodedValue(self::SEARCH_PARAM);\n                $result .= $this->appendUnescapedValue(self::SEARCH_CATEGORY_ID);\n                $result .= $this->appendUnescapedValue(self::SEARCH_VENDOR);\n                $result .= $this->appendUnescapedValue(self::SEARCH_MANUFACTURER);\n                break;\n        }\n\n        return $result;\n    }\n\n    /**\n     * Get base link of current view\n     *\n     * @param int $languageId requested language\n     *\n     * @return string\n     */\n    public function getBaseLink($languageId = null)\n    {\n        if (!isset($languageId)) {\n            $languageId = Registry::getLang()->getBaseLanguage();\n        }\n\n        $config = Registry::getConfig();\n\n        if (Registry::getUtils()->seoIsActive()) {\n            if ($displayObj = $this->getSubject($languageId)) {\n                $url = $displayObj->getLink($languageId);\n            } else {\n                $encoder = Registry::getSeoEncoder();\n                $constructedUrl = $config->getShopHomeUrl($languageId) . $this->getSeoRequestParams();\n                $url = $encoder->getStaticUrl($constructedUrl, $languageId);\n            }\n        }\n\n        if (!$url) {\n            $constructedUrl = $config->getShopCurrentURL($languageId) . $this->getRequestParams();\n            $url = Registry::getUtilsUrl()->processUrl($constructedUrl, true, null, $languageId);\n        }\n\n        return $url;\n    }\n\n\n    /**\n     * Get link of current view. In url its include also page number if it is list page\n     *\n     * @param int $languageId requested language\n     *\n     * @return string\n     */\n    public function getLink($languageId = null)\n    {\n        return $this->addPageNrParam($this->getBaseLink($languageId), $this->getActPage(), $languageId);\n    }\n\n    /**\n     * Returns view object canonical url\n     */\n    public function getCanonicalUrl()\n    {\n    }\n\n    /**\n     * Return array of id to form recommend list.\n     * Should be overridden if need.\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @return array\n     */\n    public function getSimilarRecommListIds()\n    {\n        return false;\n    }\n\n    /**\n     * Template variable getter. Returns search parameter for Html\n     * So far this method is implemented in search (search.php) view.\n     */\n    public function getSearchParamForHtml()\n    {\n    }\n\n    /**\n     * collects _GET parameters used by eShop and returns uri\n     *\n     * @param bool $addPageNumber if TRUE - page number will be added\n     *\n     * @return string\n     */\n    protected function getRequestParams($addPageNumber = true)\n    {\n        $class = $this->getClassKey();\n        $function = $this->getFncName();\n\n        $forbiddenFunctions = [\n            'tobasket',\n            'login_noredirect',\n            'addVoucher',\n            'moveleft',\n            'moveright',\n            'deleteReviewAndRating',\n        ];\n\n        if (\\in_array($function, $forbiddenFunctions, true)) {\n            $function = '';\n        }\n\n        // #680\n        $url = \"cl={$class}\";\n        if ($function) {\n            $url .= \"&amp;fnc={$function}\";\n        }\n        $url .= $this->appendValue(self::CATEGORY_ID);\n        $url .= $this->appendValue(self::MANUFACTURER_ID);\n        $url .= $this->appendValue(self::ACTIVE_PRODUCT_ID);\n        $url .= $this->appendBasenameValue(self::PAGE);\n        $url .= $this->appendBasenameValue(self::TEMPLATE);\n        $url .= $this->appendValue(self::LOAD_ID);\n        // don't include page number for navigation\n        // it will be done in \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::generatePageNavigation\n        if ($addPageNumber) {\n            $url .= $this->appendValue(self::PAGE_NUMBER);\n        }\n        // #1184M - specialchar search\n        $url .= $this->appendUnescapedEncodedValue(self::SEARCH_PARAM);\n        $url .= $this->appendValue(self::SEARCH_CATEGORY_ID);\n        $url .= $this->appendValue(self::SEARCH_VENDOR);\n        $url .= $this->appendValue(self::SEARCH_MANUFACTURER);\n        $url .= $this->appendValue(self::SEARCH_RECOMMENDATION);\n\n        // @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n        $url .= $this->appendValue(self::RECOMMENDATION_ID);\n        // END deprecated\n\n        $url .= $this->getViewConfig()->addRequestParameters();\n\n        return $url;\n    }\n\n    /**\n     * collects _GET parameters used by eShop SEO and returns uri\n     *\n     * @return string\n     */\n    protected function getSeoRequestParams()\n    {\n        $class = $this->getClassKey();\n        $function = $this->getFncName();\n\n        // #921 S\n        $forbiddenFunctions = ['tobasket', 'login_noredirect', 'addVoucher'];\n        if (\\in_array($function, $forbiddenFunctions, true)) {\n            $function = '';\n        }\n        // #680\n        $url = \"cl={$class}\";\n        if ($function) {\n            $url .= \"&amp;fnc={$function}\";\n        }\n\n        $url .= $this->appendBasenameValue(self::PAGE);\n        $url .= $this->appendBasenameValue(self::TEMPLATE);\n        $url .= $this->appendValue(self::LOAD_ID);\n        $url .= $this->appendValue(self::PAGE_NUMBER);\n\n        return $url;\n    }\n\n    /**\n     * Returns show category search\n     *\n     * @return bool\n     */\n    public function showSearch()\n    {\n        return !(Registry::getConfig()->getConfigParam('blDisableNavBars') && $this->getIsOrderStep());\n    }\n\n    /**\n     * Template variable getter. Returns sorting columns\n     *\n     * @return array\n     */\n    public function getSortColumns()\n    {\n        if ($this->_aSortColumns === null) {\n            $this->setSortColumns(Registry::getConfig()->getConfigParam('aSortCols'));\n        }\n\n        return $this->_aSortColumns;\n    }\n\n\n    /**\n     * Set sorting columns\n     *\n     * @param array $sortColumns array of column names array('name1', 'name2',...)\n     */\n    public function setSortColumns($sortColumns)\n    {\n        $this->_aSortColumns = $sortColumns;\n    }\n\n    /**\n     * Template variable getter. Returns search string\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     */\n    public function getRecommSearch()\n    {\n    }\n\n    /**\n     * Template variable getter. Returns payment id\n     */\n    public function getPaymentList()\n    {\n    }\n\n    /**\n     * Template variable getter. Returns active recommendation lists\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\RecommendationList|false\n     */\n    public function getActiveRecommList()\n    {\n        if ($this->_oActiveRecommList === null) {\n            $this->_oActiveRecommList = false;\n            if ($recommendationListId = Registry::getRequest()->getRequestEscapedParameter(self::RECOMMENDATION_ID)) {\n                $this->_oActiveRecommList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\RecommendationList::class);\n                $this->_oActiveRecommList->load($recommendationListId);\n            }\n        }\n\n        return $this->_oActiveRecommList;\n    }\n\n    /**\n     * Template variable getter. Returns accessoires of article\n     */\n    public function getAccessoires()\n    {\n    }\n\n    /**\n     * Template variable getter. Returns crosssellings\n     */\n    public function getCrossSelling()\n    {\n    }\n\n    /**\n     * Template variable getter. Returns similar article list\n     */\n    public function getSimilarProducts()\n    {\n    }\n\n    /**\n     * Template variable getter. Returns list of customer also bought thies products\n     */\n    public function getAlsoBoughtTheseProducts()\n    {\n    }\n\n    /**\n     * Return the active article id\n     */\n    public function getArticleId()\n    {\n    }\n\n    /**\n     * Returns current view title. Default is search for translation of PAGE_TITLE_{view_class_name}\n     *\n     * @return string\n     */\n    public function getTitle()\n    {\n        $language = Registry::getLang();\n        $translationName = 'PAGE_TITLE_' . strtoupper(Registry::getConfig()->getActiveView()->getClassKey());\n        $translated = $language->translateString($translationName, Registry::getLang()->getBaseLanguage(), false);\n\n        return $translationName == $translated ? null : $translated;\n    }\n\n    /**\n     * Returns active lang suffix\n     * usally it used in html lang attr to allow the browser to interpret the page in the right language\n     * e.g. to support hyphons\n     * @return string\n     */\n    public function getActiveLangAbbr()\n    {\n        if (!isset($this->_sActiveLangAbbr)) {\n            $languageService = Registry::getLang();\n            if (Registry::getConfig()->getConfigParam('bl_perfLoadLanguages')) {\n                $languages = $languageService->getLanguageArray();\n                foreach ($languages as $language) {\n                    if ($language->selected) {\n                        $this->_sActiveLangAbbr = $language->abbr;\n                        break;\n                    }\n                }\n            } else {\n                // Performance\n                // use oxid shop internal languageAbbr, this might be correct in the most cases but not guaranteed to\n                // be that configured in the admin backend for that language\n                $this->_sActiveLangAbbr = $languageService->getLanguageAbbr();\n            }\n        }\n\n        return $this->_sActiveLangAbbr;\n    }\n\n    /**\n     * Sets and caches default parameters for shop object and returns it.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Shop $shop current shop object\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\ViewConfig Current shop object\n     */\n    public function addGlobalParams($shop = null)\n    {\n        $viewConfig = parent::addGlobalParams($shop);\n\n        $this->setNrOfArtPerPage();\n\n        return $viewConfig;\n    }\n\n    /**\n     * Template variable getter. Returns additional params for url\n     *\n     * @return string\n     */\n    public function getAdditionalParams()\n    {\n        if ($this->_sAdditionalParams === null) {\n            // #1018A\n            $this->_sAdditionalParams = parent::getAdditionalParams();\n            $this->_sAdditionalParams .= 'cl=' . Registry::getConfig()->getTopActiveView()->getClassKey();\n\n            // #1834M - special char search\n            $this->_sAdditionalParams .= $this->appendUnescapedEncodedValue(self::SEARCH_PARAM);\n            $this->_sAdditionalParams .= $this->appendValue(self::SEARCH_CATEGORY_ID);\n            $this->_sAdditionalParams .= $this->appendValue(self::SEARCH_VENDOR);\n            $this->_sAdditionalParams .= $this->appendValue(self::SEARCH_MANUFACTURER);\n            $this->_sAdditionalParams .= $this->appendValue(self::CATEGORY_ID);\n            $this->_sAdditionalParams .= $this->appendValue(self::MANUFACTURER_ID);\n\n            $this->_sAdditionalParams .= $this->getViewConfig()->getAdditionalParameters();\n        }\n\n        return $this->_sAdditionalParams;\n    }\n\n    /**\n     * Generates URL for page navigation\n     *\n     * @return string $url String with working page url.\n     */\n    public function generatePageNavigationUrl()\n    {\n        return Registry::getConfig()->getShopHomeUrl() . $this->getRequestParams(false);\n    }\n\n    /**\n     * Adds page number parameter to url and returns modified url, if page number 0 drops from url\n     *\n     * @param string $url        Url to add page number\n     * @param int    $page       Active page number\n     * @param int    $languageId Language id\n     *\n     * @return string\n     */\n    protected function addPageNrParam($url, $page, $languageId = null)\n    {\n        if ($page) {\n            if ((strpos($url, 'pgNr='))) {\n                $url = preg_replace('/pgNr=[0-9]*/', 'pgNr=' . $page, $url);\n            } else {\n                $url .= ((strpos($url, '?') === false) ? '?' : '&amp;') . 'pgNr=' . $page;\n            }\n        } else {\n            $url = preg_replace('/pgNr=[0-9]*/', '', $url);\n            $url = preg_replace('/\\&amp\\;\\&amp\\;/', '&amp;', $url);\n            $url = preg_replace('/\\?\\&amp\\;/', '?', $url);\n            $url = preg_replace('/\\&amp\\;$/', '', $url);\n        }\n\n        return $url;\n    }\n\n    /**\n     * Template variable getter. Returns page navigation\n     */\n    public function getPageNavigation()\n    {\n    }\n\n    /**\n     * Template variable getter. Returns page navigation with default 7 positions\n     *\n     * @param int $positionCount Paging positions count ( 0 - unlimited )\n     *\n     * @return StdClass\n     */\n    public function getPageNavigationLimitedTop($positionCount = 7)\n    {\n        return $this->_oPageNavigation = $this->generatePageNavigation($positionCount);\n    }\n\n    /**\n     * Template variable getter. Returns page navigation with default 11 positions\n     *\n     * @param int $positionCount Paging positions count ( 0 - unlimited )\n     *\n     * @return StdClass\n     */\n    public function getPageNavigationLimitedBottom($positionCount = 11)\n    {\n        return $this->_oPageNavigation = $this->generatePageNavigation($positionCount);\n    }\n\n    /**\n     * Generates variables for page navigation\n     *\n     * @param int $positionCount Paging positions count ( 0 - unlimited )\n     *\n     * @return StdClass Object with page navigation data\n     */\n    public function generatePageNavigation($positionCount = 0)\n    {\n        startProfile('generatePageNavigation');\n\n        $pageNavigation = new StdClass();\n\n        $pageNavigation->NrOfPages = $this->_iCntPages;\n        $activePage = $this->getActPage();\n        $pageNavigation->actPage = $activePage + 1;\n        $url = $this->generatePageNavigationUrl();\n\n        if ($positionCount == 0 || ($positionCount >= $pageNavigation->NrOfPages)) {\n            $startNo = 2;\n            $finishNo = $pageNavigation->NrOfPages;\n        } else {\n            $tmpVal = $positionCount - 3;\n            $tmpVal2 = floor(($positionCount - 4) / 2);\n\n            // actual page is at the start\n            if ($pageNavigation->actPage <= $tmpVal) {\n                $startNo = 2;\n                $finishNo = $tmpVal + 1;\n            // actual page is at the end\n            } elseif ($pageNavigation->actPage >= $pageNavigation->NrOfPages - $tmpVal + 1) {\n                $startNo = $pageNavigation->NrOfPages - $tmpVal;\n                $finishNo = $pageNavigation->NrOfPages - 1;\n            // actual page is in the middle\n            } else {\n                $startNo = $pageNavigation->actPage - $tmpVal2;\n                $finishNo = $pageNavigation->actPage + $tmpVal2;\n            }\n        }\n\n        $pageNavigation->previousPage = null;\n        if ($activePage > 0) {\n            $pageNavigation->previousPage = $this->addPageNrParam($url, $activePage - 1);\n        }\n\n        $pageNavigation->nextPage = null;\n        if ($activePage < $pageNavigation->NrOfPages - 1) {\n            $pageNavigation->nextPage = $this->addPageNrParam($url, $activePage + 1);\n        }\n\n        if ($pageNavigation->NrOfPages > 1) {\n            for ($i = 1; $i < $pageNavigation->NrOfPages + 1; $i++) {\n                if ($i == 1 || $i == $pageNavigation->NrOfPages || ($i >= $startNo && $i <= $finishNo)) {\n                    $page = new stdClass();\n                    $page->url = $this->addPageNrParam($url, $i - 1);\n                    $page->selected = ($i == $pageNavigation->actPage) ? 1 : 0;\n                    $pageNavigation->changePage[$i] = $page;\n                }\n            }\n\n            // first/last one\n            $pageNavigation->firstpage = $this->addPageNrParam($url, 0);\n            $pageNavigation->lastpage = $this->addPageNrParam($url, $pageNavigation->NrOfPages - 1);\n        }\n\n        stopProfile('generatePageNavigation');\n\n        return $pageNavigation;\n    }\n\n    /**\n     * While ordering disables navigation controls if \\OxidEsales\\Eshop\\Core\\Config::blDisableNavBars\n     * is on and executes parent::render()\n     *\n     * @return string\n     */\n    public function render()\n    {\n        foreach (array_keys($this->_oaComponents) as $componentName) {\n            $this->_aViewData[$componentName] = $this->_oaComponents[$componentName]->render();\n        }\n\n        parent::render();\n\n        if ($this->getIsOrderStep()) {\n            // disabling navigation during order ...\n            if (Registry::getConfig()->getConfigParam('blDisableNavBars')) {\n                $this->_iNewsRealStatus = 1;\n                $this->setShowNewsletter(0);\n            }\n        }\n\n        $this->addListIdAndWidgetIdToViewData();\n\n        $config = Registry::getConfig();\n        $this->_aViewData[\"defaultLang\"] = $config->getConfigParam('sDefaultLang');\n        $this->_aViewData[\"shopURLParam\"] = ContainerFacade::getParameter('oxid_esales.shop_url');\n\n        return $this->_sThisTemplate;\n    }\n\n    private function addListIdAndWidgetIdToViewData()\n    {\n        $config = Registry::getConfig();\n\n        $className = Registry::getRequest()->getRequestEscapedParameter('actcl');\n        $listId = null;\n        $widgetId = null;\n\n        if ($className === 'start' && $config->getConfigParam('blEcondaRecommendationsStart')) {\n            $listId = 'recommendationsStart';\n            $widgetId = $config->getConfigParam('sEcondaWidgetIdStart');\n        } elseif ($className === 'alist' && $config->getConfigParam('blEcondaRecommendationsList')) {\n            $listId = 'recommendationsList';\n            $widgetId = $config->getConfigParam('sEcondaWidgetIdList');\n        } elseif ($className === 'details' && $config->getConfigParam('blEcondaRecommendationsDetails')) {\n            $listId = 'recommendationsDetails';\n            $widgetId = $config->getConfigParam('sEcondaWidgetIdDetails');\n        } elseif ($className === 'basket' && $config->getConfigParam('blEcondaRecommendationsBasket')) {\n            $listId = 'recommendationsBasket';\n            $widgetId = $config->getConfigParam('sEcondaWidgetIdBasket');\n        }\n\n        $this->_aViewData[\"sListId\"] = $listId;\n        $this->_aViewData[\"sWidgetId\"] = $widgetId;\n    }\n\n    /**\n     * Returns current view product object (if it is loaded)\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    public function getViewProduct()\n    {\n        return $this->getProduct();\n    }\n\n    /**\n     * Sets view product\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $product view product object\n     */\n    public function setViewProduct($product)\n    {\n        $this->_oProduct = $product;\n    }\n\n    /**\n     * Returns view product list\n     *\n     * @return array\n     */\n    public function getViewProductList()\n    {\n        return $this->_aArticleList;\n    }\n\n    /**\n     * Active page getter\n     *\n     * @return int\n     */\n    public function getActPage()\n    {\n        if ($this->_iActPage === null) {\n            $this->_iActPage = (int) Registry::getRequest()->getRequestEscapedParameter(self::PAGE_NUMBER);\n            $this->_iActPage = ($this->_iActPage < 0) ? 0 : $this->_iActPage;\n        }\n\n        return $this->_iActPage;\n    }\n\n    /**\n     * Returns active vendor set by categories component; if vendor is\n     * not set by component - will create vendor object and will try to\n     * load by id passed by request\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Vendor\n     */\n    public function getActVendor()\n    {\n        // if active vendor is not set yet - trying to load it from request params\n        // this may be useful when category component was unable to load active vendor\n        // and we still need some object to mount navigation info\n        if ($this->_oActVendor === null) {\n            $this->_oActVendor = false;\n            $vendorId = Registry::getRequest()->getRequestEscapedParameter(self::CATEGORY_ID);\n            $vendorId = $vendorId ? str_replace('v_', '', $vendorId) : $vendorId;\n            $vendor = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Vendor::class);\n            if ($vendor->load($vendorId)) {\n                $this->_oActVendor = $vendor;\n            }\n        }\n\n        return $this->_oActVendor;\n    }\n\n    /**\n     * Returns active Manufacturer set by categories component; if Manufacturer is\n     * not set by component - will create Manufacturer object and will try to\n     * load by id passed by request\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Manufacturer\n     */\n    public function getActManufacturer()\n    {\n        // if active Manufacturer is not set yet - trying to load it from request params\n        // this may be useful when category component was unable to load active Manufacturer\n        // and we still need some object to mount navigation info\n        if ($this->_oActManufacturer === null) {\n            $this->_oActManufacturer = false;\n            $manufacturerId = Registry::getRequest()->getRequestEscapedParameter(self::MANUFACTURER_ID);\n            $manufacturer = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Manufacturer::class);\n            if ($manufacturer->load($manufacturerId)) {\n                $this->_oActManufacturer = $manufacturer;\n            }\n        }\n\n        return $this->_oActManufacturer;\n    }\n\n    /**\n     * Active vendor setter\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Vendor $vendor active vendor\n     */\n    public function setActVendor($vendor)\n    {\n        $this->_oActVendor = $vendor;\n    }\n\n    /**\n     * Active Manufacturer setter\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Manufacturer $manufacturer active Manufacturer\n     */\n    public function setActManufacturer($manufacturer)\n    {\n        $this->_oActManufacturer = $manufacturer;\n    }\n\n    /**\n     * Returns fake object which is used to mount navigation info\n     *\n     * @return stdClass\n     */\n    public function getActSearch()\n    {\n        if ($this->_oActSearch === null) {\n            $this->_oActSearch = new stdClass();\n            $url = Registry::getConfig()->getShopHomeUrl();\n            $this->_oActSearch->link = \"{$url}cl=search\";\n        }\n\n        return $this->_oActSearch;\n    }\n\n    /**\n     * Returns category tree (if it is loaded)\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\CategoryList\n     */\n    public function getCategoryTree()\n    {\n        return $this->_oCategoryTree;\n    }\n\n    /**\n     * Category list setter\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\CategoryList $categoryTree category tree\n     */\n    public function setCategoryTree($categoryTree)\n    {\n        $this->_oCategoryTree = $categoryTree;\n    }\n\n    /**\n     * Returns Manufacturer tree (if it is loaded0\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\ManufacturerList\n     */\n    public function getManufacturerTree()\n    {\n        return $this->_oManufacturerTree;\n    }\n\n    /**\n     * Manufacturer tree setter\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\ManufacturerList $manufacturerTree Manufacturer tree\n     */\n    public function setManufacturerTree($manufacturerTree)\n    {\n        $this->_oManufacturerTree = $manufacturerTree;\n    }\n\n    /**\n     * Returns additional URL parameters which must be added to list products urls\n     */\n    public function getAddUrlParams()\n    {\n    }\n\n    /**\n     * Template variable getter. Returns Top 5 article list.\n     * Parameter \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::$_blTop5Action must be set to true.\n     *\n     * @param integer $count Product count in list\n     *\n     * @return array\n     */\n    public function getTop5ArticleList($count = null)\n    {\n        if ($this->_blTop5Action) {\n            if ($this->_aTop5ArticleList === null) {\n                $this->_aTop5ArticleList = false;\n                $config = Registry::getConfig();\n                if ($config->getConfigParam('bl_perfLoadAktion')) {\n                    // top 5 articles\n                    $artList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n                    $artList->loadTop5Articles($count);\n                    if ($artList->count()) {\n                        $this->_aTop5ArticleList = $artList;\n                    }\n                }\n            }\n        }\n\n        return $this->_aTop5ArticleList;\n    }\n\n    /**\n     * Template variable getter. Returns bargain article list\n     * Parameter \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::$_blBargainAction must be set to true.\n     *\n     * @return array\n     */\n    public function getBargainArticleList()\n    {\n        if ($this->_blBargainAction) {\n            if ($this->_aBargainArticleList === null) {\n                $this->_aBargainArticleList = [];\n                if (Registry::getConfig()->getConfigParam('bl_perfLoadAktion')) {\n                    $articleList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n                    $articleList->loadActionArticles('OXBARGAIN');\n                    if ($articleList->count()) {\n                        $this->_aBargainArticleList = $articleList;\n                    }\n                }\n            }\n        }\n\n        return $this->_aBargainArticleList;\n    }\n\n    /**\n     * Template variable getter. Returns if order price is lower than\n     * minimum order price setup (config param \"iMinOrderPrice\")\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; use oxBasket method\n     *\n     * @return bool\n     */\n    public function isLowOrderPrice()\n    {\n        $session = Registry::getSession();\n        if ($this->_blLowOrderPrice === null && ($basket = $session->getBasket())) {\n            $this->_blLowOrderPrice = $basket->isBelowMinOrderPrice();\n        }\n\n        return $this->_blLowOrderPrice;\n    }\n\n    /**\n     * Template variable getter. Returns formatted min order price value\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; use oxBasket method\n     *\n     * @return string\n     */\n    public function getMinOrderPrice()\n    {\n        if ($this->_sMinOrderPrice === null && $this->isLowOrderPrice()) {\n            $minOrderPrice = Price::getPriceInActCurrency(Registry::getConfig()->getConfigParam('iMinOrderPrice'));\n            $this->_sMinOrderPrice = Registry::getLang()->formatCurrency($minOrderPrice);\n        }\n\n        return $this->_sMinOrderPrice;\n    }\n\n    /**\n     * Template variable getter. Returns if newsletter is really active (for \"user\" template)\n     *\n     * @return integer\n     */\n    public function getNewsRealStatus()\n    {\n        return $this->_iNewsRealStatus;\n    }\n\n    /**\n     * Checks if current request parameters does not block SEO redirection process\n     *\n     * @return bool\n     */\n    protected function canRedirect()\n    {\n        foreach ($this->_aBlockRedirectParams as $param) {\n            if (Registry::getRequest()->getRequestEscapedParameter($param) !== null) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * Empty active product getter\n     */\n    public function getProduct()\n    {\n    }\n\n    /**\n     * Template variable getter. Returns Manufacturer list for search\n     *\n     * @return array\n     */\n    public function getManufacturerList()\n    {\n        return $this->_aManufacturerlist;\n    }\n\n    /**\n     * Sets Manufacturer list for search\n     *\n     * @param array $list manufacturer list\n     */\n    public function setManufacturerList($list)\n    {\n        $this->_aManufacturerlist = $list;\n    }\n\n    /**\n     * Sets root vendor\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Vendor $vendor vendor object\n     */\n    public function setRootVendor($vendor)\n    {\n        $this->_oRootVendor = $vendor;\n    }\n\n    /**\n     * Template variable getter. Returns root vendor\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Vendor\n     */\n    public function getRootVendor()\n    {\n        return $this->_oRootVendor;\n    }\n\n    /**\n     * Sets root Manufacturer\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Manufacturer $manufacturer manufacturer object\n     */\n    public function setRootManufacturer($manufacturer)\n    {\n        $this->_oRootManufacturer = $manufacturer;\n    }\n\n    /**\n     * Template variable getter. Returns root Manufacturer\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Manufacturer\n     */\n    public function getRootManufacturer()\n    {\n        return $this->_oRootManufacturer;\n    }\n\n    /**\n     * Template variable getter. Returns vendor id\n     *\n     * @return string\n     */\n    public function getVendorId()\n    {\n        if ($this->_sVendorId === null) {\n            $this->_sVendorId = false;\n            if (($vendor = $this->getActVendor())) {\n                $this->_sVendorId = $vendor->getId();\n            }\n        }\n\n        return $this->_sVendorId;\n    }\n\n    /**\n     * Template variable getter. Returns Manufacturer id\n     *\n     * @return string\n     */\n    public function getManufacturerId()\n    {\n        if ($this->_sManufacturerId === null) {\n            $this->_sManufacturerId = false;\n            if (($manufacturer = $this->getActManufacturer())) {\n                $this->_sManufacturerId = $manufacturer->getId();\n            }\n        }\n\n        return $this->_sManufacturerId;\n    }\n\n    /**\n     * Template variable getter. Returns more category\n     *\n     * @deprecated will be removed in v8.0\n     *\n     * @return object\n     */\n    public function getCatMoreUrl()\n    {\n        return Registry::getConfig()->getShopHomeUrl() . 'cnid=oxmore';\n    }\n\n    /**\n     * Template variable getter. Returns category path\n     *\n     * @return array\n     */\n    public function getCatTreePath()\n    {\n        return $this->_sCatTreePath;\n    }\n\n    /**\n     * Loads and returns oxContent object requested by its ident\n     *\n     * @param string $ident content identifier\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Content\n     */\n    public function getContentByIdent($ident)\n    {\n        if (!isset($this->_aContents[$ident])) {\n            $this->_aContents[$ident] = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Content::class);\n            $this->_aContents[$ident]->loadByIdent($ident);\n        }\n\n        return $this->_aContents[$ident];\n    }\n\n    /**\n     * Default content category getter, returns FALSE by default\n     *\n     * @return bool\n     */\n    public function getContentCategory()\n    {\n        return false;\n    }\n\n    /**\n     * Returns array of fields which must be filled during registration\n     *\n     * @return array|bool\n     */\n    public function getMustFillFields()\n    {\n        if ($this->_aMustFillFields === null) {\n            $this->_aMustFillFields = false;\n\n            // passing must-be-filled-fields info\n            $mustFillFields = Registry::getConfig()->getConfigParam('aMustFillFields');\n            if (is_array($mustFillFields)) {\n                $this->_aMustFillFields = array_flip($mustFillFields);\n            }\n        }\n\n        return $this->_aMustFillFields;\n    }\n\n    /**\n     * Returns if field is required.\n     *\n     * @param string $field required field to check\n     *\n     * @return array|bool\n     */\n    public function isFieldRequired($field)\n    {\n        return isset($this->getMustFillFields()[$field]);\n    }\n\n    /**\n     * Form id getter. This id used to prevent double review entry submit\n     *\n     * @return string\n     */\n    public function getFormId()\n    {\n        if ($this->_sFormId === null) {\n            $this->_sFormId = Registry::getUtilsObject()->generateUId();\n            Registry::getSession()->setVariable('sessionuformid', $this->_sFormId);\n        }\n\n        return $this->_sFormId;\n    }\n\n    /**\n     * Checks if session form id matches with request form id\n     *\n     * @return bool\n     */\n    public function canAcceptFormData()\n    {\n        if ($this->_blCanAcceptFormData === null) {\n            $this->_blCanAcceptFormData = false;\n\n            $formId = Registry::getRequest()->getRequestEscapedParameter(\"uformid\");\n            $sessionFormId = Registry::getSession()->getVariable(\"sessionuformid\");\n\n            // testing if form and session ids matches\n            if ($formId && $formId === $sessionFormId) {\n                $this->_blCanAcceptFormData = true;\n            }\n\n            // regenerating form data\n            $this->getFormId();\n        }\n\n        return $this->_blCanAcceptFormData;\n    }\n\n    /**\n     * return last finished promotion list\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\ActionList\n     */\n    public function getPromoFinishedList()\n    {\n        if (isset($this->_oPromoFinishedList)) {\n            return $this->_oPromoFinishedList;\n        }\n        $this->_oPromoFinishedList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ActionList::class);\n        $this->_oPromoFinishedList->loadFinishedByCount(2);\n\n        return $this->_oPromoFinishedList;\n    }\n\n    /**\n     * return current promotion list\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\ActionList\n     */\n    public function getPromoCurrentList()\n    {\n        if (isset($this->_oPromoCurrentList)) {\n            return $this->_oPromoCurrentList;\n        }\n        $this->_oPromoCurrentList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ActionList::class);\n        $this->_oPromoCurrentList->loadCurrent();\n\n        return $this->_oPromoCurrentList;\n    }\n\n    /**\n     * return future promotion list\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\ActionList\n     */\n    public function getPromoFutureList()\n    {\n        if (isset($this->_oPromoFutureList)) {\n            return $this->_oPromoFutureList;\n        }\n        $this->_oPromoFutureList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ActionList::class);\n        $this->_oPromoFutureList->loadFutureByCount(2);\n\n        return $this->_oPromoFutureList;\n    }\n\n    /**\n     * should promotions list be shown?\n     *\n     * @return bool\n     */\n    public function getShowPromotionList()\n    {\n        if (isset($this->_blShowPromotions)) {\n            return $this->_blShowPromotions;\n        }\n        $this->_blShowPromotions = false;\n        if (oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ActionList::class)->areAnyActivePromotions()) {\n            $this->_blShowPromotions = (count($this->getPromoFinishedList()) + count($this->getPromoCurrentList()) +\n                                        count($this->getPromoFutureList())) > 0;\n        }\n\n        return $this->_blShowPromotions;\n    }\n\n    /**\n     * Checks if private sales is on\n     *\n     * @return bool\n     */\n    public function isEnabledPrivateSales()\n    {\n        if ($this->_blEnabledPrivateSales === null) {\n            $this->_blEnabledPrivateSales = (bool) Registry::getConfig()->getConfigParam('blPsLoginEnabled');\n            if ($this->_blEnabledPrivateSales && ($canPreview = Registry::getUtils()->canPreview()) !== null) {\n                $this->_blEnabledPrivateSales = !$canPreview;\n            }\n        }\n\n        return $this->_blEnabledPrivateSales;\n    }\n\n    /**\n     * Returns input field validation error array (if available)\n     *\n     * @return array\n     */\n    public function getFieldValidationErrors()\n    {\n        return Registry::getInputValidator()->getFieldValidationErrors();\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return null\n     */\n    public function getBreadCrumb()\n    {\n        return null;\n    }\n\n    /**\n     * Sets if active root category was changed\n     *\n     * @param bool $rootCategoryChanged root category changed\n     */\n    public function setRootCatChanged($rootCategoryChanged)\n    {\n        $this->_blRootCatChanged = $rootCategoryChanged;\n    }\n\n    /**\n     * Template variable getter. Returns true if active root category was changed\n     *\n     * @return bool\n     */\n    public function isRootCatChanged()\n    {\n        return $this->_blRootCatChanged;\n    }\n\n    /**\n     * Template variable getter. Returns user address\n     *\n     * @return array\n     */\n    public function getInvoiceAddress()\n    {\n        if ($this->_aInvoiceAddress == null) {\n            $invoiceAddress = Registry::getRequest()->getRequestEscapedParameter('invadr');\n            if ($invoiceAddress) {\n                $this->_aInvoiceAddress = $invoiceAddress;\n            }\n        }\n\n        return $this->_aInvoiceAddress;\n    }\n\n    /**\n     * Template variable getter. Returns user delivery address\n     *\n     * @return array\n     */\n    public function getDeliveryAddress()\n    {\n        if ($this->_aDeliveryAddress == null) {\n            $config = Registry::getConfig();\n            //do not show deladr if address was reloaded\n            if (!Registry::getRequest()->getRequestEscapedParameter('reloadaddress')) {\n                $this->_aDeliveryAddress = Registry::getRequest()->getRequestEscapedParameter('deladr');\n            }\n        }\n\n        return $this->_aDeliveryAddress;\n    }\n\n    /**\n     * Template variable setter. Sets user delivery address\n     *\n     * @param array $deliveryAddress delivery address\n     */\n    public function setDeliveryAddress($deliveryAddress)\n    {\n        $this->_aDeliveryAddress = $deliveryAddress;\n    }\n\n    /**\n     * Template variable setter. Sets user address\n     *\n     * @param array $address user address\n     */\n    public function setInvoiceAddress($address)\n    {\n        $this->_aInvoiceAddress = $address;\n    }\n\n    /**\n     * Template variable getter. Returns logged in user name\n     *\n     * @return string\n     */\n    public function getActiveUsername()\n    {\n        if ($this->_sActiveUsername == null) {\n            $this->_sActiveUsername = false;\n            $username = Registry::getRequest()->getRequestEscapedParameter('lgn_usr');\n            if ($username) {\n                $this->_sActiveUsername = $username;\n            } elseif ($user = $this->getUser()) {\n                $this->_sActiveUsername = $user->oxuser__oxusername->value;\n            }\n        }\n\n        return $this->_sActiveUsername;\n    }\n\n    /**\n     * Template variable getter. Returns user id from wish list\n     *\n     * @return string\n     */\n    public function getWishlistUserId()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('wishid');\n    }\n\n    /**\n     * Template variable getter. Returns searched category id\n     */\n    public function getSearchCatId()\n    {\n    }\n\n    /**\n     * Template variable getter. Returns searched vendor id\n     */\n    public function getSearchVendor()\n    {\n    }\n\n    /**\n     * Template variable getter. Returns searched Manufacturer id\n     */\n    public function getSearchManufacturer()\n    {\n    }\n\n    /**\n     * Template variable getter. Returns last seen products\n     */\n    public function getLastProducts()\n    {\n    }\n\n    /**\n     * Returns added basket item notification message type\n     *\n     * @return int\n     */\n    public function getNewBasketItemMsgType()\n    {\n        return (int) Registry::getConfig()->getConfigParam(\"iNewBasketItemMessage\");\n    }\n\n    /**\n     * Checks if feature is enabled\n     *\n     * @param string $name feature name\n     *\n     * @return bool\n     */\n    public function isActive($name)\n    {\n        return Registry::getConfig()->getConfigParam(\"bl\" . $name . \"Enabled\");\n    }\n\n    /**\n     * Checks if downloadable files are turned on\n     *\n     * @return bool\n     */\n    public function isEnabledDownloadableFiles()\n    {\n        return (bool) Registry::getConfig()->getConfigParam(\"blEnableDownloads\");\n    }\n\n    /**\n     * Returns true if \"Remember me\" are ON\n     *\n     * @return boolean\n     */\n    public function showRememberMe()\n    {\n        return (bool) Registry::getConfig()->getConfigParam('blShowRememberMe');\n    }\n\n    /**\n     * Returns true if articles shown in shop with VAT.\n     * Checks country VAT and options (show vat only in basket and check if b2b mode is activated).\n     *\n     * @return boolean\n     */\n    public function isVatIncluded()\n    {\n        if ($this->_blIsVatIncluded !== null) {\n            return $this->_blIsVatIncluded;\n        }\n\n        $config = Registry::getConfig();\n        /*\n         * Do not show \"inclusive VAT\" when:\n         *\n         *   B2B mode is activated\n         * OR\n         *   the VAT will only be calculated in the basket\n         * OR\n         *   the country does not bill VAT\n         *\n         * oxcountry__oxvatstatus: Vat status: 0 - Do not bill VAT, 1 - Do not bill VAT only if provided valid VAT ID\n         * if country is not available (no session) oxvatstatus->value will return null\n         */\n        if ($config->getConfigParam('blShowNetPrice') || $config->getConfigParam('bl_perfCalcVatOnlyForBasketOrder')) {\n            return $this->_blIsVatIncluded = false;\n        }\n\n        $user = $this->getUser();\n        if ($user !== false) {\n            if ($user->getFieldData('oxustid') && $user->getFieldData('oxustidstatus') == 1) {\n                return $this->_blIsVatIncluded = false;\n            }\n        } else {\n            $user = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n        }\n\n        $activeCountry = $user->getActiveCountry();\n        if ($activeCountry !== '') {\n            $country = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Country::class);\n            if (\n                $country->load($activeCountry) &&\n                $country->oxcountry__oxvatstatus->value !== null &&\n                $country->oxcountry__oxvatstatus->value == 0\n            ) {\n                return $this->_blIsVatIncluded = false;\n            }\n        }\n\n        return $this->_blIsVatIncluded = true;\n    }\n\n    /**\n     * Returns true if price calculation is activated\n     *\n     * @return boolean\n     */\n    public function isPriceCalculated()\n    {\n        return (bool) Registry::getConfig()->getConfigParam('bl_perfLoadPrice');\n    }\n\n    /**\n     * Template variable getter. Returns user name of searched wishlist\n     *\n     * @return string\n     */\n    public function getWishlistName()\n    {\n        if ($this->getUser()) {\n            $wishId = Registry::getRequest()->getRequestEscapedParameter('wishid');\n            $userId = ($wishId) ? $wishId : Registry::getSession()->getVariable('wishid');\n            if ($userId) {\n                $wishUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n                if ($wishUser->load($userId)) {\n                    return $wishUser;\n                }\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Get widget link for Ajax calls\n     *\n     * @return string\n     */\n    public function getWidgetLink()\n    {\n        return Registry::getConfig()->getWidgetUrl();\n    }\n\n    /**\n     * Template variable getter. Returns article list count in comparison.\n     *\n     * @return integer\n     */\n    public function getCompareItemsCnt()\n    {\n        $compareController = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\CompareController::class);\n\n        return $compareController->getCompareItemsCnt();\n    }\n\n    /**\n     * @param string $sortOrder\n     *\n     * @return array\n     */\n    private function isAllowedSortingOrder($sortOrder)\n    {\n        $allowedSortOrders = array_merge((new SortingValidator())->getSortingOrders(), ['']);\n        return in_array(strtolower($sortOrder), $allowedSortOrders);\n    }\n\n    /**\n     * @param string $title\n     * @return string\n     */\n    private function replaceDoubleQuotesWithHTMLCharacters(string $title): string\n    {\n        return str_replace('\"', '&quot;', $title);\n    }\n\n    private function appendValue(string $param): string\n    {\n        $value = Registry::getRequest()->getRequestEscapedParameter($param);\n\n        return $value ? \"&amp;$param=$value\" : '';\n    }\n\n    private function appendBasenameValue(string $param): string\n    {\n        $value = Registry::getRequest()->getRequestEscapedParameter($param);\n\n        return $value ? \"&amp;$param=\" . basename($value) : '';\n    }\n\n    private function appendUnescapedEncodedValue(string $param): string\n    {\n        $value = Registry::getRequest()->getRequestParameter($param);\n\n        return $value ? \"&amp;$param=\" . rawurlencode($value) : '';\n    }\n\n    private function appendUnescapedValue(string $param): string\n    {\n        $value = Registry::getRequest()->getRequestParameter($param);\n\n        return $value ? \"&amp;$param=$value\" : '';\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/LinksController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse oxRegistry;\n\n/**\n * Interesting, useful links window.\n * Arranges interesting links window (contents may be changed in\n * administrator GUI) with short link description and URL. OXID\n * eShop -> LINKS.\n */\nclass LinksController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/info/links';\n\n    /**\n     * Links list.\n     *\n     * @var object\n     */\n    protected $_oLinksList = null;\n\n    /**\n     * Template variable getter. Returns links list\n     *\n     * @return object\n     */\n    public function getLinksList()\n    {\n        if ($this->_oLinksList === null) {\n            $this->_oLinksList = false;\n            // Load links\n            $oLinksList = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n            $oLinksList->init(\"oxlinks\");\n            $oLinksList->getList();\n            $this->_oLinksList = $oLinksList;\n        }\n\n        return $this->_oLinksList;\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n        $iBaseLanguage = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage();\n        $aPath['title'] = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('LINKS', $iBaseLanguage, false);\n        $aPath['link'] = $this->getLink();\n\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/ManufacturerListController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse oxManufacturer;\nuse oxRegistry;\nuse oxUBase;\n\n/**\n * List of articles for a selected Manufacturer.\n * Collects list of articles, according to it generates links for list gallery,\n * metatags (for search engines). Result - \"manufacturerlist\" template.\n * OXID eShop -> (Any selected shop product category).\n */\nclass ManufacturerListController extends \\OxidEsales\\Eshop\\Application\\Controller\\ArticleListController\n{\n    /**\n     * List type\n     *\n     * @var string\n     */\n    protected $_sListType = 'manufacturer';\n\n    /**\n     * List type\n     *\n     * @var string\n     */\n    protected $_blVisibleSubCats = null;\n\n    /**\n     * List type\n     *\n     * @var string\n     */\n    protected $_oSubCatList = null;\n\n    /**\n     * Recommlist\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @var object\n     */\n    protected $_oRecommList = null;\n\n    /**\n     * Template location\n     *\n     * @var string\n     */\n    protected $_sTplLocation = null;\n\n    /**\n     * Template location\n     *\n     * @var string\n     */\n    protected $_sCatTitle = null;\n\n    /**\n     * Page navigation\n     *\n     * @var object\n     */\n    protected $_oPageNavigation = null;\n\n    /**\n     * Marked which defines if current view is sortable or not\n     *\n     * @var bool\n     */\n    protected $_blShowSorting = true;\n\n    /**\n     * Current view search engine indexing state\n     *\n     * @var int\n     */\n    protected $_iViewIndexState = VIEW_INDEXSTATE_INDEX;\n\n    /**\n     * Executes parent::render(), loads active Manufacturer, prepares article\n     * list sorting rules. Loads list of articles which belong to this Manufacturer\n     * Generates page navigation data\n     * such as previous/next window URL, number of available pages, generates\n     * metatags info (\\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::_convertForMetaTags()) and returns\n     * name of template to render.\n     *\n     * @return  string  $this->_sThisTemplate   current template file name\n     */\n    public function render()\n    {\n        \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::render();\n\n        // load Manufacturer\n        if ($this->getManufacturerTree()) {\n            if (($oManufacturer = $this->getActManufacturer())) {\n                if ($oManufacturer->getId() != 'root') {\n                    // load the articles\n                    $this->getArticleList();\n\n                    // checking if requested page is correct\n                    $this->checkRequestedPage();\n\n                    // processing list articles\n                    $this->processListArticles();\n                }\n            }\n        }\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Returns product link type (OXARTICLE_LINKTYPE_MANUFACTURER)\n     *\n     * @return int\n     */\n    protected function getProductLinkType()\n    {\n        return OXARTICLE_LINKTYPE_MANUFACTURER;\n    }\n\n    /**\n     * Loads and returns article list of active Manufacturer.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Manufacturer $oManufacturer Manufacturer object\n     *\n     * @return array\n     */\n    protected function loadArticles($oManufacturer)\n    {\n        $sManufacturerId = $oManufacturer->getId();\n\n        // load only articles which we show on screen\n        $iNrofCatArticles = (int) \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iNrofCatArticles');\n        $iNrofCatArticles = $iNrofCatArticles ? $iNrofCatArticles : 1;\n\n        $oArtList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n        $oArtList->setSqlLimit($iNrofCatArticles * $this->getRequestPageNr(), $iNrofCatArticles);\n        $oArtList->setCustomSorting($this->getSortingSql($this->getSortIdent()));\n\n        // load the articles\n        $this->_iAllArtCnt = $oArtList->loadManufacturerArticles($sManufacturerId, $oManufacturer);\n\n        // counting pages\n        $this->_iCntPages = ceil($this->_iAllArtCnt / $iNrofCatArticles);\n\n        return [$oArtList, $this->_iAllArtCnt];\n    }\n\n    /**\n     * Returns active product id to load its seo meta info\n     *\n     * @return string\n     */\n    protected function getSeoObjectId()\n    {\n        if (($oManufacturer = $this->getActManufacturer())) {\n            return $oManufacturer->getId();\n        }\n    }\n\n    /**\n     * Modifies url by adding page parameters. When seo is on, url is additionally\n     * formatted by SEO engine\n     *\n     * @param string $sUrl  current url\n     * @param int    $iPage page number\n     * @param int    $iLang active language id\n     *\n     * @return string\n     */\n    protected function addPageNrParam($sUrl, $iPage, $iLang = null)\n    {\n        if (Registry::getUtils()->seoIsActive() && ($oManufacturer = $this->getActManufacturer())) {\n            if ($iPage) {\n                // only if page number > 0\n                return $oManufacturer->getBaseSeoLink($iLang, $iPage);\n            }\n        }\n\n        return parent::addPageNrParam($sUrl, $iPage, $iLang);\n    }\n\n    /**\n     * Returns current view Url\n     *\n     * @return string\n     */\n    public function generatePageNavigationUrl()\n    {\n        if ((Registry::getUtils()->seoIsActive() && ($oManufacturer = $this->getActManufacturer()))) {\n            return $oManufacturer->getLink();\n        } else {\n            return parent::generatePageNavigationUrl();\n        }\n    }\n\n    /**\n     * Template variable getter. Returns active object's reviews\n     *\n     * @return array\n     */\n    public function hasVisibleSubCats()\n    {\n        if ($this->_blVisibleSubCats === null) {\n            $this->_blVisibleSubCats = false;\n            if (($oManufacturerTree = $this->getManufacturerTree())) {\n                if (($oManufacturer = $this->getActManufacturer())) {\n                    if ($oManufacturer->getId() == 'root') {\n                        $this->_blVisibleSubCats = $oManufacturerTree->count();\n                        $this->_oSubCatList = $oManufacturerTree;\n                    }\n                }\n            }\n        }\n\n        return $this->_blVisibleSubCats;\n    }\n\n    /**\n     * Template variable getter. Returns active object's reviews\n     *\n     * @return array\n     */\n    public function getSubCatList()\n    {\n        if ($this->_oSubCatList === null) {\n            $this->_oSubCatList = $this->hasVisibleSubCats() ? $this->_oSubCatList : [];\n        }\n\n        return $this->_oSubCatList;\n    }\n\n    /**\n     * Template variable getter. Returns active object's reviews\n     *\n     * @return array\n     */\n    public function getArticleList()\n    {\n        if ($this->_aArticleList === null) {\n            $this->_aArticleList = [];\n            if (($oManufacturerTree = $this->getManufacturerTree())) {\n                $oManufacturer = $this->getActManufacturer();\n                if ($oManufacturer && ($oManufacturer->getId() != 'root') && $oManufacturer->getIsVisible()) {\n                    list($aArticleList, $iAllArtCnt) = $this->loadArticles($oManufacturer);\n                    if ($iAllArtCnt) {\n                        $this->_aArticleList = $aArticleList;\n                    }\n                }\n            }\n        }\n\n        return $this->_aArticleList;\n    }\n\n    /**\n     * Template variable getter. Returns template location\n     *\n     * @return string\n     */\n    public function getTitle()\n    {\n        if ($this->_sCatTitle === null) {\n            $this->_sCatTitle = '';\n            if ($oManufacturerTree = $this->getManufacturerTree()) {\n                if ($oManufacturer = $this->getActManufacturer()) {\n                    $this->_sCatTitle = $oManufacturer->oxmanufacturers__oxtitle->value;\n                }\n            }\n        }\n\n        return $this->_sCatTitle;\n    }\n\n    /**\n     * Template variable getter. Returns category path array\n     *\n     * @return array\n     */\n    public function getTreePath()\n    {\n        if ($oManufacturerTree = $this->getManufacturerTree()) {\n            return $oManufacturerTree->getPath();\n        }\n    }\n\n    /**\n     * Template variable getter. Returns active Manufacturer\n     *\n     * @return object\n     */\n    public function getActiveCategory()\n    {\n        if ($this->_oActCategory === null) {\n            $this->_oActCategory = false;\n            if (($oManufacturerTree = $this->getManufacturerTree())) {\n                if ($oManufacturer = $this->getActManufacturer()) {\n                    $this->_oActCategory = $oManufacturer;\n                }\n            }\n        }\n\n        return $this->_oActCategory;\n    }\n\n    /**\n     * Template variable getter. Returns template location\n     *\n     * @return string\n     */\n    public function getCatTreePath()\n    {\n        if ($this->_sCatTreePath === null) {\n            $this->_sCatTreePath = false;\n            if (($oManufacturerTree = $this->getManufacturerTree())) {\n                $this->_sCatTreePath = $oManufacturerTree->getPath();\n            }\n        }\n\n        return $this->_sCatTreePath;\n    }\n\n    /**\n     * Returns title suffix used in template\n     *\n     * @return string\n     */\n    public function getTitleSuffix()\n    {\n        if (is_object($this->getActManufacturer()->oxmanufacturers__oxshowsuffix) && $this->getActManufacturer()->oxmanufacturers__oxshowsuffix->value) {\n            return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActiveShop()->oxshops__oxtitlesuffix->value;\n        }\n\n        return '';\n    }\n\n    /**\n     * Calls and returns result of parent:: collectMetaKeyword();\n     *\n     * @param mixed $aCatPath                category path\n     * @param bool  $blRemoveDuplicatedWords remove duplicated words\n     *\n     * @return string\n     */\n    protected function prepareMetaKeyword($aCatPath, $blRemoveDuplicatedWords = true)\n    {\n        return parent::collectMetaKeyword($aCatPath);\n    }\n\n    /**\n     * Meta tags - description and keywords - generator for search\n     * engines. Uses string passed by parameters, cleans HTML tags,\n     * string duplicates, special chars. Also removes strings defined\n     * in $myConfig->aSkipTags (Admin area).\n     *\n     * @param mixed $aCatPath  category path\n     * @param int   $iLength   max length of result, -1 for no truncation\n     * @param bool  $blDescTag if true - performs additional duplicate cleaning\n     *\n     * @return  string  $sString    converted string\n     */\n    protected function prepareMetaDescription($aCatPath, $iLength = 1024, $blDescTag = false)\n    {\n        return parent::collectMetaDescription($aCatPath, $iLength, $blDescTag);\n    }\n\n    /**\n     * returns object, assosiated with current view.\n     * (the object that is shown in frontend)\n     *\n     * @param int $iLang language id\n     *\n     * @return object\n     */\n    protected function getSubject($iLang)\n    {\n        return $this->getActManufacturer();\n    }\n\n    /**\n     * Returns additional URL parameters which must be added to list products dynamic urls\n     *\n     * @return string\n     */\n    public function getAddUrlParams()\n    {\n        $sAddParams = parent::getAddUrlParams();\n        $sAddParams .= ($sAddParams ? '&amp;' : '') . \"listtype={$this->_sListType}\";\n        if ($oManufacturer = $this->getActManufacturer()) {\n            $sAddParams .= \"&amp;mnid=\" . $oManufacturer->getId();\n        }\n\n        return $sAddParams;\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n\n        $oCatTree = $this->getManufacturerTree();\n\n        if ($oCatTree) {\n            foreach ($oCatTree->getPath() as $oCat) {\n                $aCatPath = [];\n                $aCatPath['link'] = $oCat->getLink();\n                $aCatPath['title'] = $oCat->oxmanufacturers__oxtitle->value;\n\n                $aPaths[] = $aCatPath;\n            }\n        }\n\n        return $aPaths;\n    }\n\n    /**\n     * Template variable getter. Returns array of attribute values\n     * we do have here in this category\n     *\n     * @return array\n     */\n    public function getAttributes()\n    {\n        return null;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/MoreDetailsController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Article images gallery popup window.\n * If chosen article has more pictures there is ability to create\n * gallery of pictures.\n */\nclass MoreDetailsController extends \\OxidEsales\\Eshop\\Application\\Controller\\ArticleDetailsController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'moredetails';\n\n    /**\n     * Current article id\n     *\n     * @var string\n     */\n    protected $_sProductId = null;\n\n    /**\n     * Active picture id\n     *\n     * @var string\n     */\n    protected $_sActPicId = null;\n\n    /**\n     * Article zoom pictures\n     *\n     * @var array\n     */\n    protected $_aArtZoomPics = null;\n\n    /**\n     * Current view search engine indexing state\n     *\n     * @var int\n     */\n    protected $_iViewIndexState = VIEW_INDEXSTATE_NOINDEXNOFOLLOW;\n\n    /**\n     * Template variable getter. Returns current product id\n     *\n     * @return string\n     */\n    public function getProductId()\n    {\n        if ($this->_sProductId === null) {\n            $this->_sProductId = $this->getProduct()->getId();\n        }\n\n        return $this->_sProductId;\n    }\n\n    /**\n     * Template variable getter. Returns active picture id\n     *\n     * @return string\n     */\n    public function getActPictureId()\n    {\n        if ($this->_sActPicId === null) {\n            $this->_sActPicId = false;\n            $aPicGallery = $this->getProduct()->getPictureGallery();\n\n            if ($aPicGallery['ZoomPic']) {\n                $sActPicId = Registry::getRequest()->getRequestEscapedParameter('actpicid');\n                $this->_sActPicId = $sActPicId ? $sActPicId : 1;\n            }\n        }\n\n        return $this->_sActPicId;\n    }\n\n    /**\n     * Template variable getter. Returns article zoom pictures\n     *\n     * @return array\n     */\n    public function getArtZoomPics()\n    {\n        if ($this->_aArtZoomPics === null) {\n            $this->_aArtZoomPics = false;\n            //Get picture gallery\n            $aPicGallery = $this->getProduct()->getPictureGallery();\n            $blArtPic = $aPicGallery['ZoomPic'];\n            $aArtPics = $aPicGallery['ZoomPics'];\n\n            if ($blArtPic) {\n                $this->_aArtZoomPics = $aArtPics;\n            }\n        }\n\n        return $this->_aArtZoomPics;\n    }\n\n    /**\n     * Template variable getter. Returns active product\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    public function getProduct()\n    {\n        if ($this->_oProduct === null) {\n            $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            $oArticle->load(Registry::getRequest()->getRequestEscapedParameter('anid'));\n            $this->_oProduct = $oArticle;\n        }\n\n        return $this->_oProduct;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/NewsletterController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Email\\EmailValidatorServiceBridgeInterface;\n\n/**\n * Newsletter opt-in/out.\n * Arranges newsletter opt-in form, have some methods to confirm\n * user opt-in or remove user from newsletter list. OXID eShop ->\n * (Newsletter).\n */\nclass NewsletterController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Home country id\n     *\n     * @var string\n     */\n    protected $_sHomeCountryId = null;\n\n    /**\n     * Newletter status.\n     *\n     * @var integer\n     */\n    protected $_iNewsletterStatus = null;\n\n    /**\n     * User newsletter registration data.\n     *\n     * @var object\n     */\n    protected $_aRegParams = null;\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/info/newsletter';\n\n    /**\n     * Current view search engine indexing state\n     *\n     * @var int\n     */\n    protected $_iViewIndexState = VIEW_INDEXSTATE_NOINDEXNOFOLLOW;\n\n    /**\n     * Only loads newsletter subscriber data.\n     *\n     * Template variables:\n     * <b>aRegParams</b>\n     */\n    public function fill()\n    {\n        // loads submited values\n        $this->_aRegParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n    }\n\n    /**\n     * Checks for newsletter subscriber data, if OK - creates new user as\n     * subscriber or assigns existing user to newsletter group and sends\n     * confirmation email.\n     *\n     * Template variables:\n     * <b>success</b>, <b>error</b>, <b>aRegParams</b>\n     *\n     * @return bool\n     */\n    public function send()\n    {\n        $aParams = Registry::getRequest()->getRequestEscapedParameter(\"editval\");\n        $emailValidator = ContainerFacade::get(EmailValidatorServiceBridgeInterface::class);\n\n        // loads submited values\n        $this->_aRegParams = $aParams;\n\n        if (!$aParams['oxuser__oxusername']) {\n            Registry::getUtilsView()->addErrorToDisplay('ERROR_MESSAGE_COMPLETE_FIELDS_CORRECTLY');\n\n            return;\n        } elseif (!$emailValidator->isEmailValid($aParams['oxuser__oxusername'])) {\n            // #1052C - eMail validation added\n            Registry::getUtilsView()->addErrorToDisplay('MESSAGE_INVALID_EMAIL');\n\n            return;\n        }\n\n        $blSubscribe = Registry::getRequest()->getRequestEscapedParameter(\"subscribeStatus\");\n\n        $oUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n        $oUser->oxuser__oxusername = new Field($aParams['oxuser__oxusername'], Field::T_RAW);\n\n        // if such user does not exist\n        if (!$oUser->exists()) {\n            // and subscribe is off - error, on - create\n            if (!$blSubscribe) {\n                Registry::getUtilsView()->addErrorToDisplay('NEWSLETTER_EMAIL_NOT_EXIST');\n\n                return;\n            } else {\n                $oUser->oxuser__oxactive = new \\OxidEsales\\Eshop\\Core\\Field(1, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n                $oUser->oxuser__oxrights = new \\OxidEsales\\Eshop\\Core\\Field('user', \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n                $oUser->oxuser__oxshopid = new \\OxidEsales\\Eshop\\Core\\Field(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId(), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n                $oUser->oxuser__oxfname = new \\OxidEsales\\Eshop\\Core\\Field($aParams['oxuser__oxfname'], \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n                $oUser->oxuser__oxlname = new \\OxidEsales\\Eshop\\Core\\Field($aParams['oxuser__oxlname'], \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n                $oUser->oxuser__oxsal = new \\OxidEsales\\Eshop\\Core\\Field($aParams['oxuser__oxsal'], \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n                $oUser->oxuser__oxcountryid = new \\OxidEsales\\Eshop\\Core\\Field($aParams['oxuser__oxcountryid'], \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n                $blUserLoaded = $oUser->save();\n            }\n        } else {\n            $blUserLoaded = $oUser->load($oUser->getId());\n        }\n\n        // if user was added/loaded successfully and subscribe is on - subscribing to newsletter\n        if ($blSubscribe && $blUserLoaded) {\n            //removing user from subscribe list before adding\n            $oUser->setNewsSubscription(false, false);\n\n            $blOrderOptInEmail = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blOrderOptInEmail');\n            if ($oUser->setNewsSubscription(true, $blOrderOptInEmail)) {\n                // done, confirmation required?\n                if ($blOrderOptInEmail) {\n                    $this->_iNewsletterStatus = 1;\n                } else {\n                    $this->_iNewsletterStatus = 2;\n                }\n            } else {\n                Registry::getUtilsView()->addErrorToDisplay('MESSAGE_NOT_ABLE_TO_SEND_EMAIL');\n            }\n        } elseif (!$blSubscribe && $blUserLoaded) {\n            // unsubscribing user\n            $oUser->setNewsSubscription(false, false);\n            $this->_iNewsletterStatus = 3;\n        }\n    }\n\n    /**\n     * Loads user and Adds him to newsletter group.\n     *\n     * Template variables:\n     * <b>success</b>\n     */\n    public function addme()\n    {\n        // user exists ?\n        $oUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n        if ($oUser->load(Registry::getRequest()->getRequestEscapedParameter('uid'))) {\n            $sConfirmCode = md5($oUser->oxuser__oxusername->value . $oUser->oxuser__oxpasssalt->value);\n            // is confirm code ok?\n            if (Registry::getRequest()->getRequestEscapedParameter('confirm') == $sConfirmCode) {\n                $oUser->getNewsSubscription()->setOptInStatus(1);\n                $oUser->addToGroup('oxidnewsletter');\n                $this->_iNewsletterStatus = 2;\n            }\n        }\n    }\n\n    /**\n     * Loads user and removes him from newsletter group.\n     */\n    public function removeme()\n    {\n        // existing user ?\n        $oUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n        if ($oUser->load(Registry::getRequest()->getRequestEscapedParameter('uid'))) {\n            $oUser->getNewsSubscription()->setOptInStatus(0);\n\n            // removing from group ..\n            $oUser->removeFromGroup('oxidnewsletter');\n\n            $this->_iNewsletterStatus = 3;\n        }\n    }\n\n    /**\n     * simlink to function removeme bug fix #0002894\n     */\n    public function rmvm()\n    {\n        $this->removeme();\n    }\n\n    /**\n     * Template variable getter. Returns country id\n     *\n     * @return string\n     */\n    public function getHomeCountryId()\n    {\n        if ($this->_sHomeCountryId === null) {\n            $this->_sHomeCountryId = false;\n            $aHomeCountry = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('aHomeCountry');\n            if (is_array($aHomeCountry)) {\n                $this->_sHomeCountryId = current($aHomeCountry);\n            }\n        }\n\n        return $this->_sHomeCountryId;\n    }\n\n    /**\n     * Template variable getter. Returns newsletter subscription status\n     *\n     * @return integer\n     */\n    public function getNewsletterStatus()\n    {\n        return $this->_iNewsletterStatus;\n    }\n\n    /**\n     * Template variable getter. Returns user newsletter registration data\n     *\n     * @return array\n     */\n    public function getRegParams()\n    {\n        return $this->_aRegParams;\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n        $iBaseLanguage = Registry::getLang()->getBaseLanguage();\n        $aPath['title'] = Registry::getLang()->translateString('STAY_INFORMED', $iBaseLanguage, false);\n        $aPath['link'] = $this->getLink();\n\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n\n    /**\n     * Page title\n     *\n     * @return string\n     */\n    public function getTitle()\n    {\n        if ($this->getNewsletterStatus() == 4 || !$this->getNewsletterStatus()) {\n            $sConstant = 'STAY_INFORMED';\n        } elseif ($this->getNewsletterStatus() == 1) {\n            $sConstant = 'MESSAGE_THANKYOU_FOR_SUBSCRIBING_NEWSLETTERS';\n        } elseif ($this->getNewsletterStatus() == 2) {\n            $sConstant = 'MESSAGE_NEWSLETTER_CONGRATULATIONS';\n        } elseif ($this->getNewsletterStatus() == 3) {\n            $sConstant = 'SUCCESS';\n        }\n\n        return Registry::getLang()->translateString($sConstant, Registry::getLang()->getBaseLanguage(), false);\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/OrderController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Order;\nuse OxidEsales\\Eshop\\Core\\Exception\\ArticleInputException;\nuse OxidEsales\\Eshop\\Core\\Exception\\NoArticleException;\nuse OxidEsales\\Eshop\\Core\\Exception\\OutOfStockException;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\UtilsObject;\nuse OxidEsales\\Eshop\\Application\\Model\\BasketContentMarkGenerator;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse Psr\\Log\\LoggerInterface;\n\n/**\n * Order manager. Arranges user ordering data, checks/validates\n * it, on success stores ordering data to DB.\n */\nclass OrderController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Payment object\n     *\n     * @var object\n     */\n    protected $_oPayment = null;\n\n    /**\n     * Active basket\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Basket\n     */\n    protected $_oBasket = null;\n\n    /**\n     * Order user remark\n     *\n     * @var string\n     */\n    protected $_sOrderRemark = null;\n\n    /**\n     * Basket articlelist\n     *\n     * @var object\n     */\n    protected $_oBasketArtList = null;\n\n    /**\n     * Remote Address\n     *\n     * @var string\n     */\n    protected $_sRemoteAddress = null;\n\n    /**\n     * Delivery address\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Address\n     */\n    protected $_oDelAddress = null;\n\n    /**\n     * Shipping set\n     *\n     * @var object\n     */\n    protected $_oShipSet = null;\n\n    /**\n     * Config option \"blConfirmAGB\"\n     *\n     * @var bool\n     */\n    protected $_blConfirmAGB = null;\n\n    /**\n     * Config option \"blShowOrderButtonOnTop\"\n     *\n     * @var bool\n     */\n    protected $_blShowOrderButtonOnTop = null;\n\n    /**\n     * Boolean of option \"blConfirmAGB\" error\n     *\n     * @var bool\n     */\n    protected $_blConfirmAGBError = null;\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/checkout/order';\n\n    /**\n     * Order step marker\n     *\n     * @var bool\n     */\n    protected $_blIsOrderStep = true;\n\n    /**\n     * Count of wrapping + cards options\n     */\n    protected $_iWrapCnt = null;\n\n    /**\n     * Loads basket \\OxidEsales\\Eshop\\Core\\Session::getBasket(), sets $this->oBasket->blCalcNeeded = true to\n     * recalculate, sets back basket to session \\OxidEsales\\Eshop\\Core\\Session::setBasket(), executes\n     * parent::init().\n     */\n    public function init()\n    {\n        // disabling performance control variable\n        Registry::getConfig()->setConfigParam('bl_perfCalcVatOnlyForBasketOrder', false);\n\n        // recalc basket cause of payment stuff\n        if ($oBasket = $this->getBasket()) {\n            $oBasket->onUpdate();\n        }\n\n        parent::init();\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function render()\n    {\n        if ($this->getIsOrderStep()) {\n            $basket = $this->getBasket();\n            if (Registry::getConfig()->getConfigParam('blPsBasketReservationEnabled')) {\n                Registry::getSession()->getBasketReservations()->renewExpiration();\n                if (!$basket || ($basket && !$basket->getProductsCount())) {\n                    Registry::getUtils()->redirect(\n                        Registry::getConfig()->getShopHomeUrl() . 'cl=basket',\n                        true,\n                        302\n                    );\n                }\n            }\n\n            $user = $this->getUser();\n            if (!$user && ($basket && $basket->getProductsCount() > 0)) {\n                Registry::getUtils()->redirect(\n                    Registry::getConfig()->getShopHomeUrl() . 'cl=basket',\n                    false,\n                    302\n                );\n            } elseif (!$basket || !$user || ($basket && !$basket->getProductsCount())) {\n                Registry::getUtils()->redirect(\n                    Registry::getConfig()->getShopHomeUrl(),\n                    false,\n                    302\n                );\n            }\n\n            if (!$this->getPayment()) {\n                Registry::getUtils()->redirect(\n                    Registry::getConfig()->getShopCurrentURL() . '&cl=payment',\n                    true,\n                    302\n                );\n            }\n        }\n\n        try {\n            $this->_aViewData['basketSummaryHash'] = $this->getBasketSummaryHash();\n        } catch (NoArticleException $exception) {\n            Registry::getUtils()->redirect(\n                Registry::getConfig()->getShopHomeUrl() . 'cl=basket',\n                false,\n                302\n            );\n        }\n\n        parent::render();\n\n        if (!Registry::getSession()->getVariable('sess_challenge')) {\n            Registry::getSession()->setVariable('sess_challenge', $this->getUtilsObjectInstance()->generateUID());\n        }\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Checks for order rules confirmation (\"ord_agb\", \"ord_custinfo\" form values)(if no\n     * rules agreed - returns to order view), loads basket contents (plus applied\n     * price/amount discount if available - checks for stock, checks user data (if no\n     * data is set - returns to user login page). Stores order info to database\n     * (\\OxidEsales\\Eshop\\Application\\Model\\Order::finalizeOrder()). According to sum for items automatically assigns\n     * user to special user group ( \\OxidEsales\\Eshop\\Application\\Model\\User::onOrderExecute(); if this option is not\n     * disabled in admin). Finally you will be redirected to next page (order::_getNextStep()).\n     *\n     * @return string|null\n     */\n    public function execute()\n    {\n        $session = Registry::getSession();\n        if (!$session->checkSessionChallenge()) {\n            return null;\n        }\n\n        try {\n            if (!$this->validateTermsAndConditions()) {\n                $this->_blConfirmAGBError = 1;\n\n                return null;\n            }\n\n            $user = $this->getUser();\n            if (!$user) {\n                return 'user';\n            }\n            $basket = $session->getBasket();\n\n            $requestBasketSummaryHash = Registry::getRequest()->getRequestParameter('basketSummaryHash');\n            if (!$requestBasketSummaryHash) {\n                $this->notifyIfBasketSummaryValidationIsNotPossible();\n            } elseif ($requestBasketSummaryHash !== $this->getBasketSummaryHash()) {\n                $redirect = $basket->getProductsCount() === 0 ? 'basket' : 'order';\n                $this->addBasketSummaryValidationError($redirect);\n\n                return $redirect;\n            }\n\n            if ($basket->getProductsCount()) {\n                $order = oxNew(Order::class);\n\n                //finalizing ordering process (validating, storing order into DB, executing payment, setting status ...)\n                $success = $order->finalizeOrder($basket, $user);\n\n                // performing special actions after user finishes order (assignment to special user groups)\n                $user->onOrderExecute($basket, $success);\n\n                // proceeding to next view\n                return $this->getNextStep($success);\n            }\n        } catch (OutOfStockException $exception) {\n            $exception->setDestination('basket');\n            Registry::getUtilsView()->addErrorToDisplay($exception, false, true, 'basket');\n        } catch (NoArticleException $exception) {\n            return 'basket';\n        } catch (ArticleInputException $exception) {\n            Registry::getUtilsView()->addErrorToDisplay($exception);\n        }\n    }\n\n    /**\n     * Template variable getter. Returns payment object\n     *\n     * @return object\n     */\n    public function getPayment()\n    {\n        if ($this->_oPayment === null) {\n            $this->_oPayment = false;\n\n            $oBasket = $this->getBasket();\n            $oUser = $this->getUser();\n\n            // payment is set ?\n            $sPaymentid = $oBasket->getPaymentId();\n            $oPayment = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Payment::class);\n\n            if (\n                $sPaymentid && $oPayment->load($sPaymentid) &&\n                $oPayment->isValidPayment(\n                    Registry::getSession()->getVariable('dynvalue'),\n                    Registry::getConfig()->getShopId(),\n                    $oUser,\n                    $oBasket->getPriceForPayment(),\n                    Registry::getSession()->getVariable('sShipSet')\n                )\n            ) {\n                $this->_oPayment = $oPayment;\n            }\n        }\n\n        return $this->_oPayment;\n    }\n\n    /**\n     * Template variable getter. Returns active basket\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Basket\n     */\n    public function getBasket()\n    {\n        if ($this->_oBasket === null) {\n            $this->_oBasket = false;\n            $session = Registry::getSession();\n            if ($oBasket = $session->getBasket()) {\n                $this->_oBasket = $oBasket;\n            }\n        }\n\n        return $this->_oBasket;\n    }\n\n    /**\n     * Template variable getter. Returns execution function name\n     *\n     * @return string\n     */\n    public function getExecuteFnc()\n    {\n        return 'execute';\n    }\n\n    /**\n     * Template variable getter. Returns user remark\n     *\n     * @return string\n     */\n    public function getOrderRemark()\n    {\n        if ($this->_sOrderRemark === null) {\n            $this->_sOrderRemark = false;\n            if ($sRemark = Registry::getSession()->getVariable('ordrem')) {\n                $this->_sOrderRemark = Registry::getConfig()->checkParamSpecialChars($sRemark);\n            }\n        }\n\n        return $this->_sOrderRemark;\n    }\n\n    /**\n     * Template variable getter. Returns basket article list\n     *\n     * @return object\n     */\n    public function getBasketArticles()\n    {\n        if ($this->_oBasketArtList === null) {\n            $this->_oBasketArtList = false;\n            if ($oBasket = $this->getBasket()) {\n                $this->_oBasketArtList = $oBasket->getBasketArticles();\n            }\n        }\n\n        return $this->_oBasketArtList;\n    }\n\n    /**\n     * Template variable getter. Returns delivery address\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Address|null\n     */\n    public function getDelAddress()\n    {\n        if ($this->_oDelAddress === null) {\n            $this->_oDelAddress = false;\n            $oOrder = oxNew(Order::class);\n            $this->_oDelAddress = $oOrder->getDelAddressInfo();\n        }\n\n        return $this->_oDelAddress;\n    }\n\n    /**\n     * Template variable getter. Returns shipping set\n     *\n     * @return object\n     */\n    public function getShipSet()\n    {\n        if ($this->_oShipSet === null) {\n            $this->_oShipSet = false;\n            if ($oBasket = $this->getBasket()) {\n                $oShipSet = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\DeliverySet::class);\n                if ($oShipSet->load($oBasket->getShippingId())) {\n                    $this->_oShipSet = $oShipSet;\n                }\n            }\n        }\n\n        return $this->_oShipSet;\n    }\n\n    /**\n     * Template variable getter. Returns if option \"blConfirmAGB\" is on\n     *\n     * @return bool\n     */\n    public function isConfirmAGBActive()\n    {\n        if ($this->_blConfirmAGB === null) {\n            $this->_blConfirmAGB = false;\n            $this->_blConfirmAGB = Registry::getConfig()->getConfigParam('blConfirmAGB');\n        }\n\n        return $this->_blConfirmAGB;\n    }\n\n    /**\n     * Template variable getter. Returns if option \"blConfirmAGB\" was not set\n     *\n     * @return bool\n     */\n    public function isConfirmAGBError()\n    {\n        return $this->_blConfirmAGBError;\n    }\n\n    /**\n     * Template variable getter. Returns if option \"blShowOrderButtonOnTop\" is on\n     *\n     * @return bool\n     */\n    public function showOrderButtonOnTop()\n    {\n        if ($this->_blShowOrderButtonOnTop === null) {\n            $this->_blShowOrderButtonOnTop = false;\n            $this->_blShowOrderButtonOnTop = Registry::getConfig()->getConfigParam('blShowOrderButtonOnTop');\n        }\n\n        return $this->_blShowOrderButtonOnTop;\n    }\n\n    /**\n     * Returns wrapping options availability state (TRUE/FALSE)\n     *\n     * @return bool\n     */\n    public function isWrapping()\n    {\n        if (!$this->getViewConfig()->getShowGiftWrapping()) {\n            return false;\n        }\n\n        if ($this->_iWrapCnt === null) {\n            $this->_iWrapCnt = 0;\n\n            $oWrap = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Wrapping::class);\n            $this->_iWrapCnt += $oWrap->getWrappingCount('WRAP');\n            $this->_iWrapCnt += $oWrap->getWrappingCount('CARD');\n        }\n\n        return (bool) $this->_iWrapCnt;\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n\n        $iBaseLanguage = Registry::getLang()->getBaseLanguage();\n        $aPath['title'] = Registry::getLang()->translateString('ORDER', $iBaseLanguage, false);\n        $aPath['link'] = $this->getLink();\n\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n\n    /**\n     * Return error number\n     *\n     * @return int\n     */\n    public function getAddressError()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('iAddressError');\n    }\n\n    /**\n     * Return users setted delivery address md5\n     *\n     * @return string\n     */\n    public function getDeliveryAddressMD5()\n    {\n        // bill address\n        $oUser = $this->getUser();\n        $sDelAddress = $oUser->getEncodedDeliveryAddress();\n\n        // delivery address\n        if (Registry::getSession()->getVariable('deladrid')) {\n            $oDelAdress = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Address::class);\n            $oDelAdress->load(Registry::getSession()->getVariable('deladrid'));\n\n            $sDelAddress .= $oDelAdress->getEncodedDeliveryAddress();\n        }\n\n        return $sDelAddress;\n    }\n\n    /**\n     * Method returns object with explanation marks for articles in basket.\n     *\n     * @return BasketContentMarkGenerator\n     */\n    public function getBasketContentMarkGenerator()\n    {\n        return oxNew(BasketContentMarkGenerator::class, $this->getBasket());\n    }\n\n    /**\n     * Returns next order step. If ordering was sucessfull - returns string \"thankyou\" (possible\n     * additional parameters), otherwise - returns string \"payment\" with additional\n     * error parameters.\n     *\n     * @param integer $iSuccess status code\n     *\n     * @return  string  $sNextStep  partial parameter url for next step\n     */\n    protected function getNextStep($iSuccess)\n    {\n        $sNextStep = 'thankyou';\n\n        //little trick with switch for multiple cases\n        switch (true) {\n            case ($iSuccess === Order::ORDER_STATE_MAILINGERROR):\n                $sNextStep = 'thankyou?mailerror=1';\n                break;\n            case ($iSuccess === Order::ORDER_STATE_INVALIDDELADDRESSCHANGED):\n                $sNextStep = 'order?iAddressError=1';\n                break;\n            case ($iSuccess === Order::ORDER_STATE_BELOWMINPRICE):\n                $sNextStep = 'order';\n                break;\n            case ($iSuccess === Order::ORDER_STATE_VOUCHERERROR):\n                $sNextStep = 'basket';\n                break;\n            case ($iSuccess === Order::ORDER_STATE_PAYMENTERROR):\n                // no authentication, kick back to payment methods\n                Registry::getSession()->setVariable('payerror', 2);\n                $sNextStep = 'payment?payerror=2';\n                break;\n            case ($iSuccess === Order::ORDER_STATE_ORDEREXISTS):\n                break; // reload blocker activ\n            case (is_numeric($iSuccess) && $iSuccess > 3):\n                Registry::getSession()->setVariable('payerror', $iSuccess);\n                $sNextStep = 'payment?payerror=' . $iSuccess;\n                break;\n            case (!is_numeric($iSuccess) && $iSuccess):\n                //instead of error code getting error text and setting payerror to -1\n                Registry::getSession()->setVariable('payerror', -1);\n                $iSuccess = urlencode($iSuccess);\n                $sNextStep = 'payment?payerror=-1&payerrortext=' . $iSuccess;\n                break;\n            default:\n                break;\n        }\n\n        return $sNextStep;\n    }\n\n    /**\n     * Validates whether necessary terms and conditions checkboxes were checked.\n     *\n     * @return bool\n     */\n    protected function validateTermsAndConditions()\n    {\n        $blValid = true;\n        $oConfig = Registry::getConfig();\n\n        if ($oConfig->getConfigParam('blConfirmAGB') && !Registry::getRequest()->getRequestEscapedParameter('ord_agb')) {\n            $blValid = false;\n        }\n\n        if ($oConfig->getConfigParam('blEnableIntangibleProdAgreement')) {\n            $oBasket = $this->getBasket();\n\n            $blDownloadableProductsAgreement = Registry::getRequest()->getRequestEscapedParameter('oxdownloadableproductsagreement');\n            if ($blValid && $oBasket->hasArticlesWithDownloadableAgreement() && !$blDownloadableProductsAgreement) {\n                $blValid = false;\n            }\n\n            $blServiceProductsAgreement = Registry::getRequest()->getRequestEscapedParameter('oxserviceproductsagreement');\n            if ($blValid && $oBasket->hasArticlesWithIntangibleAgreement() && !$blServiceProductsAgreement) {\n                $blValid = false;\n            }\n        }\n\n        return $blValid;\n    }\n\n    /**\n     * @return UtilsObject\n     */\n    protected function getUtilsObjectInstance()\n    {\n        return Registry::getUtilsObject();\n    }\n\n    private function getBasketSummaryHash(): string\n    {\n        return md5(json_encode($this->getBasket()->getBasketSummary()));\n    }\n\n    private function notifyIfBasketSummaryValidationIsNotPossible(): void\n    {\n        ContainerFacade::get(LoggerInterface::class)\n            ->warning(\n                'Pricing and payments verification can not be performed, ' .\n                'the basketSummaryHash parameter was not sent with request data.'\n            );\n    }\n\n    private function addBasketSummaryValidationError(string $controller): void\n    {\n        Registry::getUtilsView()\n            ->addErrorToDisplay(\n                'BASKET_ITEMS_CHANGED_ERROR',\n                false,\n                true,\n                '',\n                $controller\n            );\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/OxidStartController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\SystemEventHandler;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Encapsulates methods for application initialization.\n */\nclass OxidStartController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Initializes globals and environment vars\n     *\n     * @return null\n     */\n    public function appInit()\n    {\n        $this->pageStart();\n\n        if ('oxstart' == \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getRequestControllerId() || $this->isAdmin()) {\n            return;\n        }\n\n        $oSystemEventHandler = $this->getSystemEventHandler();\n        $oSystemEventHandler->onShopStart();\n    }\n\n    /**\n     * Renders error screen\n     *\n     * @return string\n     */\n    public function render()\n    {\n        parent::render();\n\n        $errorNumber = Registry::getRequest()->getRequestEscapedParameter('execerror');\n        $templates = $this->getErrorTemplates();\n\n        if (array_key_exists($errorNumber, $templates)) {\n            return $templates[$errorNumber];\n        } else {\n            return 'message/err_unknown';\n        }\n    }\n\n    /**\n     * Creates and starts session object, sets default currency.\n     */\n    public function pageStart()\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $config->setConfigParam('iMaxMandates', $config->getConfigParam('IMS'));\n        $config->setConfigParam('iMaxArticles', $config->getConfigParam('IMA'));\n    }\n\n    /**\n     * Finalizes the script.\n     */\n    public function pageClose()\n    {\n        $systemEventHandler = $this->getSystemEventHandler();\n        $systemEventHandler->onShopEnd();\n\n        $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n        if (isset($session)) {\n            $session->freeze();\n        }\n    }\n\n    /**\n     * Return error number\n     *\n     * @return integer\n     */\n    public function getErrorNumber()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('errornr');\n    }\n\n    /**\n     * Returns which template should be used for specific error.\n     *\n     * @return array\n     */\n    protected function getErrorTemplates()\n    {\n        return [\n            'unknown' => 'message/err_unknown',\n        ];\n    }\n\n    /**\n     * Gets system event handler.\n     *\n     * @return SystemEventHandler\n     */\n    protected function getSystemEventHandler()\n    {\n        return oxNew(\\OxidEsales\\Eshop\\Core\\SystemEventHandler::class);\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/PaymentController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Application\\Model\\DeliverySetList;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Payment manager.\n * Customer payment manager class. Performs payment validation function, etc.\n */\nclass PaymentController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Paymentlist\n     *\n     * @var object\n     */\n    protected $_oPaymentList = null;\n\n    /**\n     * Paymentlist count\n     *\n     * @var integer\n     */\n    protected $_iPaymentCnt = null;\n\n    /**\n     * All delivery sets\n     *\n     * @var array\n     */\n    protected $_aAllSets = null;\n\n    /**\n     * Delivery sets count\n     *\n     * @var integer\n     */\n    protected $_iAllSetsCnt = null;\n\n    /**\n     * Payment object 'oxempty'\n     *\n     * @var object\n     */\n    protected $_oEmptyPayment = null;\n\n    /**\n     * Payment error\n     *\n     * @var string\n     */\n    protected $_sPaymentError = null;\n\n    /**\n     * Payment error text\n     *\n     * @var string\n     */\n    protected $_sPaymentErrorText = null;\n\n    /**\n     * Dyn values\n     *\n     * @var array\n     */\n    protected $_aDynValue = null;\n\n    /**\n     * Checked payment id\n     *\n     * @var string\n     */\n    protected $_sCheckedId = null;\n\n    /**\n     * Selected payment id in db\n     *\n     * @var string\n     */\n    protected $_sCheckedPaymentId = null;\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/checkout/payment';\n\n    /**\n     * Order step marker\n     *\n     * @var bool\n     */\n    protected $_blIsOrderStep = true;\n\n    /**\n     * TS protection product array\n     *\n     * @var array\n     */\n    protected $_aTsProducts = null;\n\n    /**\n     * Executes parent method parent::init().\n     */\n    public function init()\n    {\n        parent::init();\n    }\n\n    /**\n     * Executes parent::render(), checks if this connection secure\n     * (if not - redirects to secure payment page), loads user object\n     * (if user object loading was not successfull - redirects to start\n     * page), loads user delivery/shipping information. According\n     * to configuration in admin, user profile data loads delivery sets,\n     * and possible payment methods. Returns name of template to render\n     * payment::_sThisTemplate.\n     *\n     * @return  string  current template file name\n     */\n    public function render()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        if ($myConfig->getConfigParam('blPsBasketReservationEnabled')) {\n            $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n            $session->getBasketReservations()->renewExpiration();\n        }\n\n        parent::render();\n\n        //if it happens that you are not in SSL\n        //then forcing to HTTPS\n\n        //but first checking maybe there were redirection already to prevent infinite redirections\n        //due to possible buggy ssl detection on server\n        $blAlreadyRedirected = Registry::getRequest()->getRequestEscapedParameter('sslredirect') == 'forced';\n\n        if ($this->getIsOrderStep()) {\n            //additional check if we really really have a user now\n            //and the basket is not empty\n            $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n            $oBasket = $session->getBasket();\n            $blPsBasketReservationEnabled = $myConfig->getConfigParam('blPsBasketReservationEnabled');\n            if ($blPsBasketReservationEnabled && (!$oBasket || ($oBasket && !$oBasket->getProductsCount()))) {\n                Registry::getUtils()->redirect($myConfig->getShopHomeUrl() . 'cl=basket', true, 302);\n            }\n\n            $oUser = $this->getUser();\n            if (!$oUser && ($oBasket && $oBasket->getProductsCount() > 0)) {\n                Registry::getUtils()->redirect($myConfig->getShopHomeUrl() . 'cl=basket', false, 302);\n            } elseif (!$oBasket || !$oUser || ($oBasket && !$oBasket->getProductsCount())) {\n                Registry::getUtils()->redirect($myConfig->getShopHomeUrl() . 'cl=start', false, 302);\n            }\n        }\n\n        $sFncParameter = Registry::getRequest()->getRequestEscapedParameter('fnc');\n        if ($myConfig->getCurrentShopURL() != $myConfig->getShopURL() && !$blAlreadyRedirected && !$sFncParameter) {\n            $sPayErrorParameter = Registry::getRequest()->getRequestEscapedParameter('payerror');\n            $sPayErrorTextParameter = Registry::getRequest()->getRequestEscapedParameter('payerrortext');\n            $shopSecureHomeURL = $myConfig->getShopSecureHomeURL();\n\n            $sPayError = $sPayErrorParameter ? 'payerror=' . $sPayErrorParameter : '';\n            $sPayErrorText = $sPayErrorTextParameter ? 'payerrortext=' . $sPayErrorTextParameter : '';\n            $sRedirectURL = $shopSecureHomeURL . 'sslredirect=forced&cl=payment&' . $sPayError . \"&\" . $sPayErrorText;\n            Registry::getUtils()->redirect($sRedirectURL, true, 302);\n        }\n\n        if (!$this->getAllSetsCnt()) {\n            // no fitting shipping set found, setting default empty payment\n            $this->setDefaultEmptyPayment();\n            Registry::getSession()->setVariable('sShipSet', null);\n        }\n\n        $this->unsetPaymentErrors();\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Set default empty payment. If config param 'blOtherCountryOrder' is on,\n     * tries to set 'oxempty' payment to aViewData['oxemptypayment'].\n     * On error sets aViewData['payerror'] to -2\n     */\n    protected function setDefaultEmptyPayment()\n    {\n        // no shipping method there !!\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blOtherCountryOrder')) {\n            $oPayment = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Payment::class);\n            if ($oPayment->load('oxempty')) {\n                $this->_oEmptyPayment = $oPayment;\n            } else {\n                // some error with setup ??\n                $this->_sPaymentError = -2;\n            }\n        } else {\n            $this->_sPaymentError = -2;\n        }\n    }\n\n    /**\n     * Unsets payment errors from session\n     */\n    protected function unsetPaymentErrors()\n    {\n        $iPayError = Registry::getRequest()->getRequestEscapedParameter('payerror');\n        $sPayErrorText = Registry::getRequest()->getRequestEscapedParameter('payerrortext');\n\n        if (!($iPayError || $sPayErrorText)) {\n            $iPayError = Registry::getSession()->getVariable('payerror');\n            $sPayErrorText = Registry::getSession()->getVariable('payerrortext');\n        }\n\n        if ($iPayError) {\n            Registry::getSession()->deleteVariable('payerror');\n            $this->_sPaymentError = $iPayError;\n        }\n        if ($sPayErrorText) {\n            Registry::getSession()->deleteVariable('payerrortext');\n            $this->_sPaymentErrorText = $sPayErrorText;\n        }\n    }\n\n    /**\n     * Changes shipping set to chosen one. Sets basket status to not up-to-date, which later\n     * forces to recalculate it\n     */\n    public function changeshipping()\n    {\n        $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n\n        $oBasket = $session->getBasket();\n        $oBasket->setShipping(null);\n        $oBasket->onUpdate();\n        $session->setVariable('sShipSet', Registry::getRequest()->getRequestEscapedParameter('sShipSet'));\n    }\n\n    /**\n     * Validates oxiddebitnote user payment data.\n     * Returns null if problems on validating occured. If everything\n     * is OK - returns \"order\" and redirects to payment confirmation\n     * page.\n     *\n     * Session variables:\n     * <b>paymentid</b>, <b>dynvalue</b>, <b>payerror</b>\n     *\n     * @return  mixed\n     */\n    public function validatePayment()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n\n        //#1308C - check user. Function is executed before render(), and oUser is not set!\n        // Set it manually for use in methods getPaymentList(), getShippingSetList()...\n        $oUser = $this->getUser();\n        if (!$oUser) {\n            $session->setVariable('payerror', 2);\n\n            return;\n        }\n\n        if (!($sShipSetId = Registry::getRequest()->getRequestEscapedParameter('sShipSet'))) {\n            $sShipSetId = $session->getVariable('sShipSet');\n        }\n        if (!($sPaymentId = Registry::getRequest()->getRequestEscapedParameter('paymentid'))) {\n            $sPaymentId = $session->getVariable('paymentid');\n        }\n        if (!($aDynvalue = Registry::getRequest()->getRequestEscapedParameter('dynvalue'))) {\n            $aDynvalue = $session->getVariable('dynvalue');\n        }\n\n        // A. additional protection\n        if (!$myConfig->getConfigParam('blOtherCountryOrder') && $sPaymentId == 'oxempty') {\n            $sPaymentId = '';\n        }\n\n        //#1308C - check if we have paymentID, and it really exists\n        if (!$sPaymentId) {\n            $session->setVariable('payerror', 1);\n\n            return;\n        }\n\n        $oBasket = $session->getBasket();\n        $oBasket->setPayment(null);\n        $oPayment = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Payment::class);\n        $oPayment->load($sPaymentId);\n\n        // getting basket price for payment calculation\n        $dBasketPrice = $oBasket->getPriceForPayment();\n\n        $blOK = $oPayment->isValidPayment($aDynvalue, $myConfig->getShopId(), $oUser, $dBasketPrice, $sShipSetId);\n\n        if ($blOK) {\n            $session->setVariable('paymentid', $sPaymentId);\n            $session->setVariable('dynvalue', $aDynvalue);\n            $oBasket->setShipping($sShipSetId);\n            $session->deleteVariable('_selected_paymentid');\n\n            return 'order';\n        } else {\n            $session->setVariable('payerror', $oPayment->getPaymentErrorNumber());\n\n            //#1308C - delete paymentid from session, and save selected it just for view\n            $session->deleteVariable('paymentid');\n            $session->setVariable('_selected_paymentid', $sPaymentId);\n\n            return;\n        }\n    }\n\n    /**\n     * Template variable getter. Returns paymentlist\n     *\n     * @return object\n     */\n    public function getPaymentList()\n    {\n        if ($this->_oPaymentList === null) {\n            $this->_oPaymentList = false;\n\n            $sActShipSet = Registry::getRequest()->getRequestEscapedParameter('sShipSet');\n            if (!$sActShipSet) {\n                $sActShipSet = Registry::getSession()->getVariable('sShipSet');\n            }\n\n            $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n            $oBasket = $session->getBasket();\n\n            // load sets, active set, and active set payment list\n            list($aAllSets, $sActShipSet, $aPaymentList) =\n                Registry::get(DeliverySetList::class)->getDeliverySetData($sActShipSet, $this->getUser(), $oBasket);\n\n            $oBasket->setShipping($sActShipSet);\n\n            // calculating payment expences for preview for each payment\n            $this->setValues($aPaymentList, $oBasket);\n            $this->_oPaymentList = $aPaymentList;\n            $this->_aAllSets = $aAllSets;\n        }\n\n        return $this->_oPaymentList;\n    }\n\n    /**\n     * Template variable getter. Returns all delivery sets\n     *\n     * @return array\n     */\n    public function getAllSets()\n    {\n        if ($this->_aAllSets === null) {\n            $this->_aAllSets = false;\n\n            if ($this->getPaymentList()) {\n                return $this->_aAllSets;\n            }\n        }\n\n        return $this->_aAllSets;\n    }\n\n    /**\n     * Template variable getter. Returns number of delivery sets\n     *\n     * @return integer\n     */\n    public function getAllSetsCnt()\n    {\n        if ($this->_iAllSetsCnt === null) {\n            $this->_iAllSetsCnt = 0;\n\n            if ($this->getPaymentList()) {\n                $this->_iAllSetsCnt = count($this->_aAllSets);\n            }\n        }\n\n        return $this->_iAllSetsCnt;\n    }\n\n    /**\n     * Calculate payment cost for each payment. Sould be removed later\n     *\n     * @param array                                      $aPaymentList payments array\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket      basket object\n     */\n    protected function setValues(&$aPaymentList, $oBasket = null)\n    {\n        if (is_array($aPaymentList)) {\n            foreach ($aPaymentList as $oPayment) {\n                $oPayment->calculate($oBasket);\n                $oPayment->aDynValues = $oPayment->getDynValues();\n                if ($oPayment->oxpayments__oxchecked->value) {\n                    $this->_sCheckedId = $oPayment->getId();\n                }\n            }\n        }\n    }\n\n    /**\n     * Template variable getter. Returns payment object \"oxempty\"\n     *\n     * @return object\n     */\n    public function getEmptyPayment()\n    {\n        return $this->_oEmptyPayment;\n    }\n\n    /**\n     * Template variable getter. Returns error of payments\n     *\n     * @return string\n     */\n    public function getPaymentError()\n    {\n        return $this->_sPaymentError;\n    }\n\n    /**\n     * Template variable getter. Returns error text of payments\n     *\n     * @return string\n     */\n    public function getPaymentErrorText()\n    {\n        return $this->_sPaymentErrorText;\n    }\n\n    /**\n     * Return if old style bank code is supported.\n     *\n     * @return bool\n     */\n    public function isOldDebitValidationEnabled()\n    {\n        return !\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blSkipDebitOldBankInfo');\n    }\n\n    /**\n     * Template variable getter. Returns dyn values\n     *\n     * @return array\n     */\n    public function getDynValue()\n    {\n        if ($this->_aDynValue === null) {\n            $this->_aDynValue = false;\n\n            // flyspray#1217 (sarunas)\n            if (($aDynValue = Registry::getSession()->getVariable('dynvalue'))) {\n                $this->_aDynValue = $aDynValue;\n            } else {\n                $this->_aDynValue = Registry::getRequest()->getRequestEscapedParameter(\"dynvalue\");\n            }\n\n            // #701A\n            // assign debit note payment params to view data\n            $aPaymentList = $this->getPaymentList();\n            if (isset($aPaymentList['oxiddebitnote'])) {\n                $this->assignDebitNoteParams();\n            }\n        }\n\n        return $this->_aDynValue;\n    }\n\n    /**\n     * Assign debit note payment values to view data. Loads user debit note payment\n     * if available and assigns payment data to $this->_aDynValue\n     */\n    protected function assignDebitNoteParams()\n    {\n        // #701A\n        $oUserPayment = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\UserPayment::class);\n        //such info available ?\n        if ($oUserPayment->getPaymentByPaymentType($this->getUser(), 'oxiddebitnote')) {\n            $sUserPaymentField = 'oxuserpayments__oxvalue';\n            $aAddPaymentData = Registry::getUtils()->assignValuesFromText($oUserPayment->$sUserPaymentField->value);\n\n            //checking if some of values is allready set in session - leave it\n            foreach ($aAddPaymentData as $oData) {\n                if (\n                    !isset($this->_aDynValue[$oData->name]) ||\n                    (isset($this->_aDynValue[$oData->name]) && !$this->_aDynValue[$oData->name])\n                ) {\n                    $this->_aDynValue[$oData->name] = $oData->value;\n                }\n            }\n        }\n    }\n\n    /**\n     * Get checked payment ID. Tries to get checked payment ID from session,\n     * if fails, then tries to get payment ID from last order.\n     *\n     * @return string\n     */\n    public function getCheckedPaymentId()\n    {\n        $sCheckedId = null;\n        if ($this->_sCheckedPaymentId === null) {\n            if (!($sPaymentID = Registry::getRequest()->getRequestEscapedParameter('paymentid'))) {\n                $sPaymentID = Registry::getSession()->getVariable('paymentid');\n            }\n            if ($sPaymentID) {\n                $sCheckedId = $sPaymentID;\n            } elseif (($sSelectedPaymentID = Registry::getSession()->getVariable('_selected_paymentid'))) {\n                $sCheckedId = $sSelectedPaymentID;\n            } else {\n                // #1010A.\n                if ($oUser = $this->getUser()) {\n                    $oOrder = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Order::class);\n                    if (($sLastPaymentId = $oOrder->getLastUserPaymentType($oUser->getId()))) {\n                        $sCheckedId = $sLastPaymentId;\n                    }\n                }\n            }\n\n            // #M253 set to selected payment in db\n            if (!$sCheckedId && $this->_sCheckedId) {\n                $sCheckedId = $this->_sCheckedId;\n            }\n\n            // #646\n            $oPaymentList = $this->getPaymentList();\n            if (isset($oPaymentList) && $oPaymentList && isset($sCheckedId) && !isset($oPaymentList[$sCheckedId])) {\n                end($oPaymentList);\n                $sCheckedId = key($oPaymentList);\n            }\n            $this->_sCheckedPaymentId = $sCheckedId;\n        }\n\n        return $this->_sCheckedPaymentId;\n    }\n\n    /**\n     * Template variable getter. Returns payment list count\n     *\n     * @return integer\n     */\n    public function getPaymentCnt()\n    {\n        if ($this->_iPaymentCnt === null) {\n            $this->_iPaymentCnt = false;\n\n            if ($oPaymentList = $this->getPaymentList()) {\n                $this->_iPaymentCnt = count($oPaymentList);\n            }\n        }\n\n        return $this->_iPaymentCnt;\n    }\n\n    /**\n     * Function to check if array values are empty againts given array keys\n     *\n     * @param array $aData array of data to check\n     * @param array $aKeys array of array indexes\n     *\n     * @return bool\n     */\n    protected function checkArrValuesEmpty($aData, $aKeys)\n    {\n        if (!is_array($aKeys) || count($aKeys) < 1) {\n            return false;\n        }\n\n        foreach ($aKeys as $sKey) {\n            if (isset($aData[$sKey]) && !empty($aData[$sKey])) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n\n\n        $iBaseLanguage = Registry::getLang()->getBaseLanguage();\n        $aPath['title'] = Registry::getLang()->translateString('PAY', $iBaseLanguage, false);\n        $aPath['link'] = $this->getLink();\n\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n\n    /**\n     * Retuns config true if Vat is splitted\n     *\n     * @return array\n     */\n    public function isPaymentVatSplitted()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blShowVATForPayCharge');\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/PriceAlarmController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Email\\EmailValidatorServiceBridgeInterface;\n\n/**\n * PriceAlarm window.\n * Arranges \"pricealarm\" window, by sending eMail and storing into Database (etc.)\n * submission. Result - \"pricealarm\"  template. After user correctly\n * fulfils all required fields all information is sent to shop owner by\n * email.\n * OXID eShop -> pricealarm.\n */\nclass PriceAlarmController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'pricealarm';\n\n    /**\n     * Current article.\n     *\n     * @var object\n     */\n    protected $_oArticle = null;\n\n    /**\n     * Bid price.\n     *\n     * @var string\n     */\n    protected $_sBidPrice = null;\n\n    /**\n     * Price alarm status.\n     *\n     * @var integer\n     */\n    protected $_iPriceAlarmStatus = null;\n\n    /**\n     * Validates email\n     * address. If email is wrong - returns false and exits. If email\n     * address is OK - creates prcealarm object and saves it\n     * (oxpricealarm::save()). Sends pricealarm notification mail\n     * to shop owner.\n     *\n     * @return  bool    false on error\n     */\n    public function addme()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $myUtils = \\OxidEsales\\Eshop\\Core\\Registry::getUtils();\n        $emailValidator = ContainerFacade::get(EmailValidatorServiceBridgeInterface::class);\n\n        $aParams = Registry::getRequest()->getRequestEscapedParameter('pa');\n        if (!isset($aParams['email']) || !$emailValidator->isEmailValid($aParams['email'])) {\n            $this->_iPriceAlarmStatus = 0;\n\n            return;\n        }\n\n        $oCur = $myConfig->getActShopCurrencyObject();\n        // convert currency to default\n        $dPrice = $myUtils->currency2Float($aParams['price']);\n\n        $oAlarm = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\PriceAlarm::class);\n        $oAlarm->oxpricealarm__oxuserid = new Field(Registry::getSession()->getVariable('usr'));\n        $oAlarm->oxpricealarm__oxemail = new Field($aParams['email']);\n        $oAlarm->oxpricealarm__oxartid = new Field($aParams['aid']);\n        $oAlarm->oxpricealarm__oxprice = new Field($myUtils->fRound($dPrice, $oCur));\n        $oAlarm->oxpricealarm__oxshopid = new Field($myConfig->getShopId());\n        $oAlarm->oxpricealarm__oxcurrency = new Field($oCur->name);\n\n        $oAlarm->oxpricealarm__oxlang = new Field(Registry::getLang()->getBaseLanguage());\n\n        $oAlarm->save();\n\n        // Send Email\n        $oEmail = oxNew(\\OxidEsales\\Eshop\\Core\\Email::class);\n        $this->_iPriceAlarmStatus = (int) $oEmail->sendPricealarmNotification($aParams, $oAlarm);\n    }\n\n    /**\n     * Template variable getter. Returns bid price\n     *\n     * @return string\n     */\n    public function getBidPrice()\n    {\n        if ($this->_sBidPrice === null) {\n            $this->_sBidPrice = false;\n\n            $aParams = $this->getParams();\n            $oCur = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActShopCurrencyObject();\n            $iPrice = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->currency2Float($aParams['price']);\n            $this->_sBidPrice = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->formatCurrency($iPrice, $oCur);\n        }\n\n        return $this->_sBidPrice;\n    }\n\n    /**\n     * Template variable getter. Returns active article\n     *\n     * @return object\n     */\n    public function getProduct()\n    {\n        if ($this->_oArticle === null) {\n            $this->_oArticle = false;\n            $aParams = $this->getParams();\n            $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            $oArticle->load($aParams['aid']);\n            $this->_oArticle = $oArticle;\n        }\n\n        return $this->_oArticle;\n    }\n\n    /**\n     * Returns params (article id, bid price)\n     *\n     * @return array\n     */\n    private function getParams()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('pa');\n    }\n\n    /**\n     * Return pricealarm status (if it was send)\n     *\n     * @return integer\n     */\n    public function getPriceAlarmStatus()\n    {\n        return $this->_iPriceAlarmStatus;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/RecommListController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Article suggestion page.\n * Collects some article base information, sets default recomendation text,\n * sends suggestion mail to user.\n *\n * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n */\nclass RecommListController extends \\OxidEsales\\Eshop\\Application\\Controller\\ArticleListController\n{\n    /**\n     * List type\n     *\n     * @var string\n     */\n    protected $_sListType = 'recommlist';\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/recommendations/recommlist';\n\n    /**\n     * Other recommendations list\n     */\n    protected $_oOtherRecommList = null;\n\n    /**\n     * Recommlist reviews\n     *\n     * @var array\n     */\n    protected $_aReviews = null;\n\n    /**\n     * Can user rate\n     *\n     * @var bool\n     */\n    protected $_blRate = null;\n\n    /**\n     * Rating value\n     *\n     * @var double\n     */\n    protected $_dRatingValue = null;\n\n    /**\n     * Rating count\n     *\n     * @var integer\n     */\n    protected $_iRatingCnt = null;\n\n    /**\n     * Searched recommendations list\n     *\n     * @var object\n     */\n    protected $_oSearchRecommLists = null;\n\n    /**\n     * Search string\n     *\n     * @var string\n     */\n    protected $_sSearch = null;\n\n    /**\n     * Template location\n     *\n     * @var string\n     */\n    protected $_sTplLocation = null;\n\n    /**\n     * Page navigation\n     *\n     * @var object\n     */\n    protected $_oPageNavigation = null;\n\n    /**\n     * Collects current view data, return current template file name\n     *\n     * @return string\n     */\n    public function render()\n    {\n        \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::render();\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $this->_iAllArtCnt = 0;\n\n        if ($oActiveRecommList = $this->getActiveRecommList()) {\n            if (($oList = $this->getArticleList()) && $oList->count()) {\n                $this->_iAllArtCnt = $oActiveRecommList->getArtCount();\n            }\n        } else {\n            if (($oList = $this->getRecommLists()) && $oList->count()) {\n                $oRecommList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\RecommendationList::class);\n                $this->_iAllArtCnt = $oRecommList->getSearchRecommListCount($this->getRecommSearch());\n            }\n        }\n\n        if (!($oList = $this->getArticleList())) {\n            $oList = $this->getRecommLists();\n        }\n\n        if ($oList && $oList->count()) {\n            $iNrofCatArticles = (int) \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iNrofCatArticles');\n            $iNrofCatArticles = $iNrofCatArticles ? $iNrofCatArticles : 10;\n            $this->_iCntPages = ceil($this->_iAllArtCnt / $iNrofCatArticles);\n        }\n        // processing list articles\n        $this->processListArticles();\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Returns product link type (OXARTICLE_LINKTYPE_RECOMM)\n     *\n     * @return int\n     */\n    protected function getProductLinkType()\n    {\n        return OXARTICLE_LINKTYPE_RECOMM;\n    }\n\n    /**\n     * Returns additional URL parameters which must be added to list products dynamic urls\n     *\n     * @return string\n     */\n    public function getAddUrlParams()\n    {\n        $sAddParams = parent::getAddUrlParams();\n        $sAddParams .= ($sAddParams ? '&amp;' : '') . \"listtype={$this->_sListType}\";\n\n        if ($oRecommList = $this->getActiveRecommList()) {\n            $sAddParams .= \"&amp;recommid=\" . $oRecommList->getId();\n        }\n\n        return $sAddParams;\n    }\n\n    /**\n     * Returns additional URL parameters which must be added to list products seo urls\n     *\n     * @return string\n     */\n    public function getAddSeoUrlParams()\n    {\n        $sAddParams = parent::getAddSeoUrlParams();\n        if ($sParam = Registry::getRequest()->getRequestParameter(\"searchrecomm\")) {\n            $sAddParams .= \"&amp;searchrecomm=\" . rawurlencode($sParam);\n        }\n\n        return $sAddParams;\n    }\n\n    /**\n     * Saves user ratings and review text (oxreview object)\n     *\n     * @return null\n     */\n    public function saveReview()\n    {\n        if (!Registry::getSession()->checkSessionChallenge()) {\n            return;\n        }\n\n        if (\n            $this->canAcceptFormData() &&\n            ($oRecommList = $this->getActiveRecommList()) && ($oUser = $this->getUser())\n        ) {\n            //save rating\n            $dRating = Registry::getRequest()->getRequestEscapedParameter('recommlistrating');\n            if ($dRating !== null) {\n                $dRating = (int) $dRating;\n            }\n\n            if ($dRating !== null && $dRating >= 1 && $dRating <= 5) {\n                $oRating = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Rating::class);\n                if ($oRating->allowRating($oUser->getId(), 'oxrecommlist', $oRecommList->getId())) {\n                    $oRating->oxratings__oxuserid = new Field($oUser->getId());\n                    $oRating->oxratings__oxtype = new Field('oxrecommlist');\n                    $oRating->oxratings__oxobjectid = new Field($oRecommList->getId());\n                    $oRating->oxratings__oxrating = new Field($dRating);\n                    $oRating->save();\n                    $oRecommList->addToRatingAverage($dRating);\n                }\n            }\n\n            if (($sReviewText = trim((string) Registry::getRequest()->getRequestParameter('rvw_txt')))) {\n                $oReview = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Review::class);\n                $oReview->oxreviews__oxobjectid = new Field($oRecommList->getId());\n                $oReview->oxreviews__oxtype = new Field('oxrecommlist');\n                $oReview->oxreviews__oxtext = new Field($sReviewText, Field::T_RAW);\n                $oReview->oxreviews__oxlang = new Field(Registry::getLang()->getBaseLanguage());\n                $oReview->oxreviews__oxuserid = new Field($oUser->getId());\n                $oReview->oxreviews__oxrating = new Field(($dRating !== null) ? $dRating : null);\n                $oReview->save();\n            }\n        }\n    }\n\n    /**\n     * Returns array of params => values which are used in hidden forms and as additional url params\n     *\n     * @return array\n     */\n    public function getNavigationParams()\n    {\n        $aParams = \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::getNavigationParams();\n        $aParams['recommid'] = Registry::getRequest()->getRequestEscapedParameter('recommid');\n\n        return $aParams;\n    }\n\n    /**\n     * Template variable getter. Returns category's article list\n     *\n     * @return array\n     */\n    public function getArticleList()\n    {\n        if ($this->_aArticleList === null) {\n            $this->_aArticleList = false;\n            if ($oActiveRecommList = $this->getActiveRecommList()) {\n                // sets active page\n                $iActPage = (int) Registry::getRequest()->getRequestEscapedParameter('pgNr');\n                $iActPage = ($iActPage < 0) ? 0 : $iActPage;\n\n                // load only lists which we show on screen\n                $iNrofCatArticles = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iNrofCatArticles');\n                $iNrofCatArticles = $iNrofCatArticles ? $iNrofCatArticles : 10;\n\n                $this->_aArticleList = $oActiveRecommList->getArticles(\n                    $iNrofCatArticles * $iActPage,\n                    $iNrofCatArticles\n                );\n\n                if ($this->_aArticleList && $this->_aArticleList->count()) {\n                    foreach ($this->_aArticleList as $oItem) {\n                        $oItem->text = $oActiveRecommList->getArtDescription($oItem->getId());\n                    }\n                }\n            }\n        }\n\n        return $this->_aArticleList;\n    }\n\n    /**\n     * Template variable getter. Returns other recommlists\n     *\n     * @return object\n     */\n    public function getSimilarRecommLists()\n    {\n        if ($this->_oOtherRecommList === null) {\n            $this->_oOtherRecommList = false;\n            if (($oActiveRecommList = $this->getActiveRecommList()) && ($oList = $this->getArticleList())) {\n                $oRecommLists = $oActiveRecommList->getRecommListsByIds($oList->arrayKeys());\n                //do not show the same list\n                unset($oRecommLists[$oActiveRecommList->getId()]);\n                $this->_oOtherRecommList = $oRecommLists;\n            }\n        }\n\n        return $this->_oOtherRecommList;\n    }\n\n    /**\n     * Template variable getter. Returns recommlist's reviews\n     *\n     * @return array\n     */\n    public function getReviews()\n    {\n        if ($this->_aReviews === null) {\n            $this->_aReviews = false;\n            if ($this->isReviewActive() && ($oActiveRecommList = $this->getActiveRecommList())) {\n                $this->_aReviews = $oActiveRecommList->getReviews();\n            }\n        }\n\n        return $this->_aReviews;\n    }\n\n    /**\n     * Template variable getter. Returns if review module is on\n     *\n     * @return bool\n     */\n    public function isReviewActive()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('bl_perfLoadReviews');\n    }\n\n    /**\n     * Template variable getter. Returns if user can rate\n     *\n     * @return bool\n     */\n    public function canRate()\n    {\n        if ($this->_blRate === null) {\n            $this->_blRate = false;\n            if ($this->isReviewActive() && ($oActiveRecommList = $this->getActiveRecommList())) {\n                $oRating = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Rating::class);\n                $sUserVariable = Registry::getSession()->getVariable('usr');\n                $this->_blRate = $oRating->allowRating($sUserVariable, 'oxrecommlist', $oActiveRecommList->getId());\n            }\n        }\n\n        return $this->_blRate;\n    }\n\n    /**\n     * Template variable getter. Returns rating value\n     *\n     * @return double\n     */\n    public function getRatingValue()\n    {\n        if ($this->_dRatingValue === null) {\n            $this->_dRatingValue = 0.0;\n            if ($this->isReviewActive() && ($oActiveRecommList = $this->getActiveRecommList())) {\n                $this->_dRatingValue = round($oActiveRecommList->oxrecommlists__oxrating->value, 1);\n            }\n        }\n\n        return (float) $this->_dRatingValue;\n    }\n\n    /**\n     * Template variable getter. Returns rating count\n     *\n     * @return integer\n     */\n    public function getRatingCount()\n    {\n        if ($this->_iRatingCnt === null) {\n            $this->_iRatingCnt = false;\n            if ($this->isReviewActive() && ($oActiveRecommList = $this->getActiveRecommList())) {\n                $this->_iRatingCnt = $oActiveRecommList->oxrecommlists__oxratingcnt->value;\n            }\n        }\n\n        return $this->_iRatingCnt;\n    }\n\n    /**\n     * Template variable getter. Returns searched recommlist\n     *\n     * @return object\n     */\n    public function getRecommLists()\n    {\n        if ($this->_oSearchRecommLists === null) {\n            $this->_oSearchRecommLists = [];\n            if (!$this->getActiveRecommList()) {\n                // list of found oxrecommlists\n                $oRecommList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\RecommendationList::class);\n                $oList = $oRecommList->getSearchRecommLists($this->getRecommSearch());\n                if ($oList && $oList->count()) {\n                    $this->_oSearchRecommLists = $oList;\n                }\n            }\n        }\n\n        return $this->_oSearchRecommLists;\n    }\n\n    /**\n     * Template variable getter. Returns search string\n     *\n     * @return string\n     */\n    public function getRecommSearch()\n    {\n        if ($this->_sSearch === null) {\n            $this->_sSearch = false;\n            if ($sSearch = Registry::getRequest()->getRequestEscapedParameter('searchrecomm')) {\n                $this->_sSearch = $sSearch;\n            }\n        }\n\n        return $this->_sSearch;\n    }\n\n    /**\n     * Template variable getter. Returns category path array\n     *\n     * @return array\n     */\n    public function getTreePath()\n    {\n        $oLang = Registry::getLang();\n\n        $aPath[0] = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n        $aPath[0]->setLink(false);\n        $aPath[0]->oxcategories__oxtitle = new Field($oLang->translateString('RECOMMLIST'));\n\n        if ($sSearchParam = $this->getRecommSearch()) {\n            $shopHomeURL = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopHomeUrl();\n            $sUrl = $shopHomeURL . \"cl=recommlist&amp;searchrecomm=\" . rawurlencode($sSearchParam);\n            $sTitle = $oLang->translateString('RECOMMLIST_SEARCH') . ' \"' . $sSearchParam . '\"';\n\n            $aPath[1] = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n            $aPath[1]->setLink($sUrl);\n            $aPath[1]->oxcategories__oxtitle = new Field($sTitle);\n        }\n\n        return $aPath;\n    }\n\n    /**\n     * Template variable getter. Returns search string\n     *\n     * @return string\n     */\n    public function getSearchForHtml()\n    {\n        // #M1450 if active recommlist is loaded return it's title\n        if ($oActiveRecommList = $this->getActiveRecommList()) {\n            return $oActiveRecommList->oxrecommlists__oxtitle->value;\n        }\n\n        return Registry::getRequest()->getRequestEscapedParameter('searchrecomm');\n    }\n\n    /**\n     * Generates Url for page navigation\n     *\n     * @return string\n     */\n    public function generatePageNavigationUrl()\n    {\n        if ((Registry::getUtils()->seoIsActive() && ($oRecomm = $this->getActiveRecommList()))) {\n            return $oRecomm->getLink();\n        }\n\n        return \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::generatePageNavigationUrl();\n    }\n\n    /**\n     * Adds page number parameter to current Url and returns formatted url\n     *\n     * @param string $sUrl  url to append page numbers\n     * @param int    $iPage current page number\n     * @param int    $iLang requested language\n     *\n     * @return string\n     */\n    protected function addPageNrParam($sUrl, $iPage, $iLang = null)\n    {\n        if (Registry::getUtils()->seoIsActive() && ($oRecomm = $this->getActiveRecommList())) {\n            if ($iPage) {\n                // only if page number > 0\n                $sUrl = $oRecomm->getBaseSeoLink($iLang, $iPage);\n            }\n        } else {\n            $sUrl = \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::addPageNrParam($sUrl, $iPage, $iLang);\n        }\n\n        return $sUrl;\n    }\n\n    /**\n     * Template variable getter. Returns additional params for url\n     *\n     * @return string\n     */\n    public function getAdditionalParams()\n    {\n        $sAddParams = \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::getAdditionalParams();\n\n        if ($oRecomm = $this->getActiveRecommList()) {\n            $sAddParams .= \"&amp;recommid=\" . $oRecomm->getId();\n        }\n\n        if ($sSearch = $this->getRecommSearch()) {\n            $sAddParams .= \"&amp;searchrecomm=\" . rawurlencode($sSearch);\n        }\n\n        return $sAddParams;\n    }\n\n    /**\n     * get link of current view\n     *\n     * @param int $iLang requested language\n     *\n     * @return string\n     */\n    public function getLink($iLang = null)\n    {\n        if ($oRecomm = $this->getActiveRecommList()) {\n            $sLink = $oRecomm->getLink($iLang);\n        } else {\n            $sLink = \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::getLink($iLang);\n        }\n        $sSearch = Registry::getRequest()->getRequestEscapedParameter('searchrecomm');\n        if ($sSearch) {\n            $sLink .= ((strpos($sLink, '?') === false) ? '?' : '&amp;') . \"searchrecomm={$sSearch}\";\n        }\n\n        return $sLink;\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n\n        $iBaseLanguage = Registry::getLang()->getBaseLanguage();\n        $aPath['title'] = Registry::getLang()->translateString('LISTMANIA', $iBaseLanguage, false);\n        $aPath['link'] = $this->getLink();\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n\n    /**\n     * Page title\n     *\n     * @return string\n     */\n    public function getTitle()\n    {\n        $oLang = Registry::getLang();\n        if ($aActiveList = $this->getActiveRecommList()) {\n            $sTranslatedString = $oLang->translateString('LIST_BY', $oLang->getBaseLanguage(), false);\n            $sTitleField = 'oxrecommlists__oxtitle';\n            $sAuthorField = 'oxrecommlists__oxauthor';\n\n            return $aActiveList->$sTitleField->value . ' (' . $sTranslatedString . ' ' .\n                      $aActiveList->$sAuthorField->value . ')';\n        }\n        $sTranslatedString = $oLang->translateString('HITS_FOR', $oLang->getBaseLanguage(), false);\n\n        return $this->getArticleCount() . ' ' . $sTranslatedString . ' \"' . $this->getSearchForHtml() . '\"';\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/RecommendationAddController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse oxUBase;\n\n/**\n * Handles adding article to recommendation list process.\n * Due to possibility of external modules we recommned to extend the vews from oxUBase view.\n * However expreimentally we extend RecommAdd from Details view here.\n *\n * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n */\nclass RecommendationAddController extends \\OxidEsales\\Eshop\\Application\\Controller\\ArticleDetailsController\n{\n    /**\n     * Template name\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/account/recommendationadd';\n\n    /**\n     * User recommendation lists\n     *\n     * @var array\n     */\n    protected $_aUserRecommList = null;\n\n    /**\n     * Renders the view\n     *\n     * @return string\n     */\n    public function render()\n    {\n        \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::render();\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Returns user recommlists\n     *\n     * @return array\n     */\n    public function getRecommLists()\n    {\n        if ($this->_aUserRecommList === null) {\n            $oUser = $this->getUser();\n            if ($oUser) {\n                $this->_aUserRecommList = $oUser->getUserRecommLists();\n            }\n        }\n\n        return $this->_aUserRecommList;\n    }\n\n    /**\n     * Returns the title of the product added to the recommendation list.\n     *\n     * @return string\n     */\n    public function getTitle()\n    {\n        $oProduct = $this->getProduct();\n\n        return $oProduct->oxarticles__oxtitle->value . ' ' . $oProduct->oxarticles__oxvarselect->value;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/RegisterController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * User registration window.\n * Collects and arranges user object data (information, like shipping address, etc.).\n */\nclass RegisterController extends \\OxidEsales\\Eshop\\Application\\Controller\\UserController\n{\n    /**\n     * Current class template.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/account/register';\n\n    /**\n     * Successful registration confirmation template\n     *\n     * @var string\n     */\n    protected $_sSuccessTemplate = 'page/account/register_success';\n\n    /**\n     * Successful Confirmation state template name\n     *\n     * @var string\n     */\n    protected $_sConfirmTemplate = 'page/account/register_confirm';\n\n    /**\n     * Order step marker\n     *\n     * @var bool\n     */\n    protected $_blIsOrderStep = false;\n\n    /**\n     * Current view search engine indexing state\n     *\n     * @var int\n     */\n    protected $_iViewIndexState = VIEW_INDEXSTATE_NOINDEXNOFOLLOW;\n\n    /**\n     * Executes parent::render(), passes error code to template engine,\n     * returns name of template to render register::_sThisTemplate.\n     *\n     * @return string   current template file name\n     */\n    public function render()\n    {\n        parent::render();\n\n        // checking registration status\n        if ($this->isEnabledPrivateSales() && $this->isConfirmed()) {\n            $sTemplate = $this->_sConfirmTemplate;\n        } elseif ($this->getRegistrationStatus()) {\n            $sTemplate = $this->_sSuccessTemplate;\n        } else {\n            $sTemplate = $this->_sThisTemplate;\n        }\n\n        return $sTemplate;\n    }\n\n    /**\n     * Returns registration error code (if it was set)\n     *\n     * @return int|null\n     */\n    public function getRegistrationError()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('newslettererror');\n    }\n\n    /**\n     * Return registration status (if it was set)\n     *\n     * @return int|null\n     */\n    public function getRegistrationStatus()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('success');\n    }\n\n    /**\n     * Check if field is required.\n     *\n     * @param string $sField required field to check\n     *\n     * @return bool\n     */\n    public function isFieldRequired($sField)\n    {\n        return isset($this->getMustFillFields()[$sField]);\n    }\n\n    /**\n     * Registration confirmation functionality. If registration\n     * succeded - redirects to success page, if not - returns\n     * exception informing about expired confirmation link\n     *\n     * @return mixed\n     */\n    public function confirmRegistration()\n    {\n        $oUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n        if ($oUser->loadUserByUpdateId($this->getUpdateId())) {\n            // resetting update key parameter\n            $oUser->setUpdateKey(true);\n\n            // saving ..\n            $oUser->oxuser__oxactive = new \\OxidEsales\\Eshop\\Core\\Field(1);\n            $oUser->save();\n\n            // forcing user login\n            Registry::getSession()->setVariable('usr', $oUser->getId());\n\n            // redirecting to confirmation page\n            return 'register?confirmstate=1';\n        } else {\n            // confirmation failed\n            Registry::getUtilsView()->addErrorToDisplay('REGISTER_ERRLINKEXPIRED', false, true);\n\n            // redirecting to confirmation page\n            return 'account';\n        }\n    }\n\n    /**\n     * Returns special id used for password update functionality\n     *\n     * @return string\n     */\n    public function getUpdateId()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('uid');\n    }\n\n    /**\n     * Returns confirmation state: \"1\" - success, \"-1\" - error\n     *\n     * @return int\n     */\n    public function isConfirmed()\n    {\n        return (bool) Registry::getRequest()->getRequestEscapedParameter(\"confirmstate\");\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n\n        $iBaseLanguage = Registry::getLang()->getBaseLanguage();\n        $aPath['title'] = Registry::getLang()->translateString('REGISTER', $iBaseLanguage, false);\n        $aPath['link']  = $this->getLink();\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/ReviewController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Review of chosen article.\n * Collects article review data, saves new review to DB.\n */\nclass ReviewController extends \\OxidEsales\\Eshop\\Application\\Controller\\ArticleDetailsController\n{\n    /**\n     * Review user object\n     */\n    protected $_oRevUser = null;\n\n    /**\n     * Active object ($_oProduct or $_oActiveRecommList)\n     *\n     * @var object\n     */\n    protected $_oActObject = null;\n\n    /**\n     * Active recommendations list\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @var object\n     */\n    protected $_oActiveRecommList = null;\n\n    /**\n     * Active recommlist's items\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @var object\n     */\n    protected $_oActiveRecommItems = null;\n\n    /**\n     * Can user rate\n     *\n     * @var bool\n     */\n    protected $_blRate = null;\n\n    /**\n     * Array of reviews\n     *\n     * @var array\n     */\n    protected $_aReviews = null;\n\n    /**\n     * CrossSelling articlelist\n     *\n     * @var object\n     */\n    protected $_oCrossSelling = null;\n\n    /**\n     * Similar products articlelist\n     *\n     * @var object\n     */\n    protected $_oSimilarProducts = null;\n\n    /**\n     * Recommlist\n     *\n     * @var object\n     */\n    protected $_oRecommList = null;\n\n    /**\n     * Review send status\n     *\n     * @var bool\n     */\n    protected $_blReviewSendStatus = null;\n\n    /**\n     * Page navigation\n     *\n     * @var object\n     */\n    protected $_oPageNavigation = null;\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/review/review';\n\n    /**\n     * Current class login template name.\n     *\n     * @var string\n     */\n    protected $_sThisLoginTemplate = 'page/review/review_login';\n\n    /**\n     * Current view search engine indexing state\n     *\n     * @var int\n     */\n    protected $_iViewIndexState = VIEW_INDEXSTATE_NOINDEXNOFOLLOW;\n\n    /**\n     * Returns prefix ID used by template engine.\n     *\n     * @return  string  $this->_sViewID view id\n     */\n    public function generateViewId()\n    {\n        return \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::generateViewId();\n    }\n\n    /**\n     * Executes parent::init(), Loads user chosen product object (with all data).\n     */\n    public function init()\n    {\n        // @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n        if (Registry::getRequest()->getRequestEscapedParameter('recommid') && !$this->getActiveRecommList()) {\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->redirect(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopHomeUrl(), true, 302);\n        }\n        // END deprecated\n\n        \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::init();\n    }\n\n    /**\n     * Executes parent::render, loads article reviews and additional data\n     * (\\OxidEsales\\Eshop\\Application\\Model\\Article::getReviews(),\n     * \\OxidEsales\\Eshop\\Application\\Model\\Article::getCrossSelling(),\n     * \\OxidEsales\\Eshop\\Application\\Model\\Article::GetSimilarProducts()). Returns name of template file to\n     * render review::_sThisTemplate.\n     *\n     * @return  string  current template file name\n     */\n    public function render()\n    {\n        $oConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        if (!$oConfig->getConfigParam(\"bl_perfLoadReviews\")) {\n            Registry::getUtils()->redirect($oConfig->getShopHomeUrl());\n        }\n\n        \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::render();\n        if (!($this->getReviewUser())) {\n            $this->_sThisTemplate = $this->_sThisLoginTemplate;\n        } else {\n            // @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n            $oActiveRecommList = $this->getActiveRecommList();\n            $oList = $this->getActiveRecommItems();\n\n            if ($oActiveRecommList) {\n                if ($oList && $oList->count()) {\n                    $this->_iAllArtCnt = $oActiveRecommList->getArtCount();\n                }\n                // load only lists which we show on screen\n                $iNrofCatArticles = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iNrofCatArticles');\n                $iNrofCatArticles = $iNrofCatArticles ? $iNrofCatArticles : 10;\n                $this->_iCntPages = ceil($this->_iAllArtCnt / $iNrofCatArticles);\n            }\n            // END deprecated\n        }\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Saves user review text (oxreview object)\n     *\n     * @return null\n     */\n    public function saveReview()\n    {\n        if (!Registry::getSession()->checkSessionChallenge()) {\n            return;\n        }\n\n        if (($oRevUser = $this->getReviewUser()) && $this->canAcceptFormData()) {\n            if (($oActObject = $this->getActiveObject()) && ($sType = $this->getActiveType())) {\n                if (($dRating = Registry::getRequest()->getRequestEscapedParameter('rating')) === null) {\n                    $dRating = Registry::getRequest()->getRequestEscapedParameter('artrating');\n                }\n\n                if ($dRating !== null) {\n                    $dRating = (int) $dRating;\n                }\n\n                //save rating\n                if ($dRating !== null && $dRating >= 1 && $dRating <= 5) {\n                    $oRating = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Rating::class);\n                    if ($oRating->allowRating($oRevUser->getId(), $sType, $oActObject->getId())) {\n                        $oRating->oxratings__oxuserid = new Field($oRevUser->getId());\n                        $oRating->oxratings__oxtype = new Field($sType);\n                        $oRating->oxratings__oxobjectid = new Field($oActObject->getId());\n                        $oRating->oxratings__oxrating = new Field($dRating);\n                        $oRating->save();\n\n                        $oActObject->addToRatingAverage($dRating);\n\n                        $this->_blReviewSendStatus = true;\n                    }\n                }\n\n                if (($sReviewText = trim((string) Registry::getRequest()->getRequestParameter('rvw_txt')))) {\n                    $oReview = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Review::class);\n                    $oReview->oxreviews__oxobjectid = new Field($oActObject->getId());\n                    $oReview->oxreviews__oxtype = new Field($sType);\n                    $oReview->oxreviews__oxtext = new Field($sReviewText, Field::T_RAW);\n                    $oReview->oxreviews__oxlang = new Field(Registry::getLang()->getBaseLanguage());\n                    $oReview->oxreviews__oxuserid = new Field($oRevUser->getId());\n                    $oReview->oxreviews__oxrating = new Field(($dRating !== null) ? $dRating : null);\n                    $oReview->save();\n\n                    $this->_blReviewSendStatus = true;\n                }\n            }\n        }\n    }\n\n    /**\n     * Returns review user object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\User\n     */\n    public function getReviewUser()\n    {\n        if ($this->_oRevUser === null) {\n            $this->_oRevUser = false;\n            $oUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n\n            if ($sUserId = $oUser->getReviewUserId($this->getReviewUserHash())) {\n                // review user, by link or other source?\n                if ($oUser->load($sUserId)) {\n                    $this->_oRevUser = $oUser;\n                }\n            } elseif ($oUser = $this->getUser()) {\n                // session user?\n                $this->_oRevUser = $oUser;\n            }\n        }\n\n        return $this->_oRevUser;\n    }\n\n    /**\n     * Template variable getter. Returns review user id\n     *\n     * @return string\n     */\n    public function getReviewUserHash()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('reviewuserhash');\n    }\n\n    /**\n     * Template variable getter. Returns active object (oxarticle or oxrecommlist)\n     *\n     * @return object\n     */\n    protected function getActiveObject()\n    {\n        if ($this->_oActObject === null) {\n            $this->_oActObject = false;\n\n            if (($oProduct = $this->getProduct())) {\n                $this->_oActObject = $oProduct;\n            // @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n            } elseif (($oRecommList = $this->getActiveRecommList())) {\n                $this->_oActObject = $oRecommList;\n                // END deprecated\n            }\n        }\n\n        return $this->_oActObject;\n    }\n\n    /**\n     * Template variable getter. Returns active type (oxarticle or oxrecommlist)\n     *\n     * @return string\n     */\n    protected function getActiveType()\n    {\n        if ($this->getProduct()) {\n            return 'oxarticle';\n        // @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n        } elseif ($this->getActiveRecommList()) {\n            return 'oxrecommlist';\n            // END deprecated\n        }\n    }\n\n    /**\n     * Template variable getter. Returns active recommlist\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\RecommendationList|false\n     */\n    public function getActiveRecommList()\n    {\n        if (!$this->getViewConfig()->getShowListmania()) {\n            return false;\n        }\n\n        if ($this->_oActiveRecommList === null) {\n            $this->_oActiveRecommList = false;\n\n            if ($sRecommId = Registry::getRequest()->getRequestEscapedParameter('recommid')) {\n                $oActiveRecommList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\RecommendationList::class);\n                if ($oActiveRecommList->load($sRecommId)) {\n                    $this->_oActiveRecommList = $oActiveRecommList;\n                }\n            }\n        }\n\n        return $this->_oActiveRecommList;\n    }\n\n    /**\n     * Template variable getter. Returns if user can rate\n     *\n     * @return bool\n     */\n    public function canRate()\n    {\n        if ($this->_blRate === null) {\n            $this->_blRate = false;\n            if (($oActObject = $this->getActiveObject()) && ($oRevUser = $this->getReviewUser())) {\n                $oRating = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Rating::class);\n                $this->_blRate = $oRating->allowRating(\n                    $oRevUser->getId(),\n                    $this->getActiveType(),\n                    $oActObject->getId()\n                );\n            }\n        }\n\n        return $this->_blRate;\n    }\n\n    /**\n     * Template variable getter. Returns active object's reviews\n     *\n     * @return array\n     */\n    public function getReviews()\n    {\n        if ($this->_aReviews === null) {\n            $this->_aReviews = false;\n            if ($oObject = $this->getActiveObject()) {\n                $this->_aReviews = $oObject->getReviews();\n            }\n        }\n\n        return $this->_aReviews;\n    }\n\n    /**\n     * Template variable getter. Returns recommlists\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @return object\n     */\n    public function getRecommList()\n    {\n        if ($this->_oRecommList === null) {\n            $this->_oRecommList = false;\n            if ($oProduct = $this->getProduct()) {\n                $oRecommList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\RecommendationList::class);\n                $this->_oRecommList = $oRecommList->getRecommListsByIds([$oProduct->getId()]);\n            }\n        }\n\n        return $this->_oRecommList;\n    }\n\n    /**\n     * Template variable getter. Returns active recommlist's items\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @return object\n     */\n    public function getActiveRecommItems()\n    {\n        if ($this->_oActiveRecommItems === null) {\n            $this->_oActiveRecommItems = false;\n            if ($oActiveRecommList = $this->getActiveRecommList()) {\n                // sets active page\n                $iActPage = (int) Registry::getRequest()->getRequestEscapedParameter('pgNr');\n                $iActPage = ($iActPage < 0) ? 0 : $iActPage;\n\n                // load only lists which we show on screen\n                $iNrofCatArticles = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iNrofCatArticles');\n                $iNrofCatArticles = $iNrofCatArticles ? $iNrofCatArticles : 10;\n\n                $oList = $oActiveRecommList->getArticles($iNrofCatArticles * $iActPage, $iNrofCatArticles);\n\n                if ($oList && $oList->count()) {\n                    foreach ($oList as $oItem) {\n                        $oItem->text = $oActiveRecommList->getArtDescription($oItem->getId());\n                    }\n                    $this->_oActiveRecommItems = $oList;\n                }\n            }\n        }\n\n        return $this->_oActiveRecommItems;\n    }\n\n    /**\n     * Template variable getter. Returns review send status\n     *\n     * @return bool\n     */\n    public function getReviewSendStatus()\n    {\n        return $this->_blReviewSendStatus;\n    }\n\n    /**\n     * Template variable getter. Returns page navigation\n     *\n     * @return object\n     */\n    public function getPageNavigation()\n    {\n        if ($this->_oPageNavigation === null) {\n            $this->_oPageNavigation = false;\n            // @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n            if ($this->getActiveRecommList()) {\n                $this->_oPageNavigation = $this->generatePageNavigation();\n            }\n            // END deprecated\n        }\n\n        return $this->_oPageNavigation;\n    }\n\n    /**\n     * Template variable getter. Returns additional params for url\n     *\n     * @return string\n     */\n    public function getAdditionalParams()\n    {\n        $sAddParams = \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::getAdditionalParams();\n        // @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n        if ($oActRecommList = $this->getActiveRecommList()) {\n            $sAddParams .= '&amp;recommid=' . $oActRecommList->getId();\n        }\n        // END deprecated\n\n        return $sAddParams;\n    }\n\n    /**\n     * returns additional url params for dynamic url building\n     *\n     * @return string\n     */\n    public function getDynUrlParams()\n    {\n        $sParams = parent::getDynUrlParams();\n\n        if ($sCnId = Registry::getRequest()->getRequestEscapedParameter('cnid')) {\n            $sParams .= \"&amp;cnid={$sCnId}\";\n        }\n        if ($sAnId = Registry::getRequest()->getRequestEscapedParameter('anid')) {\n            $sParams .= \"&amp;anid={$sAnId}\";\n        }\n        if ($sListType = Registry::getRequest()->getRequestEscapedParameter('listtype')) {\n            $sParams .= \"&amp;listtype={$sListType}\";\n        }\n        // @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n        if ($sRecommId = Registry::getRequest()->getRequestEscapedParameter('recommid')) {\n            $sParams .= \"&amp;recommid={$sRecommId}\";\n        }\n        // END deprecated\n\n        return $sParams;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/SearchController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Application\\Model\\ArticleList;\nuse OxidEsales\\Eshop\\Application\\Model\\Search;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search\\Event\\AfterProductSearchEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search\\Event\\BeforeProductSearchEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search\\ProductSearchCriteria;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search\\ProductSearchServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Search\\EqualsFilter;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Search\\Pagination;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Search\\SearchTerm;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Search\\SortDirection;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Search\\Sorting;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\n/**\n * Articles searching class.\n * Performs searching through articles in database.\n */\nclass SearchController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Count of all found articles.\n     *\n     * @var integer\n     */\n    protected $_iAllArtCnt = 0;\n\n    /**\n     * Number of possible pages.\n     *\n     * @var integer\n     */\n    protected $_iCntPages = null;\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/search/search';\n\n    /**\n     * List type\n     *\n     * @var string\n     */\n    protected $_sListType = 'search';\n\n    /**\n     * Marked which defines if current view is sortable or not\n     *\n     * @var bool\n     */\n    protected $_blShowSorting = true;\n\n    /**\n     * If search was empty\n     *\n     * @var bool\n     */\n    protected $_blEmptySearch = null;\n\n    /**\n     * Similar recommendation lists\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @var object\n     */\n    protected $_oRecommList = null;\n\n    /**\n     * Search parameter for Html\n     *\n     * @var string\n     */\n    protected $_sSearchParamForHtml = null;\n\n    /**\n     * Search parameter\n     *\n     * @var string\n     */\n    protected $_sSearchParam = null;\n\n    /**\n     * Searched category\n     *\n     * @var string\n     */\n    protected $_sSearchCatId = null;\n\n    /**\n     * Searched vendor\n     *\n     * @var string\n     */\n    protected $_sSearchVendor = null;\n\n    /**\n     * Searched manufacturer\n     *\n     * @var string\n     */\n    protected $_sSearchManufacturer = null;\n\n    /**\n     * If called class is search\n     *\n     * @var bool\n     */\n    protected $_blSearchClass = null;\n\n    /**\n     * Page navigation\n     *\n     * @var object\n     */\n    protected $_oPageNavigation = null;\n\n    /**\n     * Current view search engine indexing state\n     *\n     * @var int\n     */\n    protected $_iViewIndexState = VIEW_INDEXSTATE_NOINDEXNOFOLLOW;\n\n    /**\n     * Array of id to form recommendation list.\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @var array\n     */\n    protected $_aSimilarRecommListIds = null;\n\n    /**\n     * Fetches search parameter from GET/POST/session, prepares search\n     * SQL (search::GetWhere()), and executes it forming the list of\n     * found articles. Article list is stored at search::_aArticleList\n     * array.\n     *\n     * @return null\n     */\n    public function init()\n    {\n        parent::init();\n\n        $searchParam = trim((string) Registry::getRequest()->getRequestParameter('searchparam'));\n        $category = rawurldecode((string) Registry::getRequest()->getRequestEscapedParameter('searchcnid'));\n        $vendor = rawurldecode((string) Registry::getRequest()->getRequestEscapedParameter('searchvendor'));\n        $manufacturer = rawurldecode((string) Registry::getRequest()->getRequestEscapedParameter('searchmanufacturer'));\n\n        $this->_sSearchCatId = $category;\n        $this->_sSearchManufacturer = $manufacturer;\n        $this->_aArticleList = null;\n        $this->_blEmptySearch = false;\n        if (!$searchParam && !$category && !$vendor && !$manufacturer) {\n            $this->_blEmptySearch = true;\n\n            return false;\n        }\n\n        if (!Registry::getConfig()->getConfigParam('bl_perfLoadManufacturerTree')) {\n            $manufacturer = '';\n        }\n\n        $filters = [];\n        if ($category) {\n            $filters['oxcatnid'] = new EqualsFilter('oxcatnid', $category);\n        }\n        if ($vendor) {\n            $filters['oxvendorid'] = new EqualsFilter('oxvendorid', $vendor);\n        }\n        if ($manufacturer) {\n            $filters['oxmanufacturerid'] = new EqualsFilter('oxmanufacturerid', $manufacturer);\n        }\n\n        $itemsPerPage = max(1, (int) Registry::getConfig()->getConfigParam('iNrofCatArticles'));\n\n        $this->loadSearchResults($itemsPerPage, $searchParam, $filters);\n\n        $this->_iCntPages = ceil($this->_iAllArtCnt / $itemsPerPage);\n    }\n\n    private function loadSearchResults(int $itemsPerPage, string $searchTerm, array $filters): void\n    {\n        if (ContainerFacade::getParameter('oxid_esales.product_search_enabled')) {\n            $logger = ContainerFacade::get(LoggerInterface::class);\n\n            if (!ContainerFacade::has(ProductSearchServiceInterface::class)) {\n                $logger->warning('Product search service is not registered, falling back to default search.');\n            } else {\n                try {\n                    $this->loadProductsWithCustomSearch($itemsPerPage, $searchTerm, $filters);\n                    return;\n                } catch (Throwable $e) {\n                    $logger->error('Unable to use search service, falling back to default search.', [$e]);\n                }\n            }\n        }\n\n        $this->loadProductsFromDatabase($searchTerm, $filters);\n    }\n\n    private function loadProductsWithCustomSearch(int $itemsPerPage, string $searchTerm, array $filters): void\n    {\n        $criteria = $this->buildSearchCriteria($itemsPerPage, $searchTerm, $filters);\n        $beforeEvent = ContainerFacade::dispatch(new BeforeProductSearchEvent($criteria));\n\n        $searchCriteria = $beforeEvent->getSearchCriteria();\n        $context = $beforeEvent->getContext();\n\n        $result = ContainerFacade::get(ProductSearchServiceInterface::class)->search($searchCriteria, $context);\n        $afterEvent = ContainerFacade::dispatch(new AfterProductSearchEvent($searchCriteria, $context, $result));\n\n        $articleList = oxNew(ArticleList::class);\n        $articleList->loadIds(array_map('strval', $afterEvent->getSearchResult()->getProductIds()));\n        $this->_aArticleList = $articleList;\n        $this->_iAllArtCnt = $afterEvent->getSearchResult()->getTotal();\n    }\n\n    private function buildSearchCriteria(int $itemsPerPage, string $searchTerm, array $filters): ProductSearchCriteria\n    {\n        $page = max(1, (int) Registry::getRequest()->getRequestParameter('pgNr') + 1);\n\n        return new ProductSearchCriteria(\n            Pagination::fromPage($page, $itemsPerPage),\n            new SearchTerm($searchTerm),\n            array_values($filters),\n            $this->buildSorting()\n        );\n    }\n\n    private function buildSorting(): array\n    {\n        $sortingData = $this->getSorting($this->getSortIdent());\n        $sortBy = $sortingData['sortby'] ?? null;\n        $sortDirection = SortDirection::tryFrom(strtoupper((string) ($sortingData['sortdir'] ?? '')));\n\n        if (!$sortBy || !$sortDirection) {\n            return [];\n        }\n\n        return [new Sorting($sortBy, $sortDirection)];\n    }\n\n    private function loadProductsFromDatabase(string $searchTerm, array $filters): void\n    {\n        $category = ($filters['oxcatnid'] ?? null)?->getValue();\n        $vendor = ($filters['oxvendorid'] ?? null)?->getValue();\n        $manufacturer = ($filters['oxmanufacturerid'] ?? null)?->getValue();\n\n        $searchHandler = oxNew(Search::class);\n        $searchList = $searchHandler->getSearchArticles(\n            $searchTerm ?: null,\n            $category,\n            $vendor,\n            $manufacturer,\n            $this->getSortingSql($this->getSortIdent())\n        );\n\n        $this->_aArticleList = $searchList;\n        $this->_iAllArtCnt = 0;\n\n        if ($searchList->count()) {\n            $this->_iAllArtCnt = $searchHandler->getSearchArticleCount(\n                $searchTerm ?: null,\n                $category,\n                $vendor,\n                $manufacturer\n            );\n        }\n    }\n\n    /**\n     * Forms search navigation URLs, executes parent::render() and\n     * returns name of template to render search::_sThisTemplate.\n     *\n     * @return  string  current template file name\n     */\n    public function render()\n    {\n        parent::render();\n        // processing list articles\n        $this->processListArticles();\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Iterates through list articles and performs list view specific tasks:\n     *  - sets type of link which needs to be generated (Manufacturer link)\n     */\n    protected function processListArticles()\n    {\n        $sAddDynParams = $this->getAddUrlParams();\n        if ($sAddDynParams && ($aArtList = $this->getArticleList())) {\n            $blSeo = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->seoIsActive();\n            foreach ($aArtList as $oArticle) {\n                // appending std and dynamic urls\n                if (!$blSeo) {\n                    // only if seo is off..\n                    $oArticle->appendStdLink($sAddDynParams);\n                }\n                $oArticle->appendLink($sAddDynParams);\n            }\n        }\n    }\n\n    /**\n     * Returns additional URL parameters which must be added to list products urls\n     *\n     * @return string\n     */\n    public function getAddUrlParams()\n    {\n        $sAddParams = parent::getAddUrlParams();\n        $sAddParams .= ($sAddParams ? '&amp;' : '') . \"listtype={$this->_sListType}\";\n        $oConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        if ($sParam = Registry::getRequest()->getRequestParameter('searchparam')) {\n            $sAddParams .= \"&amp;searchparam=\" . rawurlencode($sParam);\n        }\n\n        if ($sParam = Registry::getRequest()->getRequestEscapedParameter('searchcnid')) {\n            $sAddParams .= \"&amp;searchcnid=$sParam\";\n        }\n\n        if ($sParam = rawurldecode(Registry::getRequest()->getRequestEscapedParameter('searchvendor'))) {\n            $sAddParams .= \"&amp;searchvendor=$sParam\";\n        }\n\n        if ($sParam = rawurldecode(Registry::getRequest()->getRequestEscapedParameter('searchmanufacturer'))) {\n            $sAddParams .= \"&amp;searchmanufacturer=$sParam\";\n        }\n\n        return $sAddParams;\n    }\n\n    /**\n     * Template variable getter. Returns similar recommendation lists\n     *\n     * @return object\n     */\n    protected function isSearchClass()\n    {\n        if ($this->_blSearchClass === null) {\n            $this->_blSearchClass = false;\n            if ('search' == strtolower(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getRequestControllerId())) {\n                $this->_blSearchClass = true;\n            }\n        }\n\n        return $this->_blSearchClass;\n    }\n\n    /**\n     * Template variable getter. Returns if searched was empty\n     *\n     * @return bool\n     */\n    public function isEmptySearch()\n    {\n        return $this->_blEmptySearch;\n    }\n\n    /**\n     * Template variable getter. Returns searched article list\n     *\n     * @return array\n     */\n    public function getArticleList()\n    {\n        return $this->_aArticleList;\n    }\n\n    /**\n     * Return array of id to form recommend list.\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @return array\n     */\n    public function getSimilarRecommListIds()\n    {\n        if ($this->_aSimilarRecommListIds === null) {\n            $this->_aSimilarRecommListIds = false;\n\n            $aList = $this->getArticleList();\n            if ($aList && $aList->count() > 0) {\n                $this->_aSimilarRecommListIds = $aList->arrayKeys();\n            }\n        }\n\n        return $this->_aSimilarRecommListIds;\n    }\n\n    /**\n     * Template variable getter. Returns search parameter for Html\n     *\n     * @return string\n     */\n    public function getSearchParamForHtml()\n    {\n        if ($this->_sSearchParamForHtml === null) {\n            $this->_sSearchParamForHtml = false;\n            if ($this->isSearchClass()) {\n                $this->_sSearchParamForHtml = Registry::getRequest()->getRequestEscapedParameter('searchparam');\n            }\n        }\n\n        return $this->_sSearchParamForHtml;\n    }\n\n    /**\n     * Template variable getter. Returns search parameter\n     *\n     * @return string\n     */\n    public function getSearchParam()\n    {\n        if ($this->_sSearchParam === null) {\n            $this->_sSearchParam = false;\n            if ($this->isSearchClass()) {\n                $this->_sSearchParam = rawurlencode(Registry::getRequest()->getRequestParameter('searchparam'));\n            }\n        }\n\n        return $this->_sSearchParam;\n    }\n\n    /**\n     * Template variable getter. Returns searched category id\n     *\n     * @return string\n     */\n    public function getSearchCatId()\n    {\n        if ($this->_sSearchCatId === null) {\n            $this->_sSearchCatId = false;\n            if ($this->isSearchClass()) {\n                $this->_sSearchCatId = rawurldecode(Registry::getRequest()->getRequestEscapedParameter('searchcnid'));\n            }\n        }\n\n        return $this->_sSearchCatId;\n    }\n\n    /**\n     * Template variable getter. Returns searched vendor id\n     *\n     * @return string\n     */\n    public function getSearchVendor()\n    {\n        if ($this->_sSearchVendor === null) {\n            $this->_sSearchVendor = false;\n            if ($this->isSearchClass()) {\n                // searching in vendor #671\n                $this->_sSearchVendor = rawurldecode(Registry::getRequest()->getRequestEscapedParameter('searchvendor'));\n            }\n        }\n\n        return $this->_sSearchVendor;\n    }\n\n    /**\n     * Template variable getter. Returns searched Manufacturer id\n     *\n     * @return string\n     */\n    public function getSearchManufacturer()\n    {\n        if ($this->_sSearchManufacturer === null) {\n            $this->_sSearchManufacturer = false;\n            if ($this->isSearchClass()) {\n                // searching in Manufacturer #671\n                $sManufacturerParameter = Registry::getRequest()->getRequestEscapedParameter('searchmanufacturer');\n                $this->_sSearchManufacturer = rawurldecode($sManufacturerParameter);\n            }\n        }\n\n        return $this->_sSearchManufacturer;\n    }\n\n    /**\n     * Template variable getter. Returns page navigation\n     *\n     * @return object\n     */\n    public function getPageNavigation()\n    {\n        if ($this->_oPageNavigation === null) {\n            $this->_oPageNavigation = false;\n            $this->_oPageNavigation = $this->generatePageNavigation();\n        }\n\n        return $this->_oPageNavigation;\n    }\n\n\n    /**\n     * Template variable getter. Returns active search\n     *\n     * @return object\n     */\n    public function getActiveCategory()\n    {\n        return $this->getActSearch();\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n\n        $iBaseLanguage = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage();\n        $aPath['title'] = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('SEARCH', $iBaseLanguage, false);\n        $aPath['link'] = $this->getLink();\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n\n    /**\n     * Returns config parameters blShowListDisplayType value\n     *\n     * @return boolean\n     */\n    public function canSelectDisplayType()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blShowListDisplayType');\n    }\n\n    /**\n     * Checks if current request parameters does not block SEO redirection process\n     *\n     * @return bool\n     */\n    protected function canRedirect()\n    {\n        return false;\n    }\n\n    /**\n     * Article count getter\n     *\n     * @return int\n     */\n    public function getArticleCount()\n    {\n        return $this->_iAllArtCnt;\n    }\n\n    /**\n     * Return page title\n     *\n     * @return string\n     */\n    public function getTitle()\n    {\n        $sTitle = '';\n        $sTitle .= $this->getArticleCount();\n        $iBaseLanguage = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage();\n        $sTitle .= ' ' . \\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('HITS_FOR', $iBaseLanguage, false);\n        $sTitle .= ' \"' . $this->getSearchParamForHtml() . '\"';\n\n        return $sTitle;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/StartController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Starting shop page.\n * Shop starter, manages starting visible articles, etc.\n */\nclass StartController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * List display type\n     *\n     * @var string\n     */\n    protected $_sListDisplayType = null;\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/shop/start';\n\n    /**\n     * Start page meta description CMS ident\n     *\n     * @var string\n     */\n    protected $_sMetaDescriptionIdent = 'oxstartmetadescription';\n\n    /**\n     * Start page meta keywords CMS ident\n     *\n     * @var string\n     */\n    protected $_sMetaKeywordsIdent = 'oxstartmetakeywords';\n\n    /**\n     * Are actions on\n     *\n     * @var bool\n     */\n    protected $_blLoadActions = null;\n\n    /**\n     * Newest article list\n     *\n     * @var array\n     */\n    protected $_aNewArticleList = null;\n\n    /**\n     * Sign if to load and show top5articles action\n     *\n     * @var bool\n     */\n    protected $_blTop5Action = true;\n\n    /**\n     * Sign if to load and show bargain action\n     *\n     * @var bool\n     */\n    protected $_blBargainAction = true;\n\n    /**\n     * Executes parent::render(), loads action articles\n     * (oxarticlelist::loadActionArticles()). Returns name of\n     * template file to render.\n     *\n     * @return  string  cuurent template file name\n     */\n    public function render()\n    {\n        if (Registry::getRequest()->getRequestEscapedParameter('showexceptionpage') == '1') {\n            return 'message/exception';\n        }\n        parent::render();\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Template variable getter. Returns if actions are ON\n     *\n     * @return string\n     */\n    protected function getLoadActionsParam()\n    {\n        if ($this->_blLoadActions === null) {\n            $this->_blLoadActions = false;\n            if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('bl_perfLoadAktion')) {\n                $this->_blLoadActions = true;\n            }\n        }\n\n        return $this->_blLoadActions;\n    }\n\n    /**\n     * Template variable getter. Returns newest article list\n     *\n     * @return array\n     */\n    public function getNewestArticles()\n    {\n        if ($this->_aNewArticleList === null) {\n            $this->_aNewArticleList = [];\n            if ($this->getLoadActionsParam()) {\n                // newest articles\n                $oArtList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n                $oArtList->loadNewestArticles();\n                if ($oArtList->count()) {\n                    $this->_aNewArticleList = $oArtList;\n                }\n            }\n        }\n\n        return $this->_aNewArticleList;\n    }\n\n    /**\n     * Returns SEO suffix for page title\n     *\n     * @return string\n     */\n    public function getTitleSuffix()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActiveShop()->oxshops__oxstarttitle->value;\n    }\n\n    /**\n     * Returns view canonical url\n     *\n     * @return string\n     */\n    public function getCanonicalUrl()\n    {\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getUtils()->seoIsActive() && ($oViewConf = $this->getViewConfig())) {\n            return \\OxidEsales\\Eshop\\Core\\Registry::getUtilsUrl()->prepareCanonicalUrl($oViewConf->getHomeLink());\n        }\n    }\n\n    /**\n     * Returns active banner list\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\ActionList|null\n     */\n    public function getBanners()\n    {\n        $oBannerList = null;\n\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('bl_perfLoadAktion')) {\n            $oBannerList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ActionList::class);\n            $oBannerList->loadBanners();\n        }\n\n        return $oBannerList;\n    }\n\n    /**\n     * Returns manufacturer list for manufacturer slider\n     *\n     * @return array|null\n     */\n    public function getManufacturerForSlider()\n    {\n        $oList = null;\n\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('bl_perfLoadManufacturerTree')) {\n            $oList = $this->getManufacturerlist();\n        }\n\n        return $oList;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/TemplateController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Template preparation class.\n * Used only in some specific cases (usually when you need to outpt just template\n * having text information).\n */\nclass TemplateController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /** @inheritdoc */\n    public function render()\n    {\n        parent::render();\n\n        // security fix so that you cant access files from outside template dir\n        $sTplName = basename((string) Registry::getRequest()->getRequestEscapedParameter(\"tpl\"));\n        if ($sTplName) {\n            $sTplName = 'custom/' . $sTplName;\n        }\n\n        return $sTplName;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/TextEditorHandler.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\n/**\n * Responsible for generation of text editor output.\n *\n * Class TextEditorHandler\n */\nclass TextEditorHandler\n{\n    /**\n     * @var string The style sheet for the editor.\n     */\n    private $stylesheet = null;\n\n    /**\n     * @var bool Information in the text editor is editable by default.\n     *           In some cases it should not be etc. when product is derived.\n     */\n    protected $textEditorDisabled = false;\n\n    /**\n     * Render text editor.\n     *\n     * @param int    $width       The editor width.\n     * @param int    $height      The editor height.\n     * @param object $objectValue The object value passed to editor.\n     * @param string $fieldName   The name of object field which content is passed to editor.\n     *\n     * @return string The Editor output.\n     */\n    public function renderTextEditor($width, $height, $objectValue, $fieldName)\n    {\n        $sEditorHtml = $this->renderRichTextEditor($width, $height, $objectValue, $fieldName);\n        if (!$sEditorHtml) {\n            $sEditorHtml = $this->renderPlainTextEditor($width, $height, $objectValue, $fieldName);\n        }\n        return $sEditorHtml;\n    }\n\n    /**\n     * Returns simple textarea element filled with object text to edit.\n     *\n     * @param int    $width       The editor width.\n     * @param int    $height      The editor height.\n     * @param object $objectValue The object value passed to editor.\n     * @param string $fieldName   The name of object field which content is passed to editor.\n     *\n     * @return string The Editor output.\n     */\n    public function renderPlainTextEditor($width, $height, $objectValue, $fieldName)\n    {\n        if (strpos($width, '%') === false) {\n            $width .= 'px';\n        }\n        if (strpos($height, '%') === false) {\n            $height .= 'px';\n        }\n\n        $disabledTextEditor = $this->isTextEditorDisabled() ? 'disabled ' : '';\n\n        return \"<textarea {$disabledTextEditor}id='editor_{$fieldName}' name='$fieldName' \" .\n               \"style='width:{$width}; height:{$height};'>{$objectValue}</textarea>\";\n    }\n\n    /**\n     * Returns the generated output of wysiwyg editor.\n     *\n     * @param int    $width       The editor width.\n     * @param int    $height      The editor height.\n     * @param object $objectValue The object value passed to editor.\n     * @param string $fieldName   The name of object field which content is passed to editor.\n     *\n     * @return string The Editor output.\n     */\n    public function renderRichTextEditor($width, $height, $objectValue, $fieldName)\n    {\n        return '';\n    }\n\n    /**\n     * Set the style sheet for the editor.\n     *\n     * @param string $stylesheet The stylesheet for editor.\n     */\n    public function setStyleSheet($stylesheet)\n    {\n        $this->stylesheet = $stylesheet;\n    }\n\n    /**\n     * Get the style sheet for the editor.\n     *\n     * @return string The stylesheet for the editor.\n     */\n    public function getStyleSheet()\n    {\n        return $this->stylesheet;\n    }\n\n    /**\n     * Mark text editor disabled: information in it should not be editable.\n     */\n    public function disableTextEditor()\n    {\n        $this->textEditorDisabled = true;\n    }\n\n    /**\n     * If information in text editor is not editable.\n     *\n     * @return bool\n     */\n    public function isTextEditorDisabled()\n    {\n        return $this->textEditorDisabled;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/ThankYouController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Thankyou page.\n * Arranges Thankyou page, sets ordering status, other parameters\n */\nclass ThankYouController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * User basket object\n     *\n     * @var object\n     */\n    protected $_oBasket = null;\n\n    /**\n     * List of customer also bought thies products\n     *\n     * @var object\n     */\n    protected $_aLastProducts = null;\n\n    /**\n     * Currency conversion index value\n     *\n     * @var double\n     */\n    protected $_dConvIndex = null;\n\n    /**\n     * IPayment basket\n     *\n     * @var double\n     */\n    protected $_dIPaymentBasket = null;\n\n    /**\n     * IPayment account\n     *\n     * @var string\n     */\n    protected $_sIPaymentAccount = null;\n\n    /**\n     * IPayment user name\n     *\n     * @var string\n     */\n    protected $_sIPaymentUser = null;\n\n    /**\n     * IPayment password\n     *\n     * @var string\n     */\n    protected $_sIPaymentPassword = null;\n\n    /**\n     * Mail error\n     *\n     * @var string\n     */\n    protected $_sMailError = null;\n\n    /**\n     * Sign if to load and show bargain action\n     *\n     * @var bool\n     */\n    protected $_blBargainAction = true;\n\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/checkout/thankyou';\n\n    /**\n     * Executes parent::init(), loads basket from session\n     * (thankyou::_oBasket = \\OxidEsales\\Eshop\\Core\\Session::getBasket()) then destroys\n     * it (\\OxidEsales\\Eshop\\Core\\Session::delBasket()), unsets user session ID, if\n     * this user didn't entered password while ordering.\n     */\n    public function init()\n    {\n        parent::init();\n\n        // get basket we might need some information from it here\n        $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n        $oBasket = $session->getBasket();\n        $oBasket->setOrderId(\\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable('sess_challenge'));\n\n        // copying basket object\n        $this->_oBasket = clone $oBasket;\n\n        // delete it from the session\n        $oBasket->deleteBasket();\n        Registry::getSession()->deleteVariable('sess_challenge');\n\n        // if not in order-context, redirect to start\n        $order = $this->getOrder();\n        if (!$order || !$order->getFieldData('oxordernr')) {\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->redirect(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopHomeURL() . '&cl=start');\n        }\n    }\n\n    /**\n     * First checks for basket - if no such object available -\n     * redirects to start page. Otherwise - executes parent::render()\n     * and returns name of template to render thankyou::_sThisTemplate.\n     *\n     * @return  string  current template file name\n     */\n    public function render()\n    {\n        if (!$this->_oBasket || !$this->_oBasket->getProductsCount()) {\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->redirect(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopHomeUrl() . '&cl=start', true, 302);\n        }\n\n        parent::render();\n\n        $oUser = $this->getUser();\n\n        // removing also unregistered user info (#2580)\n        if (!$oUser || !$oUser->oxuser__oxpassword->value) {\n            Registry::getSession()->deleteVariable('usr');\n            Registry::getSession()->deleteVariable('dynvalue');\n        }\n\n        // loading order sometimes needed in template\n        if ($this->_oBasket->getOrderId()) {\n            // owners stock reminder\n            $oEmail = oxNew(\\OxidEsales\\Eshop\\Core\\Email::class);\n            $oEmail->sendStockReminder($this->_oBasket->getContents());\n        }\n\n        // we must set active class as start\n        $this->getViewConfig()->setViewConfigParam('cl', 'start');\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Template variable getter. Returns active basket\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Basket\n     */\n    public function getBasket()\n    {\n        return $this->_oBasket;\n    }\n\n    /**\n     * Template variable getter. Returns list of customer also bought these products\n     *\n     * @return object\n     */\n    public function getAlsoBoughtTheseProducts()\n    {\n        if ($this->_aLastProducts === null) {\n            $this->_aLastProducts = false;\n            // 5th order step\n            $aBasketContents = array_values($this->getBasket()->getContents());\n            if ($oBasketItem = $aBasketContents[0]) {\n                if ($oProduct = $oBasketItem->getArticle(false)) {\n                    $this->_aLastProducts = $oProduct->getCustomerAlsoBoughtThisProducts();\n                }\n            }\n        }\n\n        return $this->_aLastProducts;\n    }\n\n    /**\n     * Template variable getter. Returns currency conversion index value\n     *\n     * @return object\n     */\n    public function getCurrencyCovIndex()\n    {\n        if ($this->_dConvIndex === null) {\n            // currency conversion index value\n            $oCur = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActShopCurrencyObject();\n            $this->_dConvIndex = 1 / $oCur->rate;\n        }\n\n        return $this->_dConvIndex;\n    }\n\n    /**\n     * Template variable getter. Returns ipayment basket price\n     *\n     * @return double\n     */\n    public function getIPaymentBasket()\n    {\n        if ($this->_dIPaymentBasket === null) {\n            $this->_dIPaymentBasket = $this->getBasket()->getPrice()->getBruttoPrice() * 100;\n        }\n\n        return $this->_dIPaymentBasket;\n    }\n\n    /**\n     * Template variable getter. Returns ipayment account\n     *\n     * @return string\n     */\n    public function getIPaymentAccount()\n    {\n        if ($this->_sIPaymentAccount === null) {\n            $this->_sIPaymentAccount = false;\n            $this->_sIPaymentAccount = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iShopID_iPayment_Account');\n        }\n\n        return $this->_sIPaymentAccount;\n    }\n\n    /**\n     * Template variable getter. Returns ipayment user name\n     *\n     * @return string\n     */\n    public function getIPaymentUser()\n    {\n        if ($this->_sIPaymentUser === null) {\n            $this->_sIPaymentUser = false;\n            $this->_sIPaymentUser = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iShopID_iPayment_User');\n        }\n\n        return $this->_sIPaymentUser;\n    }\n\n    /**\n     * Template variable getter. Returns ipayment password\n     *\n     * @return string\n     */\n    public function getIPaymentPassword()\n    {\n        if ($this->_sIPaymentPassword === null) {\n            $this->_sIPaymentPassword = false;\n            $this->_sIPaymentPassword = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iShopID_iPayment_Passwort');\n        }\n\n        return $this->_sIPaymentPassword;\n    }\n\n    /**\n     * Template variable getter. Returns mail error\n     *\n     * @return string\n     */\n    public function getMailError()\n    {\n        if ($this->_sMailError === null) {\n            $this->_sMailError = false;\n            $this->_sMailError = Registry::getRequest()->getRequestEscapedParameter('mailerror');\n        }\n\n        return $this->_sMailError;\n    }\n\n    /**\n     * Template variable getter. Returns order\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Order\n     */\n    public function getOrder()\n    {\n        if (!isset($this->_oOrder)) {\n            $this->_oOrder = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Order::class);\n            // loading order sometimes needed in template\n            if ($sOrderId = $this->getBasket()->getOrderId()) {\n                $this->_oOrder->load($sOrderId);\n            }\n        }\n\n        return $this->_oOrder;\n    }\n\n    /**\n     * Template variable getter. Returns country ISO 3\n     *\n     * @return string\n     */\n    public function getCountryISO3()\n    {\n        $oOrder = $this->getOrder();\n        if ($oOrder) {\n            $oCountry = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Country::class);\n            $oCountry->load($oOrder->oxorder__oxbillcountryid->value);\n\n            return $oCountry->oxcountry__oxisoalpha3->value;\n        }\n    }\n\n    /**\n     * Returns name of a view class, which will be active for an action\n     * (given a generic fnc, e.g. logout)\n     *\n     * @return string\n     */\n    public function getActionClassName()\n    {\n        return 'start';\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n\n\n        $iLang = Registry::getLang()->getBaseLanguage();\n        $aPath['title'] = Registry::getLang()->translateString('ORDER_COMPLETED', $iLang, false);\n        $aPath['link']  = $this->getLink();\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/UserController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * User details.\n * Collects and arranges user object data (information, like shipping address, etc.).\n */\nclass UserController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Current class template.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/checkout/user';\n\n    /**\n     * Order step marker\n     *\n     * @var bool\n     */\n    protected $_blIsOrderStep = true;\n\n    /**\n     * Revers of option blOrderDisWithoutReg\n     *\n     * @var array\n     */\n    protected $_blShowNoRegOpt = null;\n\n    /**\n     * Selected Address\n     *\n     * @var object\n     */\n    protected $_sSelectedAddress = null;\n\n    /**\n     * Login option\n     *\n     * @var integer\n     */\n    protected $_iOption = null;\n\n    /**\n     * Country list\n     *\n     * @var object\n     */\n    protected $_oCountryList = null;\n\n    /**\n     * Order remark\n     *\n     * @var string\n     */\n    protected $_sOrderRemark = null;\n\n    /**\n     * Wishlist user id\n     *\n     * @var string\n     */\n    protected $_sWishId = null;\n\n    /**\n     * Loads customer basket object form session (\\OxidEsales\\Eshop\\Core\\Session::getBasket()),\n     * passes action article/basket/country list to template engine. If\n     * available - loads user delivery address data (oxAddress). Returns\n     * name template file to render user::_sThisTemplate.\n     *\n     * @return  string  $this->_sThisTemplate   current template file name\n     */\n    public function render()\n    {\n        $config = Registry::getConfig();\n\n        if ($this->getIsOrderStep()) {\n            $session = Registry::getSession();\n\n            if ($config->getConfigParam('blPsBasketReservationEnabled')) {\n                $session->getBasketReservations()->renewExpiration();\n            }\n\n            $basket = $session->getBasket();\n            $isPsBasketReservationsEnabled = $config->getConfigParam('blPsBasketReservationEnabled');\n            if (\n                $this->_blIsOrderStep && $isPsBasketReservationsEnabled &&\n                (!$basket || ($basket && !$basket->getProductsCount()))\n            ) {\n                Registry::getUtils()->redirect($config->getShopHomeUrl() . 'cl=basket', true, 302);\n            }\n        }\n\n        $this->_aViewData[\"deladr\"] = Registry::getRequest()->getRequestEscapedParameter('deladr');\n\n        parent::render();\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Template variable getter. Returns reverse option blOrderDisWithoutReg\n     *\n     * @return bool\n     */\n    public function getShowNoRegOption()\n    {\n        if ($this->_blShowNoRegOpt === null) {\n            $this->_blShowNoRegOpt = !Registry::getConfig()->getConfigParam('blOrderDisWithoutReg');\n        }\n\n        return $this->_blShowNoRegOpt;\n    }\n\n    /**\n     * Template variable getter. Returns user login option\n     *\n     * @return integer\n     */\n    public function getLoginOption()\n    {\n        if ($this->_iOption === null) {\n            // passing user chosen option value to display correct content\n            $option = Registry::getRequest()->getRequestEscapedParameter('option');\n            // if user chosen \"Option 2\"\" - we should show user details only if he is authorized\n            if ($option == 2 && !$this->getUser()) {\n                $option = 0;\n            }\n            $this->_iOption = $option;\n        }\n\n        return $this->_iOption;\n    }\n\n    /**\n     * Template variable getter. Returns order remark\n     *\n     * @return string\n     */\n    public function getOrderRemark()\n    {\n        $config = Registry::getConfig();\n        if ($this->_sOrderRemark === null) {\n            // if already connected, we can use the session\n            if ($this->getUser()) {\n                $orderRemark = Registry::getSession()->getVariable('ordrem');\n            } else {\n                // not connected so nowhere to save, we're gonna use what we get from post\n                $orderRemark = Registry::getRequest()->getRequestParameter('order_remark');\n            }\n\n            $this->_sOrderRemark = $orderRemark ? $config->checkParamSpecialChars($orderRemark) : false;\n        }\n\n        return $this->_sOrderRemark;\n    }\n\n    /**\n     * Template variable getter. Returns if user subscribed for newsletter\n     *\n     * @return bool\n     */\n    public function isNewsSubscribed()\n    {\n        if ($this->_blNewsSubscribed === null) {\n            if (($isSubscribedToNews = Registry::getRequest()->getRequestEscapedParameter('blnewssubscribed')) === null) {\n                $isSubscribedToNews = false;\n            }\n            if (($user = $this->getUser())) {\n                $isSubscribedToNews = $user->getNewsSubscription()->getOptInStatus();\n            }\n            $this->_blNewsSubscribed = $isSubscribedToNews;\n        }\n\n        if (is_null($this->_blNewsSubscribed)) {\n            $this->_blNewsSubscribed = false;\n        }\n\n        return $this->_blNewsSubscribed;\n    }\n\n    /**\n     * Template variable getter. Checks to show or not shipping address entry form\n     *\n     * @return bool\n     */\n    public function showShipAddress()\n    {\n        return Registry::getSession()->getVariable('blshowshipaddress');\n    }\n\n    /**\n     * Return true if user wants to change his billing address\n     *\n     * @return bool\n     */\n    public function modifyBillAddress()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('blnewssubscribed');\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $paths = [];\n        $path = [];\n\n        $baseLanguageId = Registry::getLang()->getBaseLanguage();\n        $path['title'] = Registry::getLang()->translateString('ADDRESS', $baseLanguageId, false);\n        $path['link'] = $this->getLink();\n\n        $paths[] = $path;\n\n        return $paths;\n    }\n\n    /**\n     * Returns warning message if user want to buy downloadable product without registration.\n     *\n     * @return bool\n     */\n    public function isDownloadableProductWarning()\n    {\n        $session = Registry::getSession();\n        $basket = $session->getBasket();\n        if ($basket && Registry::getConfig()->getConfigParam(\"blEnableDownloads\")) {\n            if ($basket->hasDownloadableProducts()) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/VendorListController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * List of articles for a selected vendor.\n * Collects list of articles, according to it generates links for list gallery,\n * meta tags (for search engines). Result - \"vendorlist\" template.\n * OXID eShop -> (Any selected shop product category).\n */\nclass VendorListController extends \\OxidEsales\\Eshop\\Application\\Controller\\ArticleListController\n{\n    /**\n     * List type\n     *\n     * @var string\n     */\n    protected $_sListType = 'vendor';\n\n    /**\n     * List type\n     *\n     * @var string\n     */\n    protected $_blVisibleSubCats = null;\n\n    /**\n     * List type\n     *\n     * @var string\n     */\n    protected $_oSubCatList = null;\n\n    /**\n     * Template location\n     *\n     * @var string\n     */\n    protected $_sTplLocation = null;\n\n    /**\n     * Template location\n     *\n     * @var string\n     */\n    protected $_sCatTitle = null;\n\n    /**\n     * Page navigation\n     *\n     * @var object\n     */\n    protected $_oPageNavigation = null;\n\n    /**\n     * Marked which defines if current view is sortable or not\n     *\n     * @var bool\n     */\n    protected $_blShowSorting = true;\n\n    /**\n     * Current view search engine indexing state\n     *\n     * @var int\n     */\n    protected $_iViewIndexState = VIEW_INDEXSTATE_INDEX;\n\n    /**\n     * Vendor list object.\n     *\n     * @var object\n     */\n    protected $_oVendorTree = null;\n\n    /**\n     * Executes parent::render(), loads active vendor, prepares article\n     * list sorting rules. Loads list of articles which belong to this vendor\n     * Generates page navigation data\n     * such as previous/next window URL, number of available pages, generates\n     * meta tags info (\\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::_convertForMetaTags()) and returns\n     * name of template to render.\n     *\n     * @return  string  $this->_sThisTemplate   current template file name\n     */\n    public function render()\n    {\n        \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::render();\n\n        // load vendor\n        if (($this->getVendorIdFromRequest() && $this->getVendorTree())) {\n            if (($oVendor = $this->getActVendor())) {\n                if ($oVendor->getId() != 'root') {\n                    // load the articles\n                    $this->getArticleList();\n\n                    // checking if requested page is correct\n                    $this->checkRequestedPage();\n\n                    // processing list articles\n                    $this->processListArticles();\n                }\n            }\n        }\n\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Returns product link type (OXARTICLE_LINKTYPE_VENDOR)\n     *\n     * @return int\n     */\n    protected function getProductLinkType()\n    {\n        return OXARTICLE_LINKTYPE_VENDOR;\n    }\n\n    /**\n     * Loads and returns article list of active vendor.\n     *\n     * @param object $oVendor vendor object\n     *\n     * @return array\n     */\n    protected function loadArticles($oVendor)\n    {\n        $sVendorId = $oVendor->getId();\n\n        // load only articles which we show on screen\n        $iNrOfCatArticles = (int) \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iNrofCatArticles');\n        $iNrOfCatArticles = $iNrOfCatArticles ? $iNrOfCatArticles : 1;\n\n        $oArtList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n        $oArtList->setSqlLimit($iNrOfCatArticles * $this->getRequestPageNr(), $iNrOfCatArticles);\n        $oArtList->setCustomSorting($this->getSortingSql($this->getSortIdent()));\n\n        // load the articles\n        $this->_iAllArtCnt = $oArtList->loadVendorArticles($sVendorId, $oVendor);\n\n        // counting pages\n        $this->_iCntPages = ceil($this->_iAllArtCnt / $iNrOfCatArticles);\n\n        return [$oArtList, $this->_iAllArtCnt];\n    }\n\n    /**\n     * Returns active product id to load its seo meta info\n     *\n     * @return string\n     */\n    protected function getSeoObjectId()\n    {\n        if (($oVendor = $this->getActVendor())) {\n            return $oVendor->getId();\n        }\n    }\n\n    /**\n     * Modifies url by adding page parameters. When seo is on, url is additionally\n     * formatted by SEO engine\n     *\n     * @param string $sUrl  current url\n     * @param int    $iPage page number\n     * @param int    $iLang active language id\n     *\n     * @return string\n     */\n    protected function addPageNrParam($sUrl, $iPage, $iLang = null)\n    {\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getUtils()->seoIsActive() && ($oVendor = $this->getActVendor())) {\n            if ($iPage) {\n                // only if page number > 0\n                $sUrl = $oVendor->getBaseSeoLink($iLang, $iPage);\n            }\n        } else {\n            $sUrl = \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::addPageNrParam($sUrl, $iPage, $iLang);\n        }\n\n        return $sUrl;\n    }\n\n    /**\n     * Returns current view Url\n     *\n     * @return string\n     */\n    public function generatePageNavigationUrl()\n    {\n        if ((\\OxidEsales\\Eshop\\Core\\Registry::getUtils()->seoIsActive() && ($oVendor = $this->getActVendor()))) {\n            return $oVendor->getLink();\n        } else {\n            return parent::generatePageNavigationUrl();\n        }\n    }\n\n    /**\n     * Returns if vendor has visible sub-cats and load them.\n     *\n     * @return bool\n     */\n    public function hasVisibleSubCats()\n    {\n        if ($this->_blVisibleSubCats === null) {\n            $this->_blVisibleSubCats = false;\n            if (($this->getVendorIdFromRequest() && $oVendorTree = $this->getVendorTree())) {\n                if (($oVendor = $this->getActVendor())) {\n                    if ($oVendor->getId() == 'root') {\n                        $this->_blVisibleSubCats = $oVendorTree->count();\n                        $this->_oSubCatList = $oVendorTree;\n                    }\n                }\n            }\n        }\n\n        return $this->_blVisibleSubCats;\n    }\n\n    /**\n     * Returns vendor subcategories\n     *\n     * @return array\n     */\n    public function getSubCatList()\n    {\n        if ($this->_oSubCatList === null) {\n            $this->_oSubCatList = [];\n            if ($this->hasVisibleSubCats()) {\n                return $this->_oSubCatList;\n            }\n        }\n\n        return $this->_oSubCatList;\n    }\n\n    /**\n     * Get vendor article list\n     *\n     * @return array\n     */\n    public function getArticleList()\n    {\n        if ($this->_aArticleList === null) {\n            $this->_aArticleList = [];\n            if (($oVendor = $this->getActVendor()) && ($oVendor->getId() != 'root')) {\n                list($aArticleList, $iAllArtCnt) = $this->loadArticles($oVendor);\n                if ($iAllArtCnt) {\n                    $this->_aArticleList = $aArticleList;\n                }\n            }\n        }\n\n        return $this->_aArticleList;\n    }\n\n    /**\n     * Return vendor title\n     *\n     * @return string\n     */\n    public function getTitle()\n    {\n        if ($this->_sCatTitle === null) {\n            $this->_sCatTitle = '';\n            if ($oVendor = $this->getActVendor()) {\n                $this->_sCatTitle = $oVendor->oxvendor__oxtitle->value;\n            }\n        }\n\n        return $this->_sCatTitle;\n    }\n\n    /**\n     * Template variable getter. Returns category path array\n     *\n     * @return array\n     */\n    public function getTreePath()\n    {\n        if ($this->getVendorIdFromRequest() && $oVendorTree = $this->getVendorTree()) {\n            return $oVendorTree->getPath();\n        }\n    }\n\n    /**\n     * Returns request parameter of vendor id.\n     *\n     * @return string\n     */\n    protected function getVendorIdFromRequest()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('cnid');\n    }\n\n    /**\n     * Template variable getter. Returns active vendor\n     *\n     * @return object\n     */\n    public function getActiveCategory()\n    {\n        if ($this->_oActCategory === null) {\n            $this->_oActCategory = false;\n            if (($this->getVendorIdFromRequest() && $oVendorTree = $this->getVendorTree())) {\n                if ($oVendor = $this->getActVendor()) {\n                    $this->_oActCategory = $oVendor;\n                }\n            }\n        }\n\n        return $this->_oActCategory;\n    }\n\n    /**\n     * Template variable getter. Returns template location\n     *\n     * @return string\n     */\n    public function getCatTreePath()\n    {\n        if ($this->_sCatTreePath === null) {\n            $this->_sCatTreePath = false;\n            if (($oVendorTree = $this->getVendorTree())) {\n                $this->_sCatTreePath = $oVendorTree->getPath();\n            }\n        }\n\n        return $this->_sCatTreePath;\n    }\n\n    /**\n     * Returns title suffix used in template\n     *\n     * @return string\n     */\n    public function getTitleSuffix()\n    {\n        if ($this->getActVendor()->getFieldData('oxshowsuffix')) {\n            return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActiveShop()->getFieldData('oxtitlesuffix');\n        }\n    }\n\n    /**\n     * Returns current view keywords separated by comma\n     * (calls parent::collectMetaKeyword())\n     *\n     * @param string $sKeywords               data to use as keywords\n     * @param bool   $blRemoveDuplicatedWords remove duplicated words\n     *\n     * @return string\n     */\n    protected function prepareMetaKeyword($sKeywords, $blRemoveDuplicatedWords = true)\n    {\n        return parent::collectMetaKeyword($sKeywords);\n    }\n\n    /**\n     * Returns current view meta description data\n     * (calls parent::collectMetaDescription())\n     *\n     * @param string $sMeta     category path\n     * @param int    $iLength   max length of result, -1 for no truncation\n     * @param bool   $blDescTag if true - performs additional duplicate cleaning\n     *\n     * @return string\n     */\n    protected function prepareMetaDescription($sMeta, $iLength = 1024, $blDescTag = false)\n    {\n        return parent::collectMetaDescription($sMeta, $iLength, $blDescTag);\n    }\n\n    /**\n     * returns object, associated with current view.\n     * (the object that is shown in frontend)\n     *\n     * @param int $iLang language id\n     *\n     * @return object\n     */\n    protected function getSubject($iLang)\n    {\n        return $this->getActVendor();\n    }\n\n    /**\n     * Returns additional URL parameters which must be added to list products dynamic urls\n     *\n     * @return string\n     */\n    public function getAddUrlParams()\n    {\n        $sAddParams = parent::getAddUrlParams();\n        $sAddParams .= ($sAddParams ? '&amp;' : '') . \"listtype={$this->_sListType}\";\n        if ($oVendor = $this->getActVendor()) {\n            $sAddParams .= \"&amp;cnid=v_\" . $oVendor->getId();\n        }\n\n        return $sAddParams;\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $oCatTree = $this->getVendorTree();\n\n        if ($oCatTree) {\n            foreach ($oCatTree->getPath() as $oCat) {\n                $aCatPath = [];\n\n                $aCatPath['link'] = $oCat->getLink();\n                $aCatPath['title'] = $oCat->oxcategories__oxtitle->value;\n\n                $aPaths[] = $aCatPath;\n            }\n        }\n\n        return $aPaths;\n    }\n\n\n    /**\n     * Returns vendor tree\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\VendorList\n     */\n    public function getVendorTree()\n    {\n        if ($this->getVendorIdFromRequest() && $this->_oVendorTree === null) {\n            /** @var \\OxidEsales\\Eshop\\Application\\Model\\VendorList $oVendorTree */\n            $oVendorTree = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\VendorList::class);\n            $oVendorTree->buildVendorTree(\n                'vendorlist',\n                $this->getActVendor()->getId(),\n                \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopHomeUrl()\n            );\n            $this->_oVendorTree = $oVendorTree;\n        }\n\n        return $this->_oVendorTree;\n    }\n\n    /**\n     * Vendor tree setter\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\VendorList $oVendorTree vendor tree\n     */\n    public function setVendorTree($oVendorTree)\n    {\n        $this->_oVendorTree = $oVendorTree;\n    }\n\n    /**\n     * Template variable getter. Returns array of attribute values\n     * we do have here in this category\n     *\n     * @return array\n     */\n    public function getAttributes()\n    {\n        return null;\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/WishListController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * The wishlist of someone else is displayed.\n */\nclass WishListController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/wishlist/wishlist';\n\n    /**\n     * user object list\n     *\n     * @return object\n     */\n    protected $_oWishUser = null;\n\n    /**\n     * wishlist object list\n     *\n     * @return object\n     */\n    protected $_oWishList = null;\n\n    /**\n     * Wishlist search param\n     *\n     * @var string\n     */\n    protected $_sSearchParam = null;\n\n    /**\n     * List of users which were found according to search condition\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    protected $_oWishListUsers = false;\n\n    /**\n     * Sign if to load and show bargain action\n     *\n     * @var bool\n     */\n    protected $_blBargainAction = true;\n\n    /**\n     * return the user which is owner of the wish list\n     *\n     * @return object|bool\n     */\n    public function getWishUser()\n    {\n        if ($this->_oWishUser === null) {\n            $this->_oWishUser = false;\n\n            $sWishIdParameter = Registry::getRequest()->getRequestEscapedParameter('wishid');\n            $sUserId = $sWishIdParameter ? $sWishIdParameter : Registry::getSession()->getVariable('wishid');\n            if ($sUserId) {\n                $oUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n                if ($oUser->load($sUserId)) {\n                    // passing wishlist information\n                    $this->_oWishUser = $oUser;\n\n                    // store this one to session\n                    Registry::getSession()->setVariable('wishid', $sUserId);\n                }\n            }\n        }\n\n        return $this->_oWishUser;\n    }\n\n    /**\n     * return the articles which are in the wish list\n     *\n     * @return object|bool\n     */\n    public function getWishList()\n    {\n        if ($this->_oWishList === null) {\n            $this->_oWishList = false;\n\n            // passing wishlist information\n            if ($oUser = $this->getWishUser()) {\n                $oWishlistBasket = $oUser->getBasket('wishlist');\n                $this->_oWishList = $oWishlistBasket->getArticles();\n\n                if (!$oWishlistBasket->isVisible()) {\n                    $this->_oWishList = false;\n                }\n            }\n        }\n\n        return $this->_oWishList;\n    }\n\n    /**\n     * Searches for wishlist of another user. Returns false if no\n     * searching conditions set (no login name defined).\n     *\n     * Template variables:\n     * <b>wish_result</b>, <b>search</b>\n     */\n    public function searchForWishList()\n    {\n        if ($sSearch = Registry::getRequest()->getRequestEscapedParameter('search')) {\n            // search for baskets\n            $oUserList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\UserList::class);\n            $oUserList->loadWishlistUsers($sSearch);\n            if ($oUserList->count()) {\n                $this->_oWishListUsers = $oUserList;\n            }\n            $this->_sSearchParam = $sSearch;\n        }\n    }\n\n    /**\n     * Returns a list of users which were found according to search condition.\n     * If no users were found - false is returned\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\ListModel|bool\n     */\n    public function getWishListUsers()\n    {\n        return $this->_oWishListUsers;\n    }\n\n    /**\n     * Returns wish list search parameter\n     *\n     * @return string\n     */\n    public function getWishListSearchParam()\n    {\n        return $this->_sSearchParam;\n    }\n\n    /**\n     * Returns Bread Crumb - you are here page1/page2/page3...\n     *\n     * @return array\n     */\n    public function getBreadCrumb()\n    {\n        $aPaths = [];\n        $aPath = [];\n\n        $iBaseLanguage = Registry::getLang()->getBaseLanguage();\n        $aPath['title'] = Registry::getLang()->translateString('PUBLIC_GIFT_REGISTRIES', $iBaseLanguage, false);\n        $aPath['link'] = $this->getLink();\n        $aPaths[] = $aPath;\n\n        return $aPaths;\n    }\n\n    /**\n     * Page title\n     *\n     * @return string\n     */\n    public function getTitle()\n    {\n        $oLang = Registry::getLang();\n        if ($oUser = $this->getWishUser()) {\n            $sTranslatedString = $oLang->translateString('GIFT_REGISTRY_OF_3', $oLang->getBaseLanguage(), false);\n            $sFirstnameField = 'oxuser__oxfname';\n            $sLastnameField = 'oxuser__oxlname';\n\n            return $sTranslatedString . ' ' . $oUser->$sFirstnameField->value . ' ' . $oUser->$sLastnameField->value;\n        }\n\n        return $oLang->translateString('PUBLIC_GIFT_REGISTRIES', $oLang->getBaseLanguage(), false);\n    }\n}\n"
  },
  {
    "path": "source/Application/Controller/WrappingController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Wrapping;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Managing Gift Wrapping\n */\nclass WrappingController extends \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController\n{\n    /**\n     * Current class template name.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = 'page/checkout/wrapping';\n\n    /**\n     * Basket items array\n     *\n     * @var array\n     */\n    protected $_aBasketItemList = null;\n\n    /**\n     * Wrapping objects list\n     */\n    protected $_oWrappings = null;\n\n    /**\n     * Card objects list\n     */\n    protected $_oCards = null;\n\n    /**\n     * Returns array of shopping basket articles\n     *\n     * @return array\n     */\n    public function getBasketItems()\n    {\n        if ($this->_aBasketItemList === null) {\n            $this->_aBasketItemList = false;\n\n            // passing basket articles\n            $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n            if ($oBasket = $session->getBasket()) {\n                $this->_aBasketItemList = $oBasket->getBasketArticles();\n            }\n        }\n\n        return $this->_aBasketItemList;\n    }\n\n    /**\n     * Return basket wrappings list if available\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    public function getWrappingList()\n    {\n        if ($this->_oWrappings === null) {\n            $this->_oWrappings = new \\OxidEsales\\Eshop\\Core\\Model\\ListModel();\n\n            // load wrapping papers\n            if ($this->getViewConfig()->getShowGiftWrapping()) {\n                $this->_oWrappings = oxNew(Wrapping::class)->getWrappingList('WRAP');\n            }\n        }\n\n        return $this->_oWrappings;\n    }\n\n    /**\n     * Returns greeting cards list if available\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    public function getCardList()\n    {\n        if ($this->_oCards === null) {\n            $this->_oCards = new \\OxidEsales\\Eshop\\Core\\Model\\ListModel();\n\n            // load gift cards\n            if ($this->getViewConfig()->getShowGiftWrapping()) {\n                $this->_oCards = oxNew(Wrapping::class)->getWrappingList('CARD');\n            }\n        }\n\n        return $this->_oCards;\n    }\n\n    /**\n     * Updates wrapping data in session basket object\n     * (\\OxidEsales\\Eshop\\Core\\Session::getBasket()) - adds wrapping info to\n     * each article in basket (if possible). Plus adds\n     * gift message and chosen card ( takes from GET/POST/session;\n     * oBasket::giftmessage, oBasket::chosencard). Then sets\n     * basket back to session (\\OxidEsales\\Eshop\\Core\\Session::setBasket()). Returns\n     * \"order\" to redirect to order confirmation secreen.\n     *\n     * @return string\n     */\n    public function changeWrapping()\n    {\n        $aWrapping = Registry::getRequest()->getRequestEscapedParameter('wrapping');\n\n        if ($this->getViewConfig()->getShowGiftWrapping()) {\n            $session = Registry::getSession();\n            $oBasket = $session->getBasket();\n            // setting wrapping info\n            if (is_array($aWrapping) && count($aWrapping)) {\n                foreach ($oBasket->getContents() as $sKey => $oBasketItem) {\n                    // wrapping ?\n                    if (isset($aWrapping[$sKey])) {\n                        $oBasketItem->setWrapping($aWrapping[$sKey]);\n                    }\n                }\n            }\n\n            $oBasket->setCardMessage(Registry::getRequest()->getRequestEscapedParameter('giftmessage'));\n            $oBasket->setCardId(Registry::getRequest()->getRequestEscapedParameter('chosencard'));\n            $oBasket->onUpdate();\n        }\n\n        return 'order';\n    }\n}\n"
  },
  {
    "path": "source/Application/Enum/NewsletterSubscriptionStatus.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Enum;\n\nenum NewsletterSubscriptionStatus: int\n{\n    case Subscribed = 1;\n    case SubscriptionConfirmed = 2;\n    case Canceled = 3;\n    case StayInformed = 4;\n}\n"
  },
  {
    "path": "source/Application/Enum/SubscriptionOptedInStatus.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Enum;\n\nenum SubscriptionOptedInStatus: int\n{\n    case Disabled = 0;\n    case Active = 1;\n    case Pending = 2;\n}\n"
  },
  {
    "path": "source/Application/Model/ActionList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse oxRegistry;\nuse oxDb;\n\n/**\n * Promotion List manager.\n */\nclass ActionList extends \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n{\n    /**\n     * List Object class name\n     *\n     * @var string\n     */\n    protected $_sObjectsInListName = 'oxactions';\n\n    /**\n     * Loads x last finished promotions\n     *\n     * @param int $iCount count to load\n     */\n    public function loadFinishedByCount($iCount)\n    {\n        $sViewName = $this->getBaseObject()->getViewName();\n        $sDate = date('Y-m-d H:i:s', \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime());\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sQ = \"select * from {$sViewName} where oxtype=2 and oxactive=1 and oxshopid='\" . \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId() . \"' and oxactiveto>0 and oxactiveto < \" . $oDb->quote($sDate) . \"\n               \" . $this->getUserGroupFilter() . \"\n               order by oxactiveto desc, oxactivefrom desc limit \" . (int) $iCount;\n        $this->selectString($sQ);\n        $this->_aArray = array_reverse($this->_aArray, true);\n    }\n\n    /**\n     * Loads last finished promotions after given timespan\n     *\n     * @param int $iTimespan timespan to load\n     */\n    public function loadFinishedByTimespan($iTimespan)\n    {\n        $sViewName = $this->getBaseObject()->getViewName();\n        $sDateTo = date('Y-m-d H:i:s', \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime());\n        $sDateFrom = date('Y-m-d H:i:s', \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime() - $iTimespan);\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sQ = \"select * from {$sViewName} where oxtype=2 and oxactive=1 and oxshopid='\" . \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId() . \"' and oxactiveto < \" . $oDb->quote($sDateTo) . \" and oxactiveto > \" . $oDb->quote($sDateFrom) . \"\n               \" . $this->getUserGroupFilter() . \"\n               order by oxactiveto, oxactivefrom\";\n        $this->selectString($sQ);\n    }\n\n    /**\n     * Loads current promotions\n     */\n    public function loadCurrent()\n    {\n        $sViewName = $this->getBaseObject()->getViewName();\n        $sDate = date('Y-m-d H:i:s', \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime());\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sQ = \"select * from {$sViewName} where oxtype=2 and oxactive=1 and oxshopid='\" . \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId() . \"' and (oxactiveto > \" . $oDb->quote($sDate) . \" or oxactiveto=0) and oxactivefrom != 0 and oxactivefrom < \" . $oDb->quote($sDate) . \"\n               \" . $this->getUserGroupFilter() . \"\n               order by oxactiveto, oxactivefrom\";\n        $this->selectString($sQ);\n    }\n\n    /**\n     * Loads next not yet started promotions by cound\n     *\n     * @param int $iCount count to load\n     */\n    public function loadFutureByCount($iCount)\n    {\n        $sViewName = $this->getBaseObject()->getViewName();\n        $sDate = date('Y-m-d H:i:s', \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime());\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sQ = \"select * from {$sViewName} where oxtype=2 and oxactive=1 and oxshopid='\" . \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId() . \"' and (oxactiveto > \" . $oDb->quote($sDate) . \" or oxactiveto=0) and oxactivefrom > \" . $oDb->quote($sDate) . \"\n               \" . $this->getUserGroupFilter() . \"\n               order by oxactiveto, oxactivefrom limit \" . (int) $iCount;\n        $this->selectString($sQ);\n    }\n\n    /**\n     * Loads next not yet started promotions before the given timespan\n     *\n     * @param int $iTimespan timespan to load\n     */\n    public function loadFutureByTimespan($iTimespan)\n    {\n        $sViewName = $this->getBaseObject()->getViewName();\n        $sDate = date('Y-m-d H:i:s', \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime());\n        $sDateTo = date('Y-m-d H:i:s', \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime() + $iTimespan);\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sQ = \"select * from {$sViewName} where oxtype=2 and oxactive=1 and oxshopid='\" . \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId() . \"' and (oxactiveto > \" . $oDb->quote($sDate) . \" or oxactiveto=0) and oxactivefrom > \" . $oDb->quote($sDate) . \" and oxactivefrom < \" . $oDb->quote($sDateTo) . \"\n               \" . $this->getUserGroupFilter() . \"\n               order by oxactiveto, oxactivefrom\";\n        $this->selectString($sQ);\n    }\n\n    /**\n     * Returns part of user group filter query\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser user object\n     *\n     * @return string\n     */\n    protected function getUserGroupFilter($oUser = null)\n    {\n        $oUser = ($oUser == null) ? $this->getUser() : $oUser;\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sTable = $tableViewNameGenerator->getViewName('oxactions');\n        $sGroupTable = $tableViewNameGenerator->getViewName('oxgroups');\n\n        $aIds = [];\n        // checking for current session user which gives additional restrictions for user itself, users group and country\n        if ($oUser && count($aGroupIds = $oUser->getUserGroups())) {\n            foreach ($aGroupIds as $oGroup) {\n                $aIds[] = $oGroup->getId();\n            }\n        }\n\n        $sGroupSql = count($aIds) ? \"EXISTS(select oxobject2action.oxid from oxobject2action where oxobject2action.oxactionid=$sTable.OXID and oxobject2action.oxclass='oxgroups' and oxobject2action.OXOBJECTID in (\" . implode(', ', \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aIds)) . \") )\" : '0';\n        return \" and (\n                if(EXISTS(select 1 from oxobject2action, $sGroupTable where $sGroupTable.oxid=oxobject2action.oxobjectid and oxobject2action.oxactionid=$sTable.OXID and oxobject2action.oxclass='oxgroups' LIMIT 1),\n                    $sGroupSql,\n                    1)\n            ) \";\n    }\n\n    /**\n     * return true if there are any active promotions\n     *\n     * @return boolean\n     */\n    public function areAnyActivePromotions()\n    {\n        return (bool) $this->fetchExistsActivePromotion();\n    }\n\n\n    /**\n     * Fetch the information, if there is an active promotion.\n     *\n     * @return string One, if there is an active promotion.\n     */\n    protected function fetchExistsActivePromotion()\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $query = \"select 1 from \" . $tableViewNameGenerator->getViewName('oxactions') . \" \n            where oxtype = :oxtype and oxactive = :oxactive and oxshopid = :oxshopid \n            limit 1\";\n\n        return \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->getOne($query, [\n            'oxtype' => 2,\n            'oxactive' => 1,\n            'oxshopid' => \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId()\n        ]);\n    }\n\n    /**\n     * load active shop banner list\n     */\n    public function loadBanners()\n    {\n        $oBaseObject = $this->getBaseObject();\n        $oViewName = $oBaseObject->getViewName();\n        $sQ = \"select * from {$oViewName} where oxtype=3 and \" . $oBaseObject->getSqlActiveSnippet()\n              . \" and oxshopid='\" . \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId() . \"' \" . $this->getUserGroupFilter()\n              . \" order by oxsort\";\n        $this->selectString($sQ);\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Actions.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Application\\Controller\\FrontendController;\n\n/**\n * Article actions manager. Collects and keeps actions of chosen article.\n */\nclass Actions extends \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel\n{\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = \"oxactions\";\n\n    /**\n     * Class constructor. Executes oxActions::init(), initiates parent constructor.\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init(\"oxactions\");\n    }\n\n    /**\n     * Adds an article to this actions\n     *\n     * @param string $articleId id of the article to be added\n     */\n    public function addArticle($articleId)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sQ = \"select max(oxsort) \n                from oxactions2article \n                where oxactionid = :oxactionid and oxshopid = :oxshopid\";\n\n        $params = [\n            'oxactionid' => $this->getId(),\n            'oxshopid' => $this->getShopId()\n        ];\n        $iSort = ((int)$oDb->getOne($sQ, $params)) + 1;\n\n        $oNewGroup = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n        $oNewGroup->init('oxactions2article');\n        $oNewGroup->oxactions2article__oxshopid = new \\OxidEsales\\Eshop\\Core\\Field($this->getShopId());\n        $oNewGroup->oxactions2article__oxactionid = new \\OxidEsales\\Eshop\\Core\\Field($this->getId());\n        $oNewGroup->oxactions2article__oxartid = new \\OxidEsales\\Eshop\\Core\\Field($articleId);\n        $oNewGroup->oxactions2article__oxsort = new \\OxidEsales\\Eshop\\Core\\Field($iSort);\n        $oNewGroup->save();\n    }\n\n    /**\n     * Removes an article from this actions\n     *\n     * @param string $articleId id of the article to be removed\n     *\n     * @return bool\n     */\n    public function removeArticle($articleId)\n    {\n        // remove actions from articles also\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sDelete = \"delete from oxactions2article where oxactionid = :oxactionid and oxartid = :oxartid and oxshopid = :oxshopid\";\n        $iRemovedArticles = $oDb->execute($sDelete, [\n            'oxactionid' => $this->getId(),\n            'oxartid' => $articleId,\n            'oxshopid' => $this->getShopId()\n        ]);\n\n        return (bool) $iRemovedArticles;\n    }\n\n    /**\n     * Removes article action, returns true on success. For\n     * performance - you can not load action object - just pass\n     * action ID.\n     *\n     * @param string $articleId Object ID\n     *\n     * @return bool\n     */\n    public function delete($articleId = null)\n    {\n        $articleId = $articleId ? $articleId : $this->getId();\n        if (!$articleId) {\n            return false;\n        }\n\n        // remove actions from articles also\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sDelete = \"delete from oxactions2article where oxactionid = :oxactionid and oxshopid = :oxshopid\";\n        $oDb->execute($sDelete, [\n            'oxactionid' => $articleId,\n            'oxshopid' => $this->getShopId()\n        ]);\n\n        return parent::delete($articleId);\n    }\n\n    /**\n     * return time left until finished\n     *\n     * @return int\n     */\n    public function getTimeLeft()\n    {\n        $iNow = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime();\n        $iFrom = strtotime($this->oxactions__oxactiveto->value);\n\n        return $iFrom - $iNow;\n    }\n\n    /**\n     * return time left until start\n     *\n     * @return int\n     */\n    public function getTimeUntilStart()\n    {\n        $iNow = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime();\n        $iFrom = strtotime($this->oxactions__oxactivefrom->value);\n\n        return $iFrom - $iNow;\n    }\n\n    /**\n     * start the promotion NOW!\n     */\n    public function start()\n    {\n        $this->oxactions__oxactivefrom = new \\OxidEsales\\Eshop\\Core\\Field(date('Y-m-d H:i:s', \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime()));\n        if ($this->oxactions__oxactiveto->value && ($this->oxactions__oxactiveto->value != '0000-00-00 00:00:00')) {\n            $iNow = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime();\n            $iTo = strtotime($this->oxactions__oxactiveto->value);\n            if ($iNow > $iTo) {\n                $this->oxactions__oxactiveto = new \\OxidEsales\\Eshop\\Core\\Field('0000-00-00 00:00:00');\n            }\n        }\n        $this->save();\n    }\n\n    /**\n     * stop the promotion NOW!\n     */\n    public function stop()\n    {\n        $this->oxactions__oxactiveto = new \\OxidEsales\\Eshop\\Core\\Field(date('Y-m-d H:i:s', \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime()));\n        $this->save();\n    }\n\n    /**\n     * check if this action is active\n     *\n     * @return bool\n     */\n    public function isRunning()\n    {\n        if (\n            !(\n            $this->oxactions__oxactive->value\n              && $this->oxactions__oxtype->value == 2\n              && $this->oxactions__oxactivefrom->value != '0000-00-00 00:00:00'\n            )\n        ) {\n            return false;\n        }\n        $iNow = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime();\n        $iFrom = strtotime($this->oxactions__oxactivefrom->value);\n        if ($iNow < $iFrom) {\n            return false;\n        }\n\n        if ($this->oxactions__oxactiveto->value != '0000-00-00 00:00:00') {\n            $iTo = strtotime($this->oxactions__oxactiveto->value);\n            if ($iNow > $iTo) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * return assigned banner article\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    public function getBannerArticle()\n    {\n        $sArtId = $this->fetchBannerArticleId();\n\n        if ($sArtId) {\n            $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n\n            if ($this->isAdmin()) {\n                $oArticle->setLanguage(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->getEditLanguage());\n            }\n\n            if ($oArticle->load($sArtId)) {\n                return $oArticle;\n            }\n        }\n\n        return null;\n    }\n\n\n    /**\n     * Fetch the oxobjectid of the article corresponding this action.\n     *\n     * @return string The id of the oxobjectid belonging to this action.\n     */\n    protected function fetchBannerArticleId()\n    {\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $articleId = $database->getOne(\n            'select oxobjectid from oxobject2action ' .\n            'where oxactionid = :oxactionid and oxclass = :oxclass',\n            [\n                'oxactionid' => $this->getId(),\n                'oxclass' => 'oxarticle'\n            ]\n        );\n\n        return $articleId;\n    }\n\n    /**\n     * Returns assigned banner article picture url\n     *\n     * @return string\n     */\n    public function getBannerPictureUrl()\n    {\n        if (isset($this->oxactions__oxpic) && $this->oxactions__oxpic->value) {\n            $sPromoDir = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsFile()->normalizeDir(\\OxidEsales\\Eshop\\Core\\UtilsFile::PROMO_PICTURE_DIR);\n\n            return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getPictureUrl($sPromoDir . $this->oxactions__oxpic->value, false);\n        }\n    }\n\n    /**\n     * Returns assigned banner link. If no link is defined and article is\n     * assigned to banner, article link will be returned.\n     *\n     * @return string\n     */\n    public function getBannerLink()\n    {\n        $sUrl = null;\n\n        if (isset($this->oxactions__oxlink) && $this->oxactions__oxlink->value) {\n            /** @var \\OxidEsales\\Eshop\\Core\\UtilsUrl $oUtilsUlr */\n            $oUtilsUlr = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsUrl();\n            $sUrl = $oUtilsUlr->addShopHost($this->oxactions__oxlink->value);\n            $sUrl = $oUtilsUlr->processUrl($sUrl);\n        } else {\n            if ($oArticle = $this->getBannerArticle()) {\n                // if article is assigned to banner, getting article link\n                $sUrl = $oArticle->getLink();\n            }\n        }\n\n        return $sUrl;\n    }\n\n    /**\n     * Returns true if Action is default.\n     *\n     * @return bool\n     */\n    public function isDefault()\n    {\n        return '0' === $this->oxactions__oxtype->value;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Address.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\n/**\n * Address handler\n */\nclass Address extends \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n{\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxaddress';\n\n    /**\n     * Active address status\n     *\n     * @var bool\n     */\n    protected $_blSelected = false;\n\n    /**\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\State\n     */\n    protected $_oStateObject = null;\n\n    /**\n     * Returns oxState object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\State\n     */\n    protected function getStateObject()\n    {\n        if (is_null($this->_oStateObject)) {\n            $this->_oStateObject = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\State::class);\n        }\n\n        return $this->_oStateObject;\n    }\n\n    /**\n     * Class constructor\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxaddress');\n    }\n\n    /**\n     * Magic getter returns address as a single line string\n     *\n     * @return string\n     */\n    public function __toString()\n    {\n        return $this->toString();\n    }\n\n    /**\n     * Formats address as a single line string\n     *\n     * @return string\n     */\n    public function toString()\n    {\n        $sFirstName = $this->oxaddress__oxfname->value;\n        $sLastName = $this->oxaddress__oxlname->value;\n        $sStreet = $this->oxaddress__oxstreet->value;\n        $sStreetNr = $this->oxaddress__oxstreetnr->value;\n        $sCity = $this->oxaddress__oxcity->value;\n\n        //format it\n        $sAddress = \"\";\n        if ($sFirstName || $sLastName) {\n            $sAddress = $sFirstName . ($sFirstName ? \" \" : \"\") . \"$sLastName, \";\n        }\n        $sAddress .= \"$sStreet $sStreetNr, $sCity\";\n\n        return trim($sAddress);\n    }\n\n    /**\n     * Returns encoded address.\n     *\n     * @return string\n     */\n    public function getEncodedDeliveryAddress()\n    {\n        return md5($this->getMergedAddressFields());\n    }\n\n    /**\n     * Get state id for current address\n     *\n     * @return mixed\n     */\n    public function getStateId()\n    {\n        return $this->oxaddress__oxstateid->value;\n    }\n\n\n    /**\n     * Get state title\n     *\n     * @param string $sId state ID\n     *\n     * @return string\n     */\n    public function getStateTitle($sId = null)\n    {\n        $oState = $this->getStateObject();\n\n        if (is_null($sId)) {\n            $sId = $this->getStateId();\n        }\n\n        return $oState->getTitleById($sId);\n    }\n\n    /**\n     * Returns TRUE if current address is selected\n     *\n     * @return bool\n     */\n    public function isSelected()\n    {\n        return $this->_blSelected;\n    }\n\n    /**\n     * Sets address state as selected\n     */\n    public function setSelected()\n    {\n        $this->_blSelected = true;\n    }\n\n    /**\n     * Returns merged address fields.\n     *\n     * @return string\n     */\n    protected function getMergedAddressFields()\n    {\n        $sDelAddress = '';\n        $sDelAddress .= $this->oxaddress__oxcompany;\n        $sDelAddress .= $this->oxaddress__oxfname;\n        $sDelAddress .= $this->oxaddress__oxlname;\n        $sDelAddress .= $this->oxaddress__oxstreet;\n        $sDelAddress .= $this->oxaddress__oxstreetnr;\n        $sDelAddress .= $this->oxaddress__oxaddinfo;\n        $sDelAddress .= $this->oxaddress__oxcity;\n        $sDelAddress .= $this->oxaddress__oxcountryid;\n        $sDelAddress .= $this->oxaddress__oxstateid;\n        $sDelAddress .= $this->oxaddress__oxzip;\n        $sDelAddress .= $this->oxaddress__oxfon;\n        $sDelAddress .= $this->oxaddress__oxfax;\n        $sDelAddress .= $this->oxaddress__oxsal;\n\n        return $sDelAddress;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/AmountPriceList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\n\n/**\n * Article amount price list\n */\nclass AmountPriceList extends \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n{\n    /**\n     * List Object class name\n     *\n     * @var string\n     */\n    protected $_sObjectsInListName = 'oxprice2article';\n\n    /**\n     * oxArticle object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected $_oArticle = null;\n\n    /**\n     * Class constructor\n     */\n    public function __construct()\n    {\n        parent::__construct('oxbase');\n        $this->init('oxbase', 'oxprice2article');\n    }\n\n    /**\n     *  Article getter\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article $_oArticle\n     */\n    public function getArticle()\n    {\n        return $this->_oArticle;\n    }\n\n    /**\n     * Article setter\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle Article\n     */\n    public function setArticle($oArticle)\n    {\n        $this->_oArticle = $oArticle;\n    }\n\n    /**\n     * Load category list data\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $article Article\n     */\n    public function load($article)\n    {\n        $this->setArticle($article);\n\n        $aData = $this->loadFromDb();\n\n        $this->assignArray($aData);\n    }\n\n    /**\n     * Get data from db\n     *\n     * @return array\n     */\n    protected function loadFromDb()\n    {\n        $sArticleId = $this->getArticle()->getId();\n        $db = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blVariantInheritAmountPrice') && $this->getArticle()->getParentId()) {\n            $sArticleId = $this->getArticle()->getParentId();\n        }\n\n        $params = [\n            'oxartid' => $sArticleId\n        ];\n\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blMallInterchangeArticles')) {\n            $sShopSelect = '1';\n        } else {\n            $sShopSelect = \" `oxshopid` = :oxshopid \";\n            $params['oxshopid'] = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId();\n        }\n\n        $sSql = \"SELECT * FROM `oxprice2article` \n            WHERE `oxartid` = :oxartid AND $sShopSelect ORDER BY `oxamount` \";\n\n        return $db->getAll($sSql, $params);\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Article.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse Exception;\nuse OxidEsales\\Eshop\\Application\\Model\\Contract\\ArticleInterface;\nuse OxidEsales\\Eshop\\Core\\Contract\\IUrl;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel;\nuse OxidEsales\\Eshop\\Core\\Price;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse OxidEsales\\EshopCommunity\\Core\\DatabaseProvider;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Dao\\ProductMediaDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRole;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaView;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaViewServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents\\AfterModelUpdateEvent;\n\n// defining supported link types\ndefine('OXARTICLE_LINKTYPE_CATEGORY', 0);\ndefine('OXARTICLE_LINKTYPE_VENDOR', 1);\ndefine('OXARTICLE_LINKTYPE_MANUFACTURER', 2);\ndefine('OXARTICLE_LINKTYPE_PRICECATEGORY', 3);\n// @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\ndefine('OXARTICLE_LINKTYPE_RECOMM', 5);\n// END deprecated\n\n/**\n * Article manager.\n * Creates fully detailed article object, with such information as VAT,\n * discounts, etc.\n */\nclass Article extends MultiLanguageModel implements ArticleInterface, IUrl\n{\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxarticle';\n\n    /**\n     * Set $_blUseLazyLoading to true if you want to load only actually used fields not full object, depending on views.\n     *\n     * @var bool\n     */\n    protected $_blUseLazyLoading = true;\n\n    /**\n     * item key the usage with oxuserbasketitem\n     *\n     * @var string (md5 hash)\n     */\n    protected $_sItemKey;\n\n    /**\n     * Variable controls price calculation type (set true, to calculate price\n     * with taxes and etc, or false to return base article price).\n     *\n     * @var bool\n     */\n    protected $_blCalcPrice = true;\n\n    /**\n     * Article oxPrice object.\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Price\n     */\n    protected $_oPrice = null;\n\n\n    /**\n     * cached article variant min price\n     *\n     * @var double|null\n     */\n    protected $_dVarMinPrice = null;\n\n    /**\n     * cached article variant max price\n     *\n     * @var double|null\n     */\n    protected $_dVarMaxPrice = null;\n\n    /**\n     * caches article vat\n     *\n     * @var double|null\n     */\n    protected $_dArticleVat = null;\n\n    /**\n     * Status of article - buyable/not buyable.\n     *\n     * @var bool\n     */\n    protected $_blNotBuyable = false;\n\n    /**\n     * Indicates if we should load variants for current article. When $_blLoadVariants is set to false then\n     * neither simple nor full variants for this article are loaded.\n     *\n     * @var bool\n     */\n    protected $_blLoadVariants = true;\n\n    /**\n     * Article variants without empty stock, not orderable flagged variants\n     *\n     * @var array\n     */\n    protected $_aVariants = null;\n\n    /**\n     * Article variants with empty stock, not orderable flagged variants\n     *\n     * @var array\n     */\n    protected $_aVariantsWithNotOrderables = null;\n\n    /**\n     * $_blNotBuyableParent is set to true, when article has variants and is not buyable due to:\n     *      a) config option\n     *      b) it is not active\n     *      c) all variants are not active\n     *\n     * @var bool\n     */\n    protected $_blNotBuyableParent = false;\n\n\n    /**\n     * $_blHasVariants is set to true if article has any variants.\n     */\n    protected $_blHasVariants = false;\n\n    /**\n     * $_blHasVariants is set to true if article has multidimensional variants.\n     */\n    protected $_blHasMdVariants = false;\n\n    /**\n     * If set true, then this object is on comparison list\n     *\n     * @var bool\n     */\n    protected $_blIsOnComparisonList = false;\n\n    /**\n     * user object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\User\n     */\n    protected $_oUser = null;\n\n    /**\n     * Performance issue. Sometimes you want to load articles without calculating\n     * correct discounts and prices etc.\n     *\n     * @var bool\n     */\n    protected $_blLoadPrice = true;\n\n    /**\n     * $_fPricePerUnit holds price per unit value in active shop currency.\n     * $_fPricePerUnit is calculated from\n     * \\OxidEsales\\Eshop\\Application\\Model\\Article::oxarticles__oxunitquantity->value\n     * and from \\OxidEsales\\Eshop\\Application\\Model\\Article::oxarticles__oxuniname->value. If either one of these\n     * values is empty then $_fPricePerUnit is not calculated. Example: In case when product price is 10 EUR and\n     * product quantity is 0.5 (liters) then $_fPricePerUnit would be 20,00\n     */\n    protected $_fPricePerUnit = null;\n\n    /**\n     * Variable used to force load parent data in export\n     */\n    protected $_blLoadParentData = false;\n\n    /**\n     * Variable used to determine if setting parentId to empty value is allowed\n     */\n    protected $_blAllowEmptyParentId = false;\n\n    /**\n     * Variable used to force load parent data in export\n     */\n    protected $_blSkipAssign = false;\n\n    /**\n     * Set $_blSkipDiscounts to true if you want to skip the discount.\n     *\n     * @var bool\n     */\n    protected $_blSkipDiscounts = null;\n\n    /**\n     * Object holding the list of attributes and attribute values associated with this article\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\AttributeList\n     */\n    protected $_oAttributeList = null;\n\n    /**\n     * Object holding the list of attributes and attribute values associated with this article and displayable in basket\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\AttributeList\n     */\n    protected $basketAttributeList = null;\n\n    /**\n     * Indicates whether the price is \"From\" price\n     *\n     * @var bool\n     */\n    protected $_blIsRangePrice = null;\n\n    /**\n     * The list of article media URLs\n     *\n     * @var string\n     */\n    protected $_aMediaUrls = null;\n\n    /**\n     * Array containing references to already loaded parent articles, in order for variant to skip parent data loading\n     *\n     * @var array\n     */\n    protected static $_aLoadedParents;\n\n    /**\n     * Cached select lists array\n     *\n     * @var array\n     */\n    protected static $_aSelList;\n\n    /**\n     * Select lists for tpl\n     *\n     * @var array\n     */\n    protected $_aDispSelList;\n\n    /**\n     * Marks that current object is managed by SEO\n     *\n     * @var bool\n     */\n    protected $_blIsSeoObject = true;\n\n    /**\n     * loaded amount prices\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\AmountPriceList\n     */\n    protected $_oAmountPriceList = null;\n\n    /**\n     * Article details link type (default is 0):\n     *     0 - category link\n     *     1 - vendor link\n     *     2 - manufacturer link\n     *\n     * @var int\n     */\n    protected $_iLinkType = 0;\n\n    /**\n     * Standard/dynamic article urls for languages\n     *\n     * @var array\n     */\n    protected $_aStdUrls = [];\n\n    /**\n     * Seo article urls for languages\n     *\n     * @var array\n     */\n    protected $_aSeoUrls = [];\n\n    /**\n     * Additional parameters to seo urls\n     *\n     * @var array\n     */\n    protected $_aSeoAddParams = [];\n\n    /**\n     * Additional parameters to std urls\n     *\n     * @var array\n     */\n    protected $_aStdAddParams = [];\n\n    /**\n     * Image url\n     *\n     * @var string\n     */\n    protected $_sDynImageDir = null;\n\n    /**\n     * More details link\n     *\n     * @var string\n     */\n    protected $_sMoreDetailLink = null;\n\n    /**\n     * To basket link\n     *\n     * @var string\n     */\n    protected $_sToBasketLink = null;\n\n    /**\n     * Article stock status when article is initially loaded.\n     *\n     * @var int\n     */\n    protected $_iStockStatusOnLoad = null;\n\n    /**\n     * Article original parameters when loaded.\n     *\n     * @var array\n     */\n    protected $_aSortingFieldsOnLoad = [];\n\n    /**\n     * Stock status\n     *\n     * @var integer\n     */\n    protected $_iStockStatus = null;\n\n    /**\n     * T price\n     *\n     * @var object\n     */\n    protected $_oTPrice = null;\n\n    /**\n     * Amount price list info\n     *\n     * @var object\n     */\n    protected $_oAmountPriceInfo = null;\n\n    /**\n     * Amount price\n     *\n     * @var double\n     */\n    protected $_dAmountPrice = null;\n\n    /**\n     * Articles manufacturer ids cache\n     *\n     * @var array\n     */\n    protected static $_aArticleManufacturers = [];\n\n    /**\n     * Articles vendor ids cache\n     *\n     * @var array\n     */\n    protected static $_aArticleVendors = [];\n\n    /**\n     * Articles category ids cache\n     *\n     * @var array\n     */\n    protected static $_aArticleCats = [];\n\n    /**\n     * Do not copy certain parent fields to variant\n     *\n     * @var array\n     */\n    protected $_aNonCopyParentFields = [\n        'oxarticles__oxinsert',\n        'oxarticles__oxtimestamp',\n        'oxarticles__oxnid',\n        'oxarticles__oxid',\n        'oxarticles__oxparentid'\n    ];\n\n    /**\n     * Override certain parent fields to variant\n     *\n     * @var array\n     */\n    protected $_aCopyParentField = [\n        'oxarticles__oxnonmaterial',\n        'oxarticles__oxfreeshipping',\n        'oxarticles__oxisdownloadable',\n        'oxarticles__oxshowcustomagreement'\n    ];\n\n    /**\n     * Multidimensional variant tree structure\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\MdVariant\n     */\n    protected $_oMdVariants = null;\n\n    /**\n     * Product long description field\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Field\n     */\n    protected $_oLongDesc = null;\n\n    /**\n     * Variant selections array\n     *\n     * @see getVariantSelections()\n     *\n     * @var array\n     */\n    protected $_aVariantSelections = [];\n\n    /**\n     * Array of product selections\n     *\n     * @var array\n     */\n    protected static $_aSelections = [];\n\n    /**\n     * Category instance cache\n     *\n     * @var array\n     */\n    protected static $_aCategoryCache = [];\n\n    /**\n     * stores if are stored any amount price\n     *\n     * @var bool\n     */\n    protected static $_blHasAmountPrice = null;\n\n    /**\n     * stores downloadable file list\n     *\n     * @var array|\\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    protected $_aArticleFiles = null;\n\n    /**\n     * If admin can edit any field.\n     *\n     * @var bool\n     */\n    protected $_blCanUpdateAnyField = null;\n\n    /**\n     * Triggered action type\n     *\n     * @var integer\n     */\n    protected $actionType = ACTION_NA;\n\n    /**\n     * Constructor, sets shop ID for article (\\OxidEsales\\Eshop\\Core\\Config::getShopId()),\n     * initiates parent constructor (parent::oxI18n()).\n     *\n     * @param array $aParams The array of names and values of oxArticle instance properties to be set on object\n     *                       instantiation\n     */\n    public function __construct($aParams = null)\n    {\n        if ($aParams && is_array($aParams)) {\n            foreach ($aParams as $sParam => $mValue) {\n                $this->$sParam = $mValue;\n            }\n        }\n        parent::__construct();\n        $this->init('oxarticles');\n    }\n\n    /**\n     * Magic getter, deals with values which are loaded on demand.\n     * Additionally it sets default value for unknown picture fields\n     *\n     * @param string $sName Variable name\n     *\n     * @return mixed\n     */\n    public function __get($sName)\n    {\n        $this->$sName = parent::__get($sName);\n        if ($this->$sName) {\n            // since the field could have been loaded via lazy loading\n            $this->assignParentFieldValue($sName);\n        }\n\n        return $this->$sName;\n    }\n\n    /**\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\AmountPriceList $amountPriceList\n     */\n    public function setAmountPriceList($amountPriceList)\n    {\n        $this->_oAmountPriceList = $amountPriceList;\n    }\n\n    /**\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\AmountPriceList\n     */\n    protected function getAmountPriceList()\n    {\n        return $this->_oAmountPriceList;\n    }\n\n    /**\n     * Checks whether object is in list or not\n     * It's needed for oxArticle so that it can pass this to widgets\n     *\n     * @return bool\n     */\n    public function isInList()\n    {\n        return parent::isInList();\n    }\n\n    /**\n     * Sets object ID, additionally sets $this->oxarticles__oxnid field value\n     *\n     * @param string $sId New ID\n     *\n     * @return string|null\n     */\n    public function setId($sId = null)\n    {\n        $sId = parent::setId($sId);\n\n        // TODO: in BaseModel::setId make it to check if exists and update, not recreate, then delete this overload\n        $this->oxarticles__oxnid = $this->oxarticles__oxid;\n\n        return $sId;\n    }\n\n    /**\n     * Returns part of sql query used in active snippet. Query checks\n     * if product \"oxactive = 1\". If config option \"blUseTimeCheck\" is TRUE\n     * additionally checks if \"oxactivefrom < current data < oxactiveto\"\n     *\n     * @param bool $blForceCoreTable force core table usage\n     *\n     * @return string\n     */\n    public function getActiveCheckQuery($blForceCoreTable = null)\n    {\n        $viewName = $this->getViewName($blForceCoreTable);\n\n        $query = \" $viewName.oxactive = 1 \";\n\n        $query .= \" and $viewName.oxhidden = 0 \";\n\n        if (Registry::getConfig()->getConfigParam('blUseTimeCheck')) {\n            $query = $this->addSqlActiveRangeSnippet($query, $viewName);\n        }\n\n        return $query;\n    }\n\n    /**\n     * Returns part of sql query used in active snippet. If config\n     * option \"blUseStock\" is TRUE checks if \"oxstockflag != 2 or\n     * ( oxstock + oxvarstock ) > 0\". If config option \"blVariantParentBuyable\"\n     * is TRUE checks if product has variants, and if has - checks is\n     * there at least one variant which is buyable. If config option\n     * option \"blUseTimeCheck\" is TRUE additionally checks if variants\n     * \"oxactivefrom < current data < oxactiveto\"\n     *\n     * @param bool $blForceCoreTable force core table usage\n     *\n     * @return string\n     */\n    public function getStockCheckQuery($blForceCoreTable = null)\n    {\n        $myConfig = Registry::getConfig();\n        $sTable = $this->getViewName($blForceCoreTable);\n\n        $sQ = \"\";\n\n        //do not check for variants\n        if ($myConfig->getConfigParam('blUseStock')) {\n            $sQ = \" and ( $sTable.oxstockflag != 2 or ( $sTable.oxstock + $sTable.oxvarstock ) > 0  ) \";\n            //V #M513: When Parent article is not purchasable,\n            // it's visibility should be displayed in shop only if any of Variants is available.\n            if (!$myConfig->getConfigParam('blVariantParentBuyable')) {\n                $activeCheck = 'art.oxactive = 1';\n                if ($myConfig->getConfigParam('blUseTimeCheck')) {\n                    $activeCheck = $this->addSqlActiveRangeSnippet($activeCheck, 'art');\n                }\n                $sQ = \" $sQ and IF( $sTable.oxvarcount = 0, 1, ( select 1 from $sTable as art\"\n                    . \" where art.oxparentid=$sTable.oxid and $activeCheck and\"\n                    . \" ( art.oxstockflag != 2 or art.oxstock > 0 ) limit 1 ) ) \";\n            }\n        }\n\n        return $sQ;\n    }\n\n    /**\n     * Returns part of query which checks if product is variant of current\n     * object. Additionally if config option \"blUseStock\" is TRUE checks\n     * stock state \"( oxstock > 0 or ( oxstock <= 0 and ( oxstockflag = 1\n     * or oxstockflag = 4 ) )\"\n     *\n     * @param bool $blRemoveNotOrderables remove or leave non orderable products\n     * @param bool $blForceCoreTable      force core table usage\n     *\n     * @return string\n     */\n    public function getVariantsQuery($blRemoveNotOrderables, $blForceCoreTable = null)\n    {\n        $sTable = $this->getViewName($blForceCoreTable);\n        $sQ = \" and $sTable.oxparentid = '\" . $this->getId() . \"' \";\n\n        //checking if variant is active and stock status\n        if (Registry::getConfig()->getConfigParam('blUseStock')) {\n            $sQ .= \" and ( $sTable.oxstock > 0 or ( $sTable.oxstock <= 0 and $sTable.oxstockflag != 2 \";\n            if ($blRemoveNotOrderables) {\n                $sQ .= \" and $sTable.oxstockflag != 3 \";\n            }\n            $sQ .= \" ) ) \";\n        }\n\n        return $sQ;\n    }\n\n    /**\n     * Return unit quantity\n     *\n     * @return string\n     */\n    public function getUnitQuantity()\n    {\n        return $this->oxarticles__oxunitquantity->value;\n    }\n\n    /**\n     * Return Size of product: length*width*height\n     *\n     * @return double\n     */\n    public function getSize()\n    {\n        return $this->oxarticles__oxlength->value *\n               $this->oxarticles__oxwidth->value *\n               $this->oxarticles__oxheight->value;\n    }\n\n    /**\n     * Return product weight\n     *\n     * @return double\n     */\n    public function getWeight()\n    {\n        return $this->oxarticles__oxweight->value;\n    }\n\n    /**\n     * Returns SQL select string with checks if items are available\n     *\n     * @param bool $blForceCoreTable forces core table usage (optional)\n     *\n     * @return string\n     */\n    public function getSqlActiveSnippet($blForceCoreTable = null)\n    {\n        return \"( {$this->createSqlActiveSnippet($blForceCoreTable)} ) \";\n    }\n\n    /**\n     *\n     * Getter for action type.\n     *\n     * @return int\n     */\n    public function getActionType()\n    {\n        return $this->actionType;\n    }\n\n    /**\n     * Returns SQL select string with checks if items are available\n     *\n     * @param bool $forceCoreTable forces core table usage (optional)\n     *\n     * @return string\n     */\n    protected function createSqlActiveSnippet($forceCoreTable)\n    {\n        // check if article is still active\n        $sQ = $this->getActiveCheckQuery($forceCoreTable);\n\n        // stock and variants check\n        $sQ .= $this->getStockCheckQuery($forceCoreTable);\n\n        return $sQ;\n    }\n\n    /**\n     * Assign condition setter. In case article assignment is skipped ($_blSkipAssign = true), it does not perform\n     * additional\n     *\n     * @param bool $blSkipAssign Whether to skip assign process for the article\n     */\n    public function setSkipAssign($blSkipAssign)\n    {\n        $this->_blSkipAssign = $blSkipAssign;\n    }\n\n    /**\n     * Disables article price loading. Should be called before assign(), or load()\n     */\n    public function disablePriceLoad()\n    {\n        $this->_blLoadPrice = false;\n    }\n\n    /**\n     * Enable article price loading, if disabled.\n     */\n    public function enablePriceLoad()\n    {\n        $this->_blLoadPrice = true;\n    }\n\n    /**\n     * Returns item key used with oxuserbasket\n     *\n     * @return string\n     */\n    public function getItemKey()\n    {\n        return $this->_sItemKey;\n    }\n\n    /**\n     * Sets item key used with oxuserbasket\n     *\n     * @param string $sItemKey Item key\n     */\n    public function setItemKey($sItemKey)\n    {\n        $this->_sItemKey = $sItemKey;\n    }\n\n    /**\n     * Disables/enables variant loading\n     *\n     * @param bool $blLoadVariants skip variant loading or not\n     */\n    public function setNoVariantLoading($blLoadVariants)\n    {\n        $this->_blLoadVariants = !$blLoadVariants;\n    }\n\n    /**\n     * Checks if article is buyable.\n     *\n     * @return bool\n     */\n    public function isBuyable()\n    {\n        return !($this->_blNotBuyableParent || $this->_blNotBuyable);\n    }\n\n    /**\n     * Checks if price alarm is enabled.\n     *\n     * @return bool\n     */\n    public function isPriceAlarm()\n    {\n        // #419 disabling price alarm if article has fixed price\n        return !(\n            (\n                $this->__isset('oxarticles__oxblfixedprice')\n                || $this->__get('oxarticles__oxblfixedprice')\n            )\n            && $this->__get('oxarticles__oxblfixedprice')->value\n        );\n    }\n\n    /**\n     * Checks whether article is inluded in comparison list\n     *\n     * @return bool\n     */\n    public function isOnComparisonList()\n    {\n        return $this->_blIsOnComparisonList;\n    }\n\n    /**\n     * Set if article is inluded in comparison list\n     *\n     * @param bool $blOnList Whether is article on the list\n     */\n    public function setOnComparisonList($blOnList)\n    {\n        $this->_blIsOnComparisonList = $blOnList;\n    }\n\n    /**\n     * A setter for $_blLoadParentData (whether article parent info should be laoded fully) class variable\n     *\n     * @param bool $blLoadParentData Whether to load parent data\n     */\n    public function setLoadParentData($blLoadParentData)\n    {\n        $this->_blLoadParentData = $blLoadParentData;\n    }\n\n    /**\n     * Getter for do we load parent data\n     *\n     * @return bool\n     */\n    public function getLoadParentData()\n    {\n        return $this->_blLoadParentData;\n    }\n\n    /**\n     * Returns true if the field is multilanguage\n     *\n     * @param string $sFieldName Field name\n     *\n     * @return bool\n     */\n    public function isMultilingualField($sFieldName)\n    {\n        if ('oxlongdesc' == $sFieldName) {\n            return true;\n        }\n\n        return parent::isMultilingualField($sFieldName);\n    }\n\n    /**\n     * Returns formatted price per unit\n     *\n     * @deprecated since v5.1 (2013-09-25); use oxPrice template engine plugin for formatting in templates\n     * @return string\n     */\n    public function getFUnitPrice()\n    {\n        if ($this->_fPricePerUnit == null) {\n            if ($oPrice = $this->getUnitPrice()) {\n                if ($dPrice = $this->getPriceForView($oPrice)) {\n                    $this->_fPricePerUnit = Registry::getLang()->formatCurrency($dPrice);\n                }\n            }\n        }\n\n        return $this->_fPricePerUnit;\n    }\n\n    /**\n     * Returns price per unit\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price|null\n     */\n    public function getUnitPrice()\n    {\n        // Performance\n        if (!Registry::getConfig()->getConfigParam('bl_perfLoadPrice') || !$this->_blLoadPrice) {\n            return null;\n        }\n\n        $oPrice = null;\n        if ((float) $this->getUnitQuantity() && $this->oxarticles__oxunitname->value) {\n            $oPrice = clone $this->getPrice();\n            $oPrice->divide((float) $this->getUnitQuantity());\n        }\n\n        return $oPrice;\n    }\n\n    /**\n     * Returns formatted article min price\n     *\n     * @deprecated since v5.1 (2013-10-04); use oxPrice template engine plugin for formatting in templates\n     *\n     * @return string\n     */\n    public function getFMinPrice()\n    {\n        $sPrice = '';\n        if ($oPrice = $this->getMinPrice()) {\n            $dPrice = $this->getPriceForView($oPrice);\n            $sPrice = Registry::getLang()->formatCurrency($dPrice);\n        }\n\n        return $sPrice;\n    }\n\n    /**\n     * Returns formatted min article variant price\n     *\n     * @deprecated since v5.1 (2013-10-04); use oxPrice template engine plugin for formatting in templates\n     *\n     * @return string\n     */\n    public function getFVarMinPrice()\n    {\n        $sPrice = '';\n        if ($oPrice = $this->getVarMinPrice()) {\n            $dPrice = $this->getPriceForView($oPrice);\n            $sPrice = Registry::getLang()->formatCurrency($dPrice);\n        }\n\n        return $sPrice;\n    }\n\n    /**\n     * Returns article min price of variants\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getVarMinPrice()\n    {\n        if (!Registry::getConfig()->getConfigParam('bl_perfLoadPrice') || !$this->_blLoadPrice) {\n            return null;\n        }\n\n        $oPrice = null;\n        $dPrice = $this->calculateVarMinPrice();\n\n        $oPrice = $this->getPriceObject();\n        $oPrice->setPrice($dPrice);\n\n        $this->calculatePrice($oPrice);\n\n        return $oPrice;\n    }\n\n    /**\n     * Calculates lowest price of available article variants.\n     *\n     * @return double\n     */\n    protected function calculateVarMinPrice()\n    {\n        $dPrice = $this->getVarMinRawPrice();\n\n        return $this->preparePrice($dPrice, $this->getArticleVat());\n    }\n\n    /**\n     * Returns article min price in calculation included variants\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getMinPrice()\n    {\n        if (!Registry::getConfig()->getConfigParam('bl_perfLoadPrice') || !$this->_blLoadPrice) {\n            return;\n        }\n\n        $oPrice = null;\n        $dPrice = $this->getRawPrice();\n        if ($this->getVarMinRawPrice() !== null && $dPrice > $this->getVarMinRawPrice()) {\n            $dPrice = $this->getVarMinRawPrice();\n        }\n\n        $dPrice = $this->prepareModifiedPrice($dPrice);\n\n        $oPrice = $this->getPriceObject();\n        $oPrice->setPrice($dPrice);\n        $this->calculatePrice($oPrice);\n\n        return $oPrice;\n    }\n\n    /**\n     * @param double $dPrice\n     *\n     * @return double\n     */\n    protected function prepareModifiedPrice($dPrice)\n    {\n        $dPrice = $this->preparePrice($dPrice, $this->getArticleVat());\n\n        return $dPrice;\n    }\n\n    /**\n     * Returns true if article has variant with different price\n     *\n     * @return bool\n     */\n    public function isRangePrice()\n    {\n        if ($this->_blIsRangePrice === null) {\n            $this->setRangePrice(false);\n\n            if ($this->hasAnyVariant()) {\n                $dPrice = $this->getRawPrice();\n                $dMinPrice = $this->getVarMinRawPrice();\n                $dMaxPrice = $this->getVarMaxPrice();\n\n                if ($dMinPrice != $dMaxPrice) {\n                    $this->setRangePrice();\n                } elseif (!$this->isParentNotBuyable() && $dPrice != $dMinPrice) {\n                    $this->setRangePrice();\n                }\n            }\n        }\n\n        return $this->_blIsRangePrice;\n    }\n\n\n    /**\n     * Setter to set if article has range price\n     *\n     * @param bool $blIsRangePrice - true if range, else false\n     *\n     * @return null\n     */\n    public function setRangePrice($blIsRangePrice = true)\n    {\n        return $this->_blIsRangePrice = $blIsRangePrice;\n    }\n\n    public function hasActiveTimeRange(): bool\n    {\n        $activeFrom = $this->oxarticles__oxactivefrom->value;\n        $activeTo = $this->oxarticles__oxactiveto->value;\n        $now = Registry::getUtilsDate()->getTime();\n\n        if (!$this->hasProductValidTimeRange()) {\n            return false;\n        }\n\n        return (Registry::getUtilsDate()->isEmptyDate($activeTo) || strtotime($activeTo) >= $now)\n            && (Registry::getUtilsDate()->isEmptyDate($activeFrom) || strtotime($activeFrom) <= $now);\n    }\n\n\n    /**\n     * Checks if article has visible status. Returns TRUE if its visible\n     *\n     * @return bool\n     */\n    public function isVisible()\n    {\n        // admin preview mode\n        if (($blCanPreview = Registry::getUtils()->canPreview()) !== null) {\n            return $blCanPreview;\n        }\n\n        $blUseTimeCheck = Registry::getConfig()->getConfigParam('blUseTimeCheck');\n        if (\n            !$this->oxarticles__oxactive->value\n            && (($blUseTimeCheck && !$this->hasActiveTimeRange()) || !$blUseTimeCheck)\n        ) {\n            return false;\n        }\n\n        // stock flags\n        if (Registry::getConfig()->getConfigParam('blUseStock') && $this->oxarticles__oxstockflag->value == 2) {\n            $iOnStock = $this->oxarticles__oxstock->value + $this->oxarticles__oxvarstock->value;\n            if (Registry::getConfig()->getConfigParam('blPsBasketReservationEnabled')) {\n                $session = Registry::getSession();\n                $iOnStock += $session->getBasketReservations()->getReservedAmount($this->getId());\n            }\n            if ($iOnStock <= 0) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * Assigns to oxarticle object some base parameters/values (such as\n     * detaillink, moredetaillink, etc).\n     *\n     * @param array $aRecord Array representing current field values\n     *\n     * @return null\n     */\n    public function assign($aRecord)\n    {\n        startProfile('articleAssign');\n\n        // load object from database\n        parent::assign($aRecord);\n\n        //clear seo urls\n        $this->_aSeoUrls = [];\n\n        $this->oxarticles__oxnid = $this->oxarticles__oxid;\n\n        // check for simple article.\n        if ($this->_blSkipAssign) {\n            return;\n        }\n\n        $this->assignParentFieldValues();\n        $this->assignNotBuyableParent();\n\n        // assign only for a first load time\n        if (!$this->isLoaded()) {\n            $this->setShopValues($this);\n        }\n\n        $this->assignStock();\n        $this->assignDynImageDir();\n        $this->assignComparisonListFlag();\n\n        stopProfile('articleAssign');\n    }\n\n    /**\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $article\n     */\n    protected function setShopValues($article)\n    {\n    }\n\n    /**\n     * Loads object data from DB (object data ID must be passed to method).\n     * Converts dates (\\OxidEsales\\Eshop\\Application\\Model\\Article::oxarticles__oxinsert)\n     * to international format (oxUtils.php \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->formatDBDate(...)).\n     * Returns true if article was loaded successfully.\n     *\n     * @param string $sOXID Article object ID\n     *\n     * @return bool\n     */\n    public function load($sOXID)\n    {\n        // A. #1325 resetting to avoid problems when reloading (details etc)\n        $this->_blNotBuyableParent = false;\n\n        $aData = $this->loadData($sOXID);\n\n        if ($aData) {\n            $this->assign($aData);\n\n            $this->saveSortingFieldValuesOnLoad();\n\n            $this->_iStockStatusOnLoad = $this->_iStockStatus;\n\n            $this->_isLoaded = true;\n\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Loads data from database and returns it.\n     *\n     * @param string $articleId\n     *\n     * @return array\n     */\n    protected function loadData($articleId)\n    {\n        return $this->loadFromDb($articleId);\n    }\n\n    /**\n     * Checks whether sorting fields changed from last article loading.\n     *\n     * @return bool\n     */\n    public function hasSortingFieldsChanged()\n    {\n        $aSortingFields = Registry::getConfig()->getConfigParam('aSortCols');\n        $aSortingFields = !empty($aSortingFields) ? (array) $aSortingFields : [];\n        $blChanged = false;\n        foreach ($aSortingFields as $sField) {\n            $sParameterName = 'oxarticles__' . $sField;\n            $currentValueOfField = $this->$sParameterName instanceof Field ? $this->$sParameterName->value : '';\n            $valueOfFieldOnLoad = $this->_aSortingFieldsOnLoad[$sParameterName] ?? null;\n            if ($valueOfFieldOnLoad !== $currentValueOfField) {\n                $blChanged = true;\n                break;\n            }\n        }\n\n        return $blChanged;\n    }\n\n    /**\n     * Calculates and saves product rating average\n     *\n     * @param integer $rating new rating value\n     */\n    public function addToRatingAverage($rating)\n    {\n        $dOldRating = $this->oxarticles__oxrating->value;\n        $dOldCnt = $this->oxarticles__oxratingcnt->value;\n        $this->oxarticles__oxrating->setValue(($dOldRating * $dOldCnt + $rating) / ($dOldCnt + 1));\n        $this->oxarticles__oxratingcnt->setValue($dOldCnt + 1);\n        $dRating = ($dOldRating * $dOldCnt + $rating) / ($dOldCnt + 1);\n        $dRatingCnt = (int) ($dOldCnt + 1);\n        // oxarticles.oxtimestamp = oxarticles.oxtimestamp to keep old timestamp value\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $query = \"update oxarticles\n                  set oxarticles.oxrating = :oxrating,\n                      oxarticles.oxratingcnt = :oxratingcnt,\n                      oxarticles.oxtimestamp = oxarticles.oxtimestamp\n                  where oxarticles.oxid = :oxid\";\n        $oDb->execute($query, [\n            'oxrating' => $dRating,\n            'oxratingcnt' => $dRatingCnt,\n            'oxid' => $this->getId()\n        ]);\n    }\n\n    /**\n     * Set product rating average\n     *\n     * @param integer $iRating new rating value\n     */\n    public function setRatingAverage($iRating)\n    {\n        $this->oxarticles__oxrating = new Field($iRating);\n    }\n\n    /**\n     * Set product rating count\n     *\n     * @param integer $iRatingCnt new rating count\n     */\n    public function setRatingCount($iRatingCnt)\n    {\n        $this->oxarticles__oxratingcnt = new Field($iRatingCnt);\n    }\n\n    /**\n     * Returns product rating average\n     *\n     * @param bool $blIncludeVariants - include variant ratings\n     *\n     * @return double\n     */\n    public function getArticleRatingAverage($blIncludeVariants = false)\n    {\n        if (!$blIncludeVariants) {\n            return round($this->oxarticles__oxrating->value, 1);\n        } else {\n            $oRating = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Rating::class);\n\n            return $oRating->getRatingAverage($this->getId(), 'oxarticle', $this->getVariantIds());\n        }\n    }\n\n    /**\n     * Returns product rating count\n     *\n     * @param bool $blIncludeVariants - include variant ratings\n     *\n     * @return int\n     */\n    public function getArticleRatingCount($blIncludeVariants = false)\n    {\n        if (!$blIncludeVariants) {\n            return $this->oxarticles__oxratingcnt->value;\n        } else {\n            $oRating = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Rating::class);\n\n            return $oRating->getRatingCount($this->getId(), 'oxarticle', $this->getVariantIds());\n        }\n    }\n\n\n    /**\n     * Collects user written reviews about an article.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    public function getReviews()\n    {\n        $aIds = [$this->getId()];\n\n        if ($this->oxarticles__oxparentid->value) {\n            $aIds[] = $this->oxarticles__oxparentid->value;\n        }\n\n        // showing variant reviews ..\n        if (Registry::getConfig()->getConfigParam('blShowVariantReviews')) {\n            $aAdd = $this->getVariantIds();\n            if (is_array($aAdd)) {\n                $aIds = array_merge($aIds, $aAdd);\n            }\n        }\n\n        $oReview = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Review::class);\n        $oRevs = $oReview->loadList('oxarticle', $aIds);\n\n        //if no review found, return null\n        if ($oRevs->count() < 1) {\n            return null;\n        }\n\n        return $oRevs;\n    }\n\n    /**\n     * Loads and returns array with cross selling information.\n     *\n     * @return array\n     */\n    public function getCrossSelling()\n    {\n        $oCrosslist = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n        $oCrosslist->loadArticleCrossSell($this->oxarticles__oxid->value);\n        if ($oCrosslist->count()) {\n            return $oCrosslist;\n        }\n    }\n\n    /**\n     * Loads and returns array with accessories information.\n     *\n     * @return array\n     */\n    public function getAccessoires()\n    {\n        $myConfig = Registry::getConfig();\n\n        // Performance\n        if (!$myConfig->getConfigParam('bl_perfLoadAccessoires')) {\n            return;\n        }\n\n        $oAcclist = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n        $oAcclist->setSqlLimit(0, $myConfig->getConfigParam('iNrofCrossellArticles'));\n        $oAcclist->loadArticleAccessoires($this->oxarticles__oxid->value);\n\n        if ($oAcclist->count()) {\n            return $oAcclist;\n        }\n    }\n\n    /**\n     * Returns a list of similar products.\n     *\n     * @return array\n     */\n    public function getSimilarProducts()\n    {\n        // Performance\n        $myConfig = Registry::getConfig();\n        if (!$myConfig->getConfigParam('bl_perfLoadSimilar')) {\n            return;\n        }\n\n        // Check configured number of similar products (bug #6062)\n        if ($myConfig->getConfigParam('iNrofSimilarArticles') < 1) {\n            return;\n        }\n\n        $sArticleTable = $this->getViewName();\n\n        $sAttribs = '';\n        $iCnt = 0;\n        $this->getAttribsString($sAttribs, $iCnt);\n\n        if (!$sAttribs) {\n            return null;\n        }\n\n        $aList = $this->getSimList($sAttribs, $iCnt);\n\n        if (count($aList)) {\n            uasort(\n                $aList,\n                function ($a, $b) {\n                    return $a <=> $b;\n                }\n            );\n\n            $sSearch = $this->generateSimListSearchStr($sArticleTable, $aList);\n\n            $oSimilarlist = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n            $oSimilarlist->setSqlLimit(0, $myConfig->getConfigParam('iNrofSimilarArticles'));\n            $oSimilarlist->selectString($sSearch);\n\n            return $oSimilarlist;\n        }\n    }\n\n    /**\n     * Loads and returns articles list, bought by same customer.\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\ArticleList|null\n     */\n    public function getCustomerAlsoBoughtThisProducts()\n    {\n        // Performance\n        $myConfig = Registry::getConfig();\n        if (!$myConfig->getConfigParam('bl_perfLoadCustomerWhoBoughtThis')) {\n            return;\n        }\n\n        // selecting products that fits\n        $sQ = $this->generateSearchStrForCustomerBought();\n\n        $oArticles = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n        $oArticles->setSqlLimit(0, $myConfig->getConfigParam('iNrofCustomerWhoArticles'));\n        $oArticles->selectString($sQ);\n        if ($oArticles->count()) {\n            return $oArticles;\n        }\n    }\n\n    /**\n     * Returns list object with info about article price that depends on amount in basket.\n     * Takes data from oxprice2article table. Returns false if such info is not set.\n     *\n     * @return mixed\n     */\n    public function loadAmountPriceInfo()\n    {\n        $myConfig = Registry::getConfig();\n        if (\n            !$myConfig->getConfigParam('bl_perfLoadPrice')\n            || !$this->_blLoadPrice\n            || !$this->_blCalcPrice\n            || !$this->hasAmountPrice()\n        ) {\n            return [];\n        }\n\n        if ($this->_oAmountPriceInfo === null) {\n            $this->_oAmountPriceInfo = [];\n            if (count(($aAmPriceList = $this->buildAmountPriceList()->getArray()))) {\n                $this->_oAmountPriceInfo = $this->fillAmountPriceList($aAmPriceList);\n            }\n        }\n\n        return $this->_oAmountPriceInfo;\n    }\n\n    /**\n     * Returns all selectlists this article has (used in oxbasket)\n     *\n     * @param string $sKeyPrefix Optional key prefix\n     *\n     * @return array\n     */\n    public function getSelectLists($sKeyPrefix = null)\n    {\n        //#1468C - more then one article in basket with different selectlist...\n        //optionall function parameter $sKeyPrefix added, used only in basket.php\n        $sKey = $this->getId();\n        if (isset($sKeyPrefix)) {\n            $sKey = $sKeyPrefix . '__' . $sKey;\n        }\n\n        if (!isset(self::$_aSelList[$sKey])) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $sSLViewName = $tableViewNameGenerator->getViewName('oxselectlist');\n\n            $sQ = \"select {$sSLViewName}.* from oxobject2selectlist join {$sSLViewName}\n                    on $sSLViewName.oxid=oxobject2selectlist.oxselnid\n                    where oxobject2selectlist.oxobjectid = :oxobjectid order by oxobject2selectlist.oxsort\";\n\n            // all selectlists this article has\n            $oLists = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n            $oLists->init('oxselectlist');\n            $oLists->selectString($sQ, ['oxobjectid' => $this->getId()]);\n\n            //#1104S if this is variant ant it has no selectlists, trying with parent\n            if ($oLists->count() == 0 && $this->getFieldData('oxparentid')) {\n                $oLists->selectString($sQ, ['oxobjectid' => $this->oxarticles__oxparentid->value]);\n            }\n\n            // We do not need to calculate price here as there are method to get current article vat\n            /*if ( $this->getPrice() != null ) {\n                $dVat = $this->getPrice()->getVat();\n            }*/\n            $dVat = $this->getArticleVat();\n\n            $iCnt = 0;\n            self::$_aSelList[$sKey] = [];\n            foreach ($oLists as $oSelectlist) {\n                self::$_aSelList[$sKey][$iCnt] = $oSelectlist->getFieldList($dVat);\n                self::$_aSelList[$sKey][$iCnt]['name'] = $oSelectlist->oxselectlist__oxtitle->value;\n                $iCnt++;\n            }\n        }\n\n        return self::$_aSelList[$sKey];\n    }\n\n    /**\n     * Returns amount of variants article has\n     *\n     * @return mixed\n     */\n    public function getVariantsCount()\n    {\n        return $this->getFieldData('oxvarcount');\n    }\n\n    /**\n     * Checks if article has multidimensional variants\n     *\n     * @return bool\n     */\n    public function hasMdVariants()\n    {\n        return $this->_blHasMdVariants;\n    }\n\n    /**\n     * Returns if article has intangible agreement with which customer will have to agree.\n     *\n     * @return bool\n     */\n    public function hasIntangibleAgreement()\n    {\n        return $this->oxarticles__oxshowcustomagreement->value\n            && $this->oxarticles__oxnonmaterial->value\n            && !$this->hasDownloadableAgreement();\n    }\n\n    /**\n     * Returns if article has downloadable agreement with which customer will have to agree.\n     *\n     * @return bool\n     */\n    public function hasDownloadableAgreement()\n    {\n        return $this->oxarticles__oxshowcustomagreement->value && $this->oxarticles__oxisdownloadable->value;\n    }\n\n    /**\n     * Returns variants selections lists array\n     *\n     * @param array  $aFilterIds    ids of active selections [optional]\n     * @param string $sActVariantId active variant id [optional]\n     * @param int    $iLimit        limit variant lists count (if non zero, return limited number of multidimensional\n     *                              variant selections)\n     *\n     * @return array\n     */\n    public function getVariantSelections($aFilterIds = null, $sActVariantId = null, $iLimit = 0)\n    {\n        $iLimit = (int) $iLimit;\n        if (!isset($this->_aVariantSelections[$iLimit])) {\n            $aVariantSelections = false;\n            if ($this->oxarticles__oxvarcount->value) {\n                $oVariants = $this->getVariants(false);\n                $aVariantSelections = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\VariantHandler::class)\n                    ->buildVariantSelections(\n                        $this->oxarticles__oxvarname->getRawValue(),\n                        $oVariants,\n                        $aFilterIds,\n                        $sActVariantId,\n                        $iLimit\n                    );\n\n                if (!empty($oVariants) && empty($aVariantSelections['rawselections'])) {\n                    $aVariantSelections = false;\n                }\n            }\n            $this->_aVariantSelections[$iLimit] = $aVariantSelections;\n        }\n\n        return $this->_aVariantSelections[$iLimit];\n    }\n\n    /**\n     * Returns product selections lists array (used in azure theme)\n     *\n     * @param int   $iLimit  if given - will load limited count of selections [optional]\n     * @param array $aFilter selection filter [optional]\n     *\n     * @return array\n     */\n    public function getSelections($iLimit = null, $aFilter = null)\n    {\n        $sId = $this->getId() . ((int) $iLimit);\n        if (!array_key_exists($sId, self::$_aSelections)) {\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $sSLViewName = $tableViewNameGenerator->getViewName('oxselectlist');\n\n            $sQ = \"select {$sSLViewName}.* from oxobject2selectlist join {$sSLViewName}\n                    on $sSLViewName.oxid=oxobject2selectlist.oxselnid\n                    where oxobject2selectlist.oxobjectid = :oxobjectid order by oxobject2selectlist.oxsort\";\n\n            if (($iLimit = (int) $iLimit)) {\n                $sQ .= \" limit $iLimit \";\n            }\n\n            // vat value for price\n            $dVat = 0;\n            if (($oPrice = $this->getPrice()) != null) {\n                $dVat = $oPrice->getVat();\n            }\n\n            // all selectlists this article has\n            $oList = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n            $oList->init('oxselectlist');\n            $oList->getBaseObject()->setVat($dVat);\n            $oList->selectString($sQ, ['oxobjectid' => $this->getId()]);\n\n            //#1104S if this is variant and it has no selectlists, trying with parent\n            if ($oList->count() == 0 && $this->oxarticles__oxparentid->value) {\n                $oList->selectString($sQ, ['oxobjectid' => $this->oxarticles__oxparentid->value]);\n            }\n\n            self::$_aSelections[$sId] = $oList->count() ? $oList : false;\n        }\n\n        if (self::$_aSelections[$sId]) {\n            // marking active from filter\n            $aFilter = ($aFilter === null) ? Registry::getRequest()->getRequestEscapedParameter(\"sel\") : $aFilter;\n            if ($aFilter) {\n                $iSelIdx = 0;\n                foreach (self::$_aSelections[$sId] as $oSelection) {\n                    if (isset($aFilter[$iSelIdx])) {\n                        $oSelection->setActiveSelectionByIndex($aFilter[$iSelIdx]);\n                    }\n                    $iSelIdx++;\n                }\n            }\n        }\n\n        return self::$_aSelections[$sId];\n    }\n\n    /**\n     * Returns variant list (list contains oxArticle objects)\n     *\n     * @param bool $blRemoveNotOrderables if true, removes from list not orderable articles, which are out of stock\n     *                                    [optional]\n     * @param bool $blForceCoreTable      if true forces core table use, default is false [optional]\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\ArticleList\n     */\n    public function getFullVariants($blRemoveNotOrderables = true, $blForceCoreTable = null)\n    {\n        return $this->loadVariantList(false, $blRemoveNotOrderables, $blForceCoreTable);\n    }\n\n    /**\n     * Collects and returns article variants.\n     * Note: Only active variants are returned by this method. If you need full variant list use\n     * \\OxidEsales\\Eshop\\Application\\Model\\Article::getAdminVariants()\n     *\n     * @param bool $blRemoveNotOrderables if true, removes from list not orderable articles, which are out of stock\n     * @param bool $blForceCoreTable      if true forces core table use, default is false [optional]\n     *\n     * @return array\n     */\n    public function getVariants($blRemoveNotOrderables = true, $blForceCoreTable = null)\n    {\n        return $this->loadVariantList($this->isInList(), $blRemoveNotOrderables, $blForceCoreTable);\n    }\n\n    /**\n     * Simple way to get variants without querying oxArticle table first. This is basically used for lists.\n     *\n     * @return null\n     */\n    public function getSimpleVariants()\n    {\n        if ($this->oxarticles__oxvarcount->value) {\n            return $this->getVariants();\n        }\n    }\n\n    /**\n     * Loads article variants and returns variants list object. Article language may\n     * be set by passing with parameter, or GET/POST/Session variable.\n     *\n     * @param string $sLanguage shop language.\n     *\n     * @return object\n     */\n    public function getAdminVariants($sLanguage = null)\n    {\n        $oVariants = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n        if (($sId = $this->getId())) {\n            $oBaseObj = $oVariants->getBaseObject();\n\n            if (is_null($sLanguage)) {\n                $oBaseObj->setLanguage(Registry::getLang()->getBaseLanguage());\n            } else {\n                $oBaseObj->setLanguage($sLanguage);\n            }\n\n            $sSql = \"select * from \" . $oBaseObj->getViewName() . \"\n                where oxparentid = :oxparentid\n                order by oxsort \";\n            $oVariants->selectString($sSql, ['oxparentid' => $sId]);\n\n            //if we have variants then depending on config option the parent may be non buyable\n            if (!Registry::getConfig()->getConfigParam('blVariantParentBuyable') && ($oVariants->count() > 0)) {\n                //$this->blNotBuyable = true;\n                $this->_blNotBuyableParent = true;\n            }\n        }\n\n        return $oVariants;\n    }\n\n    /**\n     * Loads and returns article category object. First tries to load\n     * assigned category and is such category does not exist, tries to\n     * load category by price\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Category|null\n     */\n    public function getCategory()\n    {\n        $shopId = Registry::getConfig()->getShopId();\n        $id = $this->getParentId();\n        if (!$id) {\n            $id = $this->getId();\n        }\n\n        $this->initializeShopArticleCategoryCache($shopId);\n        if (\\array_key_exists($id, self::$_aCategoryCache[$shopId])) {\n            return self::$_aCategoryCache[$shopId][$id];\n        }\n\n        startProfile('getCategory');\n\n        $category = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n        $category->setLanguage($this->getLanguage());\n\n        $str = Str::getStr();\n        $where = $category->getSqlActiveSnippet();\n        $select = $this->generateSearchStr($id);\n        $select .= (\n            $str->strstr(\n                $select,\n                'where'\n            ) ? ' and ' : ' where '\n        ) . $where . \" order by oxobject2category.oxtime limit 1\";\n\n\n        // category not found ?\n        $record = DatabaseProvider::getDb()->select($select);\n        if ($record && $record->count() > 0) {\n            $category->assign($record->fields);\n        } else {\n            $select = $this->generateSearchStr($id, true);\n            $select .= ($str->strstr($select, 'where') ? ' and ' : ' where ') . $where . \" limit 1\";\n\n            // looking for price category\n            $record = DatabaseProvider::getDb()->select($select);\n            if ($record && $record->count() > 0) {\n                $category->assign($record->fields);\n            } else {\n                $category = null;\n            }\n        }\n\n        // add the category instance to cache\n        self::$_aCategoryCache[$shopId][$id] = $category;\n        stopProfile('getCategory');\n\n        return $category;\n    }\n\n    private function initializeShopArticleCategoryCache($shopId): void\n    {\n        if (!\\array_key_exists($shopId, self::$_aCategoryCache)) {\n            self::$_aCategoryCache[$shopId] = [];\n        }\n    }\n\n    /**\n     * Returns ID's of categories where this article is assigned\n     *\n     * @param bool $blActCats select categories if all parents are active\n     * @param bool $blSkipCache Whether to skip cache\n     *\n     * @return array\n     */\n    public function getCategoryIds($blActCats = false, $blSkipCache = false)\n    {\n        $sArticleId = $this->getId();\n\n        if (!isset(self::$_aArticleCats[$sArticleId]) || $blSkipCache) {\n            $sSql = $this->getCategoryIdsSelect($blActCats);\n            $aCategoryIds = $this->selectCategoryIds($sSql, 'oxcatnid');\n\n            $sSql = $this->getSqlForPriceCategories();\n            $aPriceCategoryIds = $this->selectCategoryIds($sSql, 'oxid');\n\n            self::$_aArticleCats[$sArticleId] = array_unique(array_merge($aCategoryIds, $aPriceCategoryIds));\n        }\n\n        return self::$_aArticleCats[$sArticleId];\n    }\n\n    /**\n     * Returns current article vendor object. If $blShopCheck = false, then\n     * vendor loading will fallback to oxI18n object and blReadOnly parameter\n     * will be set to true if vendor is not assigned to current shop\n     *\n     * @param bool $blShopCheck Set false if shop check is not required (default is true)\n     *\n     * @return object\n     */\n    public function getVendor($blShopCheck = true)\n    {\n        $sVendorId = $this->getVendorId();\n        if ($sVendorId) {\n            $oVendor = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Vendor::class);\n        } elseif (!$blShopCheck && $this->oxarticles__oxvendorid->value) {\n            $oVendor = $this->createMultilanguageVendorObject();\n            $sVendorId = $this->oxarticles__oxvendorid->value;\n        }\n        if ($sVendorId && $oVendor && $oVendor->load($sVendorId) && $oVendor->oxvendor__oxactive->value) {\n            return $oVendor;\n        }\n\n        return null;\n    }\n\n    /**\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel\n     */\n    protected function createMultilanguageVendorObject()\n    {\n        $oVendor = oxNew(MultiLanguageModel::class);\n        $oVendor->init('oxvendor');\n        $oVendor->setReadOnly(true);\n\n        return $oVendor;\n    }\n\n    /**\n     * Returns article object vendor ID. Result is cached into self::$_aArticleVendors\n     *\n     * @return string\n     */\n    public function getVendorId()\n    {\n        $sVendorId = false;\n        if ($this->oxarticles__oxvendorid->value) {\n            $sVendorId = $this->oxarticles__oxvendorid->value;\n        }\n\n        return $sVendorId;\n    }\n\n    /**\n     * Returns article object Manufacturer ID. Result is cached into self::$_aArticleManufacturers\n     *\n     * @return string\n     */\n    public function getManufacturerId()\n    {\n        return $this->oxarticles__oxmanufacturerid->value ?: false;\n    }\n\n    /**\n     * Returns current article Manufacturer object. If $blShopCheck = false, then\n     * Manufacturer blReadOnly parameter will be set to true. If Manufacturer is\n     * not assigned to current shop\n     *\n     * @param bool $blShopCheck Set false if shop check is not required (default is true)\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Manufacturer|null\n     */\n    public function getManufacturer($blShopCheck = true)\n    {\n        $oManufacturer = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Manufacturer::class);\n        if (\n            !($sManufacturerId = $this->getManufacturerId()) &&\n            !$blShopCheck && $this->oxarticles__oxmanufacturerid->value\n        ) {\n            $this->updateManufacturerBeforeLoading($oManufacturer);\n            $sManufacturerId = $this->oxarticles__oxmanufacturerid->value;\n        }\n\n        if ($sManufacturerId && $oManufacturer->load($sManufacturerId)) {\n            if (!Registry::getConfig()->getConfigParam('bl_perfLoadManufacturerTree')) {\n                $oManufacturer->setReadOnly(true);\n            }\n            $oManufacturer = $oManufacturer->oxmanufacturers__oxactive->value ? $oManufacturer : null;\n        } else {\n            $oManufacturer = null;\n        }\n\n        return $oManufacturer;\n    }\n\n    /**\n     * Checks if article is assigned to category $sCatNID.\n     *\n     * @param string $sCatNid category ID\n     *\n     * @return bool\n     */\n    public function inCategory($sCatNid)\n    {\n        return in_array($sCatNid, $this->getCategoryIds());\n    }\n\n    /**\n     * Checks if article is assigned to passed category (even checks\n     * if this category is \"price category\"). Returns true on success.\n     *\n     * @param string $sCatId category ID\n     *\n     * @return bool\n     */\n    public function isAssignedToCategory($sCatId)\n    {\n        // variant handling\n        $sOXID = $this->getId();\n        if (isset($this->oxarticles__oxparentid->value) && $this->oxarticles__oxparentid->value) {\n            $sOXID = $this->oxarticles__oxparentid->value;\n        }\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sSelect = $this->generateSelectCatStr($sOXID, $sCatId);\n        $sOXID = $oDb->getOne($sSelect);\n        // article is assigned to passed category!\n        if (isset($sOXID) && $sOXID) {\n            return true;\n        }\n\n        // maybe this category is price category ?\n        if (Registry::getConfig()->getConfigParam('bl_perfLoadPrice') && $this->_blLoadPrice) {\n            $dPriceFromTo = $this->getPrice()->getBruttoPrice();\n            if ($dPriceFromTo > 0) {\n                $sSelect = $this->generateSelectCatStr($sOXID, $sCatId, $dPriceFromTo);\n                $sOXID = $oDb->getOne($sSelect);\n                // article is assigned to passed category!\n                if (isset($sOXID) && $sOXID) {\n                    return true;\n                }\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns T price\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price|null\n     */\n    public function getTPrice()\n    {\n        if (!Registry::getConfig()->getConfigParam('bl_perfLoadPrice') || !$this->_blLoadPrice) {\n            return;\n        }\n\n        // return cached result, since oPrice is created ONLY in this function [or function of EQUAL level]\n        if ($this->_oTPrice !== null) {\n            return $this->_oTPrice;\n        }\n\n        $oPrice = $this->getPriceObject();\n\n        $dBasePrice = $this->oxarticles__oxtprice->value;\n        $dBasePrice = $this->preparePrice($dBasePrice, $this->getArticleVat());\n\n        $oPrice->setPrice($dBasePrice);\n\n        $this->applyVAT($oPrice, $this->getArticleVat());\n        $this->applyCurrency($oPrice);\n\n        if ($this->isParentNotBuyable()) {\n            // if parent article is not buyable then compare agains min article variant price\n            $oPrice2 = $this->getVarMinPrice();\n        } else {\n            // else compare against article price\n            $oPrice2 = $this->getPrice();\n        }\n\n        if ($oPrice->getPrice() <= $oPrice2->getPrice()) {\n            // if RRP price is less or equal to comparable price then return\n            return;\n        }\n\n        $this->_oTPrice = $oPrice;\n\n        return $this->_oTPrice;\n    }\n\n    /**\n     * Checks if discount should be skipped for this article in basket. Returns true if yes.\n     *\n     * @return bool\n     */\n    public function skipDiscounts()\n    {\n        // already loaded skip discounts config\n        if ($this->_blSkipDiscounts !== null) {\n            return $this->_blSkipDiscounts;\n        }\n\n        if ($this->oxarticles__oxskipdiscounts->value) {\n            return true;\n        }\n\n\n        $this->_blSkipDiscounts = false;\n        if (Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\DiscountList::class)->hasSkipDiscountCategories()) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $sO2CView = $tableViewNameGenerator->getViewName('oxobject2category', $this->getLanguage());\n            $sViewName = $tableViewNameGenerator->getViewName('oxcategories', $this->getLanguage());\n            $sSelect = \"select 1 from $sO2CView as $sO2CView\n                left join {$sViewName} on {$sViewName}.oxid = $sO2CView.oxcatnid\n                where $sO2CView.oxobjectid = :oxobjectid\n                    and {$sViewName}.oxactive = :oxactive\n                    and {$sViewName}.oxskipdiscounts = :oxskipdiscounts \";\n            $params = [\n                'oxobjectid' => $this->getId(),\n                'oxactive' => 1,\n                'oxskipdiscounts' => 1\n            ];\n            $this->_blSkipDiscounts = ($oDb->getOne($sSelect, $params) == 1);\n        }\n\n        return $this->_blSkipDiscounts;\n    }\n\n    /**\n     * Sets the current oxPrice object\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Price $oPrice the new price object\n     */\n    public function setPrice(Price $oPrice)\n    {\n        $this->_oPrice = $oPrice;\n    }\n\n    /**\n     * Returns base article price from database. Price may differ according to users group\n     * Override this function if you want e.g. different prices for diff. usergroups.\n     *\n     * @param double $dAmount article amount. Default is 1\n     *\n     * @return double\n     */\n    public function getBasePrice($dAmount = 1)\n    {\n        // override this function if you want e.g. different prices\n        // for diff. user groups.\n\n        // Performance\n        $myConfig = Registry::getConfig();\n        if (!$myConfig->getConfigParam('bl_perfLoadPrice') || !$this->_blLoadPrice) {\n            return;\n        }\n\n        // GroupPrice or DB price ajusted by AmountPrice\n        $dPrice = $this->getModifiedAmountPrice($dAmount);\n\n        return $dPrice;\n    }\n\n    /**\n     * Modifies given amount price.\n     *\n     * @param int $amount\n     *\n     * @return double\n     */\n    protected function getModifiedAmountPrice($amount)\n    {\n        return $this->getAmountPrice($amount);\n    }\n\n    /**\n     * Calculates and returns price of article (adds taxes and discounts).\n     *\n     * @param float|int $dAmount article amount.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getPrice($dAmount = 1)\n    {\n        $myConfig = Registry::getConfig();\n        // Performance\n        if (!$myConfig->getConfigParam('bl_perfLoadPrice') || !$this->_blLoadPrice) {\n            return;\n        }\n\n        // return cached result, since oPrice is created ONLY in this function [or function of EQUAL level]\n        if ($dAmount != 1 || $this->_oPrice === null) {\n            // module\n            $dBasePrice = $this->getBasePrice($dAmount);\n            $dBasePrice = $this->preparePrice($dBasePrice, $this->getArticleVat());\n\n            $oPrice = $this->getPriceObject();\n\n            $oPrice->setPrice($dBasePrice);\n\n            // price handling\n            if (!$this->_blCalcPrice && $dAmount == 1) {\n                return $this->_oPrice = $oPrice;\n            }\n\n            $this->calculatePrice($oPrice);\n            if ($dAmount != 1) {\n                return $oPrice;\n            }\n\n            $this->_oPrice = $oPrice;\n        }\n\n        return $this->_oPrice;\n    }\n\n    /**\n     * sets article user\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser user to set\n     */\n    public function setArticleUser($oUser)\n    {\n        $this->_oUser = $oUser;\n    }\n\n    /**\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\User article user.\n     */\n    public function getArticleUser()\n    {\n        if ($this->_oUser) {\n            return $this->_oUser;\n        }\n\n        return $this->getUser();\n    }\n\n    /**\n     * Creates, calculates and returns oxPrice object for basket product.\n     *\n     * @param float  $dAmount  Amount\n     * @param array  $aSelList Selection list\n     * @param object $oBasket  User shopping basket object\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getBasketPrice($dAmount, $aSelList, $oBasket)\n    {\n        $oUser = $oBasket->getBasketUser();\n        $this->setArticleUser($oUser);\n\n        $oBasketPrice = $this->getPriceObject($oBasket->isCalculationModeNetto());\n\n        // get base price\n        $dBasePrice = $this->getBasePrice($dAmount);\n\n        $dBasePrice = $this->modifySelectListPrice($dBasePrice, $aSelList);\n        $dBasePrice = $this->preparePrice($dBasePrice, $this->getArticleVat(), $oBasket->isCalculationModeNetto());\n\n        // applying select list price\n\n        // setting price\n        $oBasketPrice->setPrice($dBasePrice);\n\n        $dVat = Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\VatSelector::class)->getBasketItemVat(\n            $this,\n            $oBasket\n        );\n        $this->calculatePrice($oBasketPrice, $dVat);\n\n        // returning final price object\n        return $oBasketPrice;\n    }\n\n    /**\n     * Deletes record and other information related to this article such as images from DB,\n     * also removes variants. Returns true if entry was deleted.\n     *\n     * @param string $sOXID Article id\n     *\n     * @throws \\Exception\n     *\n     * @return bool\n     */\n    public function delete($sOXID = null)\n    {\n        if (!$sOXID) {\n            $sOXID = $this->getId();\n        }\n        if (!$sOXID) {\n            return false;\n        }\n\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $database->startTransaction();\n        try {\n            // #2339 delete first variants before deleting parent product\n            $this->deleteVariantRecords($sOXID);\n            $this->load($sOXID);\n            $this->deletePics();\n            $this->onChangeResetCounts(\n                $sOXID,\n                $this->oxarticles__oxvendorid->value,\n                $this->oxarticles__oxmanufacturerid->value\n            );\n\n            // delete self\n            $deleted = parent::delete($sOXID);\n\n            $this->deleteRecords($sOXID);\n\n            Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderArticle::class)->onDeleteArticle($this);\n\n            $this->onChange(ACTION_DELETE, $sOXID, $this->getFieldData('oxparentid'));\n\n            $database->commitTransaction();\n        } catch (Exception $exception) {\n            $database->rollbackTransaction();\n\n            throw $exception;\n        }\n\n        return $deleted;\n    }\n\n    /**\n     * Reduce article stock. return the affected amount\n     *\n     * @param float $dAmount              amount to reduce\n     * @param bool  $blAllowNegativeStock are negative stocks allowed?\n     *\n     * @return float\n     */\n    public function reduceStock($dAmount, $blAllowNegativeStock = false)\n    {\n        $this->actionType = ACTION_UPDATE_STOCK;\n        $this->beforeUpdate();\n\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $query = 'select oxstock\n            from oxarticles\n            where oxid = :oxid FOR UPDATE ';\n        $actualStock = $database->getOne($query, [\n            'oxid' => $this->getId()\n        ]);\n\n        $iStockCount = $actualStock - $dAmount;\n        if (!$blAllowNegativeStock && ($iStockCount < 0)) {\n            $dAmount += $iStockCount;\n            $iStockCount = 0;\n        }\n        $this->oxarticles__oxstock = new Field($iStockCount);\n\n        $query = 'update oxarticles set oxarticles.oxstock = :oxstock where oxarticles.oxid = :oxid';\n        $database->execute($query, [\n            'oxstock' => $iStockCount,\n            'oxid' => $this->getId()\n        ]);\n        $this->onChange(ACTION_UPDATE_STOCK);\n\n        return $dAmount;\n    }\n\n    /**\n     * Recursive function. Updates quantity of sold articles.\n     * Return true if amount was changed in database.\n     *\n     * @param float $dAmount Number of articles sold\n     *\n     * @return mixed\n     */\n    public function updateSoldAmount($dAmount = 0)\n    {\n        if (!$dAmount) {\n            return;\n        }\n        $rs = false;\n        // article is not variant - should be updated current amount\n        if (!$this->oxarticles__oxparentid->value) {\n            //updating by SQL query, due to wrong behaviour if saving article using not admin mode\n            $dAmount = (float) $dAmount;\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $query = \"update oxarticles\n                      set oxarticles.oxsoldamount = (oxarticles.oxsoldamount + :amount)\n                      where oxarticles.oxid = :oxid\";\n            $rs = $oDb->execute($query, [\n                'oxid' => $this->oxarticles__oxid->value,\n                'amount' => $dAmount\n            ]);\n\n            return (bool) $rs;\n        } elseif ($this->oxarticles__oxparentid->value) {\n            // article is variant - should be updated this article parent amount\n            $oUpdateArticle = $this->getParentArticle();\n            if ($oUpdateArticle) {\n                $oUpdateArticle->updateSoldAmount($dAmount);\n            }\n        }\n\n        return $rs;\n    }\n\n    /**\n     * Disables reminder functionality for article\n     *\n     * @return bool\n     */\n    public function disableReminder()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $query = \"update oxarticles set oxarticles.oxremindactive = 2 where oxarticles.oxid = :oxid\";\n\n        return (bool) $oDb->execute($query, ['oxid' => $this->oxarticles__oxid->value]);\n    }\n\n    /**\n     * (\\OxidEsales\\Eshop\\Application\\Model\\Article::_saveArtLongDesc()) save the object using parent::save() method.\n     *\n     * @return bool\n     */\n    public function save()\n    {\n        $this->assignParentDependFields();\n        $blRet = parent::save();\n        // saving long description\n        $this->saveArtLongDesc();\n\n        return $blRet;\n    }\n\n    /**\n     * Changes article variant to parent article\n     */\n    public function resetParent()\n    {\n        $sParentId = $this->oxarticles__oxparentid->value;\n        $this->oxarticles__oxparentid = new Field('', Field::T_RAW);\n        $this->_blAllowEmptyParentId = true;\n        $this->save();\n        $this->_blAllowEmptyParentId = false;\n\n        if ($sParentId !== '') {\n            $this->onChange(ACTION_UPDATE, null, $sParentId);\n        }\n    }\n\n    /**\n     * collect article pics, icons, zoompic and puts it all in an array\n     * structure of array (ActPicID, ActPic, MorePics, Pics, Icons, ZoomPic)\n     *\n     * @return array\n     */\n    public function getPictureGallery()\n    {\n        $mediaItems = ContainerFacade::get(ProductMediaViewServiceInterface::class)->getAllByRole(\n            Id::fromString($this->getId()),\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n        $activeMedia = $this->determineActiveMedia($mediaItems);\n\n        return [\n            'activeMedia' => $activeMedia,\n            'mediaItems' => $mediaItems,\n            'hasMultipleImages' => count($mediaItems) > 1,\n        ];\n    }\n\n    private function determineActiveMedia(array $mediaItems): ?ProductMediaView\n    {\n        if (empty($mediaItems)) {\n            return null;\n        }\n\n        $requestedMediaId = Registry::getRequest()->getRequestEscapedParameter('actmediaid');\n\n        if ($requestedMediaId && isset($mediaItems[$requestedMediaId])) {\n            return $mediaItems[$requestedMediaId];\n        }\n\n        return reset($mediaItems);\n    }\n\n    /**\n     * This function is triggered whenever article is saved or deleted or after the stock is changed.\n     * Originally we need to update the oxstock for possible article parent in case parent is not buyable\n     * Plus you may want to extend this function to update some extended information.\n     * Call \\OxidEsales\\Eshop\\Application\\Model\\Article::onChange($sAction, $sOXID) with ID parameter when changes are\n     * executed over SQL.\n     * (or use module class instead of oxArticle if such exists)\n     *\n     * @param string $action          Action constant\n     * @param string $articleId       Article ID\n     * @param string $parentArticleId Parent ID\n     *\n     * @return null\n     */\n    public function onChange($action = null, $articleId = null, $parentArticleId = null)\n    {\n        $this->actionType = !is_null($action) ? $action : $this->actionType;\n        $myConfig = Registry::getConfig();\n\n        if (!isset($articleId)) {\n            if ($this->getId()) {\n                $articleId = $this->getId();\n            }\n            if (!isset($articleId)) {\n                $articleId = $this->oxarticles__oxid->value;\n            }\n            if ($this->oxarticles__oxparentid && $this->oxarticles__oxparentid->value) {\n                $parentArticleId = $this->oxarticles__oxparentid->value;\n            }\n        }\n        if (!isset($articleId)) {\n            return;\n        }\n\n        //if (isset($sOXID) && !$myConfig->blVariantParentBuyable && $myConfig->blUseStock)\n        if ($myConfig->getConfigParam('blUseStock')) {\n            //if article has variants then updating oxvarstock field\n            //getting parent id\n            if (!isset($parentArticleId)) {\n                $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n                $sQ = 'select oxparentid from oxarticles where oxid = :oxid';\n                $parentArticleId = $oDb->getOne($sQ, [\n                    'oxid' => $articleId\n                ]);\n            }\n            //if we have parent id then update stock\n            if ($parentArticleId) {\n                $this->onChangeUpdateStock($parentArticleId);\n            }\n        }\n        //if we have parent id then update count\n        //update count even if blUseStock is not active\n        if ($parentArticleId) {\n            $this->onChangeUpdateVarCount($parentArticleId);\n        }\n\n        $sId = ($parentArticleId) ? $parentArticleId : $articleId;\n        $this->setVarMinMaxPrice($sId);\n\n        $this->updateParentDependFields();\n\n        // resetting articles count cache if stock has changed and some\n        // articles goes offline (M:1448)\n        if ($action === ACTION_UPDATE_STOCK) {\n            $this->assignStock();\n            $this->onChangeStockResetCount($articleId);\n        }\n\n        ContainerFacade::dispatch(new AfterModelUpdateEvent($this));\n    }\n\n    /**\n     * Returns custom article VAT value if possible\n     * By default value is taken from oxarticle__oxvat field\n     *\n     * @return double\n     */\n    public function getCustomVAT()\n    {\n        if ($this->__isset('oxarticles__oxvat') || $this->__get('oxarticles__oxvat')) {\n            return $this->oxarticles__oxvat->value;\n        }\n    }\n\n    /**\n     * Checks if stock configuration allows to buy user chosen amount $dAmount\n     *\n     * @param double     $dAmount         buyable amount\n     * @param double|int $dArtStockAmount stock amount\n     * @param bool       $selectForUpdate Set true to select for update\n     *\n     * @return mixed\n     */\n    public function checkForStock($dAmount, $dArtStockAmount = 0, $selectForUpdate = false)\n    {\n        $myConfig = Registry::getConfig();\n        if (!$myConfig->getConfigParam('blUseStock')) {\n            return true;\n        }\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        // fetching DB info as its up-to-date\n        $sQ = 'select oxstock, oxstockflag from oxarticles\n            where oxid = :oxid';\n        $sQ .= $selectForUpdate ? ' FOR UPDATE ' : '';\n        $rs = $oDb->select($sQ, [\n            'oxid' => $this->getId()\n        ]);\n\n        $iOnStock = 0;\n        if ($rs !== false && $rs->count() > 0) {\n            $iOnStock = $rs->fields['oxstock'] - $dArtStockAmount;\n            $iStockFlag = $rs->fields['oxstockflag'];\n\n            //When using stockflag 1 and 4 with basket reservations enabled but disallowing\n            //negative stock values we would allow to reserve more items than are initially available\n            //by keeping the stock level not lower than zero. When discarding reservations\n            //stock level might differ from original value.\n            if (\n                !$myConfig->getConfigParam('blPsBasketReservationEnabled')\n                || ($myConfig->getConfigParam('blPsBasketReservationEnabled')\n                    && $myConfig->getConfigParam('blAllowNegativeStock'))\n            ) {\n                // foreign stock is also always considered as on stock\n                if ($iStockFlag == 1 || $iStockFlag == 4) {\n                    return true;\n                }\n            }\n            if (!$myConfig->getConfigParam('blAllowUnevenAmounts')) {\n                $iOnStock = floor($iOnStock);\n            }\n        }\n        if (Registry::getConfig()->getConfigParam('blPsBasketReservationEnabled')) {\n            $session = Registry::getSession();\n            $iOnStock += $session->getBasketReservations()->getReservedAmount($this->getId());\n        }\n        if ($iOnStock >= $dAmount) {\n            return true;\n        } else {\n            if ($iOnStock > 0) {\n                return $iOnStock;\n            } else {\n                $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ArticleInputException::class);\n                $oEx->setMessage('ERROR_MESSAGE_ARTICLE_ARTICLE_NOT_BUYABLE');\n                Registry::getUtilsView()->addErrorToDisplay($oEx);\n\n                return false;\n            }\n        }\n    }\n\n    /**\n     * Get article long description\n     *\n     * @return object $oField field object\n     */\n    public function getLongDescription()\n    {\n        if ($this->_oLongDesc === null) {\n            // initializing\n            $this->_oLongDesc = new Field();\n\n            // choosing which to get..\n            $sOxid = $this->getId();\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $sViewName = $tableViewNameGenerator->getViewName('oxartextends', $this->getLanguage());\n\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $sDbValue = $oDb->getOne(\"select oxlongdesc from {$sViewName} where oxid = :oxid\", [\n                'oxid' => $sOxid\n            ]);\n\n            if ($sDbValue != false) {\n                $this->_oLongDesc->setValue($sDbValue, Field::T_RAW);\n            } elseif ($this->oxarticles__oxparentid && $this->oxarticles__oxparentid->value) {\n                if (!$this->isAdmin() || $this->_blLoadParentData) {\n                    $oParent = $this->getParentArticle();\n                    if ($oParent) {\n                        $this->_oLongDesc->setValue($oParent->getLongDescription()->getRawValue(), Field::T_RAW);\n                    }\n                }\n            }\n        }\n\n        return $this->_oLongDesc;\n    }\n\n    /**\n     * Save article long description to oxartext table\n     *\n     * @param string $longDescription description to set\n     */\n    public function setArticleLongDesc($longDescription)\n    {\n        // setting current value\n        $this->_oLongDesc = new Field($longDescription, Field::T_RAW);\n        $this->oxarticles__oxlongdesc = new Field($longDescription, Field::T_RAW);\n    }\n\n    /**\n     * the uninitilized list of attributes\n     * use getAttributes\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\AttributeList\n     */\n    protected function newAttributeList()\n    {\n        return oxNew(\\OxidEsales\\Eshop\\Application\\Model\\AttributeList::class);\n    }\n\n    /**\n     * Loads and returns attribute list associated with this article\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\AttributeList\n     */\n    public function getAttributes()\n    {\n        if ($this->_oAttributeList === null) {\n            $this->_oAttributeList = $this->newAttributelist();\n            $this->_oAttributeList->loadAttributes($this->getId(), $this->getParentId());\n        }\n\n        return $this->_oAttributeList;\n    }\n\n    /**\n     * Loads and returns attribute list for display in basket\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\AttributeList\n     */\n    public function getAttributesDisplayableInBasket()\n    {\n        if ($this->basketAttributeList === null) {\n            $this->basketAttributeList = $this->newAttributelist();\n            $this->basketAttributeList->loadAttributesDisplayableInBasket($this->getId(), $this->getParentId());\n        }\n\n        return $this->basketAttributeList;\n    }\n\n\n    /**\n     * Appends article seo url with additional request parameters\n     *\n     * @param string $sAddParams additional parameters which needs to be added to product url\n     * @param int    $iLang      language id\n     */\n    public function appendLink($sAddParams, $iLang = null)\n    {\n        if ($sAddParams) {\n            if ($iLang === null) {\n                $iLang = $this->getLanguage();\n            }\n\n            $this->_aSeoAddParams[$iLang] = isset($this->_aSeoAddParams[$iLang])\n                ? $this->_aSeoAddParams[$iLang] . \"&amp;\"\n                : \"\";\n            $this->_aSeoAddParams[$iLang] .= $sAddParams;\n        }\n    }\n\n    /**\n     * Returns raw article seo url\n     *\n     * @param int  $iLang  language id\n     * @param bool $blMain force to return main url [optional]\n     *\n     * @return string\n     */\n    public function getBaseSeoLink($iLang, $blMain = false)\n    {\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderArticle $oEncoder */\n        $oEncoder = Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderArticle::class);\n        if (!$blMain) {\n            return $oEncoder->getArticleUrl($this, $iLang, $this->getLinkType());\n        }\n\n        return $oEncoder->getArticleMainUrl($this, $iLang);\n    }\n\n    /**\n     * Gets article link\n     *\n     * @param int  $iLang  language id [optional]\n     * @param bool $blMain force to return main url [optional]\n     *\n     * @return string\n     */\n    public function getLink($iLang = null, $blMain = false)\n    {\n        if (!Registry::getUtils()->seoIsActive()) {\n            return $this->getStdLink($iLang);\n        }\n\n        if ($iLang === null) {\n            $iLang = $this->getLanguage();\n        }\n\n        $iLinkType = $this->getLinkType();\n        if (!isset($this->_aSeoUrls[$iLang][$iLinkType])) {\n            $this->_aSeoUrls[$iLang][$iLinkType] = $this->getBaseSeoLink($iLang, $blMain);\n        }\n\n        $sUrl = $this->_aSeoUrls[$iLang][$iLinkType];\n        if (isset($this->_aSeoAddParams[$iLang])) {\n            $sUrl .= ((strpos($sUrl . $this->_aSeoAddParams[$iLang], '?') === false) ? '?' : '&amp;')\n                . $this->_aSeoAddParams[$iLang];\n        }\n\n        return $sUrl;\n    }\n\n    /**\n     * Returns main object URL. If SEO is ON returned link will be in SEO form,\n     * else URL will have dynamic form\n     *\n     * @param int $iLang language id [optional]\n     *\n     * @return string\n     */\n    public function getMainLink($iLang = null)\n    {\n        return $this->getLink($iLang, true);\n    }\n\n    /**\n     * Resets details link\n     *\n     * @param int $iType type of link to load\n     */\n    public function setLinkType($iType)\n    {\n        // resetting details link, to force new\n        $this->_sDetailLink = null;\n\n        // setting link type\n        $this->_iLinkType = (int) $iType;\n    }\n\n    /**\n     * Get link type\n     *\n     * @return int\n     */\n    public function getLinkType()\n    {\n        return $this->_iLinkType;\n    }\n\n    /**\n     * Appends article dynamic url with additional request parameters\n     *\n     * @param string $sAddParams additional parameters which needs to be added to product url\n     * @param int    $iLang      language id\n     */\n    public function appendStdLink($sAddParams, $iLang = null)\n    {\n        if ($sAddParams) {\n            if ($iLang === null) {\n                $iLang = $this->getLanguage();\n            }\n\n            $this->_aStdAddParams[$iLang] = isset($this->_aStdAddParams[$iLang])\n                ? $this->_aStdAddParams[$iLang] . \"&amp;\"\n                : \"\";\n            $this->_aStdAddParams[$iLang] .= $sAddParams;\n        }\n    }\n\n    /**\n     * Returns base dynamic url: shopurl/index.php?cl=details\n     *\n     * @param int  $iLang   language id\n     * @param bool $blAddId add current object id to url or not [optional]\n     * @param bool $blFull  return full including domain name [optional]\n     *\n     * @return string\n     */\n    public function getBaseStdLink($iLang, $blAddId = true, $blFull = true)\n    {\n        $sUrl = '';\n        if ($blFull) {\n            //always returns shop url, not admin\n            $sUrl = Registry::getConfig()->getShopUrl($iLang, false);\n        }\n\n        $sUrl .= \"index.php?cl=details\" . ($blAddId ? \"&amp;anid=\" . $this->getId() : \"\");\n\n        return $sUrl . (isset($this->_aStdAddParams[$iLang]) ? \"&amp;\" . $this->_aStdAddParams[$iLang] : \"\");\n    }\n\n    /**\n     * Returns standard URL to product\n     *\n     * @param int   $iLang   required language. optional\n     * @param array $aParams additional params to use [optional]\n     *\n     * @return string\n     */\n    public function getStdLink($iLang = null, $aParams = [])\n    {\n        if ($iLang === null) {\n            $iLang = $this->getLanguage();\n        }\n\n        if (!isset($this->_aStdUrls[$iLang])) {\n            $this->_aStdUrls[$iLang] = $this->getBaseStdLink($iLang);\n        }\n\n        return Registry::getUtilsUrl()->processUrl($this->_aStdUrls[$iLang], true, $aParams, $iLang);\n    }\n\n    /**\n     * Return article media URL\n     *\n     * @return array\n     */\n    public function getMediaUrls()\n    {\n        if ($this->_aMediaUrls === null) {\n            $this->_aMediaUrls = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n            $this->_aMediaUrls->init(\"oxmediaurl\");\n            $this->_aMediaUrls->getBaseObject()->setLanguage($this->getLanguage());\n\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $sViewName = $tableViewNameGenerator->getViewName(\"oxmediaurls\", $this->getLanguage());\n            $sQ = \"select * from {$sViewName} where oxobjectid = :oxobjectid\";\n            $this->_aMediaUrls->selectString($sQ, [\n                'oxobjectid' => $this->getId()\n            ]);\n        }\n\n        return $this->_aMediaUrls;\n    }\n\n    /**\n     * Get image url\n     *\n     * @return array\n     */\n    public function getDynImageDir()\n    {\n        return $this->_sDynImageDir;\n    }\n\n    /**\n     * Returns select lists to display\n     *\n     * @return array\n     */\n    public function getDispSelList()\n    {\n        if ($this->_aDispSelList === null) {\n            if (\n                Registry::getConfig()->getConfigParam('bl_perfLoadSelectLists')\n                && Registry::getConfig()->getConfigParam('bl_perfLoadSelectListsInAList')\n            ) {\n                $this->_aDispSelList = $this->getSelectLists();\n            }\n        }\n\n        return $this->_aDispSelList;\n    }\n\n    /**\n     * Get more details link\n     *\n     * @return string\n     */\n    public function getMoreDetailLink()\n    {\n        if ($this->_sMoreDetailLink == null) {\n            // and assign special article values\n            $this->_sMoreDetailLink = Registry::getConfig()->getShopHomeUrl() . 'cl=moredetails';\n\n            // not always it is okey, as not all the time active category is the same as primary article cat.\n            if ($sActCat = Registry::getRequest()->getRequestEscapedParameter('cnid')) {\n                $this->_sMoreDetailLink .= '&amp;cnid=' . $sActCat;\n            }\n            $this->_sMoreDetailLink .= '&amp;anid=' . $this->getId();\n        }\n\n        return $this->_sMoreDetailLink;\n    }\n\n    /**\n     * Get to basket link\n     *\n     * @return string\n     */\n    public function getToBasketLink()\n    {\n        if ($this->_sToBasketLink == null) {\n            $myConfig = Registry::getConfig();\n\n            if (Registry::getUtils()->isSearchEngine()) {\n                $this->_sToBasketLink = $this->getLink();\n            } else {\n                // and assign special article values\n                $this->_sToBasketLink = $myConfig->getShopHomeUrl();\n\n                // override some classes as these should never showup\n                $actControllerId = Registry::getConfig()->getRequestControllerId();\n                if ($actControllerId == 'thankyou') {\n                    $actControllerId = 'basket';\n                }\n                $this->_sToBasketLink .= 'cl=' . $actControllerId;\n\n                // this is not very correct\n                if ($sActCat = Registry::getRequest()->getRequestEscapedParameter('cnid')) {\n                    $this->_sToBasketLink .= '&amp;cnid=' . $sActCat;\n                }\n\n                $this->_sToBasketLink .= '&amp;fnc=tobasket&amp;aid=' . $this->getId() . '&amp;anid=' . $this->getId();\n\n                if ($sTpl = basename(Registry::getRequest()->getRequestEscapedParameter('tpl'))) {\n                    $this->_sToBasketLink .= '&amp;tpl=' . $sTpl;\n                }\n            }\n        }\n\n        return $this->_sToBasketLink;\n    }\n\n    /**\n     * Get stock status\n     *\n     * @return integer\n     */\n    public function getStockStatus()\n    {\n        return $this->_iStockStatus;\n    }\n\n    /**\n     * Get stock status as it was on loading this object.\n     *\n     * @return integer\n     */\n    public function getStockStatusOnLoad()\n    {\n        return $this->_iStockStatusOnLoad;\n    }\n\n    /**\n     * Get stock\n     *\n     * @return float\n     */\n    public function getStock()\n    {\n        return $this->oxarticles__oxstock->value;\n    }\n\n    /**\n     * Returns formatted delivery date. If the date is past or not set ('0000-00-00') returns false.\n     *\n     * @deprecated since v6.2 (2020-02-26); use getRestockDate();\n     * @return string|bool\n     */\n    public function getDeliveryDate()\n    {\n        return $this->getRestockDate();\n    }\n\n    /**\n     * Returns formatted delivery date. If the date is past or not set ('0000-00-00') returns false.\n     *\n     * @return string|bool\n     */\n    public function getRestockDate()\n    {\n        $restockDate = $this->getFieldData('oxdelivery');\n        if ($restockDate >= date('Y-m-d')) {\n            return Registry::getUtilsDate()->formatDBDate($restockDate);\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns rounded T price.\n     *\n     * @deprecated since v5.1 (2013-10-03); use getTPrice() and oxPrice modifier;\n     *\n     * @return double|bool\n     */\n    public function getFTPrice()\n    {\n        // module\n        if ($oPrice = $this->getTPrice()) {\n            if ($dPrice = $this->getPriceForView($oPrice)) {\n                return Registry::getLang()->formatCurrency($dPrice);\n            }\n        }\n    }\n\n    /**\n     * Returns formatted product's price.\n     *\n     * @deprecated since v5.1 (2013-10-04); use oxPrice template engine plugin for formatting in templates\n     *\n     * @return double\n     */\n    public function getFPrice()\n    {\n        if ($oPrice = $this->getPrice()) {\n            $dPrice = $this->getPriceForView($oPrice);\n\n            return Registry::getLang()->formatCurrency($dPrice);\n        }\n    }\n\n    /**\n     * Resets oxremindactive status.\n     * If remindActive status is 2, reminder is already sent.\n     */\n    public function resetRemindStatus()\n    {\n        if (\n            $this->oxarticles__oxremindactive->value == 2 &&\n            $this->oxarticles__oxremindamount->value <= $this->oxarticles__oxstock->value\n        ) {\n            $this->oxarticles__oxremindactive->value = 1;\n        }\n    }\n\n    /**\n     * Returns formatted product's NETTO price.\n     *\n     * @deprecated since v5.1 (2013-10-03); use getPrice() and oxPrice modifier;\n     *\n     * @return double\n     */\n    public function getFNetPrice()\n    {\n        if ($oPrice = $this->getPrice()) {\n            return Registry::getLang()->formatCurrency($oPrice->getNettoPrice());\n        }\n    }\n\n    /**\n     * Returns true if parent is not buyable\n     *\n     * @return bool\n     */\n    public function isParentNotBuyable()\n    {\n        return $this->_blNotBuyableParent;\n    }\n\n    /**\n     * Returns true if article is not buyable\n     *\n     * @return bool\n     */\n    public function isNotBuyable()\n    {\n        return $this->_blNotBuyable;\n    }\n\n    /**\n     * Sets product state - buyable or not\n     *\n     * @param bool $blBuyable state - buyable or not (default false)\n     */\n    public function setBuyableState($blBuyable = false)\n    {\n        $this->_blNotBuyable = !$blBuyable;\n    }\n\n    /**\n     * Sets selectlists of current product\n     *\n     * @param array $aSelList selectlist\n     */\n    public function setSelectlist($aSelList)\n    {\n        $this->_aDispSelList = $aSelList;\n    }\n\n    public function getMedia(int $position): ProductMediaView\n    {\n        return ContainerFacade::get(ProductMediaViewServiceInterface::class)\n            ->getByPosition(Id::fromString($this->getId()), $position);\n    }\n\n    public function getIcon(): ProductMediaView\n    {\n        return ContainerFacade::get(ProductMediaViewServiceInterface::class)->getByRole(\n            Id::fromString($this->getId()),\n            ProductMediaRole::from(ProductMediaRole::ICON)\n        );\n    }\n\n    public function getThumbnail(): ProductMediaView\n    {\n        return ContainerFacade::get(ProductMediaViewServiceInterface::class)->getByRole(\n            Id::fromString($this->getId()),\n            ProductMediaRole::from(ProductMediaRole::THUMBNAIL)\n        );\n    }\n\n\n    /**\n     * apply article and article use\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Price $oPrice target price\n     */\n    public function applyVats(Price $oPrice)\n    {\n        $this->applyVAT($oPrice, $this->getArticleVat());\n    }\n\n    /**\n     * Applies discounts which should be applied in general case (for 0 amount)\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Price $oPrice Price object\n     */\n    public function applyDiscountsForVariant($oPrice)\n    {\n        // apply discounts\n        if (!$this->skipDiscounts()) {\n            $oDiscountList = Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\DiscountList::class);\n            $aDiscounts = $oDiscountList->getArticleDiscounts($this, $this->getArticleUser());\n\n            reset($aDiscounts);\n            foreach ($aDiscounts as $oDiscount) {\n                $oPrice->setDiscount($oDiscount->getAddSum(), $oDiscount->getAddSumType());\n            }\n            $oPrice->calculateDiscount();\n        }\n    }\n\n    /**\n     * Get parent article\n     *\n     * @return Article\n     */\n    public function getParentArticle()\n    {\n        if ($this->oxarticles__oxparentid && ($sParentId = $this->oxarticles__oxparentid->value)) {\n            $sIndex = $sParentId . \"_\" . $this->getLanguage();\n            if (!isset(self::$_aLoadedParents[$sIndex])) {\n                self::$_aLoadedParents[$sIndex] = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n                self::$_aLoadedParents[$sIndex]->_blLoadPrice = false;\n                self::$_aLoadedParents[$sIndex]->_blLoadVariants = false;\n\n                if (!self::$_aLoadedParents[$sIndex]->loadInLang($this->getLanguage(), $sParentId)) {\n                    //return false in case parent product failed to load\n                    self::$_aLoadedParents[$sIndex] = false;\n                }\n            }\n\n            return self::$_aLoadedParents[$sIndex];\n        }\n    }\n\n    /**\n     * Updates article variants oxremindactive field, as variants inherit this setting from parent\n     */\n    public function updateVariantsRemind()\n    {\n        // check if it is parent article\n        if (!$this->isVariant() && $this->hasAnyVariant()) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $sUpdate = \"update oxarticles\n                        set oxremindactive = :oxremindactive\n                        where oxparentid = :oxparentid and\n                              oxshopid = :oxshopid\";\n            $oDb->execute($sUpdate, [\n                'oxremindactive' => $this->oxarticles__oxremindactive->value,\n                'oxparentid' => $this->getId(),\n                'oxshopid' => $this->getShopId()\n            ]);\n        }\n    }\n\n    /**\n     * Returns product id (oxid)\n     * (required for interface oxIArticle)\n     *\n     * @return string\n     */\n    public function getProductId()\n    {\n        return $this->getId();\n    }\n\n    /**\n     * Returns product parent id (oxparentid)\n     *\n     * @return string\n     */\n    public function getParentId()\n    {\n        return $this->oxarticles__oxparentid instanceof Field ? $this->oxarticles__oxparentid->value : '';\n    }\n\n    /**\n     * Returns false if object is not derived from oxorderarticle class\n     *\n     * @return bool\n     */\n    public function isOrderArticle()\n    {\n        return false;\n    }\n\n    /**\n     * Returns TRUE if product is variant, and false if not\n     *\n     * @return bool\n     */\n    public function isVariant(): bool\n    {\n        $isVariant = false;\n        if (isset($this->oxarticles__oxparentid) && false !== $this->oxarticles__oxparentid) {\n            $isVariant = (bool) $this->oxarticles__oxparentid->value;\n        }\n\n        return $isVariant;\n    }\n\n    /**\n     * Returns TRUE if product is multidimensional variant, and false if not\n     *\n     * @return bool\n     */\n    public function isMdVariant()\n    {\n        $oMdVariant = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\VariantHandler::class);\n\n        return $oMdVariant->isMdVariant($this);\n    }\n\n    /**\n     * get Sql for loading price categories which include this article\n     *\n     * @param string $sFields fields to load from oxCategories\n     *\n     * @return string\n     */\n    public function getSqlForPriceCategories($sFields = '')\n    {\n        if (!$sFields) {\n            $sFields = 'oxid';\n        }\n        $sSelectWhere = \"select $sFields from \" . $this->getObjectViewName('oxcategories') . \" where\";\n        $sQuotedPrice = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quote(\n            $this->getFieldData('oxprice') ?? ''\n        );\n\n        return \"$sSelectWhere oxpricefrom != 0 and oxpriceto != 0\"\n                . \" and oxpricefrom <= $sQuotedPrice and oxpriceto >= $sQuotedPrice\"\n               . \" union $sSelectWhere oxpricefrom != 0 and oxpriceto = 0 and oxpricefrom <= $sQuotedPrice\"\n               . \" union $sSelectWhere oxpricefrom = 0 and oxpriceto != 0 and oxpriceto >= $sQuotedPrice\";\n    }\n\n    /**\n     * Checks if article is assigned to price category $sCatNID.\n     *\n     * @param string $categoryPriceId Price category ID\n     *\n     * @return bool\n     */\n    public function inPriceCategory($categoryPriceId)\n    {\n        return (bool) $this->fetchFirstInPriceCategory($categoryPriceId);\n    }\n\n    /**\n     * Fetch the article corresponding to this object in the price category with the given id.\n     *\n     * @param string $categoryPriceId The id of the category we want to check, if this article is in.\n     *\n     * @return string One, if the given article is in the given price category, else empty string.\n     */\n    protected function fetchFirstInPriceCategory($categoryPriceId)\n    {\n        $database = $this->getDatabase();\n\n        $query = $this->createFetchFirstInPriceCategorySql($categoryPriceId);\n\n        $result = $database->getOne($query);\n\n        return $result;\n    }\n\n    /**\n     * Create the sql for the fetchFirstInPriceCategory method.\n     *\n     * @param string $categoryPriceId The price category id.\n     *\n     * @return string The wished sql.\n     */\n    protected function createFetchFirstInPriceCategorySql($categoryPriceId)\n    {\n        $database = $this->getDatabase();\n\n        $quotedPrice = $database->quote($this->oxarticles__oxprice->value);\n        $quotedCategoryId = $database->quote($categoryPriceId);\n\n        $query = \"select 1 from \" . $this->getObjectViewName('oxcategories')\n            . \" where oxid=$quotedCategoryId and\"\n            . \"(   (oxpricefrom != 0 and oxpriceto != 0 and oxpricefrom <= $quotedPrice and oxpriceto >= $quotedPrice)\"\n            . \" or (oxpricefrom != 0 and oxpriceto = 0 and oxpricefrom <= $quotedPrice)\"\n            . \" or (oxpricefrom = 0 and oxpriceto != 0 and oxpriceto >= $quotedPrice)\"\n            . \")\";\n\n        return $query;\n    }\n\n    /**\n     * Get the database object.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Database\\Adapter\\DatabaseInterface\n     */\n    protected function getDatabase()\n    {\n        return \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n    }\n\n    /**\n     * Returns multidimensional variant structure\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\MdVariant\n     */\n    public function getMdVariants()\n    {\n        if ($this->_oMdVariants) {\n            return $this->_oMdVariants;\n        }\n\n        $oParentArticle = $this->getParentArticle();\n        if ($oParentArticle) {\n            $oVariants = $oParentArticle->getVariants();\n        } else {\n            $oVariants = $this->getVariants();\n        }\n\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\VariantHandler $oVariantHandler */\n        $oVariantHandler = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\VariantHandler::class);\n        $this->_oMdVariants = $oVariantHandler->buildMdVariants($oVariants, $this->getId());\n\n        return $this->_oMdVariants;\n    }\n\n    /**\n     * Returns first level variants from multidimensional variants list\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\MdVariant\n     */\n    public function getMdSubvariants()\n    {\n        return $this->getMdVariants()->getMdSubvariants();\n    }\n\n    /**\n     * Return article picture file name\n     *\n     * @param string $sFieldName article picture field name\n     * @param int    $iIndex     article picture index\n     *\n     * @return string\n     */\n    public function getPictureFieldValue($sFieldName, $iIndex = null)\n    {\n        if ($sFieldName) {\n            $sFieldName = \"oxarticles__\" . $sFieldName . $iIndex;\n\n            if ($this->$sFieldName && $this->$sFieldName->value) {\n                return $this->$sFieldName->value;\n            }\n        }\n\n        return '';\n    }\n\n\n    /**\n     * @param string $file\n     *\n     * @return string\n     */\n    public function getMasterPicturePath(string $file): string\n    {\n        return Registry::getConfig()->getMasterPicturePath($file);\n    }\n\n    /**\n     * Returns oxarticles__oxunitname value processed by \\OxidEsales\\Eshop\\Core\\Language::translateString()\n     *\n     * @return string\n     */\n    public function getUnitName()\n    {\n        if ($this->oxarticles__oxunitname->value) {\n            return Registry::getLang()->translateString($this->oxarticles__oxunitname->value);\n        }\n    }\n\n    public function getArticleFiles($addFromParent = false)\n    {\n        if ($this->_aArticleFiles === null) {\n            $this->_aArticleFiles = false;\n\n            $filesQuery = \"SELECT * FROM `oxfiles` WHERE `oxartid` = :oxartid\";\n            $filesQueryParameters = ['oxartid' => $this->getId()];\n\n            if (!Registry::getConfig()->getConfigParam('blVariantParentBuyable') && $addFromParent) {\n                $filesQuery .= \" OR `oxartId` = :oxparentid\";\n                $filesQueryParameters['oxparentid'] = $this->oxarticles__oxparentid->value;\n            }\n\n            $articleFiles = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n            $articleFiles->init(\"oxfile\");\n            $articleFiles->selectString($filesQuery, $filesQueryParameters);\n            $this->_aArticleFiles = $articleFiles;\n        }\n\n        return $this->_aArticleFiles;\n    }\n\n    /**\n     * Returns oxarticles__oxisdownloadable value\n     *\n     * @return bool\n     */\n    public function isDownloadable()\n    {\n        return $this->oxarticles__oxisdownloadable->value;\n    }\n\n    /**\n     * Checks if articles has amount price\n     *\n     * @return bool\n     */\n    public function hasAmountPrice()\n    {\n        if (self::$_blHasAmountPrice === null) {\n            self::$_blHasAmountPrice = false;\n\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $sQ = \"SELECT 1 FROM `oxprice2article` LIMIT 1\";\n\n            if ($oDb->getOne($sQ)) {\n                self::$_blHasAmountPrice = true;\n            }\n        }\n\n        return self::$_blHasAmountPrice;\n    }\n\n    /**\n     * Loads and returns variants list.\n     *\n     * @param bool      $loadSimpleVariants    if parameter $blSimple - list will be filled with oxSimpleVariant\n     *                                         objects, else - oxArticle\n     * @param bool      $blRemoveNotOrderables if true, removes from list not orderable articles, which are out of\n     *                                         stock [optional]\n     * @param bool|null $forceCoreTableUsage   if true forces core table use, default is false [optional]\n     *\n     * @return array|\\OxidEsales\\Eshop\\Application\\Model\\SimpleVariantList|\\OxidEsales\\Eshop\\Application\\Model\\ArticleList\n     */\n    protected function loadVariantList($loadSimpleVariants, $blRemoveNotOrderables = true, $forceCoreTableUsage = null)\n    {\n        $variants = [];\n        if (($articleId = $this->getId())) {\n            //do not load me as a parent later\n            self::$_aLoadedParents[$articleId . \"_\" . $this->getLanguage()] = $this;\n\n            $config = Registry::getConfig();\n\n            if (\n                !$this->_blLoadVariants ||\n                (!$this->isAdmin() && !$config->getConfigParam('blLoadVariants')) ||\n                (!$this->isAdmin() && !$this->oxarticles__oxvarcount->value)\n            ) {\n                return $variants;\n            }\n\n            // cache\n            $cacheKey = $loadSimpleVariants ? \"simple\" : \"full\";\n            if ($blRemoveNotOrderables) {\n                if (isset($this->_aVariants[$cacheKey])) {\n                    return $this->_aVariants[$cacheKey];\n                }\n                $this->_aVariants[$cacheKey] = &$variants;\n            } elseif (!$blRemoveNotOrderables) {\n                if (isset($this->_aVariantsWithNotOrderables[$cacheKey])) {\n                    return $this->_aVariantsWithNotOrderables[$cacheKey];\n                }\n                $this->_aVariantsWithNotOrderables[$cacheKey] = &$variants;\n            }\n\n            if (($this->_blHasVariants = $this->hasAnyVariant($forceCoreTableUsage))) {\n                //load simple variants for lists\n                if ($loadSimpleVariants) {\n                    $variants = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\SimpleVariantList::class);\n                    $variants->setParent($this);\n                } else {\n                    //loading variants\n                    $variants = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n                    $variants->getBaseObject()->modifyCacheKey('_variants');\n                }\n\n                startProfile(\"selectVariants\");\n                $forceCoreTableUsage = (bool) $forceCoreTableUsage;\n\n                $baseObject = $variants->getBaseObject();\n                $this->updateVariantsBaseObject($baseObject, $forceCoreTableUsage);\n\n                $sArticleTable = $this->getViewName($forceCoreTableUsage);\n\n                $query = $this->getLoadVariantsQuery(\n                    $blRemoveNotOrderables,\n                    $forceCoreTableUsage,\n                    $baseObject,\n                    $sArticleTable\n                );\n                $variants->selectString($query);\n\n                //if this is multidimensional variants, make additional processing\n                if ($config->getConfigParam('blUseMultidimensionVariants')) {\n                    $oMdVariants = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\VariantHandler::class);\n                    $this->_blHasMdVariants = $oMdVariants->isMdVariant($variants->current());\n                }\n                stopProfile(\"selectVariants\");\n            }\n\n            //if we have variants then depending on config option the parent may be non buyable\n            if (!$config->getConfigParam('blVariantParentBuyable') && $this->_blHasVariants) {\n                $this->_blNotBuyableParent = true;\n            }\n\n            // If all variants are inactive, the article may be non-buyable (config-dependent)\n            if (\n                !$config->getConfigParam('blVariantParentBuyable')\n                && count($variants) == 0\n                && $this->_blHasVariants\n            ) {\n                $this->_blNotBuyable = true;\n            }\n        }\n\n        return $variants;\n    }\n\n    /**\n     * Selects category IDs from given SQL statement and ID field name\n     *\n     * @param string $query sql statement\n     * @param string $field category ID field name\n     *\n     * @return array\n     */\n    protected function selectCategoryIds($query, $field)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $aResult = $oDb->getAll($query);\n        $aReturn = [];\n\n        foreach ($aResult as $aValue) {\n            $aValue = array_change_key_case($aValue, CASE_LOWER);\n\n            $aReturn[] = $aValue[$field];\n        }\n\n        return $aReturn;\n    }\n\n    /**\n     * Returns query for article categories select\n     *\n     * @param bool $blActCats select categories if all parents are active\n     *\n     * @return string\n     */\n    protected function getCategoryIdsSelect($blActCats = false)\n    {\n        $sO2CView = $this->getObjectViewName('oxobject2category');\n        $sCatView = $this->getObjectViewName('oxcategories');\n\n        $sArticleIdSql = 'oxobject2category.oxobjectid='\n            . \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quote($this->getId());\n        if ($this->getParentId()) {\n            $sArticleIdSql = '(' . $sArticleIdSql . ' or oxobject2category.oxobjectid='\n                . \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quote($this->getParentId()) . ')';\n        }\n        $sActiveCategorySql = $blActCats ? $this->getActiveCategorySelectSnippet() : '';\n\n        $sSelect = \"select\n                        oxobject2category.oxcatnid as oxcatnid\n                     from $sO2CView as oxobject2category\n                        left join $sCatView as oxcategories on oxcategories.oxid = oxobject2category.oxcatnid\n                    where $sArticleIdSql and oxcategories.oxid is not null\n                    and oxcategories.oxactive = 1 $sActiveCategorySql\n                    order by oxobject2category.oxtime\";\n\n        return $sSelect;\n    }\n\n    /**\n     * Returns active category select snippet\n     *\n     * @return string\n     */\n    protected function getActiveCategorySelectSnippet()\n    {\n        $sCatView = $this->getObjectViewName('oxcategories');\n\n        return \"and oxcategories.oxhidden = 0 and (select count(cats.oxid) from $sCatView as cats\"\n            . \" where cats.oxrootid = oxcategories.oxrootid and cats.oxleft < oxcategories.oxleft \"\n            . \"and cats.oxright > oxcategories.oxright and ( cats.oxhidden = 1 or cats.oxactive = 0 ) ) = 0 \";\n    }\n\n    /**\n     * Calculates price of article (adds taxes, currency and discounts).\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Price $oPrice price object\n     * @param double                       $dVat   vat value, optional, if passed, bypasses\n     *                                             \"bl_perfCalcVatOnlyForBasketOrder\" config value\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    protected function calculatePrice($oPrice, $dVat = null)\n    {\n        // apply VAT only if configuration requires it\n        if (isset($dVat) || !Registry::getConfig()->getConfigParam('bl_perfCalcVatOnlyForBasketOrder')) {\n            $this->applyVAT($oPrice, isset($dVat) ? $dVat : $this->getArticleVat());\n        }\n\n        // apply currency\n        $this->applyCurrency($oPrice);\n        // apply discounts\n        if (!$this->skipDiscounts()) {\n            $oDiscountList = Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\DiscountList::class);\n            $aDiscounts = $oDiscountList->getArticleDiscounts($this, $this->getArticleUser());\n\n            reset($aDiscounts);\n            foreach ($aDiscounts as $oDiscount) {\n                $oPrice->setDiscount($oDiscount->getAddSum(), $oDiscount->getAddSumType());\n            }\n            $oPrice->calculateDiscount();\n        }\n\n        return $oPrice;\n    }\n\n    /**\n     * Checks if parent has ANY variant assigned\n     *\n     * @param bool $blForceCoreTable force core table usage\n     *\n     * @return bool\n     */\n    protected function hasAnyVariant($blForceCoreTable = null)\n    {\n        if (($sId = $this->getId())) {\n            if ($this->oxarticles__oxshopid->value == Registry::getConfig()->getShopId()) {\n                return (bool) $this->oxarticles__oxvarcount->value;\n            }\n            $sArticleTable = $this->getViewName($blForceCoreTable);\n\n            $db = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            return (bool)$db->getOne(\"select 1 from $sArticleTable where oxparentid = :oxparentid\", [\n                'oxparentid' => $sId\n            ]);\n        }\n\n        return false;\n    }\n\n    /**\n     * Check if stock status has changed since loading the article\n     *\n     * @return bool\n     */\n    protected function isStockStatusChanged()\n    {\n        return $this->_iStockStatus != $this->_iStockStatusOnLoad;\n    }\n\n    /**\n     * Check if visibility has changed since loading the article\n     *\n     * @return bool\n     */\n    protected function isVisibilityChanged()\n    {\n        return $this->isStockStatusChanged() && ($this->_iStockStatus == -1 || $this->_iStockStatusOnLoad == -1);\n    }\n\n    /**\n     * inserts article long description to artextends table\n     *\n     * @return null\n     */\n    protected function saveArtLongDesc()\n    {\n        if (in_array(\"oxlongdesc\", $this->_aSkipSaveFields)) {\n            return;\n        }\n\n        if ($this->_blEmployMultilanguage) {\n            $sValue = $this->getLongDescription()->getRawValue();\n            if ($sValue !== null) {\n                $oArtExt = oxNew(MultiLanguageModel::class);\n                $oArtExt->init('oxartextends');\n                $oArtExt->setLanguage((int) $this->getLanguage());\n                if (!$oArtExt->load($this->getId())) {\n                    $oArtExt->setId($this->getId());\n                }\n                $oArtExt->oxartextends__oxlongdesc = new Field($sValue, Field::T_RAW);\n                $oArtExt->save();\n            }\n        } else {\n            $oArtExt = oxNew(MultiLanguageModel::class);\n            $oArtExt->setEnableMultilang(false);\n            $oArtExt->init('oxartextends');\n            $aObjFields = $oArtExt->getAllFields(true);\n            if (!$oArtExt->load($this->getId())) {\n                $oArtExt->setId($this->getId());\n            }\n\n            foreach ($aObjFields as $sKey => $sValue) {\n                if (preg_match('/^oxlongdesc(_(\\d{1,2}))?$/', $sKey)) {\n                    $sField = $this->getFieldLongName($sKey);\n\n                    if (isset($this->$sField)) {\n                        $sLongDesc = null;\n                        if ($this->$sField instanceof Field) {\n                            $sLongDesc = $this->$sField->getRawValue();\n                        } elseif (is_object($this->$sField)) {\n                            $sLongDesc = $this->$sField->value;\n                        }\n                        if (isset($sLongDesc)) {\n                            $sAEField = $oArtExt->getFieldLongName($sKey);\n                            $oArtExt->$sAEField = new Field($sLongDesc, Field::T_RAW);\n                        }\n                    }\n                }\n            }\n            $oArtExt->save();\n        }\n    }\n\n    /**\n     * Removes object data fields (oxarticles__oxtimestamp, oxarticles__oxparentid, oxarticles__oxinsert).\n     */\n    protected function skipSaveFields()\n    {\n        $this->_aSkipSaveFields = [];\n\n        $this->_aSkipSaveFields[] = 'oxtimestamp';\n        // $this->_aSkipSaveFields[] = 'oxlongdesc';\n        $this->_aSkipSaveFields[] = 'oxinsert';\n        $this->addSkippedSaveFieldsForMapping();\n\n        if (\n            !$this->_blAllowEmptyParentId\n            && (\n                !isset($this->oxarticles__oxparentid->value)\n                || $this->oxarticles__oxparentid->value == ''\n            )\n        ) {\n            $this->_aSkipSaveFields[] = 'oxparentid';\n        }\n    }\n\n    /**\n     * Merges two discount arrays. If there are two the same\n     * discounts, discount values will be added.\n     *\n     * @param array $aDiscounts     Discount array\n     * @param array $aItemDiscounts Discount array\n     *\n     * @return array $aDiscounts\n     */\n    protected function mergeDiscounts($aDiscounts, $aItemDiscounts)\n    {\n        foreach ($aItemDiscounts as $sKey => $oDiscount) {\n            // add prices of the same discounts\n            if (array_key_exists($sKey, $aDiscounts)) {\n                $aDiscounts[$sKey]->dDiscount += $oDiscount->dDiscount;\n            } else {\n                $aDiscounts[$sKey] = $oDiscount;\n            }\n        }\n\n        return $aDiscounts;\n    }\n\n    /**\n     * get user Group A, B or C price, returns db price if user is not in groups\n     *\n     * @return double\n     */\n    protected function getGroupPrice()\n    {\n        $sPriceSufix = $this->getUserPriceSufix();\n        $sVarName = \"oxarticles__oxprice{$sPriceSufix}\";\n        $dPrice = $this->$sVarName->value;\n\n        // #1437/1436C - added config option, and check for zero A,B,C price values\n        if (Registry::getConfig()->getConfigParam('blOverrideZeroABCPrices') && (float) $dPrice == 0) {\n            $dPrice = $this->oxarticles__oxprice->value;\n        }\n\n        return $dPrice;\n    }\n\n    /**\n     * Modifies article price depending on given amount.\n     * Takes data from oxprice2article table.\n     *\n     * @param int $amount Basket amount\n     *\n     * @return double\n     */\n    protected function getAmountPrice($amount = 1)\n    {\n        startProfile(\"_getAmountPrice\");\n\n        $dPrice = $this->getGroupPrice();\n        $oAmtPrices = $this->buildAmountPriceList();\n        foreach ($oAmtPrices as $oAmPrice) {\n            if (\n                $oAmPrice->oxprice2article__oxamount->value <= $amount\n                && $amount <= $oAmPrice->oxprice2article__oxamountto->value\n                && $dPrice > $oAmPrice->oxprice2article__oxaddabs->value\n            ) {\n                $dPrice = $oAmPrice->oxprice2article__oxaddabs->value;\n            }\n        }\n\n        stopProfile(\"_getAmountPrice\");\n\n        return $dPrice;\n    }\n\n    /**\n     * Modifies article price according to selected select list value\n     *\n     * @param double $dPrice      Modifiable price\n     * @param array  $aChosenList Selection list array\n     *\n     * @return double\n     */\n    protected function modifySelectListPrice($dPrice, $aChosenList = null)\n    {\n        $myConfig = Registry::getConfig();\n        // #690\n        if (\n            $myConfig->getConfigParam('bl_perfLoadSelectLists')\n            && $myConfig->getConfigParam('bl_perfUseSelectlistPrice')\n        ) {\n            $aSelLists = $this->getSelectLists();\n\n            foreach ($aSelLists as $key => $aSel) {\n                if (isset($aChosenList[$key]) && isset($aSel[$aChosenList[$key]])) {\n                    $oSel = $aSel[$aChosenList[$key]];\n                    if ($oSel->priceUnit == 'abs') {\n                        $dPrice += $oSel->price;\n                    } elseif ($oSel->priceUnit == '%') {\n                        $dPrice += Price::percent($dPrice, $oSel->price);\n                    }\n                }\n            }\n        }\n\n        return $dPrice;\n    }\n\n    /**\n     * Fills amount price list object and sets amount price for article object\n     *\n     * @param array $aAmPriceList Amount price list\n     *\n     * @return array\n     */\n    protected function fillAmountPriceList($aAmPriceList)\n    {\n        $oLang = Registry::getLang();\n\n        // trying to find lowest price value\n        foreach ($aAmPriceList as $sId => $oItem) {\n            /** @var \\OxidEsales\\Eshop\\Core\\Price $oItemPrice */\n            $oItemPrice = $this->getPriceObject();\n            if ($oItem->oxprice2article__oxaddabs->value) {\n                $dBasePrice = $oItem->oxprice2article__oxaddabs->value;\n                $dBasePrice = $this->prepareModifiedPrice($dBasePrice);\n\n                $oItemPrice->setPrice($dBasePrice);\n                $this->calculatePrice($oItemPrice);\n            } else {\n                $dBasePrice = $this->getGroupPrice();\n\n                $dBasePrice = $this->prepareModifiedPrice($dBasePrice);\n\n                $oItemPrice->setPrice($dBasePrice);\n                $oItemPrice->subtractPercent($oItem->oxprice2article__oxaddperc->value);\n            }\n\n            $aAmPriceList[$sId]->fbrutprice = $oLang->formatCurrency($oItemPrice->getBruttoPrice());\n            $aAmPriceList[$sId]->fnetprice = $oLang->formatCurrency($oItemPrice->getNettoPrice());\n\n            if ($quantity = $this->getUnitQuantity()) {\n                $aAmPriceList[$sId]->fbrutamountprice = $oLang->formatCurrency(\n                    $oItemPrice->getBruttoPrice() / $quantity\n                );\n                $aAmPriceList[$sId]->fnetamountprice = $oLang->formatCurrency($oItemPrice->getNettoPrice() / $quantity);\n            }\n        }\n\n        return $aAmPriceList;\n    }\n\n    /**\n     * Collects and returns active/all variant ids of article.\n     *\n     * @param bool $blActiveVariants Parameter to load only active variants.\n     *\n     * @return array\n     */\n    public function getVariantIds($activeVariants = true)\n    {\n        $sId = $this->getId();\n        if (!$sId) {\n            return [];\n        }\n\n        $activeSqlSnippet = \"\";\n        if ($activeVariants) {\n            $activeSqlSnippet = \" and \" . $this->getSqlActiveSnippet(true);\n        }\n        $variantsQuery = sprintf(\n            'select oxid from %s where oxparentid = :oxparentid %s order by oxsort',\n            $this->getViewName(true),\n            $activeSqlSnippet\n        );\n        return DatabaseProvider::getDb()->getCol(\n            $variantsQuery,\n            [\n                'oxparentid' => $sId\n            ]\n        );\n    }\n\n    /**\n     * retrieve article VAT (cached)\n     *\n     * @return double\n     */\n    public function getArticleVat()\n    {\n        if (!isset($this->_dArticleVat)) {\n            $this->_dArticleVat = Registry::get(\n                \\OxidEsales\\Eshop\\Application\\Model\\VatSelector::class\n            )->getArticleVat($this);\n        }\n\n        return $this->_dArticleVat;\n    }\n\n    public function hasProductValidTimeRange(): bool\n    {\n        return !Registry::getUtilsDate()->isEmptyDate($this->oxarticles__oxactivefrom->value)\n            || !Registry::getUtilsDate()->isEmptyDate($this->oxarticles__oxactiveto->value);\n    }\n\n    public function isProductAlwaysActive(): bool\n    {\n        return !empty($this->oxarticles__oxactive->value);\n    }\n\n    /**\n     * Applies VAT to article\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Price $oPrice Price object\n     * @param double                       $dVat   VAT percent\n     */\n    protected function applyVAT(Price $oPrice, $dVat)\n    {\n        startProfile(__FUNCTION__);\n        $oPrice->setVAT($dVat);\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\VatSelector $oVatSelector */\n        $oVatSelector = Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\VatSelector::class);\n        if (($dVat = $oVatSelector->getArticleUserVat($this)) !== false) {\n            $oPrice->setUserVat($dVat);\n        }\n        stopProfile(__FUNCTION__);\n    }\n\n    /**\n     * Applies currency factor\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Price $oPrice Price object\n     * @param object                       $oCur   Currency object\n     */\n    protected function applyCurrency(Price $oPrice, $oCur = null)\n    {\n        if (!$oCur) {\n            $oCur = Registry::getConfig()->getActShopCurrencyObject();\n        }\n\n        $oPrice->multiply($oCur->rate);\n    }\n\n    /**\n     * gets attribs string\n     *\n     * @param string $sAttributeSql Attribute selection snippet\n     * @param int    $iCnt          The number of selected attributes\n     */\n    protected function getAttribsString(&$sAttributeSql, &$iCnt)\n    {\n        // we do not use lists here as we don't need this overhead right now\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sSelect = 'select oxattrid from oxobject2attribute\n            where oxobject2attribute.oxobjectid = :oxobjectid';\n        if ($this->getParentId()) {\n            $sSelect .= ' OR oxobject2attribute.oxobjectid = :oxparentid';\n        }\n        $sAttributeSql = '';\n        $aAttributeIds = $oDb->getCol($sSelect, [\n            'oxobjectid' => $this->getId(),\n            'oxparentid' => $this->getParentId()\n        ]);\n        if (is_array($aAttributeIds) && count($aAttributeIds)) {\n            $aAttributeIds = array_unique($aAttributeIds);\n            $iCnt = count($aAttributeIds);\n            $sAttributeSql .= 't1.oxattrid IN ( ' . implode(',', $oDb->quoteArray($aAttributeIds)) . ') ';\n        }\n    }\n\n    /**\n     * Gets similar list.\n     *\n     * @param string $sAttributeSql Attribute selection snippet\n     * @param int    $iCnt          Similar list article count\n     *\n     * @return array\n     */\n    protected function getSimList($sAttributeSql, $iCnt)\n    {\n        // #523A\n        $iAttrPercent = Registry::getConfig()->getConfigParam('iAttributesPercent') / 100;\n        // 70% same attributes\n        if (!$iAttrPercent || $iAttrPercent < 0 || $iAttrPercent > 1) {\n            $iAttrPercent = 0.70;\n        }\n        // #1137V iAttributesPercent = 100 doesn't work\n        $iHitMin = ceil($iCnt * $iAttrPercent);\n\n        $aExcludeIds = [];\n        $aExcludeIds[] = $this->getId();\n        if ($this->getParentId()) {\n            $aExcludeIds[] = $this->getParentId();\n        }\n\n        // we do not use lists here as we don't need this overhead right now\n        $sSelect = \"select oxobjectid from oxobject2attribute as t1 where ( $sAttributeSql ) and t1.oxobjectid NOT IN (\"\n            . implode(', ', \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aExcludeIds))\n            . \") group by t1.oxobjectid having count(*) >= :minhit LIMIT 0, 20\";\n\n        return \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->getCol($sSelect, [\n            'minhit' => $iHitMin\n        ]);\n    }\n\n    /**\n     * Generates search string for similar list.\n     *\n     * @param string $sArticleTable Article table name\n     * @param array  $aList         A list of original articles\n     *\n     * @return string\n     */\n    protected function generateSimListSearchStr($sArticleTable, $aList)\n    {\n        $sFieldList = $this->getSelectFields();\n        $aList = array_slice($aList, 0, Registry::getConfig()->getConfigParam('iNrofSimilarArticles'));\n\n        $sSearch = \"select $sFieldList from $sArticleTable where \" . $this->getSqlActiveSnippet()\n            . \"  and $sArticleTable.oxissearch = 1 and $sArticleTable.oxid in ( \";\n\n        $sSearch .= implode(',', \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aList)) . ')';\n\n        // #524A -- randomizing articles in attribute list\n        $sSearch .= ' order by rand() ';\n\n        return $sSearch;\n    }\n\n    /**\n     * Generates SearchString for getCategory()\n     *\n     * @param string $sOXID            Article ID\n     * @param bool   $blSearchPriceCat Whether to perform the search within price categories\n     *\n     * @return string\n     */\n    protected function generateSearchStr($sOXID, $blSearchPriceCat = false)\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sCatView = $tableViewNameGenerator->getViewName('oxcategories', $this->getLanguage());\n        $sO2CView = $tableViewNameGenerator->getViewName('oxobject2category');\n\n        // we do not use lists here as we don't need this overhead right now\n        if (!$blSearchPriceCat) {\n            return \"select {$sCatView}.* from {$sO2CView} as oxobject2category left join {$sCatView} on\n                  {$sCatView}.oxid = oxobject2category.oxcatnid where oxobject2category.oxobjectid=\"\n                . \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quote($sOXID)\n                . \" and {$sCatView}.oxid is not null \";\n        }\n        return \"select {$sCatView}.* from {$sCatView} where\n                      '{$this->oxarticles__oxprice->value}' >= {$sCatView}.oxpricefrom and\n                      '{$this->oxarticles__oxprice->value}' <= {$sCatView}.oxpriceto \";\n    }\n\n    /**\n     * Generates SQL select string for getCustomerAlsoBoughtThisProduct\n     *\n     * @return string\n     */\n    protected function generateSearchStrForCustomerBought()\n    {\n        $sArtTable = $this->getViewName();\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sOrderArtTable = $tableViewNameGenerator->getViewName('oxorderarticles');\n\n        // fetching filter params\n        $articlesIn = \" '{$this->oxarticles__oxid->value}' \";\n        if ($this->oxarticles__oxparentid->value) {\n            // adding article parent\n            $articlesIn .= \", '{$this->oxarticles__oxparentid->value}' \";\n            $sParentIdForVariants = $this->oxarticles__oxparentid->value;\n        } else {\n            $sParentIdForVariants = $this->getId();\n        }\n\n        $database = DatabaseProvider::getDb();\n\n        $articlesIds = $database->getCol(\n            \"select oxid from {$sArtTable} where oxparentid = :oxparentid and oxid != :oxid \",\n            [\n                'oxparentid' => $sParentIdForVariants,\n                'oxid' => $this->oxarticles__oxid->value\n            ]\n        );\n        foreach ($articlesIds as $articlesId) {\n            $articlesIn .= \", \" . $database->quote($articlesId) . \" \";\n        }\n\n        $iLimit = (int) Registry::getConfig()->getConfigParam('iNrofCustomerWhoArticles');\n        $iLimit = $iLimit ? ($iLimit * 10) : 50;\n\n        return \"select distinct {$sArtTable}.* from (\"\n                   . \" select d.oxorderid as suborderid from {$sOrderArtTable} as d use index\"\n                   . \" ( oxartid ) where d.oxartid in ( {$articlesIn} ) limit {$iLimit}\"\n               . \" ) as suborder\"\n               . \" left join {$sOrderArtTable} force index ( oxorderid )\"\n                        . \" on suborder.suborderid = {$sOrderArtTable}.oxorderid\"\n               . \" left join {$sArtTable} on {$sArtTable}.oxid = {$sOrderArtTable}.oxartid\"\n               . \" where {$sArtTable}.oxid not in ( {$articlesIn} )\"\n                . \" and ( {$sArtTable}.oxissearch = 1 or {$sArtTable}.oxparentid <> '' )\"\n                . \" and \" . $this->getSqlActiveSnippet();\n    }\n\n    /**\n     * Generates select string for isAssignedToCategory()\n     *\n     * @param string $sOXID        Article ID\n     * @param string $sCatId       Category ID\n     * @param bool   $dPriceFromTo Article price for price categories\n     *\n     * @return string\n     */\n    protected function generateSelectCatStr($sOXID, $sCatId, $dPriceFromTo = false)\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sCategoryView = $tableViewNameGenerator->getViewName('oxcategories');\n        $sO2CView = $tableViewNameGenerator->getViewName('oxobject2category');\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sOXID = $oDb->quote($sOXID);\n        $sCatId = $oDb->quote($sCatId);\n\n        if (!$dPriceFromTo) {\n            $sSelect = \"select oxobject2category.oxcatnid from $sO2CView as oxobject2category \";\n            $sSelect .= \"left join $sCategoryView as oxcategories on oxcategories.oxid = oxobject2category.oxcatnid \";\n            $sSelect .= \"where oxobject2category.oxcatnid=$sCatId and oxobject2category.oxobjectid=$sOXID \";\n            $sSelect .= \"and oxcategories.oxactive = 1 order by oxobject2category.oxtime \";\n        } else {\n            $dPriceFromTo = $oDb->quote($dPriceFromTo);\n            $sSelect = \"select oxcategories.oxid from $sCategoryView as oxcategories where \";\n            $sSelect .= \"oxcategories.oxid=$sCatId and $dPriceFromTo >= oxcategories.oxpricefrom and \";\n            $sSelect .= \"$dPriceFromTo <= oxcategories.oxpriceto \";\n        }\n\n        return $sSelect;\n    }\n\n    /**\n     * Collecting assigned to article amount-price list.\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\AmountPriceList\n     */\n    protected function buildAmountPriceList()\n    {\n        if ($this->getAmountPriceList() === null) {\n            /** @var \\OxidEsales\\Eshop\\Application\\Model\\AmountPriceList $oAmPriceList */\n            $oAmPriceList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\AmountPriceList::class);\n            $this->setAmountPriceList($oAmPriceList);\n\n            if (!$this->skipDiscounts()) {\n                //collecting assigned to article amount-price list\n                $oAmPriceList->load($this);\n\n                // prepare abs prices if currently having percentages\n                $oBasePrice = $this->getGroupPrice();\n                foreach ($oAmPriceList as $oAmPrice) {\n                    if ($oAmPrice->oxprice2article__oxaddperc->value) {\n                        $oAmPrice->oxprice2article__oxaddabs = new Field(\n                            Price::percent($oBasePrice, 100 - $oAmPrice->oxprice2article__oxaddperc->value),\n                            Field::T_RAW\n                        );\n                    }\n                }\n            }\n\n            $this->setAmountPriceList($oAmPriceList);\n        }\n\n        return $this->_oAmountPriceList;\n    }\n\n    /**\n     * Detects if field is empty.\n     *\n     * @param string $sFieldName Field name\n     *\n     * @return bool\n     */\n    protected function isFieldEmpty($sFieldName)\n    {\n        $mValue = $this->$sFieldName->value;\n\n        if (is_null($mValue)) {\n            return true;\n        }\n\n        if ($mValue === '') {\n            return true;\n        }\n\n        // certain fields with zero value treat as empty\n        $aZeroValueFields = ['oxarticles__oxprice', 'oxarticles__oxvat', 'oxarticles__oxunitquantity'];\n\n        if (!$mValue && in_array($sFieldName, $aZeroValueFields)) {\n            return true;\n        }\n\n\n        if (!strcmp($mValue, '0000-00-00 00:00:00') || !strcmp($mValue, '0000-00-00')) {\n            return true;\n        }\n\n        $sFieldName = strtolower($sFieldName);\n\n        if (\n            $sFieldName == 'oxarticles__oxicon' && (strpos($mValue, \"nopic_ico.jpg\") !== false || strpos(\n                $mValue,\n                \"nopic.jpg\"\n            ) !== false)\n        ) {\n            return true;\n        }\n\n        if (\n            strpos($mValue, \"nopic.jpg\") !== false && ($sFieldName == 'oxarticles__oxthumb' || substr(\n                $sFieldName,\n                0,\n                17\n            ) == 'oxarticles__oxpic' || substr($sFieldName, 0, 18) == 'oxarticles__oxzoom')\n        ) {\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Assigns parent field values to article\n     *\n     * @param string $sFieldName field name\n     *\n     * @return null\n     */\n    protected function assignParentFieldValue($sFieldName)\n    {\n        if (!($oParentArticle = $this->getParentArticle())) {\n            return;\n        }\n\n        $sCopyFieldName = $this->getFieldLongName($sFieldName);\n\n        // assigning only these which parent article has\n        if ($oParentArticle->$sCopyFieldName != null) {\n            // only overwrite database values\n            if (substr($sCopyFieldName, 0, 12) != 'oxarticles__') {\n                return;\n            }\n\n            //do not copy certain fields\n            if (in_array($sCopyFieldName, $this->_aNonCopyParentFields)) {\n                return;\n            }\n\n            //skip picture parent value assignment in case master image is set for variant\n            if (\n                $this->isFieldEmpty($sCopyFieldName)\n                && $this->isImageField($sCopyFieldName)\n                && $this->hasMasterImage(1)\n            ) {\n                return;\n            }\n\n            //COPY THE VALUE\n            if ($this->isFieldEmpty($sCopyFieldName)) {\n                $this->$sCopyFieldName = clone $oParentArticle->$sCopyFieldName;\n            }\n        }\n    }\n\n    /**\n     * Detects if field is an image field by field name\n     *\n     * @param string $sFieldName Field name\n     *\n     * @return bool\n     */\n    protected function isImageField($sFieldName)\n    {\n        return (stristr($sFieldName, '_oxthumb') || stristr($sFieldName, '_oxicon') || stristr(\n            $sFieldName,\n            '_oxzoom'\n        ) || stristr($sFieldName, '_oxpic'));\n    }\n\n    /**\n     * Assigns parent field values to article\n     */\n    protected function assignParentFieldValues()\n    {\n        startProfile('articleAssignParentInternal');\n        if ($this->getFieldData('oxparentid')) {\n            // yes, we are in fact a variant\n            if (!$this->isAdmin() || ($this->_blLoadParentData && $this->isAdmin())) {\n                foreach ($this->_aFieldNames as $sFieldName => $sVal) {\n                    $this->assignParentFieldValue($sFieldName);\n                }\n            }\n        }\n        stopProfile('articleAssignParentInternal');\n    }\n\n    /**\n     * if we have variants then depending on config option the parent may be non buyable\n     */\n    protected function assignNotBuyableParent()\n    {\n        if (\n            !Registry::getConfig()->getConfigParam('blVariantParentBuyable') &&\n            (\n                $this->_blHasVariants ||\n                $this->getFieldData('oxvarstock') ||\n                $this->getFieldData('oxvarcount')\n            )\n        ) {\n            $this->_blNotBuyableParent = true;\n        }\n    }\n\n    /**\n     * Assigns stock status to article\n     */\n    protected function assignStock()\n    {\n        $myConfig = Registry::getConfig();\n        // -----------------------------------\n        // stock\n        // -----------------------------------\n\n        // #1125 A. must round (using floor()) value taken from database and cast to int\n        if (!$myConfig->getConfigParam('blAllowUnevenAmounts') && !$this->isAdmin()) {\n            $stock = $this->getFieldData('oxstock') ?? 0;\n            $this->oxarticles__oxstock = new Field((int) floor($stock));\n        }\n        //GREEN light\n        $this->_iStockStatus = 0;\n\n        // if we have flag /*1 or*/ 4 - we show always green light\n        $stockFlag = $this->getFieldData('oxstockflag');\n        if ($myConfig->getConfigParam('blUseStock') && $stockFlag != 4) {\n            //ORANGE light\n            $stock = $this->getAvailableStock();\n\n            if ($stock > 0 && $this->isLowStock()) {\n                $this->_iStockStatus = 1;\n            }\n\n            //RED light\n            if ($stock <= 0) {\n                $this->_iStockStatus = -1;\n            }\n        }\n\n        // stock\n        $stockFlag = $this->getFieldData('oxstockflag');\n        if ($myConfig->getConfigParam('blUseStock') && ($stockFlag == 3 || $stockFlag == 2)) {\n            $iOnStock = $this->oxarticles__oxstock->value;\n            if (Registry::getConfig()->getConfigParam('blPsBasketReservationEnabled')) {\n                $session = Registry::getSession();\n                if ($reservations = $session->getBasketReservations()) {\n                    $iOnStock += $reservations->getReservedAmount($this->getId());\n                }\n            }\n            if ($iOnStock <= 0) {\n                $this->setBuyableState(false);\n            }\n        }\n\n        //exceptional handling for variant parent stock:\n        if ($this->_blNotBuyable && $this->oxarticles__oxvarstock->value) {\n            $this->setBuyableState(true);\n            //but then at least setting notBuaybleParent to true\n            $this->_blNotBuyableParent = true;\n        }\n\n        //special treatment for lists when blVariantParentBuyable config option is set to false\n        //then we just hide \"to basket\" button.\n        //If variants are not loaded in the list and this article has variants and parent is not buyable\n        //then this article is not buyable\n        if (\n            !$myConfig->getConfigParam('blVariantParentBuyable')\n            && !$myConfig->getConfigParam('blLoadVariants')\n            && $this->oxarticles__oxvarstock->value\n        ) {\n            $this->setBuyableState(false);\n        }\n\n        // Set non-buyable if no variants (e.g. not loaded/inactive) and $this is a non-buyable parent\n        if (!$this->_blNotBuyable && $this->_blNotBuyableParent && $this->oxarticles__oxvarcount->value == 0) {\n            $this->setBuyableState(false);\n        }\n    }\n\n    /**\n     * assigns dynimagedir to article\n     */\n    protected function assignDynImageDir()\n    {\n        $myConfig = Registry::getConfig();\n\n        $sThisShop = $this->oxarticles__oxshopid->value;\n\n        $this->_sDynImageDir = $myConfig->getPictureUrl(null, false);\n        $this->dabsimagedir = $myConfig->getPictureDir(false); //$sThisShop\n        $this->nossl_dimagedir = $myConfig->getPictureUrl(null, false, false, null, $sThisShop); //$sThisShop\n        $this->ssl_dimagedir = $myConfig->getPictureUrl(null, false, true, null, $sThisShop); //$sThisShop\n    }\n\n    /**\n     * Adds a flag if article is on comparisonlist.\n     */\n    protected function assignComparisonListFlag()\n    {\n        // #657 add a flag if article is on comparisonlist\n\n        $aItems = Registry::getSession()->getVariable('aFiltcompproducts');\n        if (isset($aItems[$this->getId()])) {\n            $this->_blIsOnComparisonList = true;\n        }\n    }\n\n    /**\n     * Sets article creation date\n     * (\\OxidEsales\\Eshop\\Application\\Model\\Article::oxarticles__oxinsert). Then executes parent method\n     * parent::_insert() and returns insertion status.\n     *\n     * @return bool\n     */\n    protected function insert()\n    {\n        // set oxinsert\n        $sNow = date('Y-m-d H:i:s', Registry::getUtilsDate()->getTime());\n        $this->oxarticles__oxinsert = new Field($sNow);\n        if (!is_object($this->oxarticles__oxsubclass) || $this->oxarticles__oxsubclass->value == '') {\n            $this->oxarticles__oxsubclass = new Field('oxarticle');\n        }\n\n        return parent::insert();\n    }\n\n    /**\n     * Executes \\OxidEsales\\Eshop\\Application\\Model\\Article::_skipSaveFields() and updates article information\n     *\n     * @return bool\n     */\n    protected function update()\n    {\n        $this->setUpdateSeo(true);\n        $this->setUpdateSeoOnFieldChange('oxtitle');\n\n        $this->skipSaveFields();\n\n        return parent::update();\n    }\n\n    /**\n     * Deletes records in database\n     *\n     * @param string $articleId Article ID\n     *\n     * @return int\n     */\n    protected function deleteRecords($articleId)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        //remove other records\n        $sDelete = 'delete from oxobject2article where oxarticlenid = :articleId or oxobjectid = :articleId';\n        $oDb->execute($sDelete, [\n            'articleId' => $articleId\n        ]);\n\n        $sDelete = 'delete from oxobject2attribute where oxobjectid = :articleId';\n        $oDb->execute($sDelete, [\n            'articleId' => $articleId\n        ]);\n\n        $sDelete = 'delete from oxobject2category where oxobjectid = :articleId';\n        $oDb->execute($sDelete, [\n            'articleId' => $articleId\n        ]);\n\n        $sDelete = 'delete from oxobject2selectlist where oxobjectid = :articleId';\n        $oDb->execute($sDelete, [\n            'articleId' => $articleId\n        ]);\n\n        $sDelete = 'delete from oxprice2article where oxartid = :articleId';\n        $oDb->execute($sDelete, [\n            'articleId' => $articleId\n        ]);\n\n        $sDelete = 'delete from oxreviews where oxtype=\"oxarticle\" and oxobjectid = :articleId';\n        $oDb->execute($sDelete, [\n            'articleId' => $articleId\n        ]);\n\n        $sDelete = 'delete from oxratings where oxobjectid = :articleId';\n        $oDb->execute($sDelete, [\n            'articleId' => $articleId\n        ]);\n\n        $sDelete = 'delete from oxaccessoire2article where oxobjectid = :articleId or oxarticlenid = :articleId';\n        $oDb->execute($sDelete, [\n            'articleId' => $articleId\n        ]);\n\n        //#1508C - deleting oxobject2delivery entries added\n        $sDelete = 'delete from oxobject2delivery where oxobjectid = :articleId and oxtype=\\'oxarticles\\' ';\n        $oDb->execute($sDelete, [\n            'articleId' => $articleId\n        ]);\n\n        $sDelete = 'delete from oxartextends where oxid = :articleId';\n        $oDb->execute($sDelete, [\n            'articleId' => $articleId\n        ]);\n\n        //delete the record\n        foreach ($this->getLanguageSetTables(\"oxartextends\") as $sSetTbl) {\n            $oDb->execute(\"delete from $sSetTbl where oxid = :articleId\", [\n                'articleId' => $articleId\n            ]);\n        }\n\n        $sDelete = 'delete from oxactions2article where oxartid = :articleId';\n        $oDb->execute($sDelete, [\n            'articleId' => $articleId\n        ]);\n\n        $sDelete = 'delete from oxobject2list where oxobjectid = :articleId';\n\n        return $oDb->execute($sDelete, [\n            'articleId' => $articleId\n        ]);\n    }\n\n    /**\n     * Deletes variant records\n     *\n     * @param string $sOXID Article ID\n     */\n    protected function deleteVariantRecords($sOXID)\n    {\n        if ($sOXID) {\n            //collect variants to remove recursively\n            $query = 'select oxid from ' . $this->getViewName() . ' where oxparentid = :oxparentid';\n            $products = DatabaseProvider::getDb()->getCol($query, [\n                'oxparentid' => $sOXID\n            ]);\n            $product = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            foreach ($products as $productId) {\n                $product->setId($productId);\n                $product->delete();\n            }\n        }\n    }\n\n    /**\n     * Delete pics\n     */\n    protected function deletePics()\n    {\n        $productMediaDao = ContainerFacade::get(ProductMediaDaoInterface::class);\n        $productId = Id::fromString($this->getId());\n\n        $mediaCollection = $productMediaDao->getAll($productId);\n\n        foreach ($mediaCollection as $productMedia) {\n            $productMediaDao->delete($productMedia->getId());\n        }\n    }\n\n    /**\n     * Resets category and vendor counts. This method is supposed to be called on article change trigger.\n     *\n     * @param string $sOxid           object to reset id ID\n     * @param string $sVendorId       Vendor ID\n     * @param string $sManufacturerId Manufacturer ID\n     */\n    protected function onChangeResetCounts($sOxid, $sVendorId = null, $sManufacturerId = null)\n    {\n        $myUtilsCount = Registry::getUtilsCount();\n\n        if ($sVendorId) {\n            $myUtilsCount->resetVendorArticleCount($sVendorId);\n        }\n\n        if ($sManufacturerId) {\n            $myUtilsCount->resetManufacturerArticleCount($sManufacturerId);\n        }\n\n        $aCategoryIds = $this->getCategoryIds();\n        //also reseting category counts\n        foreach ($aCategoryIds as $sCatId) {\n            $myUtilsCount->resetCatArticleCount($sCatId);\n        }\n    }\n\n    /**\n     * Updates article stock. This method is supposed to be called on article change trigger.\n     *\n     * @param string $parentId product parent id\n     */\n    protected function onChangeUpdateStock($parentId)\n    {\n        if ($parentId) {\n            $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $query = 'SELECT oxstock, oxvendorid, oxmanufacturerid FROM oxarticles WHERE oxid = :oxid';\n            $rs = $database->select($query, [\n                'oxid' => $parentId\n            ]);\n\n            $query = 'SELECT SUM(oxstock) FROM ' . $this->getViewName(true) . '\n                WHERE oxparentid = :oxparentid\n                AND ' . $this->getSqlActiveSnippet(true) . '\n                AND oxstock > 0 ';\n            $stock = (float) $database->getOne($query, [\n                'oxparentid' => $parentId\n            ]);\n\n            $query = 'UPDATE oxarticles SET oxvarstock = :oxvarstock WHERE oxid = :oxid';\n            $database->execute($query, [\n                'oxvarstock' => $stock,\n                'oxid' => $parentId\n            ]);\n\n            //now lets update category counts\n            //first detect stock status change for this article (to or from 0)\n            if ($stock < 0) {\n                $stock = 0;\n            }\n            $oldStock = $rs->fields['oxstock'] ?? null;\n            if ($oldStock < 0) {\n                $oldStock = 0;\n            }\n            if ($this->getFieldData('oxstockflag') == 2 && $oldStock xor $stock) {\n                //means the stock status could be changed (oxstock turns from 0 to 1 or from 1 to 0)\n                // so far we leave it like this but later we could move all count resets to one or two functions\n                $this->onChangeResetCounts(\n                    $parentId,\n                    $rs->fields['oxvendorid'] ?? null,\n                    $rs->fields['oxmanufacturerid'] ?? null\n                );\n            }\n        }\n    }\n\n    /**\n     * Resets article count cache when stock value is zero and article goes offline.\n     *\n     * @param string $sOxid product id\n     */\n    protected function onChangeStockResetCount($sOxid)\n    {\n        $myConfig = Registry::getConfig();\n\n        if (\n            $myConfig->getConfigParam('blUseStock') && $this->oxarticles__oxstockflag->value == 2 &&\n            ($this->oxarticles__oxstock->value + $this->oxarticles__oxvarstock->value) <= 0\n        ) {\n            $this->onChangeResetCounts(\n                $sOxid,\n                $this->oxarticles__oxvendorid->value,\n                $this->oxarticles__oxmanufacturerid->value\n            );\n        }\n    }\n\n    /**\n     * Updates variant count. This method is supposed to be called on article change trigger.\n     *\n     * @param string $parentId Parent ID\n     */\n    protected function onChangeUpdateVarCount($parentId)\n    {\n        if ($parentId) {\n            $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n            $query = \"SELECT COUNT(*) AS varcount FROM oxarticles WHERE oxparentid = :oxparentid\";\n            $varCount = (int) $database->getOne($query, [\n                'oxparentid' => $parentId\n            ]);\n\n            $query = \"UPDATE oxarticles SET oxvarcount = :oxvarcount WHERE oxid = :oxid\";\n            $database->execute($query, [\n                'oxvarcount' => $varCount,\n                'oxid' => $parentId\n            ]);\n        }\n    }\n\n    /**\n     * Updates variant min price. This method is supposed to be called on article change trigger.\n     *\n     * @param string $sParentId Parent ID\n     */\n    protected function setVarMinMaxPrice($sParentId)\n    {\n        if ($sParentId) {\n            $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $sQ = '\n                SELECT\n                    MIN( IF( `oxarticles`.`oxprice` > 0, `oxarticles`.`oxprice`, `p`.`oxprice` ) ) AS `varminprice`,\n                    MAX( IF( `oxarticles`.`oxprice` > 0, `oxarticles`.`oxprice`, `p`.`oxprice` ) ) AS `varmaxprice`\n                FROM ' . $this->getViewName(true) . ' AS `oxarticles`\n                    LEFT JOIN ' . $this->getViewName(true) . ' AS `p`\n                     ON ( `p`.`oxid` = `oxarticles`.`oxparentid` AND `p`.`oxprice` > 0 )\n                WHERE ' . $this->getSqlActiveSnippet(true) . '\n                    AND ( `oxarticles`.`oxparentid` = :oxparentid )';\n            $aPrices = $database->getRow($sQ, [\n                'oxparentid' => $sParentId\n            ]);\n            if (isset($aPrices['varminprice'], $aPrices['varmaxprice'])) {\n                $sQ = '\n                    UPDATE `oxarticles`\n                    SET\n                        `oxvarminprice` = :oxvarminprice,\n                        `oxvarmaxprice` = :oxvarmaxprice\n                    WHERE\n                        `oxid` = :oxid';\n                $params = [\n                    'oxvarminprice' => $aPrices['varminprice'],\n                    'oxvarmaxprice' => $aPrices['varmaxprice'],\n                    'oxid' => $sParentId\n                ];\n            } else {\n                $sQ = '\n                    UPDATE `oxarticles`\n                    SET\n                        `oxvarminprice` = `oxprice`,\n                        `oxvarmaxprice` = `oxprice`\n                    WHERE\n                        `oxid` = :oxid';\n                $params = ['oxid' => $sParentId];\n            }\n            $database->execute($sQ, $params);\n        }\n    }\n\n    /**\n     * Checks if article has uploaded master image for selected picture\n     *\n     * @param int $iIndex master picture index\n     *\n     * @return bool\n     */\n    protected function hasMasterImage($iIndex)\n    {\n        $sPicName = basename($this->{\"oxarticles__oxpic\" . $iIndex}->value);\n\n        if ($sPicName == \"nopic.jpg\" || $sPicName == \"\") {\n            return false;\n        }\n        if (\n            $this->isVariant() &&\n            $this->getParentArticle() &&\n            $this->getParentArticle()->{\"oxarticles__oxpic\" . $iIndex}->value\n                == $this->{\"oxarticles__oxpic\" . $iIndex}->value\n        ) {\n            return false;\n        }\n\n        $sMasterPic = 'product/' . $iIndex . \"/\" . $sPicName;\n\n        if (Registry::getConfig()->getMasterPicturePath($sMasterPic)) {\n            return true;\n        }\n\n        return false;\n    }\n\n\n    /**\n     * Checks and return true if price view mode is netto\n     *\n     * @return bool\n     */\n    protected function isPriceViewModeNetto()\n    {\n        $blResult = (bool) Registry::getConfig()->getConfigParam('blShowNetPrice');\n        $oUser = $this->getArticleUser();\n        if ($oUser) {\n            $blResult = $oUser->isPriceViewModeNetto();\n        }\n\n        return $blResult;\n    }\n\n\n    /**\n     * Depending on view mode prepare oxPrice object\n     *\n     * @param bool $blCalculationModeNetto - if calculation mode netto - true\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    protected function getPriceObject($blCalculationModeNetto = null)\n    {\n        /** @var \\OxidEsales\\Eshop\\Core\\Price $oPrice */\n        $oPrice = oxNew(Price::class);\n\n        if ($blCalculationModeNetto === null) {\n            $blCalculationModeNetto = $this->isPriceViewModeNetto();\n        }\n\n        if ($blCalculationModeNetto) {\n            $oPrice->setNettoPriceMode();\n        } else {\n            $oPrice->setBruttoPriceMode();\n        }\n\n        return $oPrice;\n    }\n\n    /**\n     * Depending on view mode prepare price for viewing\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Price $oPrice price object\n     *\n     * @return double\n     */\n    protected function getPriceForView($oPrice)\n    {\n        if ($this->isPriceViewModeNetto()) {\n            $dPrice = $oPrice->getNettoPrice();\n        } else {\n            $dPrice = $oPrice->getBruttoPrice();\n        }\n\n        return $dPrice;\n    }\n\n\n    /**\n     * Depending on view mode prepare price before calculation\n     *\n     * @param double $dPrice                 - price\n     * @param double $dVat                   - VAT\n     * @param bool   $blCalculationModeNetto - if calculation mode netto - true\n     *\n     * @return double\n     */\n    protected function preparePrice($dPrice, $dVat, $blCalculationModeNetto = null)\n    {\n        if ($blCalculationModeNetto === null) {\n            $blCalculationModeNetto = $this->isPriceViewModeNetto();\n        }\n\n        $oCurrency = Registry::getConfig()->getActShopCurrencyObject();\n\n        $blEnterNetPrice = Registry::getConfig()->getConfigParam('blEnterNetPrice');\n        if ($blCalculationModeNetto && !$blEnterNetPrice) {\n            $dPrice = round(Price::brutto2Netto($dPrice, $dVat), $oCurrency->decimal);\n        } elseif (!$blCalculationModeNetto && $blEnterNetPrice) {\n            $dPrice = round(Price::netto2Brutto($dPrice, $dVat), $oCurrency->decimal);\n        }\n\n        return $dPrice;\n    }\n\n\n    /**\n     * Return price suffix\n     *\n     * @return null\n     */\n    protected function getUserPriceSufix()\n    {\n        $sPriceSuffix = '';\n        $oUser = $this->getArticleUser();\n\n        if ($oUser) {\n            if ($oUser->inGroup('oxidpricea')) {\n                $sPriceSuffix = 'a';\n            } elseif ($oUser->inGroup('oxidpriceb')) {\n                $sPriceSuffix = 'b';\n            } elseif ($oUser->inGroup('oxidpricec')) {\n                $sPriceSuffix = 'c';\n            }\n        }\n\n        return $sPriceSuffix;\n    }\n\n    /**\n     * Return prepared price\n     *\n     * @return null\n     */\n    protected function getRawPrice()\n    {\n        $sPriceSuffix = $this->getUserPriceSufix();\n        if ($sPriceSuffix === '') {\n            $dPrice = $this->oxarticles__oxprice->value;\n        } else {\n            if (Registry::getConfig()->getConfigParam('blOverrideZeroABCPrices')) {\n                $dPrice = ($this->{'oxarticles__oxprice' . $sPriceSuffix}->value != 0)\n                    ? $this->{'oxarticles__oxprice' . $sPriceSuffix}->value\n                    : $this->oxarticles__oxprice->value;\n            } else {\n                $dPrice = $this->{'oxarticles__oxprice' . $sPriceSuffix}->value;\n            }\n        }\n\n        return $dPrice;\n    }\n\n    /**\n     * Return variant min price\n     *\n     * @return null\n     */\n    protected function getVarMinRawPrice()\n    {\n        if ($this->_dVarMinPrice === null) {\n            $dPrice = $this->getShopVarMinPrice();\n\n            if (is_null($dPrice)) {\n                $sPriceSuffix = $this->getUserPriceSufix();\n                if ($sPriceSuffix === '') {\n                    $dPrice = $this->oxarticles__oxvarminprice->value;\n                } else {\n                    $sSql = 'SELECT ';\n                    if (Registry::getConfig()->getConfigParam('blOverrideZeroABCPrices')) {\n                        $sSql .= 'MIN( IF(`oxprice' . $sPriceSuffix . '` = 0, `oxprice`, `oxprice'\n                            . $sPriceSuffix . '`) ) AS `varminprice` ';\n                    } else {\n                        $sSql .= 'MIN(`oxprice' . $sPriceSuffix . '`) AS `varminprice` ';\n                    }\n\n                    $sSql .= ' FROM ' . $this->getViewName(true) . '\n                    WHERE ' . $this->getSqlActiveSnippet(true) . '\n                        AND ( `oxparentid` = :oxparentid )';\n\n                    $dPrice = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->getOne($sSql, [\n                        'oxparentid' => $this->getId()\n                    ]);\n                }\n            }\n\n            $this->_dVarMinPrice = $dPrice;\n        }\n\n        return $this->_dVarMinPrice;\n    }\n\n    /**\n     * Return variant max price\n     *\n     * @return null\n     */\n    protected function getVarMaxPrice()\n    {\n        if ($this->_dVarMaxPrice === null) {\n            $dPrice = $this->getShopVarMaxPrice();\n\n            if (is_null($dPrice)) {\n                $sPriceSuffix = $this->getUserPriceSufix();\n                if ($sPriceSuffix === '') {\n                    $dPrice = $this->oxarticles__oxvarmaxprice->value;\n                } else {\n                    $sSql = 'SELECT ';\n                    if (Registry::getConfig()->getConfigParam('blOverrideZeroABCPrices')) {\n                        $sSql .= 'MAX( IF(`oxprice' . $sPriceSuffix . '` = 0, `oxprice`, `oxprice'\n                            . $sPriceSuffix . '`) ) AS `varmaxprice` ';\n                    } else {\n                        $sSql .= 'MAX(`oxprice' . $sPriceSuffix . '`) AS `varmaxprice` ';\n                    }\n\n                    $sSql .= ' FROM ' . $this->getViewName(true) . '\n                        WHERE ' . $this->getSqlActiveSnippet(true) . '\n                            AND ( `oxparentid` = :oxparentid )';\n\n                    $dPrice = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->getOne($sSql, [\n                        'oxparentid' => $this->getId()\n                    ]);\n                }\n            }\n\n            $this->_dVarMaxPrice = $dPrice;\n        }\n\n        return $this->_dVarMaxPrice;\n    }\n\n    /**\n     * Place to hook to return variant min price if it might be different,\n     * for example for subshops.\n     *\n     * @return double|null\n     */\n    protected function getShopVarMinPrice()\n    {\n        return null;\n    }\n\n    /**\n     * Place to hook to return variant max price if it might be different,\n     * for example for subshops.\n     *\n     * @return double|null\n     */\n    protected function getShopVarMaxPrice()\n    {\n        return null;\n    }\n\n    /**\n     * Get data from db\n     *\n     * @param string $articleId id\n     *\n     * @return array\n     */\n    protected function loadFromDb($articleId)\n    {\n        $sSelect = $this->buildSelectString([$this->getViewName() . \".oxid\" => $articleId]);\n\n        return \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->getRow($sSelect);\n    }\n\n\n    /**\n     * Place to hook and change amount if it should be calculated by different logic,\n     * for example VPE.\n     *\n     * @param double $amount Amount\n     */\n    public function checkForVpe($amount)\n    {\n    }\n\n    /**\n     * Set parent field value to child - variants in DB\n     *\n     * @return bool\n     */\n    protected function updateParentDependFields()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        foreach ($this->getCopyParentFields() as $sField) {\n            $sValue = isset($this->$sField->value) ? $this->$sField->value : 0;\n            $sSqlSets[] = '`' . str_replace('oxarticles__', '', $sField) . '` = ' . $oDb->quote($sValue);\n        }\n\n        $sSql = \"UPDATE `oxarticles` SET \";\n        $sSql .= implode(', ', $sSqlSets) . '';\n        $sSql .= \" WHERE `oxparentid` = :oxparentid\";\n\n        return $oDb->execute($sSql, ['oxparentid' => $this->getId()]);\n    }\n\n    /**\n     * Returns array of fields which should not changed in variants\n     *\n     * @return array\n     */\n    protected function getCopyParentFields()\n    {\n        return $this->_aCopyParentField;\n    }\n\n    /**\n     * Set parent field value to child - variants\n     */\n    protected function assignParentDependFields()\n    {\n        $sParent = $this->getParentArticle();\n        if ($sParent) {\n            foreach ($this->getCopyParentFields() as $sField) {\n                $this->$sField = new Field($sParent->$sField->value);\n            }\n        }\n    }\n\n    /**\n     * Saves values of sorting fields on article load.\n     */\n    protected function saveSortingFieldValuesOnLoad()\n    {\n        $aSortingFields = Registry::getConfig()->getConfigParam('aSortCols');\n        $aSortingFields = !empty($aSortingFields) ? (array) $aSortingFields : [];\n\n        foreach ($aSortingFields as $sField) {\n            $sFullField = $this->getFieldLongName($sField);\n            $this->_aSortingFieldsOnLoad[$sFullField] = $this->$sFullField->value;\n        }\n    }\n\n    /**\n     * Forms query to load variants.\n     *\n     * @param bool                      $blRemoveNotOrderables\n     * @param bool                      $forceCoreTableUsage\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article|\\OxidEsales\\Eshop\\Application\\Model\\SimpleVariant $baseObject\n     * @param string                    $sArticleTable\n     *\n     * @return string\n     */\n    protected function getLoadVariantsQuery($blRemoveNotOrderables, $forceCoreTableUsage, $baseObject, $sArticleTable)\n    {\n        return \"select \" . $baseObject->getSelectFields($forceCoreTableUsage) . \" from $sArticleTable where \" .\n               $this->getActiveCheckQuery($forceCoreTableUsage) .\n               $this->getVariantsQuery($blRemoveNotOrderables, $forceCoreTableUsage) .\n               \" order by $sArticleTable.oxsort\";\n    }\n\n    /**\n     * Set needed parameters to article list object like language.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Model\\BaseModel $baseObject          article list template object.\n     * @param bool|null                              $forceCoreTableUsage if true forces core table use, default is\n     *                                                                    false [optional]\n     */\n    protected function updateVariantsBaseObject($baseObject, $forceCoreTableUsage = null)\n    {\n        $baseObject->setLanguage($this->getLanguage());\n    }\n\n    /**\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Manufacturer $oManufacturer\n     */\n    protected function updateManufacturerBeforeLoading($oManufacturer)\n    {\n        $oManufacturer->setReadOnly(true);\n    }\n\n    protected function addSqlActiveRangeSnippet($query, $tableName): string\n    {\n        $dateUtils = Registry::getUtilsDate();\n        $secondsToRoundForQueryCache = $this->getSecondsToRoundForQueryCache();\n        $dateNow = $dateUtils->getRoundedRequestDateDBFormatted($secondsToRoundForQueryCache);\n        $defaultDBDate = $dateUtils->formatDBDate('-');\n\n        $activeToCondition = \"$tableName.oxactivefrom <= '$dateNow' AND \" .\n            \"$tableName.oxactivefrom != '$defaultDBDate' AND \" .\n            \"$tableName.oxactiveto = '$defaultDBDate'\";\n        $activeFromToCondition = \"$tableName.oxactivefrom <= '$dateNow' AND $tableName.oxactiveto >= '$dateNow'\";\n\n        $query = $query ? \" $query or \" : '';\n\n        return \" ( $query (($activeToCondition) OR ($activeFromToCondition)) )\";\n    }\n\n    private function getAvailableStock(): int\n    {\n        return (int) ($this->_blNotBuyableParent\n            ? $this->oxarticles__oxvarstock->value\n            : $this->oxarticles__oxstock->value);\n    }\n\n    private function isLowStock(): bool\n    {\n        return $this->getAvailableStock() <= $this->getLowStockThreshold();\n    }\n\n    private function getLowStockThreshold(): int\n    {\n        return (int) ($this->oxarticles__oxlowstockactive->value ?\n            $this->oxarticles__oxremindamount->value :\n            Registry::getConfig()->getConfigParam('sStockWarningLimit'));\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/ArticleList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse Exception;\nuse OxidEsales\\Eshop\\Core\\Database\\Adapter\\DatabaseInterface;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\n\n/**\n * Article list manager.\n * Collects list of article according to collection rules (categories, etc.).\n */\nclass ArticleList extends \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n{\n    /**\n     * @var string SQL addon for sorting\n     */\n    protected $_sCustomSorting;\n\n    /**\n     * List Object class name\n     *\n     * @var string\n     */\n    protected $_sObjectsInListName = 'oxarticle';\n\n    /**\n     * Set to true if Select Lists should be laoded\n     *\n     * @var bool\n     */\n    protected $_blLoadSelectLists = false;\n\n    /**\n     * Set Custom Sorting, simply an order by....\n     *\n     * @param string $sSorting Custom sorting\n     */\n    public function setCustomSorting($sSorting)\n    {\n        $this->_sCustomSorting = $sSorting;\n    }\n\n    /**\n     * Call enableSelectLists() for loading select lists in lst articles\n     */\n    public function enableSelectLists()\n    {\n        $this->_blLoadSelectLists = true;\n    }\n\n    /**\n     * @inheritdoc\n     * In addition to the parent method, this method includes profiling.\n     *\n     * @param string $sql        SQL select statement or prepared statement\n     * @param array  $parameters Parameters to be used in a prepared statement\n     */\n    public function selectString($sql, array $parameters = [])\n    {\n        startProfile(\"loadinglists\");\n        parent::selectString($sql, $parameters);\n        stopProfile(\"loadinglists\");\n    }\n\n    public function getHistoryArticles(): array\n    {\n        $sessionHistory = Registry::getSession()->getVariable('aHistoryArticles');\n        if (is_array($sessionHistory)) {\n            return $sessionHistory;\n        }\n\n        $cookieHistory = Registry::getUtilsServer()->getOxCookie('aHistoryArticles');\n\n        return is_string($cookieHistory) ? explode('|', $cookieHistory) : [];\n    }\n\n    /**\n     * Set history article id's to session or cookie\n     *\n     * @param array $aArticlesIds array history article ids\n     */\n    public function setHistoryArticles($aArticlesIds)\n    {\n        $session = Registry::getSession();\n        if ($session->getId()) {\n            $session->setVariable('aHistoryArticles', $aArticlesIds);\n            // clean cookie, if session started\n            Registry::getUtilsServer()->setOxCookie('aHistoryArticles', '');\n        } else {\n            Registry::getUtilsServer()->setOxCookie('aHistoryArticles', implode('|', $aArticlesIds));\n        }\n    }\n\n    /**\n     * Loads up to 4 history (normally recently seen) articles from session, and adds $sArtId to history.\n     * Returns article id array.\n     *\n     * @param string $sArtId Article ID\n     * @param int    $iCnt   product count\n     */\n    public function loadHistoryArticles($sArtId, $iCnt = 4)\n    {\n        $aHistoryArticles = $this->getHistoryArticles();\n        $aHistoryArticles[] = $sArtId;\n\n        // removing duplicates\n        $aHistoryArticles = array_unique($aHistoryArticles);\n        if (count($aHistoryArticles) > ($iCnt + 1)) {\n            array_shift($aHistoryArticles);\n        }\n\n        $this->setHistoryArticles($aHistoryArticles);\n\n        //remove current article and return array\n        //asignment =, not ==\n        if (($iCurrentArt = array_search($sArtId, $aHistoryArticles)) !== false) {\n            unset($aHistoryArticles[$iCurrentArt]);\n        }\n\n        $aHistoryArticles = array_values($aHistoryArticles);\n        $this->loadIds($aHistoryArticles);\n        $this->sortByIds($aHistoryArticles);\n    }\n\n    /**\n     * sort this list by given order.\n     *\n     * @param array $aIds ordered ids\n     */\n    public function sortByIds($aIds)\n    {\n        $this->_aOrderMap = array_flip($aIds);\n        uksort($this->_aArray, [$this, 'sortByOrderMapCallback']);\n    }\n\n    /**\n     * callback function only used from sortByIds\n     *\n     * @param string $key1 1st key\n     * @param string $key2 2nd key\n     *\n     * @see oxArticleList::sortByIds\n     *\n     * @return int\n     */\n    protected function sortByOrderMapCallback($key1, $key2)\n    {\n        if (isset($this->_aOrderMap[$key1])) {\n            if (isset($this->_aOrderMap[$key2])) {\n                $iDiff = $this->_aOrderMap[$key2] - $this->_aOrderMap[$key1];\n                if ($iDiff > 0) {\n                    return -1;\n                } elseif ($iDiff < 0) {\n                    return 1;\n                } else {\n                    return 0;\n                }\n            } else {\n                // first is here, but 2nd is not - 1st gets more priority\n                return -1;\n            }\n        } elseif (isset($this->_aOrderMap[$key2])) {\n            // first is not here, but 2nd is - 2nd gets more priority\n            return 1;\n        } else {\n            // both unset, equal\n            return 0;\n        }\n    }\n\n    /**\n     * Loads newest shops articles from DB.\n     *\n     * @param int $iLimit Select limit\n     */\n    public function loadNewestArticles($iLimit = null)\n    {\n        //has module?\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        if (!$myConfig->getConfigParam('bl_perfLoadPriceForAddList')) {\n            $this->getBaseObject()->disablePriceLoad();\n        }\n\n        $this->_aArray = [];\n        switch ($myConfig->getConfigParam('iNewestArticlesMode')) {\n            case 0:\n                // switched off, do nothing\n                break;\n            case 1:\n                // manually entered\n                $this->loadActionArticles('oxnewest', $iLimit);\n                break;\n            case 2:\n                $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n                $sArticleTable = $tableViewNameGenerator->getViewName('oxarticles');\n                if ($myConfig->getConfigParam('blNewArtByInsert')) {\n                    $sType = 'oxinsert';\n                } else {\n                    $sType = 'oxtimestamp';\n                }\n                $sSelect = \"select * from $sArticleTable \";\n                $sSelect .= \"where oxparentid = '' and \" . $this->getBaseObject()->getSqlActiveSnippet() . \" and oxissearch = 1 order by $sType desc \";\n                if (!($iLimit = (int) $iLimit)) {\n                    $iLimit = $myConfig->getConfigParam('iNrofNewcomerArticles');\n                }\n                $sSelect .= \"limit \" . $iLimit;\n\n                $this->selectString($sSelect);\n                break;\n        }\n    }\n\n    /**\n     * Load top 5 articles\n     *\n     * @param int $iLimit Select limit\n     */\n    public function loadTop5Articles($iLimit = null)\n    {\n        //has module?\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        if (!$myConfig->getConfigParam('bl_perfLoadPriceForAddList')) {\n            $this->getBaseObject()->disablePriceLoad();\n        }\n\n        switch ($myConfig->getConfigParam('iTop5Mode')) {\n            case 0:\n                // switched off, do nothing\n                break;\n            case 1:\n                // manually entered\n                $this->loadActionArticles('oxtop5', $iLimit);\n                break;\n            case 2:\n                $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n                $sArticleTable = $tableViewNameGenerator->getViewName('oxarticles');\n\n                //by default limit 5\n                $sLimit = ($iLimit > 0) ? \"limit \" . $iLimit : 'limit 5';\n\n                $sSelect = \"select * from $sArticleTable \";\n                $sSelect .= \"where \" . $this->getBaseObject()->getSqlActiveSnippet() . \" and $sArticleTable.oxissearch = 1 \";\n                $sSelect .= \"and $sArticleTable.oxparentid = '' and $sArticleTable.oxsoldamount>0 \";\n                $sSelect .= \"order by $sArticleTable.oxsoldamount desc $sLimit\";\n\n                $this->selectString($sSelect);\n                break;\n        }\n    }\n\n    /**\n     * Loads shop AktionArticles.\n     *\n     * @param string $sActionID Action id\n     * @param int    $iLimit    Select limit\n     *\n     * @return null\n     */\n    public function loadActionArticles($sActionID, $iLimit = null)\n    {\n        // Performance\n        if (!trim($sActionID)) {\n            return;\n        }\n\n        $sShopID = Registry::getConfig()->getShopId();\n        $sActionID = strtolower($sActionID);\n\n        $oBaseObject = $this->getBaseObject();\n        $sArticleTable = $oBaseObject->getViewName();\n        $sArticleFields = $oBaseObject->getSelectFields();\n\n        $oBase = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Actions::class);\n        $sActiveSql = $oBase->getSqlActiveSnippet();\n        $sViewName = $oBase->getViewName();\n\n        $sLimit = ($iLimit > 0) ? \"limit \" . $iLimit : '';\n\n        $sSelect = \"select $sArticleFields from oxactions2article\n                              left join $sArticleTable on $sArticleTable.oxid = oxactions2article.oxartid\n                              left join $sViewName on $sViewName.oxid = oxactions2article.oxactionid\n                              where oxactions2article.oxshopid = :oxshopid\n                                  and oxactions2article.oxactionid = :oxactionid\n                                  and $sActiveSql\n                                  and $sArticleTable.oxid is not null and \" . $oBaseObject->getSqlActiveSnippet() . \"\n                              order by oxactions2article.oxsort $sLimit\";\n\n        $this->selectString($sSelect, [\n            'oxshopid' => $sShopID,\n            'oxactionid' => $sActionID\n        ]);\n    }\n\n    /**\n     * Loads article cross selling\n     *\n     * @param string $sArticleId Article id\n     *\n     * @return null\n     */\n    public function loadArticleCrossSell($sArticleId)\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        // Performance\n        if (!$myConfig->getConfigParam('bl_perfLoadCrossselling')) {\n            return null;\n        }\n\n        $oBaseObject = $this->getBaseObject();\n        $sArticleTable = $oBaseObject->getViewName();\n\n        $sSelect = \"SELECT $sArticleTable.*\n            FROM $sArticleTable INNER JOIN oxobject2article ON oxobject2article.oxobjectid=$sArticleTable.oxid \n            WHERE oxobject2article.oxarticlenid = :oxarticlenid\n              AND {$oBaseObject->getSqlActiveSnippet()} \n            ORDER BY oxobject2article.oxsort\";\n\n        // #525 bidirectional cross selling\n        if ($myConfig->getConfigParam('blBidirectCross')) {\n            $sSelect = \"\n                (\n                    SELECT $sArticleTable.*, O2A1.OXSORT as sorting FROM $sArticleTable\n                        INNER JOIN oxobject2article AS O2A1 on\n                            ( O2A1.oxobjectid = $sArticleTable.oxid AND O2A1.oxarticlenid = :oxarticlenid )\n                    WHERE 1\n                    AND \" . $oBaseObject->getSqlActiveSnippet() . \"\n                    AND ($sArticleTable.oxid != :oxarticlenid)\n                )\n                UNION\n                (\n                    SELECT $sArticleTable.*, O2A2.OXSORT as sorting FROM $sArticleTable\n                        INNER JOIN oxobject2article AS O2A2 ON\n                            ( O2A2.oxarticlenid = $sArticleTable.oxid AND O2A2.oxobjectid = :oxarticlenid )\n                    WHERE 1\n                    AND \" . $oBaseObject->getSqlActiveSnippet() . \"\n                    AND ($sArticleTable.oxid != :oxarticlenid)\n                )\n                ORDER BY sorting\";\n        }\n\n        $this->setSqlLimit(0, $myConfig->getConfigParam('iNrofCrossellArticles'));\n        $this->selectString($sSelect, [\n            'oxarticlenid' => $sArticleId\n        ]);\n    }\n\n    /**\n     * Loads article accessories\n     *\n     * @param string $sArticleId Article id\n     *\n     * @return null\n     */\n    public function loadArticleAccessoires($sArticleId)\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        // Performance\n        if (!$myConfig->getConfigParam('bl_perfLoadAccessoires')) {\n            return;\n        }\n\n        $oBaseObject = $this->getBaseObject();\n        $sArticleTable = $oBaseObject->getViewName();\n\n        $sSelect = \"select $sArticleTable.* from oxaccessoire2article\n            left join $sArticleTable on oxaccessoire2article.oxobjectid=$sArticleTable.oxid \";\n        $sSelect .= \"where oxaccessoire2article.oxarticlenid = :oxarticlenid \";\n        $sSelect .= \" and $sArticleTable.oxid is not null and \" . $oBaseObject->getSqlActiveSnippet();\n        //sorting articles\n        $sSelect .= \" order by oxaccessoire2article.oxsort\";\n\n        $this->selectString($sSelect, [\n            'oxarticlenid' => $sArticleId\n        ]);\n    }\n\n    /**\n     * Loads only ID's and create Fake objects for cmp_categories.\n     *\n     * @param string $sCatId         Category tree ID\n     * @param array  $aSessionFilter Like array ( catid => array( attrid => value,...))\n     */\n    public function loadCategoryIds($sCatId, $aSessionFilter)\n    {\n        $sArticleTable = $this->getBaseObject()->getViewName();\n        $sSelect = $this->getCategorySelect($sArticleTable . '.oxid as oxid', $sCatId, $aSessionFilter);\n\n        $this->createIdListFromSql($sSelect);\n    }\n\n    /**\n     * Loads articles for the give Category\n     *\n     * @param string $sCatId         Category tree ID\n     * @param array  $aSessionFilter Like array ( catid => array( attrid => value,...))\n     * @param int    $iLimit         Limit\n     *\n     * @return integer total Count of Articles in this Category\n     */\n    public function loadCategoryArticles($sCatId, $aSessionFilter, $iLimit = null)\n    {\n        $sArticleFields = $this->getBaseObject()->getSelectFields();\n\n        $sSelect = $this->getCategorySelect($sArticleFields, $sCatId, $aSessionFilter);\n\n        // calc count - we can not use count($this) here as we might have paging enabled\n        // #1970C - if any filters are used, we can not use cached category article count\n        $iArticleCount = null;\n        if ($aSessionFilter) {\n            $iArticleCount = DatabaseProvider::getDb()->getOne($this->getCategoryCountSelect($sCatId, $aSessionFilter));\n        }\n\n        if ($iLimit = (int) $iLimit) {\n            $sSelect .= \" LIMIT $iLimit\";\n        }\n\n        $this->selectString($sSelect);\n\n        if ($iArticleCount !== null) {\n            return $iArticleCount;\n        }\n\n        // this select is FAST so no need to hazzle here with getNrOfArticles()\n        return \\OxidEsales\\Eshop\\Core\\Registry::getUtilsCount()->getCatArticleCount($sCatId);\n    }\n\n    /**\n     * Loads articles for the recommlist\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @param string $sRecommId       Recommlist ID\n     * @param string $sArticlesFilter Additional filter for recommlist's items\n     */\n    public function loadRecommArticles($sRecommId, $sArticlesFilter = null)\n    {\n        $sSelect = $this->getArticleSelect($sRecommId, $sArticlesFilter);\n        $this->selectString($sSelect);\n    }\n\n    /**\n     * Loads only ID's and create Fake objects.\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @param string $sRecommId       Recommlist ID\n     * @param string $sArticlesFilter Additional filter for recommlist's items\n     */\n    public function loadRecommArticleIds($sRecommId, $sArticlesFilter)\n    {\n        $sSelect = $this->getArticleSelect($sRecommId, $sArticlesFilter);\n\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sArtView = $tableViewNameGenerator->getViewName('oxarticles');\n        $sPartial = substr($sSelect, strpos($sSelect, ' from '));\n        $sSelect = \"select distinct $sArtView.oxid $sPartial \";\n\n        $this->createIdListFromSql($sSelect);\n    }\n\n    /**\n     * Returns the appropriate SQL select\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @param string $sRecommId       Recommlist ID\n     * @param string $sArticlesFilter Additional filter for recommlist's items\n     *\n     * @return string\n     */\n    protected function getArticleSelect($sRecommId, $sArticlesFilter = null)\n    {\n        $sRecommId = DatabaseProvider::getDb()->quote($sRecommId);\n\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sArtView = $tableViewNameGenerator->getViewName('oxarticles');\n        $sSelect = \"select distinct $sArtView.*, oxobject2list.oxdesc from oxobject2list \";\n        $sSelect .= \"left join $sArtView on oxobject2list.oxobjectid = $sArtView.oxid \";\n        $sSelect .= \"where (oxobject2list.oxlistid = $sRecommId) \" . $sArticlesFilter;\n\n        return $sSelect;\n    }\n\n    /**\n     * Loads only ID's and create Fake objects for cmp_categories.\n     *\n     * @param string $sSearchStr          Search string\n     * @param string $sSearchCat          Search within category\n     * @param string $sSearchVendor       Search within vendor\n     * @param string $sSearchManufacturer Search within manufacturer\n     */\n    public function loadSearchIds($sSearchStr = '', $sSearchCat = '', $sSearchVendor = '', $sSearchManufacturer = '')\n    {\n        $oDb = DatabaseProvider::getDb();\n        $sSearchCat = $sSearchCat ? $sSearchCat : null;\n        $sSearchVendor = $sSearchVendor ? $sSearchVendor : null;\n        $sSearchManufacturer = $sSearchManufacturer ? $sSearchManufacturer : null;\n\n        $sWhere = null;\n\n        if ($sSearchStr) {\n            $sWhere = $this->getSearchSelect($sSearchStr);\n        }\n\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sArticleTable = $tableViewNameGenerator->getViewName('oxarticles');\n\n        // longdesc field now is kept on different table\n        $sDescJoin = $this->getDescriptionJoin();\n\n        // load the articles\n        $sSelect = \"select $sArticleTable.oxid, $sArticleTable.oxtimestamp from $sArticleTable $sDescJoin where \";\n\n        // must be additional conditions in select if searching in category\n        if ($sSearchCat) {\n            $sO2CView = $tableViewNameGenerator->getViewName('oxobject2category');\n            $sSelect = \"select $sArticleTable.oxid from $sO2CView as oxobject2category, $sArticleTable $sDescJoin \";\n            $sSelect .= \"where oxobject2category.oxcatnid=\" . $oDb->quote($sSearchCat) . \" and oxobject2category.oxobjectid=$sArticleTable.oxid and \";\n        }\n        $sSelect .= $this->getBaseObject()->getSqlActiveSnippet();\n        $sSelect .= \" and $sArticleTable.oxparentid = '' and $sArticleTable.oxissearch = 1 \";\n\n        // #671\n        if ($sSearchVendor) {\n            $sSelect .= \" and $sArticleTable.oxvendorid = \" . $oDb->quote($sSearchVendor) . \" \";\n        }\n\n        if ($sSearchManufacturer) {\n            $sSelect .= \" and $sArticleTable.oxmanufacturerid = \" . $oDb->quote($sSearchManufacturer) . \" \";\n        }\n        $sSelect .= $sWhere;\n\n        if ($this->_sCustomSorting) {\n            $sSelect .= \" order by {$this->_sCustomSorting} \";\n        }\n\n        $this->createIdListFromSql($sSelect);\n    }\n\n    /**\n     * Loads Id list of appropriate price products\n     *\n     * @param float $dPriceFrom Starting price\n     * @param float $dPriceTo   Max price\n     */\n    public function loadPriceIds($dPriceFrom, $dPriceTo)\n    {\n        $sSelect = $this->getPriceSelect($dPriceFrom, $dPriceTo);\n        $this->createIdListFromSql($sSelect);\n    }\n\n    /**\n     * Loads articles, that price is bigger than passed $dPriceFrom and smaller\n     * than passed $dPriceTo. Returns count of selected articles.\n     *\n     * @param double $dPriceFrom Price from\n     * @param double $dPriceTo   Price to\n     * @param object $oCategory  Active category object\n     *\n     * @return integer\n     */\n    public function loadPriceArticles($dPriceFrom, $dPriceTo, $oCategory = null)\n    {\n        $sSelect = $this->getPriceSelect($dPriceFrom, $dPriceTo);\n\n        startProfile(\"loadPriceArticles\");\n        $this->selectString($sSelect);\n        stopProfile(\"loadPriceArticles\");\n\n        if (!$oCategory) {\n            return $this->count();\n        }\n\n        return \\OxidEsales\\Eshop\\Core\\Registry::getUtilsCount()->getPriceCatArticleCount($oCategory->getId(), $dPriceFrom, $dPriceTo);\n    }\n\n    /**\n     * Loads Products for specified vendor\n     *\n     * @param string $sVendorId Vendor id\n     */\n    public function loadVendorIDs($sVendorId)\n    {\n        $sSelect = $this->getVendorSelect($sVendorId);\n        $this->createIdListFromSql($sSelect);\n    }\n\n    /**\n     * Loads Products for specified Manufacturer\n     *\n     * @param string $sManufacturerId Manufacturer id\n     */\n    public function loadManufacturerIDs($sManufacturerId)\n    {\n        $sSelect = $this->getManufacturerSelect($sManufacturerId);\n        $this->createIdListFromSql($sSelect);\n    }\n\n    /**\n     * Loads articles that belongs to vendor, passed by parameter $sVendorId.\n     * Returns count of selected articles.\n     *\n     * @param string $sVendorId Vendor ID\n     * @param object $oVendor   Active vendor object\n     *\n     * @return integer\n     */\n    public function loadVendorArticles($sVendorId, $oVendor = null)\n    {\n        $sSelect = $this->getVendorSelect($sVendorId);\n        $this->selectString($sSelect);\n\n        return \\OxidEsales\\Eshop\\Core\\Registry::getUtilsCount()->getVendorArticleCount($sVendorId);\n    }\n\n    /**\n     * Loads articles that belongs to Manufacturer, passed by parameter $sManufacturerId.\n     * Returns count of selected articles.\n     *\n     * @param string $sManufacturerId Manufacturer ID\n     * @param object $oManufacturer   Active Manufacturer object\n     *\n     * @return integer\n     */\n    public function loadManufacturerArticles($sManufacturerId, $oManufacturer = null)\n    {\n        $sSelect = $this->getManufacturerSelect($sManufacturerId);\n        $this->selectString($sSelect);\n\n        return \\OxidEsales\\Eshop\\Core\\Registry::getUtilsCount()->getManufacturerArticleCount($sManufacturerId);\n    }\n\n    /**\n     * Load the list by article ids\n     *\n     * @param array $aIds Article ID array\n     *\n     * @return null\n     */\n    public function loadIds($aIds)\n    {\n        if (!count($aIds)) {\n            $this->clear();\n\n            return;\n        }\n\n        $baseObject = $this->getBaseObject();\n        $articleTable = $baseObject->getViewName();\n        $articleFields = $baseObject->getSelectFields();\n        $quotedIds = implode(',', DatabaseProvider::getDb()->quoteArray($aIds));\n\n        $this->selectString(\n            \"select $articleFields from $articleTable \"\n            . \"where $articleTable.oxid in ( $quotedIds ) and \"\n            . $baseObject->getSqlActiveSnippet()\n            . \" order by field($articleTable.oxid, $quotedIds)\"\n        );\n    }\n\n    /**\n     * Loads the article list by orders ids\n     *\n     * @param array $aOrders user orders array\n     *\n     * @return null\n     */\n    public function loadOrderArticles($aOrders)\n    {\n        if (!count($aOrders)) {\n            $this->clear();\n\n            return;\n        }\n\n        foreach ($aOrders as $iKey => $oOrder) {\n            $aOrdersIds[] = $oOrder->getId();\n        }\n\n        $oBaseObject = $this->getBaseObject();\n        $sArticleTable = $oBaseObject->getViewName();\n        $sArticleFields = $oBaseObject->getSelectFields();\n        $sArticleFields = str_replace(\"`$sArticleTable`.`oxid`\", \"`oxorderarticles`.`oxartid` AS `oxid`\", $sArticleFields);\n\n        $sSelect = \"SELECT $sArticleFields FROM oxorderarticles \";\n        $sSelect .= \"left join $sArticleTable on oxorderarticles.oxartid = $sArticleTable.oxid \";\n        $sSelect .= \"WHERE oxorderarticles.oxorderid IN ( '\" . implode(\"','\", $aOrdersIds) . \"' ) \";\n        $sSelect .= \"order by $sArticleTable.oxid \";\n\n        $this->selectString($sSelect);\n\n        // not active or not available products must not have button \"tobasket\"\n        $sNow = date('Y-m-d H:i:s');\n        foreach ($this as $oArticle) {\n            if (\n                !$oArticle->oxarticles__oxactive->value &&\n                (\n                    $oArticle->oxarticles__oxactivefrom->value > $sNow ||\n                 $oArticle->oxarticles__oxactiveto->value < $sNow\n                )\n            ) {\n                $oArticle->setBuyableState(false);\n            }\n        }\n    }\n\n    /**\n     * Loads list of low stock state products\n     *\n     * @param array $aBasketContents product ids array\n     */\n    public function loadStockRemindProducts($aBasketContents)\n    {\n        if (is_array($aBasketContents) && count($aBasketContents)) {\n            $database = DatabaseProvider::getDb();\n            foreach ($aBasketContents as $oBasketItem) {\n                $aArtIds[] = $database->quote($oBasketItem->getProductId());\n            }\n\n            $oBaseObject = $this->getBaseObject();\n\n            $sFieldNames = $oBaseObject->getSelectFields();\n            $tableName = $oBaseObject->getViewName();\n\n            // fetching actual db stock state and reminder status\n            $this->selectString(\n                sprintf(\n                    \"select %s from %s where oxid in ( %s ) and oxremindactive = '1' and\".\n                    \" oxstock <= oxremindamount\",\n                    $sFieldNames, $tableName, implode(\",\", $aArtIds))\n            );\n\n            // updating stock reminder state\n            if ($this->count()) {\n                $database->execute(\n                    sprintf(\n                        \"update %s set oxremindactive = '2' where :tableName in ( %s ) and oxremindactive = '1'\"\n                        .\" and oxstock <= oxremindamount\",\n                        $tableName, implode(\",\", $aArtIds)\n                    ),\n                    [\n                        'tableName' => $tableName . '.oxid'\n                    ]\n                );\n            }\n        }\n    }\n\n    /**\n     * Calculates, updates and returns next price renew time\n     *\n     * @return int\n     */\n    public function renewPriceUpdateTime()\n    {\n        $iTimeToUpdate = $this->fetchNextUpdateTime();\n\n        // next day?\n        $iCurrUpdateTime = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime();\n        $iNextUpdateTime = $iCurrUpdateTime + 3600 * 24;\n\n        // renew next update time\n        if (!$iTimeToUpdate || $iTimeToUpdate > $iNextUpdateTime) {\n            $iTimeToUpdate = $iNextUpdateTime;\n        }\n\n        \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->saveShopConfVar(\"num\", \"iTimeToUpdatePrices\", $iTimeToUpdate);\n\n        return $iTimeToUpdate;\n    }\n\n    /**\n     * Updates prices where new price > 0, update time != '0000-00-00 00:00:00'\n     * and <= CURRENT_TIMESTAMP. Returns update execution state (result of \\OxidEsales\\Eshop\\Core\\DatabaseProvider::execute())\n     *\n     * @param bool $blForceUpdate if true, forces price update without timeout check, default value is FALSE\n     *\n     * @throws Exception\n     *\n     * @return mixed\n     */\n    public function updateUpcomingPrices($blForceUpdate = false)\n    {\n        $updated = false;\n\n        if ($blForceUpdate || $this->canUpdatePrices()) {\n            // Transaction picks master automatically (see ESDEV-3804 and ESDEV-3822).\n            $database = DatabaseProvider::getDb();\n\n            $database->startTransaction();\n            try {\n                $currUpdateTime = date(\"Y-m-d H:i:s\", \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime());\n\n                // Collect article id's for later recalculation.\n\n                $updatedProductIds = $database->getCol(\n                    \"SELECT `oxid` FROM `oxarticles` WHERE `oxupdatepricetime` > 0 AND `oxupdatepricetime` \"\n                    .\"<= :oxupdatepricetime\",\n                    [\n                        'oxupdatepricetime' => $currUpdateTime\n                    ]\n                );\n\n                // updating oxarticles\n                $updated = $this->updateOxArticles($currUpdateTime, $database);\n\n                // renew update time in case update is not forced\n                if (!$blForceUpdate) {\n                    $this->renewPriceUpdateTime();\n                }\n\n                $database->commitTransaction();\n            } catch (Exception $exception) {\n                $database->rollbackTransaction();\n                throw $exception;\n            }\n\n            // recalculate oxvarminprice and oxvarmaxprice for parent\n            if (is_array($updatedProductIds)) {\n                foreach ($updatedProductIds as $productId) {\n                    $product = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n                    $product->load($productId);\n                    $product->onChange();\n                }\n            }\n\n            $this->updateArticles($updatedProductIds);\n        }\n\n        return $updated;\n    }\n\n    /**\n     * fills the list simply with keys of the oxid and the position as value for the given sql\n     *\n     * @param string $sSql SQL select\n     */\n    protected function createIdListFromSql($sSql)\n    {\n        $rs = DatabaseProvider::getDb()->select($sSql);\n        if ($rs != false && $rs->count() > 0) {\n            while (!$rs->EOF) {\n                $rs->fields = array_change_key_case($rs->fields, CASE_LOWER);\n                $this[$rs->fields['oxid']] = $rs->fields['oxid']; //only the oxid\n                $rs->fetchRow();\n            }\n        }\n    }\n\n    /**\n     * Returns sql to fetch ids of articles fitting current filter\n     *\n     * @param string $sCatId  category id\n     * @param array  $aFilter filters for this category\n     *\n     * @return string\n     */\n    protected function getFilterIdsSql($sCatId, $aFilter)\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sO2CView = $tableViewNameGenerator->getViewName('oxobject2category');\n        $sO2AView = $tableViewNameGenerator->getViewName('oxobject2attribute');\n\n        $sFilter = '';\n        $iCnt = 0;\n\n        $oDb = DatabaseProvider::getDb();\n        foreach ($aFilter as $sAttrId => $sValue) {\n            $sValue = (string) $sValue;\n            if ($sValue !== '') {\n                if ($sFilter) {\n                    $sFilter .= ' or ';\n                }\n                $sValue = $oDb->quote($sValue);\n                $sAttrId = $oDb->quote($sAttrId);\n\n                $sFilter .= \"( oa.oxattrid = {$sAttrId} and oa.oxvalue = {$sValue} )\";\n                $iCnt++;\n            }\n        }\n        if ($sFilter) {\n            $sFilter = \"WHERE $sFilter \";\n        }\n\n        $sFilterSelect = \"select oc.oxobjectid as oxobjectid, count(*) as cnt from \";\n        $sFilterSelect .= \"(SELECT * FROM $sO2CView WHERE $sO2CView.oxcatnid = '$sCatId' GROUP BY $sO2CView.oxobjectid, $sO2CView.oxcatnid) as oc \";\n        $sFilterSelect .= \"INNER JOIN $sO2AView as oa ON ( oa.oxobjectid = oc.oxobjectid ) \";\n\n        return $sFilterSelect . \"{$sFilter} GROUP BY oa.oxobjectid HAVING cnt = $iCnt \";\n    }\n\n    /**\n     * Returns filtered articles sql \"oxid in (filtered ids)\" part\n     *\n     * @param string $sCatId  category id\n     * @param array  $aFilter filters for this category\n     *\n     * @return string\n     */\n    protected function getFilterSql($sCatId, $aFilter)\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sArticleTable = $tableViewNameGenerator->getViewName('oxarticles');\n        $aIds = DatabaseProvider::getDb()->getAll($this->getFilterIdsSql($sCatId, $aFilter));\n        $sIds = '';\n\n        if ($aIds) {\n            foreach ($aIds as $aArt) {\n                if ($sIds) {\n                    $sIds .= ', ';\n                }\n                $sIds .= DatabaseProvider::getDb()->quote(current($aArt));\n            }\n\n            if ($sIds) {\n                $sFilterSql = \" and $sArticleTable.oxid in ( $sIds ) \";\n            }\n            // bug fix #0001695: if no articles found return false\n        } elseif (!(empty(current($aFilter)) && count(array_unique($aFilter)) == 1)) {\n            $sFilterSql = \" and false \";\n        }\n\n        return $sFilterSql;\n    }\n\n    /**\n     * Creates SQL Statement to load Articles, etc.\n     *\n     * @param string $sFields        Fields which are loaded e.g. \"oxid\" or \"*\" etc.\n     * @param string $sCatId         Category tree ID\n     * @param array  $aSessionFilter Like array ( catid => array( attrid => value,...))\n     *\n     * @return string SQL\n     */\n    protected function getCategorySelect($sFields, $sCatId, $aSessionFilter)\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sArticleTable = $tableViewNameGenerator->getViewName('oxarticles');\n        $sO2CView = $tableViewNameGenerator->getViewName('oxobject2category');\n\n        // ----------------------------------\n        // sorting\n        $sSorting = '';\n        if ($this->_sCustomSorting) {\n            $sSorting = \" {$this->_sCustomSorting} , \";\n        }\n\n        // ----------------------------------\n        // filtering ?\n        $sFilterSql = '';\n        $iLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage();\n        if ($aSessionFilter && isset($aSessionFilter[$sCatId][$iLang])) {\n            $sFilterSql = $this->getFilterSql($sCatId, $aSessionFilter[$sCatId][$iLang]);\n        }\n\n        $oDb = DatabaseProvider::getDb();\n\n        $sSelect = \"SELECT $sFields, $sArticleTable.oxtimestamp FROM $sO2CView as oc left join $sArticleTable\n                    ON $sArticleTable.oxid = oc.oxobjectid\n                    WHERE \" . $this->getBaseObject()->getSqlActiveSnippet() . \" and $sArticleTable.oxparentid = ''\n                    and oc.oxcatnid = \" . $oDb->quote($sCatId) . \" $sFilterSql ORDER BY $sSorting oc.oxpos, oc.oxobjectid \";\n\n        return $sSelect;\n    }\n\n    /**\n     * Creates SQL Statement to load Articles Count, etc.\n     *\n     * @param string $sCatId         Category tree ID\n     * @param array  $aSessionFilter Like array ( catid => array( attrid => value,...))\n     *\n     * @return string SQL\n     */\n    protected function getCategoryCountSelect($sCatId, $aSessionFilter)\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sArticleTable = $tableViewNameGenerator->getViewName('oxarticles');\n        $sO2CView = $tableViewNameGenerator->getViewName('oxobject2category');\n\n\n        // ----------------------------------\n        // filtering ?\n        $sFilterSql = '';\n        $iLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage();\n        if ($aSessionFilter && isset($aSessionFilter[$sCatId][$iLang])) {\n            $sFilterSql = $this->getFilterSql($sCatId, $aSessionFilter[$sCatId][$iLang]);\n        }\n\n        $oDb = DatabaseProvider::getDb();\n\n        $sSelect = \"SELECT COUNT(*) FROM $sO2CView as oc left join $sArticleTable\n                    ON $sArticleTable.oxid = oc.oxobjectid\n                    WHERE \" . $this->getBaseObject()->getSqlActiveSnippet() . \" and $sArticleTable.oxparentid = ''\n                    and oc.oxcatnid = \" . $oDb->quote($sCatId) . \" $sFilterSql \";\n\n        return $sSelect;\n    }\n\n    /**\n     * Forms and returns SQL query string for search in DB.\n     *\n     * @param string $sSearchString searching string\n     *\n     * @return string\n     */\n    protected function getSearchSelect($sSearchString)\n    {\n        // check if it has string at all\n        if (!$sSearchString || !str_replace(' ', '', $sSearchString)) {\n            return '';\n        }\n\n        $oDb = DatabaseProvider::getDb();\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $sArticleTable = $this->getBaseObject()->getViewName();\n\n        $aSearch = explode(' ', $sSearchString);\n\n        $sSearch = ' and ( ';\n        $blSep = false;\n\n        // #723\n        if ($myConfig->getConfigParam('blSearchUseAND')) {\n            $sSearchSep = ' and ';\n        } else {\n            $sSearchSep = ' or ';\n        }\n\n        $aSearchCols = $myConfig->getConfigParam('aSearchCols');\n        $myUtilsString = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsString();\n        foreach ($aSearch as $sSearchString) {\n            if (!strlen($sSearchString)) {\n                continue;\n            }\n\n            if ($blSep) {\n                $sSearch .= $sSearchSep;\n            }\n            $blSep2 = false;\n            $sSearch .= '( ';\n\n            $sUml = $myUtilsString->prepareStrForSearch($sSearchString);\n            foreach ($aSearchCols as $sField) {\n                if ($blSep2) {\n                    $sSearch .= ' or ';\n                }\n\n                // as long description now is on different table table must differ\n                $sSearchTable = $this->getSearchTableName($sArticleTable, $sField);\n\n                $sSearch .= $sSearchTable . '.' . $sField . ' like ' . $oDb->quote('%' . $sSearchString . '%') . ' ';\n                if ($sUml) {\n                    $sSearch .= ' or ' . $sSearchTable . '.' . $sField . ' like ' . $oDb->quote('%' . $sUml . '%');\n                }\n                $blSep2 = true;\n            }\n            $sSearch .= ' ) ';\n            $blSep = true;\n        }\n        $sSearch .= ' ) ';\n\n        return $sSearch;\n    }\n\n    /**\n     * Builds SQL for selecting articles by price\n     *\n     * @param double $dPriceFrom Starting price\n     * @param double $dPriceTo   Max price\n     *\n     * @return string\n     */\n    protected function getPriceSelect($dPriceFrom, $dPriceTo)\n    {\n        $oBaseObject = $this->getBaseObject();\n        $sArticleTable = $oBaseObject->getViewName();\n        $sSelectFields = $oBaseObject->getSelectFields();\n\n        $sSelect = \"select {$sSelectFields} from {$sArticleTable} where oxvarminprice >= 0 \";\n        $sSelect .= $dPriceTo ? \"and oxvarminprice <= \" . (float) $dPriceTo . \" \" : \" \";\n        $sSelect .= $dPriceFrom ? \"and oxvarminprice  >= \" . (float) $dPriceFrom . \" \" : \" \";\n\n        $sSelect .= \" and \" . $oBaseObject->getSqlActiveSnippet() . \" and {$sArticleTable}.oxissearch = 1\";\n\n        if (!$this->_sCustomSorting) {\n            $sSelect .= \" order by {$sArticleTable}.oxvarminprice asc , {$sArticleTable}.oxid\";\n        } else {\n            $sSelect .= \" order by {$this->_sCustomSorting}, {$sArticleTable}.oxid \";\n        }\n\n        return $sSelect;\n    }\n\n    /**\n     * Builds vendor select SQL statement\n     *\n     * @param string $sVendorId Vendor ID\n     *\n     * @return string\n     */\n    protected function getVendorSelect($sVendorId)\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sArticleTable = $tableViewNameGenerator->getViewName('oxarticles');\n        $oBaseObject = $this->getBaseObject();\n        $sFieldNames = $oBaseObject->getSelectFields();\n        $sSelect = \"select $sFieldNames from $sArticleTable \";\n        $sSelect .= \"where $sArticleTable.oxvendorid = \" . DatabaseProvider::getDb()->quote($sVendorId) . \" \";\n        $sSelect .= \" and \" . $oBaseObject->getSqlActiveSnippet() . \" and $sArticleTable.oxparentid = ''  \";\n\n        if ($this->_sCustomSorting) {\n            $sSelect .= \" ORDER BY {$this->_sCustomSorting} \";\n        }\n\n        return $sSelect;\n    }\n\n    /**\n     * Builds Manufacturer select SQL statement\n     *\n     * @param string $sManufacturerId Manufacturer ID\n     *\n     * @return string\n     */\n    protected function getManufacturerSelect($sManufacturerId)\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sArticleTable = $tableViewNameGenerator->getViewName('oxarticles');\n        $oBaseObject = $this->getBaseObject();\n        $sFieldNames = $oBaseObject->getSelectFields();\n        $sSelect = \"select $sFieldNames from $sArticleTable \";\n        $sSelect .= \"where $sArticleTable.oxmanufacturerid = \" . DatabaseProvider::getDb()->quote($sManufacturerId) . \" \";\n        $sSelect .= \" and \" . $oBaseObject->getSqlActiveSnippet() . \" and $sArticleTable.oxparentid = ''  \";\n\n        if ($this->_sCustomSorting) {\n            $sSelect .= \" ORDER BY {$this->_sCustomSorting} \";\n        }\n\n        return $sSelect;\n    }\n\n    /**\n     * Checks if price update can be executed - current time > next price update time\n     *\n     * @return bool\n     */\n    protected function canUpdatePrices()\n    {\n        if (ContainerFacade::getParameter('oxid_esales.cron_enabled')) {\n            return false;\n        }\n        $timeToUpdate = Registry::getConfig()->getConfigParam(\"iTimeToUpdatePrices\");\n\n        return empty($timeToUpdate) || $timeToUpdate <= Registry::getUtilsDate()->getTime();\n    }\n\n    /**\n     * Method fetches next update time for renewing price update time.\n     *\n     * @return string\n     */\n    protected function fetchNextUpdateTime()\n    {\n        // Function is called inside a transaction or from admin backend which uses master connection only.\n        // Transaction picks master automatically (see ESDEV-3804 and ESDEV-3822).\n        $database = DatabaseProvider::getDb();\n\n        // fetching next update time\n        $sQ = $this->getQueryToFetchNextUpdateTime();\n\n        $iTimeToUpdate = $database->getOne(sprintf($sQ, \"`oxarticles`\"));\n\n        return $iTimeToUpdate;\n    }\n\n    /**\n     * Returns query to fetch next update time.\n     *\n     * @return string\n     */\n    protected function getQueryToFetchNextUpdateTime()\n    {\n        return \"select unix_timestamp( oxupdatepricetime ) from %s where oxupdatepricetime > 0 order by oxupdatepricetime asc\";\n    }\n\n    /**\n     * Updates article.\n     *\n     * @param string            $sCurrUpdateTime\n     * @param DatabaseInterface $oDb\n     *\n     * @return mixed\n     */\n    protected function updateOxArticles($sCurrUpdateTime, $oDb)\n    {\n        $sQ = $this->getQueryToUpdateOxArticle($sCurrUpdateTime);\n        $blUpdated = $oDb->execute(sprintf($sQ, \"`oxarticles`\"));\n\n        return $blUpdated;\n    }\n\n    /**\n     * Method returns query to update article.\n     *\n     * @param string $sCurrUpdateTime\n     *\n     * @return string\n     */\n    protected function getQueryToUpdateOxArticle($sCurrUpdateTime)\n    {\n        $sQ = \"UPDATE %s SET\n                       `oxprice`  = IF( `oxupdateprice` > 0, `oxupdateprice`, `oxprice` ),\n                       `oxpricea` = IF( `oxupdatepricea` > 0, `oxupdatepricea`, `oxpricea` ),\n                       `oxpriceb` = IF( `oxupdatepriceb` > 0, `oxupdatepriceb`, `oxpriceb` ),\n                       `oxpricec` = IF( `oxupdatepricec` > 0, `oxupdatepricec`, `oxpricec` ),\n                       `oxupdatepricetime` = 0,\n                       `oxupdateprice`     = 0,\n                       `oxupdatepricea`    = 0,\n                       `oxupdatepriceb`    = 0,\n                       `oxupdatepricec`    = 0\n                   WHERE\n                       `oxupdatepricetime` > 0 AND\n                       `oxupdatepricetime` <= '{$sCurrUpdateTime}'\";\n        return $sQ;\n    }\n\n    /**\n     * Method is used for overloading.\n     *\n     * @param array $aUpdatedArticleIds\n     */\n    protected function updateArticles($aUpdatedArticleIds)\n    {\n    }\n\n    /**\n     * Get description join. Needed in case of searching for data in table oxartextends or its views.\n     *\n     * @return string\n     */\n    protected function getDescriptionJoin()\n    {\n        $table = Registry::get(\\OxidEsales\\Eshop\\Core\\TableViewNameGenerator::class)->getViewName('oxarticles');\n        $descriptionJoin = '';\n        $searchColumns = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('aSearchCols');\n\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        if (is_array($searchColumns) && in_array('oxlongdesc', $searchColumns)) {\n            $viewName = $tableViewNameGenerator->getViewName('oxartextends');\n            $descriptionJoin = \" LEFT JOIN $viewName ON {$viewName}.oxid={$table}.oxid \";\n        }\n        return $descriptionJoin;\n    }\n\n    /**\n     * Get search table name.\n     * Needed in case of searching for data in table oxartextends or its views.\n     *\n     * @param string $table\n     * @param string $field Chose table depending on field.\n     *\n     * @return string\n     */\n    protected function getSearchTableName($table, $field)\n    {\n        $searchTable = $table;\n\n        if ($field == 'oxlongdesc') {\n            $searchTable = Registry::get(\\OxidEsales\\Eshop\\Core\\TableViewNameGenerator::class)->getViewName('oxartextends');\n        }\n\n        return $searchTable;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Attribute.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse oxRegistry;\nuse oxField;\nuse OxidEsales\\Eshop\\Core\\Str;\n\n/**\n * Article attributes manager.\n * Collects and keeps attributes of chosen article.\n */\nclass Attribute extends \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel\n{\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxattribute';\n\n    /**\n     * Selected attribute value\n     *\n     * @var string\n     */\n    protected $_sActiveValue = null;\n\n    /**\n     * Attribute values\n     *\n     * @var array\n     */\n    protected $_aValues = null;\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxattribute');\n    }\n\n    /**\n     * Removes attributes from articles, returns true on success.\n     *\n     * @param string $sOXID Object ID\n     *\n     * @return bool\n     */\n    public function delete($sOXID = null)\n    {\n        if (!$sOXID) {\n            $sOXID = $this->getId();\n        }\n\n        if (!$this->canDeleteAttribute($sOXID)) {\n            return false;\n        }\n\n        // remove attributes from articles also\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sDelete = \"delete from oxobject2attribute where oxattrid = :oxattrid\";\n        $oDb->execute($sDelete, [\n            'oxattrid' => $sOXID\n        ]);\n\n        // #657 ADDITIONAL removes attribute connection to category\n        $sDelete = \"delete from oxcategory2attribute where oxattrid = :oxattrid\";\n        $oDb->execute($sDelete, [\n            'oxattrid' => $sOXID\n        ]);\n\n        return parent::delete($sOXID);\n    }\n\n    /**\n     * Assigns attribute to variant\n     *\n     * @param array $aMDVariants article ids with selectionlist values\n     * @param array $aSelTitle   selection list titles\n     */\n    public function assignVarToAttribute($aMDVariants, $aSelTitle)\n    {\n        $myLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n        $aConfLanguages = $myLang->getLanguageIds();\n        $sAttrId = $this->getAttrId($aSelTitle[0]);\n        if (!$sAttrId) {\n            $sAttrId = $this->createAttribute($aSelTitle);\n        }\n        foreach ($aMDVariants as $sVarId => $oValue) {\n            if (strpos($sVarId, \"mdvar_\") === 0) {\n                foreach ($oValue as $sId) {\n                    $sVarId = substr($sVarId, 6);\n                    $oNewAssign = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel::class);\n                    $oNewAssign->init(\"oxobject2attribute\");\n                    $sNewId = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsObject()->generateUID();\n                    if ($oNewAssign->load($sId)) {\n                        $oNewAssign->oxobject2attribute__oxobjectid = new Field($sVarId);\n                        $oNewAssign->setId($sNewId);\n                        $oNewAssign->save();\n                    }\n                }\n            } else {\n                $oNewAssign = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel::class);\n                $oNewAssign->setEnableMultilang(false);\n                $oNewAssign->init(\"oxobject2attribute\");\n                $oNewAssign->oxobject2attribute__oxobjectid = new Field($sVarId);\n                $oNewAssign->oxobject2attribute__oxattrid = new Field($sAttrId);\n                foreach ($aConfLanguages as $sKey => $sLang) {\n                    $sPrefix = $myLang->getLanguageTag($sKey);\n                    $oNewAssign->{'oxobject2attribute__oxvalue' . $sPrefix} = new Field($oValue[$sKey]->name);\n                }\n                $oNewAssign->save();\n            }\n        }\n    }\n\n    /**\n     * Searches for attribute by oxtitle. If exists returns attribute id\n     *\n     * @param string $sSelTitle selection list title\n     *\n     * @return mixed attribute id or false\n     */\n    protected function getAttrId($sSelTitle)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDB();\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sAttViewName = $tableViewNameGenerator->getViewName('oxattribute');\n\n        return $oDb->getOne(\"select oxid from $sAttViewName where LOWER(oxtitle) = :oxtitle \", [\n            'oxtitle' => Str::getStr()->strtolower($sSelTitle)\n        ]);\n    }\n\n    /**\n     * Checks if attribute exists\n     *\n     * @param array $aSelTitle selection list title\n     *\n     * @return string attribute id\n     */\n    protected function createAttribute($aSelTitle)\n    {\n        $myLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n        $aConfLanguages = $myLang->getLanguageIds();\n        $oAttr = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel::class);\n        $oAttr->setEnableMultilang(false);\n        $oAttr->init('oxattribute');\n        foreach ($aConfLanguages as $sKey => $sLang) {\n            $sPrefix = $myLang->getLanguageTag($sKey);\n            $oAttr->{'oxattribute__oxtitle' . $sPrefix} = new Field($aSelTitle[$sKey]);\n        }\n        $oAttr->save();\n\n        return $oAttr->getId();\n    }\n\n    /**\n     * Returns all oxobject2attribute Ids of article\n     *\n     * @param string $sArtId article ids\n     *\n     * @return null\n     */\n    public function getAttributeAssigns($sArtId)\n    {\n        if ($sArtId) {\n            $sSelect = \"select o2a.oxid from oxobject2attribute as o2a \"\n            . \"where o2a.oxobjectid = :oxobjectid order by o2a.oxpos\";\n\n            return DatabaseProvider::getDb()->getCol($sSelect, [\n                'oxobjectid' => $sArtId\n            ]);\n        }\n    }\n\n\n    /**\n     * Set attribute title\n     *\n     * @param string $sTitle - attribute title\n     */\n    public function setTitle($sTitle)\n    {\n        $this->setFieldData('oxtitle', Str::getStr()->htmlspecialchars($sTitle));\n    }\n\n    /**\n     * Get attribute Title\n     *\n     * @return String\n     */\n    public function getTitle()\n    {\n        return $this->getFieldData('oxtitle');\n    }\n\n    /**\n     * Add attribute value\n     *\n     * @param string $sValue - attribute value\n     */\n    public function addValue($sValue)\n    {\n        $this->_aValues[] = Str::getStr()->htmlspecialchars($sValue);\n    }\n\n    /**\n     * Set attribute selected value\n     *\n     * @param string $sValue - attribute value\n     */\n    public function setActiveValue($sValue)\n    {\n        $this->_sActiveValue = Str::getStr()->htmlspecialchars($sValue);\n    }\n\n    /**\n     * Get attribute Selected value\n     *\n     * @return String\n     */\n    public function getActiveValue()\n    {\n        return $this->_sActiveValue;\n    }\n\n    /**\n     * Get attribute values\n     *\n     * @return Array\n     */\n    public function getValues()\n    {\n        return $this->_aValues;\n    }\n\n    /**\n     * Checks if possible to delete attribute.\n     *\n     * @param string $oxId\n     *\n     * @return bool\n     */\n    protected function canDeleteAttribute($oxId)\n    {\n        $canDelete = true;\n        if (!$oxId) {\n            $canDelete = false;\n        }\n\n        return $canDelete;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/AttributeList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse oxRegistry;\nuse stdClass;\n\n/**\n * Attribute list manager.\n */\nclass AttributeList extends \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n{\n    /**\n     * Class constructor\n     */\n    public function __construct()\n    {\n        parent::__construct('oxattribute');\n    }\n\n    /**\n     * Load all attributes by article Id's\n     *\n     * @param array $aIds article id's\n     *\n     * @return array $aAttributes;\n     */\n    public function loadAttributesByIds($aIds)\n    {\n        if (!count($aIds)) {\n            return;\n        }\n\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sAttrViewName = $tableViewNameGenerator->getViewName('oxattribute');\n        $sViewName = $tableViewNameGenerator->getViewName('oxobject2attribute');\n\n        $oxObjectIdsSql = implode(',', DatabaseProvider::getDb()->quoteArray($aIds));\n\n        $sSelect = \"select $sAttrViewName.oxid, $sAttrViewName.oxtitle, {$sViewName}.oxvalue, {$sViewName}.oxobjectid \";\n        $sSelect .= \"from {$sViewName} left join $sAttrViewName on $sAttrViewName.oxid = {$sViewName}.oxattrid \";\n        $sSelect .= \"where {$sViewName}.oxobjectid in ( \" . $oxObjectIdsSql . \" ) \";\n        $sSelect .= \"order by {$sViewName}.oxpos, $sAttrViewName.oxpos\";\n\n        return $this->createAttributeListFromSql($sSelect);\n    }\n\n    /**\n     * Fills array with keys and products with value\n     *\n     * @param string $sSelect SQL select\n     *\n     * @return array $aAttributes\n     */\n    protected function createAttributeListFromSql($select)\n    {\n        $attributes = [];\n        $result = DatabaseProvider::getDb()->select($select);\n        if ($result != false && $result->count() > 0) {\n            while (!$result->EOF) {\n                $fields = array_values($result->fields);\n                if (!isset($attributes[$fields[0]])) {\n                    $attributes[$fields[0]] = new stdClass();\n                }\n\n                $attributes[$fields[0]]->title = $fields[1];\n                if (!isset($attributes[$fields[0]]->aProd[$fields[3]])) {\n                    $attributes[$fields[0]]->aProd[$fields[3]] = new stdClass();\n                }\n                $attributes[$fields[0]]->aProd[$fields[3]]->value = $fields[2];\n                $result->fetchRow();\n            }\n        }\n\n        return $attributes;\n    }\n\n    /**\n     * Load attributes by article Id\n     *\n     * @param string $sArticleId article id\n     * @param string $sParentId  article parent id\n     */\n    public function loadAttributes($sArticleId, $sParentId = null)\n    {\n        if ($sArticleId) {\n            $oDb = DatabaseProvider::getDb();\n\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $sAttrViewName = $tableViewNameGenerator->getViewName('oxattribute');\n            $sViewName = $tableViewNameGenerator->getViewName('oxobject2attribute');\n\n            $sSelect = \"select {$sAttrViewName}.`oxid`, {$sAttrViewName}.`oxtitle`, o2a.`oxvalue` from {$sViewName} as o2a \";\n            $sSelect .= \"left join {$sAttrViewName} on {$sAttrViewName}.oxid = o2a.oxattrid \";\n            $sSelect .= \"where o2a.oxobjectid = :oxobjectid and o2a.oxvalue != '' \";\n            $sSelect .= \"order by o2a.oxpos, {$sAttrViewName}.oxpos\";\n\n            $aAttributes = $oDb->getAll($sSelect, [\n                'oxobjectid' => $sArticleId\n            ]);\n\n            if ($sParentId) {\n                $aParentAttributes = $oDb->getAll($sSelect, [\n                    'oxobjectid' => $sParentId\n                ]);\n                $aAttributes = $this->mergeAttributes($aAttributes, $aParentAttributes);\n            }\n\n            $this->assignArray($aAttributes);\n        }\n    }\n\n    /**\n     * Load displayable in baskte/order attributes by article Id\n     *\n     * @param string $sArtId    article ids\n     * @param string $sParentId parent id\n     */\n    public function loadAttributesDisplayableInBasket($sArtId, $sParentId = null)\n    {\n        if ($sArtId) {\n            $oDb = DatabaseProvider::getDb();\n\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $sAttrViewName = $tableViewNameGenerator->getViewName('oxattribute');\n            $sViewName = $tableViewNameGenerator->getViewName('oxobject2attribute');\n\n            $sSelect = \"select o2a.*, {$sAttrViewName}.* from $sViewName as o2a \";\n            $sSelect .= \"left join {$sAttrViewName} on {$sAttrViewName}.oxid = o2a.oxattrid \";\n            $sSelect .= \"where o2a.oxobjectid = :oxobjectid and {$sAttrViewName}.oxdisplayinbasket  = 1 and o2a.oxvalue != '' \";\n            $sSelect .= \"order by o2a.oxpos, {$sAttrViewName}.oxpos\";\n\n            $aAttributes = $oDb->getAll($sSelect, [\n                'oxobjectid' => $sArtId\n            ]);\n\n            if ($sParentId) {\n                $aParentAttributes = $oDb->getAll($sSelect, [\n                    'oxobjectid' => $sParentId\n                ]);\n                $aAttributes = $this->mergeAttributes($aAttributes, $aParentAttributes);\n            }\n\n            $this->assignArray($aAttributes);\n        }\n    }\n\n    /**\n     * get category attributes by category Id\n     *\n     * @param string  $sCategoryId category Id\n     * @param integer $iLang       language No\n     *\n     * @return object\n     */\n    public function getCategoryAttributes($sCategoryId, $iLang)\n    {\n        $aSessionFilter = \\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable('session_attrfilter');\n\n        $oArtList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n        $oArtList->loadCategoryIDs($sCategoryId, $aSessionFilter);\n\n        // Only if we have articles\n        if (count($oArtList) > 0) {\n            $oDb = DatabaseProvider::getDb();\n            $sArtIds = '';\n            foreach (array_keys($oArtList->getArray()) as $sId) {\n                if ($sArtIds) {\n                    $sArtIds .= ',';\n                }\n                $sArtIds .= $oDb->quote($sId);\n            }\n\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $sAttTbl = $tableViewNameGenerator->getViewName('oxattribute', $iLang);\n            $sO2ATbl = $tableViewNameGenerator->getViewName('oxobject2attribute', $iLang);\n            $sC2ATbl = $tableViewNameGenerator->getViewName('oxcategory2attribute', $iLang);\n\n            $sSelect = \"SELECT DISTINCT att.oxid as oxid, att.oxtitle as oxtitle, o2a.oxvalue as oxvalue\" .\n                       \" FROM $sAttTbl as att, $sO2ATbl as o2a ,$sC2ATbl as c2a\" .\n                       \" WHERE att.oxid = o2a.oxattrid AND c2a.oxobjectid = :oxobjectid AND c2a.oxattrid = att.oxid\"\n                       . \" AND o2a.oxvalue !='' AND o2a.oxobjectid IN ($sArtIds)\" .\n                       \" ORDER BY c2a.oxsort , att.oxpos, att.oxtitle, o2a.oxvalue\";\n\n            $rs = $oDb->select($sSelect, [\n                'oxobjectid' => $sCategoryId\n            ]);\n\n            if ($rs != false && $rs->count() > 0) {\n                while (!$rs->EOF && list($sAttId, $sAttTitle, $sAttValue) = array_values($rs->fields)) {\n                    if (!$this->offsetExists($sAttId)) {\n                        $oAttribute = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Attribute::class);\n                        $oAttribute->setTitle($sAttTitle);\n\n                        $this->offsetSet($sAttId, $oAttribute);\n                        $iLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage();\n                        if (isset($aSessionFilter[$sCategoryId][$iLang][$sAttId])) {\n                            $oAttribute->setActiveValue($aSessionFilter[$sCategoryId][$iLang][$sAttId]);\n                        }\n                    } else {\n                        $oAttribute = $this->offsetGet($sAttId);\n                    }\n\n                    $oAttribute->addValue($sAttValue);\n                    $rs->fetchRow();\n                }\n            }\n        }\n\n        return $this;\n    }\n\n    /**\n     * Merge attribute arrays\n     *\n     * @param array $aAttributes       array of attributes\n     * @param array $aParentAttributes array of parent article attributes\n     *\n     * @return array $aAttributes\n     */\n    protected function mergeAttributes($aAttributes, $aParentAttributes)\n    {\n        if (count($aParentAttributes)) {\n            $aAttrIds = [];\n            foreach ($aAttributes as $aAttribute) {\n                $aAttrIds[] = $aAttribute['OXID'];\n            }\n\n            foreach ($aParentAttributes as $aAttribute) {\n                if (!in_array($aAttribute['OXID'], $aAttrIds)) {\n                    $aAttributes[] = $aAttribute;\n                }\n            }\n        }\n\n        return $aAttributes;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Basket.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\Exception\\NoArticleException;\nuse OxidEsales\\Eshop\\Core\\Price;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse Psr\\Log\\LoggerInterface;\nuse stdClass;\n\n/**\n * Basket manager\n */\n#[\\AllowDynamicProperties]\nclass Basket extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Array or oxbasketitem objects\n     *\n     * @var array\n     */\n    protected $_aBasketContents = [];\n\n    /**\n     * Number of different product type in basket.\n     * The value is updated only after recalculating a basket.\n     *\n     * @var int\n     */\n    protected $_iProductsCnt = 0;\n\n    /**\n     * Number of basket items.\n     * The value is updated only after recalculating a basket.\n     *\n     * @var double\n     */\n    protected $_dItemsCnt = 0.0;\n\n    /**\n     * Basket weight.\n     * The value is updated only after recalculating a basket.\n     *\n     * @var double\n     */\n    protected $_dWeight = 0.0;\n\n    /**\n     * Total basket price\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Price\n     */\n    protected $_oPrice = null;\n\n    /**\n     * Basket calculation mode netto\n     *\n     * @var bool\n     */\n    protected $_isCalculationModeNetto = null;\n\n    /**\n     * Basket netto sum\n     *\n     * @var float\n     */\n    protected $_dNettoSum = null;\n\n    /**\n     * Basket brutto sum\n     *\n     * @var float\n     */\n    protected $_dBruttoSum = null;\n\n    /**\n     * The list of all basket item prices\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\PriceList\n     */\n    protected $_oProductsPriceList = null;\n\n    /**\n     * Basket discounts information\n     *\n     * @var array\n     */\n    protected $_aDiscounts = [];\n\n    /**\n     * Basket items discounts information\n     *\n     * @var array\n     */\n    protected $_aItemDiscounts = [];\n\n    /**\n     * Basket order ID. Usually this ID is set on last order step\n     *\n     * @var string\n     */\n    protected $_sOrderId = null;\n\n    /**\n     * Array of vouchers applied on basket price\n     *\n     * @var array\n     */\n    protected $_aVouchers = [];\n\n    /**\n     * Additional costs array of \\OxidEsales\\Eshop\\Core\\Price objects\n     *\n     * @var array\n     */\n    protected $_aCosts = [];\n\n    /**\n     * Sum price of articles applicable to discounts\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\PriceList\n     */\n    protected $_oDiscountProductsPriceList = null;\n\n    /**\n     * Sum price of articles not applicable to discounts\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\PriceList\n     */\n    protected $_oNotDiscountedProductsPriceList = null;\n\n    /**\n     * Basket recalculation marker\n     *\n     * @var bool\n     */\n    protected $_blUpdateNeeded = true;\n\n    /**\n     * oxBasket summary object, usually used for discount calculations etc\n     *\n     * @var array\n     */\n    protected $_aBasketSummary = null;\n\n    /**\n     * Basket Payment ID\n     *\n     * @var string\n     */\n    protected $_sPaymentId = null;\n\n    /**\n     * Basket Shipping set ID\n     *\n     * @var string\n     */\n    protected $_sShippingSetId = null;\n\n    /**\n     * Ref. to session user\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\User\n     */\n    protected $_oUser = null;\n\n    /**\n     * Total basket products discount price object (does not include voucher discount)\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Price\n     */\n    protected $_oTotalDiscount = null;\n\n    /**\n     * Basket voucher discount price object\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Price\n     */\n    protected $_oVoucherDiscount = null;\n\n    /**\n     * Basket currency\n     *\n     * @var stdClass\n     */\n    protected $_oCurrency = null;\n\n    /**\n     * Skip or not vouchers availability checking\n     *\n     * @var bool\n     */\n    protected $_blSkipVouchersAvailabilityChecking = null;\n\n    /**\n     * Netto price including discount and voucher\n     *\n     * @var double\n     */\n    protected $_dDiscountedProductNettoPrice = null;\n\n    /**\n     * All VAT values with discount and voucher\n     *\n     * @var array\n     */\n    protected $_aDiscountedVats = null;\n\n    /**\n     * Skip discounts marker\n     *\n     * @var boolean\n     */\n    protected $_blSkipDiscounts = false;\n\n    /**\n     * User set delivery costs\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Price\n     */\n    protected $_oDeliveryPrice = null;\n\n    /**\n     * Basket product stock check (live db check) status\n     *\n     * @var bool\n     */\n    protected $_blCheckStock = true;\n\n    /**\n     * discount calculation marker\n     *\n     * @var bool\n     */\n    protected $_blCalcDiscounts = true;\n\n    /**\n     * Basket category id\n     *\n     * @var string\n     */\n    protected $_sBasketCategoryId = null;\n\n    /**\n     * Category change warning state\n     *\n     * @var bool\n     */\n    protected $_blShowCatChangeWarning = false;\n\n    /**\n     * new basket item addition state\n     *\n     * @var bool\n     */\n    protected $_blNewITemAdded = null;\n\n    /**\n     * if basket has downloadable product\n     *\n     * @var bool\n     */\n    protected $_blDownloadableProducts = null;\n\n\n    /**\n     * Save basket to data base if user is logged in\n     *\n     * @var bool\n     */\n    protected $_blSaveToDataBase = null;\n\n    /**\n     * Save card id\n     *\n     * @var string\n     */\n    protected $_sCardId = null;\n\n    /**\n     * Card message.\n     *\n     * @var string\n     */\n    protected $_sCardMessage = '';\n\n    /**\n     * Enables or disable saving to data base\n     *\n     * @param boolean $blSave\n     */\n    public function enableSaveToDataBase($blSave = true)\n    {\n        $this->_blSaveToDataBase = $blSave;\n    }\n\n    /**\n     * Returns true if saving to data base enabled\n     *\n     * @return boolean\n     */\n    public function isSaveToDataBaseEnabled()\n    {\n        if (is_null($this->_blSaveToDataBase)) {\n            $this->_blSaveToDataBase = (bool) !Registry::getConfig()->getConfigParam('blPerfNoBasketSaving');\n        }\n\n        return $this->_blSaveToDataBase;\n    }\n\n\n    /**\n     * Return true if calculation mode is netto\n     *\n     * @return bool\n     */\n    public function isCalculationModeNetto()\n    {\n        if ($this->_isCalculationModeNetto === null) {\n            $this->setCalculationModeNetto($this->isPriceViewModeNetto());\n        }\n\n        return $this->_isCalculationModeNetto;\n    }\n\n    /**\n     * Set netto calculation mode\n     *\n     * @param bool $blNettoMode - true in netto; false - turn off\n     */\n    public function setCalculationModeNetto($blNettoMode = true)\n    {\n        $this->_isCalculationModeNetto = (bool) $blNettoMode;\n    }\n\n    /**\n     * Return basket netto sum (in B2B view mode sum include discount)\n     *\n     * @return float\n     */\n    public function getNettoSum()\n    {\n        return $this->_dNettoSum;\n    }\n\n    /**\n     * Return basket brutto sum (in B2C view mode sum include discount)\n     *\n     * @return float\n     */\n    public function getBruttoSum()\n    {\n        return $this->_dBruttoSum;\n    }\n\n    /**\n     * Set basket netto sum\n     *\n     * @param float $dNettoSum sum of basket in netto mode\n     */\n    public function setNettoSum($dNettoSum)\n    {\n        $this->_dNettoSum = $dNettoSum;\n    }\n\n    /**\n     * Set basket brutto sum\n     *\n     * @param float $dBruttoSum sum of basket in brutto mode\n     */\n    public function setBruttoSum($dBruttoSum)\n    {\n        $this->_dBruttoSum = $dBruttoSum;\n    }\n\n    /**\n     * Checks if configuration allows basket usage or if user agent is search engine\n     *\n     * @return bool\n     */\n    public function isEnabled()\n    {\n        return !Registry::getUtils()->isSearchEngine();\n    }\n\n    /**\n     * change old key to new one but retain key position in array\n     *\n     * @param string $sOldKey old key\n     * @param string $sNewKey new key to place in old one's place\n     * @param mixed  $value   (optional)\n     */\n    protected function changeBasketItemKey($sOldKey, $sNewKey, $value = null)\n    {\n        reset($this->_aBasketContents);\n        $iOldKeyPlace = 0;\n        while (key($this->_aBasketContents) != $sOldKey && next($this->_aBasketContents)) {\n            ++$iOldKeyPlace;\n        }\n        $aNewCopy = array_merge(\n            array_slice($this->_aBasketContents, 0, $iOldKeyPlace, true),\n            [$sNewKey => $value],\n            array_slice($this->_aBasketContents, $iOldKeyPlace + 1, count($this->_aBasketContents) - $iOldKeyPlace, true)\n        );\n        $this->_aBasketContents = $aNewCopy;\n    }\n\n    /**\n     * Adds user item to basket. Returns oxBasketItem object if adding succeeded\n     *\n     * @param string $sProductID       id of product\n     * @param double $dAmount          product amount\n     * @param mixed  $aSel             product select lists (default null)\n     * @param mixed  $aPersParam       product persistent parameters (default null)\n     * @param bool   $blOverride       marker to accumulate passed amount or renew (default false)\n     * @param bool   $blBundle         marker if product is bundle or not (default false)\n     * @param mixed  $sOldBasketItemId id if old basket item if to change it\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\ArticleInputException\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\NoArticleException\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\OutOfStockException\n     *\n     * @return null|\\OxidEsales\\Eshop\\Application\\Model\\BasketItem\n     */\n    public function addToBasket($sProductID, $dAmount, $aSel = null, $aPersParam = null, $blOverride = false, $blBundle = false, $sOldBasketItemId = null)\n    {\n        // enabled ?\n        if (!$this->isEnabled()) {\n            return null;\n        }\n\n        // basket exclude\n        if (Registry::getConfig()->getConfigParam('blBasketExcludeEnabled')) {\n            if (!$this->canAddProductToBasket($sProductID)) {\n                $this->setCatChangeWarningState(true);\n\n                return null;\n            } else {\n                $this->setCatChangeWarningState(false);\n            }\n        }\n\n        $sItemId = $this->getItemKey($sProductID, $aSel, $aPersParam, $blBundle);\n        if ($sOldBasketItemId && (strcmp($sOldBasketItemId, $sItemId) != 0)) {\n            if (isset($this->_aBasketContents[$sItemId])) {\n                // we are merging, so params will just go to the new key\n                unset($this->_aBasketContents[$sOldBasketItemId]);\n                // do not override stock\n                $blOverride = false;\n            } else {\n                // value is null - means isset will fail and real values will be filled\n                $this->changeBasketItemKey($sOldBasketItemId, $sItemId);\n            }\n        }\n\n        // after some checks item must be removed from basket\n        $blRemoveItem = false;\n\n        // initialling exception storage\n        $oEx = null;\n\n        if (isset($this->_aBasketContents[$sItemId])) {\n            //updating existing\n            try {\n                // setting stock check status\n                $this->_aBasketContents[$sItemId]->setStockCheckStatus($this->getStockCheckMode());\n                //validate amount\n                //possibly throws exception\n                $this->_aBasketContents[$sItemId]->setAmount($dAmount, $blOverride, $sItemId);\n            } catch (\\OxidEsales\\Eshop\\Core\\Exception\\OutOfStockException $oEx) {\n                // rethrow later\n            }\n        } else {\n            //inserting new\n            $oBasketItem = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\BasketItem::class);\n            try {\n                $oBasketItem->setStockCheckStatus($this->getStockCheckMode());\n                $oBasketItem->init($sProductID, $dAmount, $aSel, $aPersParam, $blBundle);\n            } catch (NoArticleException $oEx) {\n                // in this case that the article does not exist remove the item from the basket by setting its amount to 0\n                //$oBasketItem->dAmount = 0;\n                $blRemoveItem = true;\n            } catch (\\OxidEsales\\Eshop\\Core\\Exception\\OutOfStockException $oEx) {\n                // rethrow later\n            } catch (\\OxidEsales\\Eshop\\Core\\Exception\\ArticleInputException $oEx) {\n                // rethrow later\n                $blRemoveItem = true;\n            }\n\n            $this->_aBasketContents[$sItemId] = $oBasketItem;\n        }\n\n        //in case amount is 0 removing item\n        if ($this->_aBasketContents[$sItemId]->getAmount() == 0 || $blRemoveItem) {\n            $this->removeItem($sItemId);\n        } elseif ($blBundle) {\n            //marking bundles\n            $this->_aBasketContents[$sItemId]->setBundle(true);\n        }\n\n        //calling update method\n        $this->onUpdate();\n\n        if ($oEx) {\n            throw $oEx;\n        }\n\n        // notifying that new basket item was added\n        if (!$blBundle) {\n            $this->addedNewItem($blOverride);\n        }\n\n        // returning basket item object\n        if (isset($this->_aBasketContents[$sItemId])\n            && $this->_aBasketContents[$sItemId] instanceof \\OxidEsales\\Eshop\\Application\\Model\\BasketItem) {\n            $this->_aBasketContents[$sItemId]->setBasketItemKey($sItemId);\n        }\n\n        return $this->_aBasketContents[$sItemId] ?? null;\n    }\n\n    /**\n     * Adds order article to basket (method normally used while recalculating order)\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\OrderArticle $oOrderArticle order article to store in basket\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\BasketItem\n     */\n    public function addOrderArticleToBasket($oOrderArticle)\n    {\n        // adding only if amount > 0\n        if ($oOrderArticle->oxorderarticles__oxamount->value > 0 && !$oOrderArticle->isBundle()) {\n            $this->_isForOrderRecalculation = true;\n            $sItemId = $oOrderArticle->getId();\n\n            //inserting new\n            $this->_aBasketContents[$sItemId] = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\BasketItem::class);\n            $this->_aBasketContents[$sItemId]->initFromOrderArticle($oOrderArticle);\n            $this->_aBasketContents[$sItemId]->setWrapping($oOrderArticle->oxorderarticles__oxwrapid->value);\n            $this->_aBasketContents[$sItemId]->setBundle($oOrderArticle->isBundle());\n\n            //calling update method\n            $this->onUpdate();\n\n            return $this->_aBasketContents[$sItemId];\n        } elseif ($oOrderArticle->isBundle()) {\n            // deleting bundles, they are handled automatically\n            $oOrderArticle->delete();\n        }\n    }\n\n    /**\n     * Sets stock control mode\n     *\n     * @param bool $blCheck stock control mode\n     */\n    public function setStockCheckMode($blCheck)\n    {\n        $this->_blCheckStock = $blCheck;\n    }\n\n    /**\n     * Returns stock control mode\n     *\n     * @return bool\n     */\n    public function getStockCheckMode()\n    {\n        return $this->_blCheckStock;\n    }\n\n    /**\n     * Returns unique basket item identifier which consist from product ID,\n     * select lists data, persistent info and bundle property\n     *\n     * @param string $sProductId       basket item id\n     * @param array  $aSel             basket item selectlists\n     * @param array  $aPersParam       basket item persistent parameters\n     * @param bool   $blBundle         bundle marker\n     * @param string $sAdditionalParam possible additional information\n     *\n     * @return string\n     */\n    public function getItemKey($sProductId, $aSel = null, $aPersParam = null, $blBundle = false, $sAdditionalParam = '')\n    {\n        $aSel = ($aSel != null) ? $aSel : [0 => '0'];\n\n        $sItemKey = md5($sProductId . '|' . serialize($aSel) . '|' . serialize($aPersParam) . '|' . (int) $blBundle . '|' . serialize($sAdditionalParam));\n\n        return $sItemKey;\n    }\n\n\n    /**\n     * Removes item from basket\n     *\n     * @param string $sItemKey basket item key\n     */\n    public function removeItem($sItemKey)\n    {\n        if (Registry::getConfig()->getConfigParam('blPsBasketReservationEnabled')) {\n            if (isset($this->_aBasketContents[$sItemKey])) {\n                $sArticleId = $this->_aBasketContents[$sItemKey]->getProductId();\n                if ($sArticleId) {\n                    $session = Registry::getSession();\n                    $session->getBasketReservations()->discardArticleReservation($sArticleId);\n                }\n            }\n        }\n        unset($this->_aBasketContents[$sItemKey]);\n\n        // basket exclude\n        if (!count($this->_aBasketContents) && Registry::getConfig()->getConfigParam('blBasketExcludeEnabled')) {\n            $this->setBasketRootCatId(null);\n        }\n    }\n\n    /**\n     * Unsets bundled basket items from basket contents array\n     */\n    protected function clearBundles()\n    {\n        reset($this->_aBasketContents);\n        foreach ($this->_aBasketContents as $sItemKey => $oBasketItem) {\n            if ($oBasketItem->isBundle()) {\n                $this->removeItem($sItemKey);\n            }\n        }\n    }\n\n    /**\n     * Returns array of bundled articles IDs for basket item\n     *\n     * @param object $oBasketItem basket item object\n     *\n     * @return array\n     */\n    protected function getArticleBundles($oBasketItem)\n    {\n        $aBundles = [];\n\n        if ($oBasketItem->isBundle()) {\n            return $aBundles;\n        }\n\n        $oArticle = $oBasketItem->getArticle(true);\n        if ($oArticle && $oArticle->getFieldData('oxbundleid')) {\n            $aBundles[$oArticle->getFieldData('oxbundleid')] = 1;\n        }\n\n        return $aBundles;\n    }\n\n    /**\n     * Returns array of bundled discount articles\n     *\n     * @param object $oBasketItem basket item object\n     * @param array  $aBundles    array of found bundles\n     *\n     * @return array\n     */\n    protected function getItemBundles($oBasketItem, $aBundles = [])\n    {\n        if ($oBasketItem->isBundle()) {\n            return [];\n        }\n\n        // does this object still exists ?\n        if ($oArticle = $oBasketItem->getArticle()) {\n            $aDiscounts = Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\DiscountList::class)->getBasketItemBundleDiscounts($oArticle, $this, $this->getBasketUser());\n\n            foreach ($aDiscounts as $oDiscount) {\n                $iAmnt = $oDiscount->getBundleAmount($oBasketItem->getAmount());\n                if ($iAmnt) {\n                    //init array element\n                    if (!isset($aBundles[$oDiscount->oxdiscount__oxitmartid->value])) {\n                        $aBundles[$oDiscount->oxdiscount__oxitmartid->value] = 0;\n                    }\n\n                    if ($oDiscount->oxdiscount__oxitmmultiple->value) {\n                        $aBundles[$oDiscount->oxdiscount__oxitmartid->value] += $iAmnt;\n                    } else {\n                        $aBundles[$oDiscount->oxdiscount__oxitmartid->value] = $iAmnt;\n                    }\n                }\n            }\n        }\n\n        return $aBundles;\n    }\n\n    /**\n     * Returns array of bundled discount articles for whole basket\n     *\n     * @param array $aBundles array of found bundles\n     *\n     * @return array\n     */\n    protected function getBasketBundles($aBundles = [])\n    {\n        $aDiscounts = Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\DiscountList::class)->getBasketBundleDiscounts($this, $this->getBasketUser());\n\n        // calculating amount of non bundled/discount items\n        $dAmount = 0;\n        foreach ($this->_aBasketContents as $oBasketItem) {\n            if (!($oBasketItem->isBundle() || $oBasketItem->isDiscountArticle())) {\n                $dAmount += $oBasketItem->getAmount();\n            }\n        }\n\n        foreach ($aDiscounts as $oDiscount) {\n            if ($oDiscount->oxdiscount__oxitmartid->value) {\n                if (!isset($aBundles[$oDiscount->oxdiscount__oxitmartid->value])) {\n                    $aBundles[$oDiscount->oxdiscount__oxitmartid->value] = 0;\n                }\n\n                $aBundles[$oDiscount->oxdiscount__oxitmartid->value] += $oDiscount->getBundleAmount($dAmount);\n            }\n        }\n\n        return $aBundles;\n    }\n\n    /**\n     * Iterates through basket contents and adds bundles to items + adds\n     * global basket bundles\n     */\n    protected function addBundles()\n    {\n        $bundles = [];\n        foreach ($this->_aBasketContents as $key => $basketItem) {\n            try {\n                if (!$basketItem->isDiscountArticle() && !$basketItem->isBundle()) {\n                    $bundles = $this->getItemBundles($basketItem, $bundles);\n                } else {\n                    continue;\n                }\n\n                $artBundles = $this->getArticleBundles($basketItem);\n                $this->addBundlesToBasket($artBundles);\n            } catch (NoArticleException $exception) {\n                $this->handleNoArticleException($basketItem, $exception);\n            } catch (\\OxidEsales\\Eshop\\Core\\Exception\\ArticleInputException $exception) {\n                $this->removeItem($key);\n                Registry::getUtilsView()->addErrorToDisplay($exception);\n            }\n        }\n\n        $bundles = $this->getBasketBundles($bundles);\n\n        if ($bundles) {\n            $this->addBundlesToBasket($bundles);\n        }\n    }\n\n    /**\n     * Adds bundles to basket\n     *\n     * @param array $aBundles added bundle articles\n     */\n    protected function addBundlesToBasket($aBundles)\n    {\n        foreach ($aBundles as $sBundleId => $dAmount) {\n            if ($dAmount) {\n                try {\n                    if ($oBundleItem = $this->addToBasket($sBundleId, $dAmount, null, null, false, true)) {\n                        $oBundleItem->setAsDiscountArticle(true);\n                    }\n                } catch (\\OxidEsales\\Eshop\\Core\\Exception\\ArticleException $oEx) {\n                    // caught and ignored\n                    if ($oEx instanceof \\OxidEsales\\Eshop\\Core\\Exception\\OutOfStockException && $oEx->getRemainingAmount() > 0) {\n                        $sItemId = $this->getItemKey($sBundleId, null, null, true);\n                        $this->_aBasketContents[$sItemId]->setAsDiscountArticle(true);\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * Iterates through basket items and calculates its prices and discounts\n     */\n    protected function calcItemsPrice()\n    {\n        // resetting\n        $this->setSkipDiscounts(false);\n        $this->_iProductsCnt = 0; // count different types\n        $this->_dItemsCnt = 0; // count of item units\n        $this->_dWeight = 0; // basket weight\n\n        $this->_oProductsPriceList = oxNew(\\OxidEsales\\Eshop\\Core\\PriceList::class);\n        $this->_oDiscountProductsPriceList = oxNew(\\OxidEsales\\Eshop\\Core\\PriceList::class);\n        $this->_oNotDiscountedProductsPriceList = oxNew(\\OxidEsales\\Eshop\\Core\\PriceList::class);\n\n        $oDiscountList = Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\DiscountList::class);\n\n        /** @var \\oxBasketItem $oBasketItem */\n        foreach ($this->_aBasketContents as $oBasketItem) {\n            $this->_iProductsCnt++;\n            $this->_dItemsCnt += $oBasketItem->getAmount();\n            $this->_dWeight += $oBasketItem->getWeight();\n\n            if (!$oBasketItem->isDiscountArticle() && ($oArticle = $oBasketItem->getArticle(true))) {\n                $oBasketPrice = $oArticle->getBasketPrice($oBasketItem->getAmount(), $oBasketItem->getSelList(), $this);\n                $oBasketItem->setRegularUnitPrice(clone $oBasketPrice);\n\n                if (!$oArticle->skipDiscounts() && $this->canCalcDiscounts()) {\n                    // apply basket type discounts for item\n                    $aDiscounts = $oDiscountList->getBasketItemDiscounts($oArticle, $this, $this->getBasketUser());\n                    reset($aDiscounts);\n                    /** @var \\oxDiscount $oDiscount */\n                    foreach ($aDiscounts as $oDiscount) {\n                        $oBasketPrice->setDiscount($oDiscount->getAddSum(), $oDiscount->getAddSumType());\n                    }\n                    $oBasketPrice->calculateDiscount();\n                } else {\n                    $oBasketItem->setSkipDiscounts(true);\n                    $this->setSkipDiscounts(true);\n                }\n\n                $oBasketItem->setPrice($oBasketPrice);\n                $this->_oProductsPriceList->addToPriceList($oBasketItem->getPrice());\n\n                //P collect discount values for basket items which are discountable\n                if (!$oArticle->skipDiscounts()) {\n                    $this->_oDiscountProductsPriceList->addToPriceList($oBasketItem->getPrice());\n                } else {\n                    $this->_oNotDiscountedProductsPriceList->addToPriceList($oBasketItem->getPrice());\n                    $oBasketItem->setSkipDiscounts(true);\n                    $this->setSkipDiscounts(true);\n                }\n            } elseif ($oBasketItem->isBundle()) {\n                // if bundles price is set to zero\n                $oPrice = oxNew(Price::class);\n                $oBasketItem->setPrice($oPrice);\n            }\n        }\n    }\n\n    /**\n     * Sets discount calculation mode\n     *\n     * @param bool $blCalcDiscounts calculate discounts or not\n     */\n    public function setDiscountCalcMode($blCalcDiscounts)\n    {\n        $this->_blCalcDiscounts = $blCalcDiscounts;\n    }\n\n    /**\n     * Returns true if discount calculation is enabled\n     *\n     * @return bool\n     */\n    public function canCalcDiscounts()\n    {\n        return $this->_blCalcDiscounts;\n    }\n\n    /**\n     * Merges two discount arrays. If there are two the same\n     * discounts, discount values will be added.\n     *\n     * @param array $aDiscounts     Discount array\n     * @param array $aItemDiscounts Discount array\n     *\n     * @return array $aDiscounts\n     */\n    protected function mergeDiscounts($aDiscounts, $aItemDiscounts)\n    {\n        foreach ($aItemDiscounts as $sKey => $oDiscount) {\n            // add prices of the same discounts\n            if (array_key_exists($sKey, $aDiscounts)) {\n                $aDiscounts[$sKey]->dDiscount += $oDiscount->dDiscount;\n            } else {\n                $aDiscounts[$sKey] = $oDiscount;\n            }\n        }\n\n        return $aDiscounts;\n    }\n\n    /**\n     * Iterates through basket items and calculates its delivery costs\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    protected function calcDeliveryCost()\n    {\n        if ($this->_oDeliveryPrice !== null) {\n            return $this->_oDeliveryPrice;\n        }\n        $myConfig = Registry::getConfig();\n        $oDeliveryPrice = oxNew(Price::class);\n\n        if (Registry::getConfig()->getConfigParam('blDeliveryVatOnTop')) {\n            $oDeliveryPrice->setNettoPriceMode();\n        } else {\n            $oDeliveryPrice->setBruttoPriceMode();\n        }\n\n        // don't calculate if not logged in\n        $oUser = $this->getBasketUser();\n\n        if (!$oUser && !$myConfig->getConfigParam('blCalculateDelCostIfNotLoggedIn')) {\n            return $oDeliveryPrice;\n        }\n\n        $fDelVATPercent = $this->getAdditionalServicesVatPercent();\n        $oDeliveryPrice->setVat($fDelVATPercent);\n\n        // list of active delivery costs\n        if ($myConfig->getConfigParam('bl_perfLoadDelivery')) {\n            $aDeliveryList = Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\DeliveryList::class)->getDeliveryList(\n                $this,\n                $oUser,\n                $this->findDelivCountry(),\n                $this->getShippingId()\n            );\n\n            if (count($aDeliveryList) > 0) {\n                foreach ($aDeliveryList as $oDelivery) {\n                    $oDeliveryPrice->addPrice($oDelivery->getDeliveryPrice($fDelVATPercent));\n                }\n            }\n        }\n\n        return $oDeliveryPrice;\n    }\n\n    /**\n     * Basket user getter\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\User\n     */\n    public function getBasketUser()\n    {\n        if ($this->_oUser == null) {\n            return $this->getUser();\n        }\n\n        return $this->_oUser;\n    }\n\n    /**\n     * Basket user setter\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser Basket user\n     */\n    public function setBasketUser($oUser)\n    {\n        $this->_oUser = $oUser;\n    }\n\n    /**\n     * Get most used vat percent:\n     *\n     * @return double\n     */\n    public function getMostUsedVatPercent()\n    {\n        if ($this->_oProductsPriceList) {\n            return $this->_oProductsPriceList->getMostUsedVatPercent();\n        }\n    }\n\n    /**\n     * Get most used vat percent:\n     *\n     * @return double\n     */\n    public function getAdditionalServicesVatPercent()\n    {\n        if ($this->_oProductsPriceList) {\n            if (Registry::getConfig()->getConfigParam('sAdditionalServVATCalcMethod') == 'proportional') {\n                return $this->_oProductsPriceList->getProportionalVatPercent();\n            } else {\n                return $this->_oProductsPriceList->getMostUsedVatPercent();\n            }\n        }\n    }\n\n    /**\n     * Get most used vat percent:\n     *\n     * @return double\n     */\n    public function isProportionalCalculationOn()\n    {\n        if (Registry::getConfig()->getConfigParam('sAdditionalServVATCalcMethod') == 'proportional') {\n            return true;\n        }\n\n        return false;\n    }\n\n\n    //P\n    /**\n     * Performs final sum calculation and rounding.\n     */\n    protected function calcTotalPrice()\n    {\n        // 1. add products price\n        $dPrice = $this->_dBruttoSum;\n\n\n        /** @var \\OxidEsales\\Eshop\\Core\\Price $oTotalPrice */\n        $oTotalPrice = oxNew(Price::class);\n        $oTotalPrice->setBruttoPriceMode();\n        $oTotalPrice->setPrice($dPrice);\n\n        // 2. subtract discounts\n        if ($dPrice && !$this->isCalculationModeNetto()) {\n            // 2.2 applying basket discounts\n            $oTotalPrice->subtract($this->_oTotalDiscount->getBruttoPrice());\n\n            // 2.3 applying voucher discounts\n            if ($oVoucherDisc = $this->getVoucherDiscount()) {\n                $oTotalPrice->subtract($oVoucherDisc->getBruttoPrice());\n            }\n        }\n\n        // 2.3 add delivery cost\n        if (isset($this->_aCosts['oxdelivery'])) {\n            $oTotalPrice->add($this->_aCosts['oxdelivery']->getBruttoPrice());\n        }\n\n        // 2.4 add wrapping price\n        if (isset($this->_aCosts['oxwrapping'])) {\n            $oTotalPrice->add($this->_aCosts['oxwrapping']->getBruttoPrice());\n        }\n        if (isset($this->_aCosts['oxgiftcard'])) {\n            $oTotalPrice->add($this->_aCosts['oxgiftcard']->getBruttoPrice());\n        }\n\n        // 2.5 add payment price\n        if (isset($this->_aCosts['oxpayment'])) {\n            $oTotalPrice->add($this->_aCosts['oxpayment']->getBruttoPrice());\n        }\n\n        $this->setPrice($oTotalPrice);\n    }\n\n    /**\n     * Voucher discount setter\n     *\n     * @param double $dDiscount voucher discount value\n     */\n    public function setVoucherDiscount($dDiscount)\n    {\n        $this->_oVoucherDiscount = oxNew(Price::class);\n        $this->_oVoucherDiscount->setBruttoPriceMode();\n        $this->_oVoucherDiscount->add($dDiscount);\n    }\n\n    /**\n     * Calculates voucher discount\n     */\n    protected function calcVoucherDiscount()\n    {\n        if (Registry::getConfig()->getConfigParam('bl_showVouchers') && ($this->_oVoucherDiscount === null || ($this->_blUpdateNeeded && !$this->isAdmin()))) {\n            $this->_oVoucherDiscount = $this->getPriceObject();\n\n            // calculating price to apply discount\n            $dPrice = $this->_oDiscountProductsPriceList->getSum($this->isCalculationModeNetto()) - $this->_oTotalDiscount->getPrice();\n\n            // recalculating\n            if (count($this->_aVouchers)) {\n                $oLang = Registry::getLang();\n                foreach ($this->_aVouchers as $sVoucherId => $oStdVoucher) {\n                    $oVoucher = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Voucher::class);\n                    try { // checking\n                        $oVoucher->load($oStdVoucher->sVoucherId);\n\n                        if (!$this->_blSkipVouchersAvailabilityChecking) {\n                            $oVoucher->checkBasketVoucherAvailability($this->_aVouchers, $dPrice);\n                            $oVoucher->checkUserAvailability($this->getBasketUser());\n                            $oVoucher->markAsReserved();\n                        }\n\n                        // assigning real voucher discount value as this is the only place where real value is calculated\n                        $dVoucherdiscount = $oVoucher->getDiscountValue($dPrice);\n\n                        if ($dVoucherdiscount > 0) {\n                            $dVatPart = ($dPrice - $dVoucherdiscount) / $dPrice * 100;\n\n                            if (!$this->_aDiscountedVats) {\n                                if ($oPriceList = $this->getDiscountProductsPrice()) {\n                                    $this->_aDiscountedVats = $oPriceList->getVatInfo($this->isCalculationModeNetto());\n                                }\n                            }\n\n                            // apply discount to vat\n                            foreach ($this->_aDiscountedVats as $sKey => $dVat) {\n                                $this->_aDiscountedVats[$sKey] = Price::percent($dVat, $dVatPart);\n                            }\n                        }\n\n                        // accumulating discount value\n                        $this->_oVoucherDiscount->add($dVoucherdiscount);\n\n                        // collecting formatted for preview\n                        $oStdVoucher->fVoucherdiscount = $oLang->formatCurrency($dVoucherdiscount, $this->getBasketCurrency());\n                        $oStdVoucher->dVoucherdiscount = $dVoucherdiscount;\n\n                        // subtracting voucher discount\n                        $dPrice = $dPrice - $dVoucherdiscount;\n                    } catch (\\OxidEsales\\Eshop\\Core\\Exception\\VoucherException $oEx) {\n                        // removing voucher on error\n                        $oVoucher->unMarkAsReserved();\n                        unset($this->_aVouchers[$sVoucherId]);\n\n                        // storing voucher error info\n                        Registry::getUtilsView()->addErrorToDisplay($oEx, false, true);\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * Performs netto price and VATs calculations including discounts and vouchers.\n     */\n    protected function applyDiscounts()\n    {\n        //apply discounts for brutto price\n        $dDiscountedSum = $this->getDiscountedProductsSum();\n\n        $oUtils = Registry::getUtils();\n        $dVatSum = 0;\n        foreach ($this->_aDiscountedVats as $dVat) {\n            $dVatSum += $oUtils->fRound($dVat, $this->_oCurrency);\n        }\n\n        $oNotDiscounted = $this->getNotDiscountProductsPrice();\n\n        if ($this->isCalculationModeNetto()) {\n            // netto view mode\n            $this->setNettoSum($this->getProductsPrice()->getSum());\n            $this->setBruttoSum($oNotDiscounted->getSum(false) + $dDiscountedSum + $dVatSum);\n        } else {\n            // brutto view mode\n            $this->setNettoSum($oNotDiscounted->getSum() + $dDiscountedSum - $dVatSum);\n            $this->setBruttoSum($this->getProductsPrice()->getSum(false));\n        }\n    }\n\n    /**\n     * Returns true if view mode is netto\n     *\n     * @return bool\n     */\n    public function isPriceViewModeNetto()\n    {\n        $blResult = (bool) Registry::getConfig()->getConfigParam('blShowNetPrice');\n        $oUser = $this->getBasketUser();\n        if ($oUser) {\n            $blResult = $oUser->isPriceViewModeNetto();\n        }\n\n        return $blResult;\n    }\n\n    /**\n     * Returns prepared price object depending on view mode\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    protected function getPriceObject()\n    {\n        $oPrice = oxNew(Price::class);\n\n        if ($this->isCalculationModeNetto()) {\n            $oPrice->setNettoPriceMode();\n        } else {\n            $oPrice->setBruttoPriceMode();\n        }\n\n        return $oPrice;\n    }\n\n    /**\n     * Loads basket discounts and calculates discount values.\n     */\n    protected function calcBasketDiscount()\n    {\n        // resetting\n        $this->_aDiscounts = [];\n\n        // P using prices sum which has discount, not sum of skipped discounts\n        $dOldPrice = $this->_oDiscountProductsPriceList->getSum($this->isCalculationModeNetto());\n\n        // add basket discounts\n        if ($this->_oTotalDiscount !== null && isset($this->_isForOrderRecalculation) && $this->_isForOrderRecalculation) {\n            //if total discount was set on order recalculation\n            $oTotalPrice = $this->getTotalDiscount();\n            $oDiscount = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Discount::class);\n            $oDiscount->oxdiscount__oxaddsum = new \\OxidEsales\\Eshop\\Core\\Field($oTotalPrice->getPrice());\n            $oDiscount->oxdiscount__oxaddsumtype = new \\OxidEsales\\Eshop\\Core\\Field('abs');\n            $aDiscounts[] = $oDiscount;\n        } else {\n            // discounts for basket\n            $aDiscounts = Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\DiscountList::class)->getBasketDiscounts($this, $this->getBasketUser());\n        }\n\n        if ($oPriceList = $this->getDiscountProductsPrice()) {\n            $this->_aDiscountedVats = $oPriceList->getVatInfo($this->isCalculationModeNetto());\n        }\n\n        /** @var \\oxDiscount $oDiscount */\n        foreach ($aDiscounts as $oDiscount) {\n            // storing applied discounts\n            $oStdDiscount = $oDiscount->getSimpleDiscount();\n\n            // skipping bundle discounts\n            if ($oDiscount->oxdiscount__oxaddsumtype->value == 'itm') {\n                continue;\n            }\n\n            // saving discount info\n            $oStdDiscount->dDiscount = $oDiscount->getAbsValue($dOldPrice);\n\n            $dVatPart = 100 - $oDiscount->getPercentage($dOldPrice);\n\n            // if discount is more than basket sum\n            if ($dOldPrice < $oStdDiscount->dDiscount) {\n                $oStdDiscount->dDiscount = $dOldPrice;\n                $dVatPart = 0;\n            }\n\n            // apply discount to vat\n            foreach ($this->_aDiscountedVats as $sKey => $dVat) {\n                $this->_aDiscountedVats[$sKey] = Price::percent($dVat, $dVatPart);\n            }\n\n            //storing discount\n            if ($oStdDiscount->dDiscount != 0) {\n                $this->_aDiscounts[$oDiscount->getId()] = $oStdDiscount;\n                // subtracting product price after discount\n                $dOldPrice = $dOldPrice - $oStdDiscount->dDiscount;\n            }\n        }\n    }\n\n    /**\n     * Calculates total basket discount value.\n     */\n    protected function calcBasketTotalDiscount()\n    {\n        if ($this->_oTotalDiscount === null || (!$this->isAdmin())) {\n            $this->_oTotalDiscount = $this->getPriceObject();\n\n            if (is_array($this->_aDiscounts)) {\n                foreach ($this->_aDiscounts as $oDiscount) {\n                    // skipping bundle discounts\n                    if ($oDiscount->sType == 'itm') {\n                        continue;\n                    }\n\n                    // add discount value to total basket discount\n                    $this->_oTotalDiscount->add($oDiscount->dDiscount);\n                }\n            }\n        }\n    }\n\n    /**\n     * Adds Gift price info to $this->oBasket (additional field for\n     * basket item \"oWrap\"\"). Loads each basket item, checks for\n     * wrapping data, updates if available and stores back into\n     * $this->oBasket. Returns price object for wrapping.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    protected function calcBasketWrapping()\n    {\n        $oWrappingPrices = oxNew(\\OxidEsales\\Eshop\\Core\\PriceList::class);\n\n        /** @var \\oxBasketItem $oBasketItem */\n        foreach ($this->_aBasketContents as $oBasketItem) {\n            if (($oWrapping = $oBasketItem->getWrapping())) {\n                $oWrappingPrice = $oWrapping->getWrappingPrice($oBasketItem->getAmount());\n                $oWrappingPrice->setVat($oBasketItem->getPrice()->getVat());\n\n                $oWrappingPrices->addToPriceList($oWrappingPrice);\n            }\n        }\n\n\n        return $oWrappingPrices->calculateToPrice();\n    }\n\n    /**\n     * Adds Gift price info to $this->oBasket (additional field for\n     * basket item \"oWrap\"\"). Loads each basket item, checks for\n     * wrapping data, updates if available and stores back into\n     * $this->oBasket. Returns oxprice object for wrapping.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    protected function calcBasketGiftCard()\n    {\n        $oGiftCardPrice = oxNew(Price::class);\n\n        if (Registry::getConfig()->getConfigParam('blWrappingVatOnTop')) {\n            $oGiftCardPrice->setNettoPriceMode();\n        } else {\n            $oGiftCardPrice->setBruttoPriceMode();\n        }\n\n        $dVATPercent = $this->getAdditionalServicesVatPercent();\n\n        $oGiftCardPrice->setVat($dVATPercent);\n\n        // gift card price calculation\n        if (($oCard = $this->getCard())) {\n            if ($dVATPercent !== null) {\n                $oCard->setWrappingVat($dVATPercent);\n            }\n            $oGiftCardPrice->addPrice($oCard->getWrappingPrice());\n        }\n\n        return $oGiftCardPrice;\n    }\n\n    /**\n     * Payment cost calculation, applying payment discount if available.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    protected function calcPaymentCost()\n    {\n        // resetting values\n        $oPaymentPrice = oxNew(Price::class);\n\n        // payment\n        if (($this->_sPaymentId = $this->getPaymentId())) {\n            $oPayment = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Payment::class);\n            $oPayment->load($this->_sPaymentId);\n\n            $oPayment->calculate($this);\n            $oPaymentPrice = $oPayment->getPrice();\n        }\n\n        return $oPaymentPrice;\n    }\n\n    /**\n     * Sets basket additional costs\n     *\n     * @param string $sCostName additional costs\n     * @param object $oPrice    \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function setCost($sCostName, $oPrice = null)\n    {\n        $this->_aCosts[$sCostName] = $oPrice;\n    }\n\n    /**\n     * Executes all needed functions to calculate basket price and other needed\n     * info\n     *\n     * @param bool $blForceUpdate set this parameter to TRUE to force basket recalculation\n     *\n     * @return null\n     */\n    public function calculateBasket($blForceUpdate = false)\n    {\n        /*\n        //would be good to perform the reset of previous calculation\n        //at least you can use it for the debug\n        $this->_aDiscounts = array();\n        $this->_aItemDiscounts = array();\n        $this->_oTotalDiscount = null;\n        $this->_dDiscountedProductNettoPrice = 0;\n        $this->_aDiscountedVats = array();\n        $this->_oPrice = null;\n        $this->_oNotDiscountedProductsPriceList = null;\n        $this->_oProductsPriceList = null;\n        $this->_oDiscountProductsPriceList = null;*/\n\n        if (!$this->isEnabled()) {\n            return;\n        }\n\n        if ($blForceUpdate) {\n            $this->onUpdate();\n        }\n\n        if (!($this->_blUpdateNeeded || $blForceUpdate)) {\n            return;\n        }\n\n        $this->_aCosts = [];\n\n        //  1. saving basket to the database\n        $this->save();\n\n        //  2. remove all bundles\n        $this->clearBundles();\n\n        //  3. generate bundle items\n        $this->addBundles();\n\n        //  4. calculating item prices\n        $this->calcItemsPrice();\n\n        //  5. calculating/applying discounts\n        $this->calcBasketDiscount();\n\n        //  6. calculating basket total discount\n        $this->calcBasketTotalDiscount();\n\n        //  7. check for vouchers\n        $this->calcVoucherDiscount();\n\n        //  8. applies all discounts to pricelist\n        $this->applyDiscounts();\n\n        //  9. calculating additional costs:\n        //  9.1: delivery\n        $this->setCost('oxdelivery', $this->calcDeliveryCost());\n\n        //  9.2: adding wrapping and gift card costs\n        $this->setCost('oxwrapping', $this->calcBasketWrapping());\n\n        $this->setCost('oxgiftcard', $this->calcBasketGiftCard());\n\n        //  9.3: adding payment cost\n        $this->setCost('oxpayment', $this->calcPaymentCost());\n\n        //  10. calculate total price\n        $this->calcTotalPrice();\n\n        //  11. formatting discounts\n        $this->formatDiscount();\n\n        //  12.setting to up-to-date status\n        $this->afterUpdate();\n    }\n\n    /**\n     * Notifies basket that recalculation is needed\n     */\n    public function onUpdate()\n    {\n        $this->_blUpdateNeeded = true;\n    }\n\n    /**\n     * Marks basket as up-to-date\n     */\n    public function afterUpdate()\n    {\n        $this->_blUpdateNeeded = false;\n    }\n\n    /**\n     * Function collects summary information about basket. Usually this info\n     * is used while calculating discounts or so. Data is stored in static\n     * class parameter \\OxidEsales\\Eshop\\Application\\Model\\Basket::$_aBasketSummary\n     *\n     * @return object\n     */\n    public function getBasketSummary()\n    {\n        if ($this->_blUpdateNeeded || $this->_aBasketSummary === null) {\n            $this->_aBasketSummary = new stdclass();\n            $this->_aBasketSummary->aArticles = [];\n            $this->_aBasketSummary->aCategories = [];\n            $this->_aBasketSummary->iArticleCount = 0;\n            $this->_aBasketSummary->dArticlePrice = 0;\n            $this->_aBasketSummary->dArticleDiscountablePrice = 0;\n        }\n\n        if (!$this->isEnabled()) {\n            return $this->_aBasketSummary;\n        }\n\n        $myConfig = Registry::getConfig();\n        /** @var \\OxidEsales\\EshopCommunity\\Application\\Model\\BasketItem $oBasketItem */\n        foreach ($this->_aBasketContents as $oBasketItem) {\n            if (!$oBasketItem->isBundle() && $oArticle = $oBasketItem->getArticle(false)) {\n                $aCatIds = $oArticle->getCategoryIds();\n                //#M530 if price is not loaded for articles\n                $dPrice = 0;\n                $dDiscountablePrice = 0;\n                if (($oPrice = $oArticle->getBasketPrice($oBasketItem->getAmount(), $oBasketItem->getSelList(), $this))) {\n                    $dPrice = $oPrice->getPrice();\n                    if (!$oArticle->skipDiscounts()) {\n                        $dDiscountablePrice = $dPrice;\n                    }\n                }\n\n                foreach ($aCatIds as $sCatId) {\n                    if (!isset($this->_aBasketSummary->aCategories[$sCatId])) {\n                        $priceObject = new stdClass();\n                        $priceObject->dPrice = 0;\n                        $priceObject->dDiscountablePrice = 0;\n                        $priceObject->dAmount = 0;\n                        $priceObject->iCount = 0;\n                        $this->_aBasketSummary->aCategories[$sCatId] = $priceObject;\n                    }\n\n                    $categorySummaryPrice = $this->_aBasketSummary->aCategories[$sCatId];\n                    $categorySummaryPrice->dPrice += $dPrice * $oBasketItem->getAmount();\n                    $categorySummaryPrice->dDiscountablePrice += $dDiscountablePrice * $oBasketItem->getAmount();\n                    $categorySummaryPrice->dAmount += $oBasketItem->getAmount();\n                    $categorySummaryPrice->iCount++;\n                }\n\n                // variant handling\n                if (($sParentId = $oArticle->getParentId()) && $myConfig->getConfigParam('blVariantParentBuyable')) {\n                    if (!isset($this->_aBasketSummary->aArticles[$sParentId])) {\n                        $this->_aBasketSummary->aArticles[$sParentId] = 0;\n                    }\n                    $this->_aBasketSummary->aArticles[$sParentId] += $oBasketItem->getAmount();\n                }\n\n                if (!isset($this->_aBasketSummary->aArticles[$oBasketItem->getProductId()])) {\n                    $this->_aBasketSummary->aArticles[$oBasketItem->getProductId()] = 0;\n                }\n\n                $this->_aBasketSummary->aArticles[$oBasketItem->getProductId()] += $oBasketItem->getAmount();\n                $this->_aBasketSummary->iArticleCount += $oBasketItem->getAmount();\n                $this->_aBasketSummary->dArticlePrice += $dPrice * $oBasketItem->getAmount();\n                $this->_aBasketSummary->dArticleDiscountablePrice += $dDiscountablePrice * $oBasketItem->getAmount();\n            }\n        }\n\n        return $this->_aBasketSummary;\n    }\n\n    /**\n     * Checks and sets voucher information. Checks it's availability according\n     * to few conditions: oxvoucher::checkVoucherAvailability(),\n     * oxvoucher::checkUserAvailability(). After all voucher is marked as reserved\n     * (oxvoucher::MarkAsReserved())\n     *\n     * @param string $sVoucherId voucher ID\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\VoucherException\n     */\n    public function addVoucher($sVoucherId)\n    {\n        // calculating price to check\n        // P using prices sum which has discount, not sum of skipped discounts\n        $dPrice = 0;\n        if ($this->_oDiscountProductsPriceList) {\n            $dPrice = $this->_oDiscountProductsPriceList->getSum($this->isCalculationModeNetto());\n        }\n\n        // trying to load voucher and apply it\n        $oVoucher = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Voucher::class);\n\n        if (!$this->_blSkipVouchersAvailabilityChecking) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster();\n\n            $oDb->startTransaction();\n\n            try {\n                $oVoucher->getVoucherByNr($sVoucherId, $this->_aVouchers, true);\n                $oVoucher->checkVoucherAvailability($this->_aVouchers, $dPrice);\n                $oVoucher->checkUserAvailability($this->getBasketUser());\n                $oVoucher->markAsReserved();\n            } catch (\\Exception $exception) {\n                $oDb->rollbackTransaction();\n\n                if ($exception instanceof \\OxidEsales\\Eshop\\Core\\Exception\\VoucherException) {\n                    throw $exception;\n                } else {\n                    $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\VoucherException::class);\n                    $oEx->setMessage('Something went wrong, please try again');\n                    $oEx->setVoucherNr($oVoucher->oxvouchers__oxvouchernr->value);\n                    throw $oEx;\n                }\n            }\n\n            $oDb->commitTransaction();\n        } else {\n            $oVoucher->load($sVoucherId);\n        }\n\n        // saving voucher info\n        $this->_aVouchers[$oVoucher->oxvouchers__oxid->value] = $oVoucher->getSimpleVoucher();\n\n\n        $this->onUpdate();\n    }\n\n    /**\n     * Removes voucher from basket and unreserved it.\n     *\n     * @param string $sVoucherId removable voucher ID\n     */\n    public function removeVoucher($sVoucherId)\n    {\n        // removing if it exists\n        if (isset($this->_aVouchers[$sVoucherId])) {\n            $oVoucher = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Voucher::class);\n            $oVoucher->load($sVoucherId);\n\n            $oVoucher->unMarkAsReserved();\n\n            // unset it if exists this voucher in DB or not\n            unset($this->_aVouchers[$sVoucherId]);\n            $this->onUpdate();\n        }\n    }\n\n    /**\n     * Resets user related information kept in basket object\n     */\n    public function resetUserInfo()\n    {\n        $this->setPayment(null);\n        $this->setShipping(null);\n    }\n\n    /**\n     * Formatting discounts\n     */\n    protected function formatDiscount()\n    {\n        // discount information\n        // formatting discount value\n        $this->aDiscounts = $this->getDiscounts();\n        if (is_array($this->aDiscounts) && count($this->aDiscounts) > 0) {\n            $oLang = Registry::getLang();\n            foreach ($this->aDiscounts as $oDiscount) {\n                $oDiscount->fDiscount = $oLang->formatCurrency($oDiscount->dDiscount, $this->getBasketCurrency());\n            }\n        }\n    }\n\n    /**\n     * Populates current basket from the saved one.\n     *\n     * @return null\n     */\n    public function load()\n    {\n        $oUser = $this->getBasketUser();\n        if (!$oUser) {\n            return;\n        }\n\n        $oBasket = $oUser->getBasket('savedbasket');\n\n        // restoring from saved history\n        $aSavedItems = $oBasket->getItems();\n        foreach ($aSavedItems as $oItem) {\n            try {\n                $oSelList = $oItem->getSelList();\n\n                $this->addToBasket($oItem->oxuserbasketitems__oxartid->value, $oItem->oxuserbasketitems__oxamount->value, $oSelList, $oItem->getPersParams(), true);\n            } catch (\\OxidEsales\\Eshop\\Core\\Exception\\ArticleException $oEx) {\n                // caught and ignored\n            }\n        }\n    }\n\n    /**\n     * Saves existing basket to database\n     */\n    protected function save()\n    {\n        if ($this->isSaveToDataBaseEnabled()) {\n            if ($oUser = $this->getBasketUser()) {\n                //first delete all contents\n                //#2039\n                $oSavedBasket = $oUser->getBasket('savedbasket');\n                $oSavedBasket->delete();\n\n                //then save\n                /** @var \\oxBasketItem $oBasketItem */\n                foreach ($this->_aBasketContents as $oBasketItem) {\n                    // discount or bundled products will be added automatically if available\n                    if (!$oBasketItem->isBundle() && !$oBasketItem->isDiscountArticle()) {\n                        $oSavedBasket->addItemToBasket($oBasketItem->getProductId(), $oBasketItem->getAmount(), $oBasketItem->getSelList(), true, $oBasketItem->getPersParams());\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * Cleans up saved basket data. This method usually is initiated by\n     * \\OxidEsales\\Eshop\\Application\\Model\\Basket::deleteBasket() method which cleans up basket data when\n     * user completes order.\n     */\n    protected function deleteSavedBasket()\n    {\n        // deleting basket if session user available\n        if ($oUser = $this->getBasketUser()) {\n            $oUser->getBasket('savedbasket')->delete();\n        }\n\n        // basket exclude\n        if (Registry::getConfig()->getConfigParam('blBasketExcludeEnabled')) {\n            $this->setBasketRootCatId(null);\n        }\n    }\n\n    /**\n     * Tries to fetch user delivery country ID\n     *\n     * @return string\n     */\n    protected function findDelivCountry()\n    {\n        $myConfig = Registry::getConfig();\n        $oUser = $this->getBasketUser();\n\n        $sDeliveryCountry = null;\n\n        if (!$oUser) {\n            // don't calculate if not logged in unless specified otherwise\n            $aHomeCountry = $myConfig->getConfigParam('aHomeCountry');\n            if ($myConfig->getConfigParam('blCalculateDelCostIfNotLoggedIn') && is_array($aHomeCountry)) {\n                $sDeliveryCountry = current($aHomeCountry);\n            }\n        } else {\n            // ok, logged in\n            if ($sCountryId = $myConfig->getGlobalParameter('delcountryid')) {\n                $sDeliveryCountry = $sCountryId;\n            } elseif ($sAddressId = Registry::getSession()->getVariable('deladrid')) {\n                $oDeliveryAddress = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Address::class);\n                if ($oDeliveryAddress->load($sAddressId)) {\n                    $sDeliveryCountry = $oDeliveryAddress->oxaddress__oxcountryid->value;\n                }\n            }\n\n            // still not found ?\n            if (!$sDeliveryCountry) {\n                $sDeliveryCountry = $oUser->getFieldData('oxcountryid');\n            }\n        }\n\n        return $sDeliveryCountry;\n    }\n\n    /**\n     * Deletes user basket object from session\n     */\n    public function deleteBasket()\n    {\n        $session = Registry::getSession();\n        $this->_aBasketContents = [];\n        $session->delBasket();\n\n        if (Registry::getConfig()->getConfigParam('blPsBasketReservationEnabled')) {\n            $session->getBasketReservations()->discardReservations();\n        }\n\n        // merging basket history\n        $this->deleteSavedBasket();\n    }\n\n    /**\n     * Set basket payment ID\n     *\n     * @param string $sPaymentId payment id\n     */\n    public function setPayment($sPaymentId = null)\n    {\n        $this->_sPaymentId = $sPaymentId;\n    }\n\n    /**\n     * Get basket payment, if payment id is not set, try to get it from session\n     *\n     * @return string\n     */\n    public function getPaymentId()\n    {\n        if (!$this->_sPaymentId) {\n            $this->_sPaymentId = Registry::getSession()->getVariable('paymentid');\n        }\n\n        return $this->_sPaymentId;\n    }\n\n    /**\n     * Set basket shipping set ID\n     *\n     * @param string $sShippingSetId delivery set id\n     */\n    public function setShipping($sShippingSetId = null)\n    {\n        $this->_sShippingSetId = $sShippingSetId;\n        Registry::getSession()->setVariable('sShipSet', $sShippingSetId);\n    }\n\n    /**\n     * Set basket shipping price\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Price $oShippingPrice delivery costs\n     */\n    public function setDeliveryPrice($oShippingPrice = null)\n    {\n        $this->_oDeliveryPrice = $oShippingPrice;\n    }\n\n    /**\n     * Get basket shipping set, if shipping set id is not set, try to get it from session\n     *\n     * @return string oxDeliverySet\n     */\n    public function getShippingId()\n    {\n        if (!$this->_sShippingSetId) {\n            $this->_sShippingSetId = Registry::getSession()->getVariable('sShipSet');\n        }\n\n        $sActPaymentId = $this->getPaymentId();\n        // setting default if none is set\n        if (!$this->_sShippingSetId && $sActPaymentId != 'oxempty') {\n            $oUser = $this->getUser();\n\n            // choosing first preferred delivery set\n            list(, $sActShipSet) = Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\DeliverySetList::class)->getDeliverySetData(null, $oUser, $this);\n            // in case nothing was found and no user set - choosing default\n            $this->_sShippingSetId = $sActShipSet ? $sActShipSet : ($oUser ? null : 'oxidstandard');\n        } elseif (!$this->isAdmin() && $sActPaymentId == 'oxempty') {\n            // in case 'oxempty' is payment id - delivery set must be reset\n            $this->_sShippingSetId = null;\n        }\n\n        return $this->_sShippingSetId;\n    }\n\n    /**\n     * Returns array of basket oxarticle objects\n     *\n     * @return array\n     */\n    public function getBasketArticles()\n    {\n        $aBasketArticles = [];\n        /** @var \\oxBasketItem $oBasketItem */\n        foreach ($this->_aBasketContents as $sItemKey => $oBasketItem) {\n            try {\n                $oProduct = $oBasketItem->getArticle(true);\n\n                if (Registry::getConfig()->getConfigParam('bl_perfLoadSelectLists')) {\n                    // marking chosen select list\n                    $aSelList = $oBasketItem->getSelList();\n                    if (is_array($aSelList) && ($aSelectlist = $oProduct->getSelectLists($sItemKey))) {\n                        reset($aSelList);\n                        foreach ($aSelList as $conkey => $iSel) {\n                            $aSelectlist[$conkey][$iSel]->selected = 1;\n                        }\n                        $oProduct->setSelectlist($aSelectlist);\n                    }\n                }\n            } catch (NoArticleException $oEx) {\n                Registry::getUtilsView()->addErrorToDisplay($oEx);\n                $this->removeItem($sItemKey);\n                $this->calculateBasket(true);\n                continue;\n            } catch (\\OxidEsales\\Eshop\\Core\\Exception\\ArticleInputException $oEx) {\n                Registry::getUtilsView()->addErrorToDisplay($oEx);\n                $this->removeItem($sItemKey);\n                $this->calculateBasket(true);\n                continue;\n            }\n\n            $aBasketArticles[$sItemKey] = $oProduct;\n        }\n\n        return $aBasketArticles;\n    }\n\n    /**\n     * Returns price list object of discounted products\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\PriceList\n     */\n    public function getDiscountProductsPrice()\n    {\n        return $this->_oDiscountProductsPriceList;\n    }\n\n    /**\n     * Returns basket products price list object\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\PriceList\n     */\n    public function getProductsPrice()\n    {\n        if (is_null($this->_oProductsPriceList)) {\n            $this->_oProductsPriceList = oxNew(\\OxidEsales\\Eshop\\Core\\PriceList::class);\n        }\n\n        return $this->_oProductsPriceList;\n    }\n\n    /**\n     * Returns basket price object\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getPrice()\n    {\n        if (is_null($this->_oPrice)) {\n            /** @var \\OxidEsales\\Eshop\\Core\\Price $price */\n            $price = oxNew(Price::class);\n            $this->setPrice($price);\n        }\n\n        return $this->_oPrice;\n    }\n\n    /**\n     * Set basket total sum price object\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Price $oPrice Price object\n     */\n    public function setPrice($oPrice)\n    {\n        $this->_oPrice = $oPrice;\n    }\n\n\n    /**\n     * Returns unique order ID assigned to current basket.\n     * This id is only available on last order step\n     *\n     * @return string\n     */\n    public function getOrderId()\n    {\n        return $this->_sOrderId;\n    }\n\n    /**\n     * Basket order ID setter\n     *\n     * @param string $sId unique id for basket order\n     */\n    public function setOrderId($sId)\n    {\n        $this->_sOrderId = $sId;\n    }\n\n    /**\n     * Returns array of basket costs. By passing cost identifier method will return\n     * this cost if available\n     *\n     * @param string $sId cost id ( optional )\n     *\n     * @return array|\\OxidEsales\\Eshop\\Core\\Price|null\n     */\n    public function getCosts($sId = null)\n    {\n        // if user want some specific cost - return it\n        if ($sId) {\n            return isset($this->_aCosts[$sId]) ? $this->_aCosts[$sId] : null;\n        }\n\n        return $this->_aCosts;\n    }\n\n    /**\n     * Returns array of vouchers applied to basket\n     *\n     * @return array\n     */\n    public function getVouchers()\n    {\n        return $this->_aVouchers;\n    }\n\n    /**\n     * Returns number of different products stored in basket.\n     *\n     * @return int\n     */\n    public function getProductsCount()\n    {\n        return count($this->_aBasketContents);\n    }\n\n    /**\n     * Returns count of items stored in basket.\n     *\n     * @return double\n     */\n    public function getItemsCount()\n    {\n        $itemsCount = 0;\n\n        foreach ($this->_aBasketContents as $oBasketItem) {\n            $itemsCount += $oBasketItem->getAmount();\n        }\n\n        return $itemsCount;\n    }\n\n    /**\n     * Returns total basket weight.\n     *\n     * @return double\n     */\n    public function getWeight()\n    {\n        $weight = 0;\n\n        foreach ($this->_aBasketContents as $oBasketItem) {\n            $weight += $oBasketItem->getWeight();\n        }\n\n        return $weight;\n    }\n\n    /**\n     * Returns basket items array\n     *\n     * @return array\n     */\n    public function getContents()\n    {\n        return $this->_aBasketContents;\n    }\n\n    /**\n     * Returns array of plain of formatted VATs which were calculated for basket\n     *\n     * @param bool $blFormatCurrency enables currency formatting\n     *\n     * @return array\n     */\n    public function getProductVats($blFormatCurrency = true)\n    {\n        if (!$this->_oNotDiscountedProductsPriceList) {\n            return [];\n        }\n\n        $aVats = $this->_oNotDiscountedProductsPriceList->getVatInfo($this->isCalculationModeNetto());\n\n        $oUtils = Registry::getUtils();\n        foreach ((array)$this->_aDiscountedVats as $sKey => $dVat) {\n            if (!isset($aVats[$sKey])) {\n                $aVats[$sKey] = 0;\n            }\n            // add prices of the same discounts\n            $aVats[$sKey] += $oUtils->fRound($dVat, $this->_oCurrency);\n        }\n\n        if ($blFormatCurrency) {\n            $oLang = Registry::getLang();\n            foreach ($aVats as $sKey => $dVat) {\n                $aVats[$sKey] = $oLang->formatCurrency($dVat, $this->getBasketCurrency());\n            }\n        }\n\n        return $aVats;\n    }\n\n    /**\n     * Gift card message setter\n     *\n     * @param string $sMessage gift card message\n     */\n    public function setCardMessage($sMessage)\n    {\n        $this->_sCardMessage = $sMessage;\n    }\n\n    /**\n     * Returns gift card message text\n     *\n     * @return string\n     */\n    public function getCardMessage()\n    {\n        return $this->_sCardMessage;\n    }\n\n    /**\n     * Gift card ID setter\n     *\n     * @param string $sCardId gift card id\n     */\n    public function setCardId($sCardId)\n    {\n        $this->_sCardId = $sCardId;\n    }\n\n    /**\n     * Returns applied gift card ID\n     *\n     * @return string\n     */\n    public function getCardId()\n    {\n        return $this->_sCardId;\n    }\n\n    /**\n     * Returns gift card object (if available)\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Wrapping|null\n     */\n    public function getCard()\n    {\n        $oCard = null;\n        if ($sCardId = $this->getCardId()) {\n            $oCard = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Wrapping::class);\n            $oCard->load($sCardId);\n            $oCard->setWrappingVat($this->getAdditionalServicesVatPercent());\n        }\n\n        return $oCard;\n    }\n\n    /**\n     * Returns total basket discount Price object\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getTotalDiscount()\n    {\n        return $this->_oTotalDiscount;\n    }\n\n    /**\n     * Returns applied discount information array\n     *\n     * @return array\n     */\n    public function getDiscounts()\n    {\n        if ($this->getTotalDiscount() && $this->getTotalDiscount()->getBruttoPrice() == 0 && count($this->_aItemDiscounts) == 0) {\n            return [];\n        }\n\n        return array_merge($this->_aItemDiscounts, $this->_aDiscounts);\n    }\n\n    /**\n     * Returns basket voucher discount price object\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getVoucherDiscount()\n    {\n        if (Registry::getConfig()->getConfigParam('bl_showVouchers')) {\n            return $this->_oVoucherDiscount;\n        }\n\n        return null;\n    }\n\n    /**\n     * Set basket currency\n     *\n     * @param stdClass $oCurrency currency object\n     */\n    public function setBasketCurrency($oCurrency)\n    {\n        $this->_oCurrency = $oCurrency;\n    }\n\n    /**\n     * Basket currency getter\n     *\n     * @return stdClass\n     */\n    public function getBasketCurrency()\n    {\n        if ($this->_oCurrency === null) {\n            $this->_oCurrency = Registry::getConfig()->getActShopCurrencyObject();\n        }\n\n        return $this->_oCurrency;\n    }\n\n    /**\n     * Set skip or not vouchers availability checking\n     *\n     * @param bool $blSkipChecking skip or not vouchers checking\n     */\n    public function setSkipVouchersChecking($blSkipChecking = null)\n    {\n        $this->_blSkipVouchersAvailabilityChecking = $blSkipChecking;\n    }\n\n    /**\n     * Returns true if discount must be skipped for one of the products\n     *\n     * @return bool\n     */\n    public function hasSkipedDiscount()\n    {\n        return $this->_blSkipDiscounts;\n    }\n\n    /**\n     * Used to set \"skip discounts\" status for basket\n     *\n     * @param bool $blSkip set true to skip discounts\n     */\n    public function setSkipDiscounts($blSkip)\n    {\n        $this->_blSkipDiscounts = $blSkip;\n    }\n\n    /**\n     * Formatted Products net price getter\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return string\n     */\n    public function getProductsNetPrice()\n    {\n        return Registry::getLang()->formatCurrency($this->getNettoSum(), $this->getBasketCurrency());\n    }\n\n    /**\n     * Formatted Products price getter\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return string\n     */\n    public function getFProductsPrice()\n    {\n        return Registry::getLang()->formatCurrency($this->getBruttoSum(), $this->getBasketCurrency());\n    }\n\n    /**\n     * Returns VAT of delivery costs\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return double\n     */\n    public function getDelCostVatPercent()\n    {\n        return $this->getCosts('oxdelivery')->getVat();\n    }\n\n    /**\n     * Returns formatted VAT of delivery costs\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return string|bool\n     */\n    public function getDelCostVat()\n    {\n        $dDelVAT = $this->getCosts('oxdelivery')->getVatValue();\n\n        // blShowVATForDelivery option will be used, only for displaying, but not calculation\n        if ($dDelVAT > 0 && Registry::getConfig()->getConfigParam('blShowVATForDelivery')) {\n            return Registry::getLang()->formatCurrency($dDelVAT, $this->getBasketCurrency());\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns formatted netto price of delivery costs\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return string\n     */\n    public function getDelCostNet()\n    {\n        $oConfig = Registry::getConfig();\n\n        // blShowVATForDelivery option will be used, only for displaying, but not calculation\n        if ($oConfig->getConfigParam('blShowVATForDelivery') && ($this->getBasketUser() || $oConfig->getConfigParam('blCalculateDelCostIfNotLoggedIn'))) {\n            $dNetPrice = $this->getCosts('oxdelivery')->getNettoPrice();\n            if ($dNetPrice > 0) {\n                return Registry::getLang()->formatCurrency($dNetPrice, $this->getBasketCurrency());\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns VAT of payment costs\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return double\n     */\n    public function getPayCostVatPercent()\n    {\n        return $this->getCosts('oxpayment')->getVat();\n    }\n\n    /**\n     * Returns formatted VAT of payment costs\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return string\n     */\n    public function getPayCostVat()\n    {\n        $dPayVAT = $this->getCosts('oxpayment')->getVatValue();\n\n        // blShowVATForPayCharge option will be used, only for displaying, but not calculation\n        if ($dPayVAT > 0 && Registry::getConfig()->getConfigParam('blShowVATForPayCharge')) {\n            return Registry::getLang()->formatCurrency($dPayVAT, $this->getBasketCurrency());\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns formatted netto price of payment costs\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return string\n     */\n    public function getPayCostNet()\n    {\n        // blShowVATForPayCharge option will be used, only for displaying, but not calculation\n        if (Registry::getConfig()->getConfigParam('blShowVATForPayCharge')) {\n            $oPaymentCost = $this->getCosts('oxpayment');\n            if ($oPaymentCost && $oPaymentCost->getNettoPrice()) {\n                return Registry::getLang()->formatCurrency($this->getCosts('oxpayment')->getNettoPrice(), $this->getBasketCurrency());\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns payment costs brutto value\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return double|bool\n     */\n    public function getPaymentCosts()\n    {\n        $oPaymentCost = $this->getCosts('oxpayment');\n        if ($oPaymentCost && $oPaymentCost->getBruttoPrice()) {\n            return $oPaymentCost->getBruttoPrice();\n        }\n    }\n\n    /**\n     * Returns payment costs\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getPaymentCost()\n    {\n        return $this->getCosts('oxpayment');\n    }\n\n    /**\n     * Returns if exists formatted payment costs\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return string|bool\n     */\n    public function getFPaymentCosts()\n    {\n        $oPaymentCost = $this->getCosts('oxpayment');\n        if ($oPaymentCost && $oPaymentCost->getBruttoPrice()) {\n            return Registry::getLang()->formatCurrency($oPaymentCost->getBruttoPrice(), $this->getBasketCurrency());\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns value of voucher discount\n     *\n     * @return double\n     */\n    public function getVoucherDiscValue()\n    {\n        if ($this->getVoucherDiscount()) {\n            return $this->getVoucherDiscount()->getBruttoPrice();\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns formatted voucher discount\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return string|bool\n     */\n    public function getFVoucherDiscountValue()\n    {\n        if ($oVoucherDiscount = $this->getVoucherDiscount()) {\n            if ($oVoucherDiscount->getBruttoPrice()) {\n                return Registry::getLang()->formatCurrency($oVoucherDiscount->getBruttoPrice(), $this->getBasketCurrency());\n            }\n        }\n\n        return false;\n    }\n\n\n    /**\n     * Returns VAT of wrapping costs\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return double\n     */\n    public function getWrappCostVatPercent()\n    {\n        return $this->getCosts('oxwrapping')->getVat();\n    }\n\n\n    /**\n     * Returns VAT of gift card costs\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return double\n     */\n    public function getGiftCardCostVatPercent()\n    {\n        return $this->getCosts('oxgiftcard')->getVat();\n    }\n\n    /**\n     * Returns formatted VAT of wrapping costs\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return string|bool\n     */\n    public function getWrappCostVat()\n    {\n        // blShowVATForWrapping option will be used, only for displaying, but not calculation\n        if (Registry::getConfig()->getConfigParam('blShowVATForWrapping')) {\n            $oPrice = $this->getCosts('oxwrapping');\n\n            if ($oPrice && $oPrice->getVatValue() > 0) {\n                return Registry::getLang()->formatCurrency($oPrice->getVatValue(), $this->getBasketCurrency());\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns formatted netto price of wrapping costs\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return string\n     */\n    public function getWrappCostNet()\n    {\n        // blShowVATForWrapping option will be used, only for displaying, but not calculation\n        if (Registry::getConfig()->getConfigParam('blShowVATForWrapping')) {\n            $oPrice = $this->getCosts('oxwrapping');\n\n            if ($oPrice && $oPrice->getNettoPrice() > 0) {\n                return Registry::getLang()->formatCurrency($oPrice->getNettoPrice(), $this->getBasketCurrency());\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns if exists formatted wrapping costs\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return string|bool\n     */\n    public function getFWrappingCosts()\n    {\n        $oPrice = $this->getCosts('oxwrapping');\n\n        if ($oPrice && $oPrice->getBruttoPrice()) {\n            return Registry::getLang()->formatCurrency($oPrice->getBruttoPrice(), $this->getBasketCurrency());\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns array of wrapping costs\n     *\n     * @return array\n     */\n    public function getWrappingCost()\n    {\n        return $this->getCosts('oxwrapping');\n    }\n\n    /**\n     * Returns formatted VAT of gift card costs\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return string|bool\n     */\n    public function getGiftCardCostVat()\n    {\n        // blShowVATForWrapping option will be used, only for displaying, but not calculation\n        if (Registry::getConfig()->getConfigParam('blShowVATForWrapping')) {\n            $oPrice = $this->getCosts('oxgiftcard');\n\n            if ($oPrice && $oPrice->getVatValue() > 0) {\n                return Registry::getLang()->formatCurrency($oPrice->getVatValue(), $this->getBasketCurrency());\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns formatted netto price of gift card costs\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return string\n     */\n    public function getGiftCardCostNet()\n    {\n        // blShowVATForWrapping option will be used, only for displaying, but not calculation\n        if (Registry::getConfig()->getConfigParam('blShowVATForWrapping')) {\n            $oPrice = $this->getCosts('oxgiftcard');\n\n            if ($oPrice && $oPrice->getNettoPrice() > 0) {\n                return Registry::getLang()->formatCurrency($oPrice->getNettoPrice(), $this->getBasketCurrency());\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns if exists formatted gift card costs\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return string|bool\n     */\n    public function getFGiftCardCosts()\n    {\n        $oPrice = $this->getCosts('oxgiftcard');\n\n        if ($oPrice && $oPrice->getBruttoPrice()) {\n            return Registry::getLang()->formatCurrency($oPrice->getBruttoPrice(), $this->getBasketCurrency());\n        }\n\n        return false;\n    }\n\n    /**\n     * Gets gift card cost.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getGiftCardCost()\n    {\n        return $this->getCosts('oxgiftcard');\n    }\n\n    /**\n     * Returns formatted basket total price\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return string\n     */\n    public function getFPrice()\n    {\n        return Registry::getLang()->formatCurrency($this->getPrice()->getBruttoPrice(), $this->getBasketCurrency());\n    }\n\n    /**\n     * Returns if exists formatted delivery costs\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return string|bool\n     */\n    public function getFDeliveryCosts()\n    {\n        $oPrice = $this->getCosts('oxdelivery');\n\n        if ($oPrice && ($this->getBasketUser() || Registry::getConfig()->getConfigParam('blCalculateDelCostIfNotLoggedIn'))) {\n            return Registry::getLang()->formatCurrency($oPrice->getBruttoPrice(), $this->getBasketCurrency());\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns if exists delivery costs\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return string|bool\n     */\n    public function getDeliveryCosts()\n    {\n        if ($oDeliveryCost = $this->getCosts('oxdelivery')) {\n            return $oDeliveryCost->getBruttoPrice();\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns delivery costs\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getDeliveryCost()\n    {\n        return $this->getCosts('oxdelivery');\n    }\n\n    /**\n     * Sets total discount value\n     *\n     * @param double $dDiscount new total discount value\n     */\n    public function setTotalDiscount($dDiscount)\n    {\n        $this->_oTotalDiscount = oxNew(Price::class);\n        $this->_oTotalDiscount->setBruttoPriceMode();\n        $this->_oTotalDiscount->add($dDiscount);\n    }\n\n    /**\n     * Get basket price for payment cost calculation. Returned price\n     * is with applied discounts, vouchers and added delivery cost\n     *\n     * @return double\n     */\n    public function getPriceForPayment()\n    {\n        $dPrice = $this->getDiscountedProductsBruttoPrice();\n        //#1905 not discounted products should be included in payment amount calculation\n        if ($oPriceList = $this->getNotDiscountProductsPrice()) {\n            $dPrice += $oPriceList->getBruttoSum();\n        }\n\n        // adding delivery price to final price\n        if (isset($this->_aCosts['oxdelivery'])) {\n            $oDeliveryPrice = $this->_aCosts['oxdelivery'];\n            $dPrice += $oDeliveryPrice->getBruttoPrice();\n        }\n\n        return $dPrice;\n    }\n\n\n    /**\n     * Returns ( current basket products sum - total discount - voucher discount )\n     *\n     * @return double\n     */\n    public function getDiscountedProductsSum()\n    {\n        if ($oProductsPrice = $this->getDiscountProductsPrice()) {\n            $dPrice = $oProductsPrice->getSum($this->isCalculationModeNetto());\n        }\n\n        // subtracting total discount\n        if ($oPrice = $this->getTotalDiscount()) {\n            $dPrice -= $oPrice->getPrice();\n        }\n\n        if ($oVoucherPrice = $this->getVoucherDiscount()) {\n            $dPrice -= $oVoucherPrice->getPrice();\n        }\n\n        return $dPrice;\n    }\n\n    /**\n     * Gets total discount sum.\n     *\n     * @return float|int\n     */\n    public function getTotalDiscountSum()\n    {\n        $dPrice = 0;\n        // subtracting total discount\n        if ($oPrice = $this->getTotalDiscount()) {\n            $dPrice += $oPrice->getPrice();\n        }\n\n        if ($oVoucherPrice = $this->getVoucherDiscount()) {\n            $dPrice += $oVoucherPrice->getPrice();\n        }\n\n        return $dPrice;\n    }\n\n    /**\n     * Returns ( current basket products sum - total discount - voucher discount )\n     *\n     * @return double\n     */\n    public function getDiscountedProductsBruttoPrice()\n    {\n        if ($oProductsPrice = $this->getDiscountProductsPrice()) {\n            $dPrice = $oProductsPrice->getBruttoSum();\n        }\n\n        // subtracting total discount\n        if ($oPrice = $this->getTotalDiscount()) {\n            $dPrice -= $oPrice->getBruttoPrice();\n        }\n\n        if ($oVoucherPrice = $this->getVoucherDiscount()) {\n            $dPrice -= $oVoucherPrice->getBruttoPrice();\n        }\n\n        return $dPrice;\n    }\n\n    /**\n     * Returns TRUE if ( current basket products sum - total discount - voucher discount ) > 0\n     *\n     * @return bool\n     */\n    public function isBelowMinOrderPrice()\n    {\n        $blIsBelowMinOrderPrice = false;\n        $sConfValue = Registry::getConfig()->getConfigParam('iMinOrderPrice');\n        if (is_numeric($sConfValue) && $this->getProductsCount()) {\n            $dMinOrderPrice = Price::getPriceInActCurrency((float) $sConfValue);\n            $dNotDiscountedProductPrice = 0;\n            if ($oPrice = $this->getNotDiscountProductsPrice()) {\n                $dNotDiscountedProductPrice = $oPrice->getBruttoSum();\n            }\n            $blIsBelowMinOrderPrice = ($dMinOrderPrice > ($this->getDiscountedProductsBruttoPrice() + $dNotDiscountedProductPrice));\n        }\n\n        return $blIsBelowMinOrderPrice;\n    }\n\n    /**\n     * Returns stock of article in basket, including bundle article\n     *\n     * @param string $sArtId        article id\n     * @param string $sExpiredArtId item id of updated article\n     *\n     * @return double\n     */\n    public function getArtStockInBasket($sArtId, $sExpiredArtId = null)\n    {\n        $dArtStock = 0;\n        foreach ($this->_aBasketContents as $sItemKey => $oOrderArticle) {\n            if ($oOrderArticle && ($sExpiredArtId == null || $sExpiredArtId != $sItemKey)) {\n                if ($oOrderArticle->getArticle(true)->getId() == $sArtId) {\n                    $dArtStock += $oOrderArticle->getAmount();\n                }\n            }\n        }\n\n        return $dArtStock;\n    }\n\n    /**\n     * Checks if product can be added to basket\n     *\n     * @param string $sProductId product id\n     *\n     * @return bool\n     */\n    public function canAddProductToBasket($sProductId)\n    {\n        $blCanAdd = null;\n\n        // if basket category is not set..\n        if ($this->_sBasketCategoryId === null) {\n            $oCat = null;\n\n            // request category\n            if ($oView = Registry::getConfig()->getActiveView()) {\n                if ($oCat = $oView->getActiveCategory()) {\n                    if (!$this->isProductInRootCategory($sProductId, $oCat->oxcategories__oxrootid->value)) {\n                        $oCat = null;\n                    } else {\n                        $blCanAdd = true;\n                    }\n                }\n            }\n\n            // product main category\n            if (!$oCat) {\n                $oProduct = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n                if ($oProduct->load($sProductId)) {\n                    $oCat = $oProduct->getCategory();\n                }\n            }\n\n            // root category id\n            if ($oCat) {\n                $this->setBasketRootCatId($oCat->oxcategories__oxrootid->value);\n            }\n        }\n\n        // avoiding double check..\n        if ($blCanAdd === null) {\n            $blCanAdd = $this->_sBasketCategoryId ? $this->isProductInRootCategory($sProductId, $this->getBasketRootCatId()) : true;\n        }\n\n        return $blCanAdd;\n    }\n\n    /**\n     * Checks if product is in root category\n     *\n     * @param string $sProductId product id\n     * @param string $sRootCatId root category id\n     *\n     * @return bool\n     */\n    protected function isProductInRootCategory($sProductId, $sRootCatId)\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sO2CTable = $tableViewNameGenerator->getViewName('oxobject2category');\n        $sCatTable = $tableViewNameGenerator->getViewName('oxcategories');\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sParentId = $oDb->getOne(\"select oxparentid from oxarticles where oxid = :oxid\", [\n            'oxid' => $sProductId\n        ]);\n        $sProductId = $sParentId ? $sParentId : $sProductId;\n\n        $sQ = \"select 1 from {$sO2CTable}\n                 left join {$sCatTable} on {$sCatTable}.oxid = {$sO2CTable}.oxcatnid\n                 where {$sO2CTable}.oxobjectid = :oxobjectid and\n                       {$sCatTable}.oxrootid = :oxrootid\";\n\n        return (bool) $oDb->getOne($sQ, [\n            'oxobjectid' => $sProductId,\n            'oxrootid' => $sRootCatId\n        ]);\n    }\n\n    /**\n     * Set active basket root category\n     *\n     * @param string $sRoot Root category id\n     */\n    public function setBasketRootCatId($sRoot)\n    {\n        $this->_sBasketCategoryId = $sRoot;\n    }\n\n    /**\n     * Get active basket root category\n     *\n     * @return string\n     */\n    public function getBasketRootCatId()\n    {\n        return $this->_sBasketCategoryId;\n    }\n\n    /**\n     * Sets category change warn state\n     *\n     * @param bool $blShow to show warning or not\n     */\n    public function setCatChangeWarningState($blShow)\n    {\n        $this->_blShowCatChangeWarning = $blShow;\n    }\n\n    /**\n     * Tells to show category change warning\n     *\n     * @return bool\n     */\n    public function showCatChangeWarning()\n    {\n        return $this->_blShowCatChangeWarning;\n    }\n\n    /**\n     * Returns price list object of not discounted products\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\PriceList in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine\n     *                                          plugin\n     */\n    public function getNotDiscountProductsPrice()\n    {\n        return $this->_oNotDiscountedProductsPriceList;\n    }\n\n    /**\n     * Is called when new basket item is successfully added.\n     *\n     * @param bool $blOverride marker to accumulate passed amount or renew (default false).\n     */\n    protected function addedNewItem($blOverride)\n    {\n        if (!$blOverride) {\n            $this->_blNewITemAdded = null;\n            Registry::getSession()->setVariable(\"blAddedNewItem\", true);\n        }\n    }\n\n    /**\n     * Resets new basket item addition state on unserialization\n     */\n    public function __wakeUp()\n    {\n        $this->_blNewITemAdded = null;\n        $this->_isCalculationModeNetto = null;\n    }\n\n    /**\n     * Returns true if new product was just added to basket\n     *\n     * @return bool\n     */\n    public function isNewItemAdded()\n    {\n        if ($this->_blNewITemAdded == null) {\n            $this->_blNewITemAdded = (bool) Registry::getSession()->getVariable(\"blAddedNewItem\");\n            Registry::getSession()->deleteVariable(\"blAddedNewItem\");\n        }\n\n        return $this->_blNewITemAdded;\n    }\n\n    /**\n     * Returns true if at least one product is downloadable in basket\n     *\n     * @return bool\n     */\n    public function hasDownloadableProducts()\n    {\n        $this->_blDownloadableProducts = false;\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\BasketItem $oBasketItem */\n        foreach ($this->_aBasketContents as $oBasketItem) {\n            if ($oBasketItem->getArticle(false) && $oBasketItem->getArticle(false)->isDownloadable()) {\n                $this->_blDownloadableProducts = true;\n                break;\n            }\n        }\n\n        return $this->_blDownloadableProducts;\n    }\n\n    /**\n     * Returns whether there are any articles in basket with intangible products agreement enabled.\n     *\n     * @return bool\n     */\n    public function hasArticlesWithIntangibleAgreement()\n    {\n        $blHasArticlesWithIntangibleAgreement = false;\n\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\BasketItem $oBasketItem */\n        foreach ($this->_aBasketContents as $oBasketItem) {\n            if ($oBasketItem->getArticle(false) && $oBasketItem->getArticle(false)->hasIntangibleAgreement()) {\n                $blHasArticlesWithIntangibleAgreement = true;\n                break;\n            }\n        }\n\n        return $blHasArticlesWithIntangibleAgreement;\n    }\n\n    /**\n     * Returns whether there are any articles in basket with downloadable products agreement enabled.\n     *\n     * @return bool\n     */\n    public function hasArticlesWithDownloadableAgreement()\n    {\n        $blHasArticlesWithIntangibleAgreement = false;\n\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\BasketItem $oBasketItem */\n        foreach ($this->_aBasketContents as $oBasketItem) {\n            if ($oBasketItem->getArticle(false) && $oBasketItem->getArticle(false)->hasDownloadableAgreement()) {\n                $blHasArticlesWithIntangibleAgreement = true;\n                break;\n            }\n        }\n\n        return $blHasArticlesWithIntangibleAgreement;\n    }\n\n    /**\n     * Returns min order price value\n     *\n     * @return float\n     */\n    public function getMinOrderPrice()\n    {\n        return Price::getPriceInActCurrency(Registry::getConfig()->getConfigParam('iMinOrderPrice'));\n    }\n\n    private function handleNoArticleException(BasketItem $basketItem, NoArticleException $exception): void\n    {\n        $this->removeItem($basketItem->getBasketItemKey());\n\n        $message = sprintf(\n            Registry::getLang()->translateString('ERROR_MESSAGE_ARTICLE_ARTICLE_DOES_NOT_EXIST'),\n            $basketItem->getTitle()\n        );\n\n        Registry::getUtilsView()->addErrorToDisplay($message);\n        ContainerFacade::get(LoggerInterface::class)->warning(\n            $message,\n            [\n                'product id' => $exception->getProductId(),\n                'shop id' => $basketItem->getShopId(),\n            ]\n        );\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/BasketContentMarkGenerator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\n/**\n * Class oxBasketContentMarkGenerator which forms explanation marks.\n */\nclass BasketContentMarkGenerator\n{\n    /**\n     * Default value for explanation mark.\n     */\n    const DEFAULT_EXPLANATION_MARK = '**';\n\n    /**\n     * Marks added to array by article type.\n     *\n     * @var array\n     */\n    private $_aMarks;\n\n    /**\n     * Basket that is used to get article type(downloadable, intangible etc..).\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Basket\n     */\n    private $_oBasket;\n\n    /**\n     * Sets basket that is used to get article type(downloadable, intangible etc..).\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket\n     */\n    public function __construct(\\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket)\n    {\n        $this->_oBasket = $oBasket;\n    }\n\n    /**\n     * Returns explanation mark by given mark identification (skippedDiscount, downloadable, intangible).\n     *\n     * @param string $sMarkIdentification Mark identification.\n     *\n     * @return string\n     */\n    public function getMark($sMarkIdentification)\n    {\n        if (is_null($this->_aMarks)) {\n            $sCurrentMark = self::DEFAULT_EXPLANATION_MARK;\n            $aMarks = $this->formMarks($sCurrentMark);\n            $this->_aMarks = $aMarks;\n        }\n\n        return $this->_aMarks[$sMarkIdentification];\n    }\n\n    /**\n     * Basket that is used to get article type(downloadable, intangible etc..).\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Basket\n     */\n    private function getBasket()\n    {\n        return $this->_oBasket;\n    }\n\n    /**\n     * Forms marks for articles.\n     *\n     * @param string $sCurrentMark Current mark.\n     *\n     * @return array\n     */\n    private function formMarks($sCurrentMark)\n    {\n        $oBasket = $this->getBasket();\n        $aMarks = [];\n        if ($oBasket->hasSkipedDiscount()) {\n            $aMarks['skippedDiscount'] = $sCurrentMark;\n            $sCurrentMark .= '*';\n        }\n        if ($oBasket->hasArticlesWithDownloadableAgreement()) {\n            $aMarks['downloadable'] = $sCurrentMark;\n            $sCurrentMark .= '*';\n        }\n        if ($oBasket->hasArticlesWithIntangibleAgreement()) {\n            $aMarks['intangible'] = $sCurrentMark;\n        }\n\n        return $aMarks;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/BasketItem.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxRegistry;\nuse oxArticleInputException;\nuse oxOutOfStockException;\nuse oxNoArticleException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaView;\nuse stdClass;\n\n/**\n * UserBasketItem class, responsible for storing most important fields\n */\n#[\\AllowDynamicProperties]\nclass BasketItem extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Product ID\n     *\n     * @var string\n     */\n    protected $_sProductId = null;\n\n    /**\n     * Basket product title\n     *\n     * @var string\n     */\n    protected $_sTitle = null;\n\n    /**\n     * Variant var select\n     *\n     * @var string\n     */\n    protected $_sVarSelect = null;\n\n    /**\n     * Product icon name\n     *\n     * @var string\n     */\n    protected $_sIcon = null;\n\n    /**\n     * Product details link\n     *\n     * @var string\n     */\n    protected $_sLink = null;\n\n    /**\n     * Item price\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Price\n     */\n    protected $_oPrice = null;\n\n    /**\n     * Item unit price\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Price\n     */\n    protected $_oUnitPrice = null;\n\n    /**\n     * Basket item total amount\n     *\n     * @var double\n     */\n    protected $_dAmount = 0.0;\n\n    /**\n     * Total basket item weight\n     *\n     * @var double\n     */\n    protected $_dWeight = 0;\n\n    /**\n     * Basket item select lists\n     *\n     * @var array\n     */\n    protected $_aSelList = [];\n\n    /**\n     * Shop id where product was put into basket\n     *\n     * @var string\n     */\n    protected $_sShopId = null;\n\n    /**\n     * Native product shop Id\n     *\n     * @var string\n     */\n    protected $_sNativeShopId = null;\n\n    /**\n     * Skip discounts marker\n     *\n     * @var boolean\n     */\n    protected $_blSkipDiscounts = false;\n\n    /**\n     * Persistent basket item parameters\n     *\n     * @var array\n     */\n    protected $_aPersistentParameters = [];\n\n    /**\n     * Buundle marker - marks if item is bundle or not\n     *\n     * @var boolean\n     */\n    protected $_blBundle = false;\n\n    /**\n     * Discount bundle marker - marks if item is discount bundle or not\n     *\n     * @var boolean\n     */\n    protected $_blIsDiscountArticle = false;\n\n    /**\n     * This item article\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected $_oArticle = null;\n\n    /**\n     * Image NON SSL url\n     *\n     * @var string\n     */\n    protected $_sDimageDirNoSsl = null;\n\n    /**\n     * Image SSL url\n     *\n     * @var string\n     */\n    protected $_sDimageDirSsl = null;\n\n    /**\n     * User chosen selectlists\n     *\n     * @var array\n     */\n    protected $_aChosenSelectlist = [];\n\n    /**\n     * Used wrapping paper Id\n     *\n     * @var string\n     */\n    protected $_sWrappingId = null;\n\n    /**\n     * Wishlist user Id\n     *\n     * @var string\n     */\n    protected $_sWishId = null;\n\n    /**\n     * Wish article Id\n     *\n     * @var string\n     */\n    protected $_sWishArticleId = null;\n\n    /**\n     * Article stock check (live db check) status\n     *\n     * @var bool\n     */\n    protected $_blCheckArticleStock = true;\n\n\n    /**\n     * Basket Item language Id\n     *\n     * @var bool\n     */\n    protected $_iLanguageId = null;\n\n    protected $_oIcon = null;\n\n\n    /**\n     * Regular Item unit price - price without basket item discounts\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Price\n     */\n    protected $_oRegularUnitPrice = null;\n\n    /**\n     * Basket item's individual key.\n     *\n     * @var string\n     */\n    protected $basketItemKey = null;\n\n    /**\n     * Getter for basketItemkey.\n     *\n     * @return string|null\n     */\n    public function getBasketItemKey()\n    {\n        return $this->basketItemKey;\n    }\n\n    /**\n     * Setter for basketItemkey.\n     *\n     * @param string $itemKey\n     */\n    public function setBasketItemKey($itemKey)\n    {\n        $this->basketItemKey = $itemKey;\n    }\n\n    /**\n     * Return regular unit price\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getRegularUnitPrice()\n    {\n        return $this->_oRegularUnitPrice;\n    }\n\n    /**\n     * Set regular unit price\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Price $oRegularUnitPrice regular price\n     */\n    public function setRegularUnitPrice($oRegularUnitPrice)\n    {\n        $this->_oRegularUnitPrice = $oRegularUnitPrice;\n    }\n\n\n    /**\n     * Assigns basic params to basket item\n     *  - oxbasketitem::_setArticle();\n     *  - oxbasketitem::setAmount();\n     *  - oxbasketitem::_setSelectList();\n     *  - oxbasketitem::setPersParams();\n     *  - oxbasketitem::setBundle().\n     *\n     * @param string $sProductID product id\n     * @param double $dAmount    amount\n     * @param array  $aSel       selection\n     * @param array  $aPersParam persistent params\n     * @param bool   $blBundle   bundle\n     *\n     * @throws oxNoArticleException\n     * @throws oxOutOfStockException\n     * @throws oxArticleInputException\n     */\n    public function init($sProductID, $dAmount, $aSel = null, $aPersParam = null, $blBundle = null)\n    {\n        $this->setArticle($sProductID);\n        $this->setAmount($dAmount);\n        $this->setSelectList($aSel);\n        $this->setPersParams($aPersParam);\n        $this->setBundle($blBundle);\n        $this->setLanguageId(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage());\n    }\n\n    /**\n     * Initializes basket item from oxorderarticle object\n     *  - oxbasketitem::_setFromOrderArticle() - assigns $oOrderArticle parameter\n     *  to oxBasketItem::_oArticle. Thus oxOrderArticle is used as oxArticle (calls\n     *  standard methods implemented by oxIArticle interface);\n     *  - oxbasketitem::setAmount();\n     *  - oxbasketitem::_setSelectList();\n     *  - oxbasketitem::setPersParams().\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\OrderArticle $oOrderArticle order article to load info from\n     */\n    public function initFromOrderArticle($oOrderArticle)\n    {\n        $this->setFromOrderArticle($oOrderArticle);\n        $this->setAmount($oOrderArticle->oxorderarticles__oxamount->value);\n        $this->setSelectList($oOrderArticle->getOrderArticleSelectList());\n        $this->setPersParams($oOrderArticle->getPersParams());\n        $this->setBundle($oOrderArticle->isBundle());\n    }\n\n    /**\n     * Marks if item is discount bundle ( oxbasketitem::_blIsDiscountArticle )\n     *\n     * @param bool $blIsDiscountArticle if item is discount bundle\n     */\n    public function setAsDiscountArticle($blIsDiscountArticle)\n    {\n        $this->_blIsDiscountArticle = $blIsDiscountArticle;\n    }\n\n    /**\n     * Sets stock control mode\n     *\n     * @param bool $blStatus stock control mode\n     */\n    public function setStockCheckStatus($blStatus)\n    {\n        $this->_blCheckArticleStock = $blStatus;\n    }\n\n    /**\n     * Returns stock control mode\n     *\n     * @return bool\n     */\n    public function getStockCheckStatus()\n    {\n        return $this->_blCheckArticleStock;\n    }\n\n    /**\n     * Sets item amount and weight which depends on amount\n     * ( oxbasketitem::dAmount, oxbasketitem::dWeight )\n     *\n     * @param double $dAmount    amount\n     * @param bool   $blOverride Whether to override current amount.\n     * @param string $sItemKey   item key\n     *\n     * @throws oxArticleInputException\n     * @throws oxOutOfStockException\n     */\n    public function setAmount($dAmount, $blOverride = true, $sItemKey = null)\n    {\n        try {\n            //validating amount\n            $dAmount = \\OxidEsales\\Eshop\\Core\\Registry::getInputValidator()->validateBasketAmount($dAmount);\n        } catch (\\OxidEsales\\Eshop\\Core\\Exception\\ArticleInputException $oEx) {\n            $oEx->setArticleNr($this->getProductId());\n            $oEx->setProductId($this->getProductId());\n            // setting additional information for exception and then rethrowing\n            throw $oEx;\n        }\n\n        $oArticle = $this->getArticle(true);\n        $dAmount = $this->applyPackageOnAmount($oArticle, $dAmount);\n\n        // setting default\n        $iOnStock = true;\n\n        if ($blOverride) {\n            $this->_dAmount = $dAmount;\n        } else {\n            $this->_dAmount += $dAmount;\n        }\n\n        // checking for stock\n        if ($this->getStockCheckStatus() == true) {\n            $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n            $dArtStockAmount = $session->getBasket()->getArtStockInBasket($oArticle->getId(), $sItemKey);\n            $selectForUpdate = false;\n            if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blPsBasketReservationEnabled')) {\n                $selectForUpdate = true;\n            }\n            $iOnStock = $oArticle->checkForStock($this->_dAmount, $dArtStockAmount, $selectForUpdate);\n            if ($iOnStock !== true) {\n                if ($iOnStock === false) {\n                    // no stock !\n                    $this->_dAmount = 0;\n                } else {\n                    // limited stock\n                    $this->_dAmount = $iOnStock;\n                }\n            }\n        }\n\n        // calculating general weight\n        $this->_dWeight = $oArticle->oxarticles__oxweight->value * $this->_dAmount;\n\n        if ($iOnStock !== true) {\n            /** @var \\OxidEsales\\Eshop\\Core\\Exception\\OutOfStockException $oEx */\n            $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\OutOfStockException::class);\n            $oEx->setMessage('ERROR_MESSAGE_OUTOFSTOCK_OUTOFSTOCK');\n            $oEx->setArticleNr($oArticle->oxarticles__oxartnum->value);\n            $oEx->setProductId($oArticle->getProductId());\n            $oEx->setRemainingAmount($this->_dAmount);\n            $oEx->setBasketIndex($sItemKey);\n            throw $oEx;\n        }\n    }\n\n    /**\n     * Apply checks for package on amount\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $article\n     * @param double                                      $amount\n     *\n     * @return double\n     */\n    protected function applyPackageOnAmount($article, $amount)\n    {\n        return $amount;\n    }\n\n    /**\n     * Sets $this->_oPrice\n     *\n     * @param object $oPrice price\n     */\n    public function setPrice($oPrice)\n    {\n        $this->_oUnitPrice = clone $oPrice;\n\n        $this->_oPrice = clone $oPrice;\n        $this->_oPrice->multiply($this->getAmount());\n    }\n\n    public function getIcon(): ProductMediaView\n    {\n        if ($this->_oIcon === null) {\n            $this->_oIcon = $this->getArticle()->getIcon();\n        }\n\n        return $this->_oIcon;\n    }\n\n    /**\n     * Retrieves the article .Throws an exception if article does not exist,\n     * is not buyable or visible.\n     *\n     * @param bool   $blCheckProduct       checks if product is buyable and visible\n     * @param string $sProductId           product id\n     * @param bool   $blDisableLazyLoading disable lazy loading\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\ArticleException exception in case of no current object product id is set\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\NoArticleException exception in case if product not exitst or not visible\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\ArticleInputException exception if product is not buyable (stock and so on)\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article|\\OxidEsales\\Eshop\\Application\\Model\\OrderArticle\n     */\n    public function getArticle($blCheckProduct = false, $sProductId = null, $blDisableLazyLoading = false)\n    {\n        if ($this->_oArticle === null || (!$this->_oArticle->isOrderArticle() && $blDisableLazyLoading)) {\n            $sProductId = $sProductId ? $sProductId : $this->_sProductId;\n            if (!$sProductId) {\n                //this exception may not be caught, anyhow this is a critical exception\n                /** @var \\OxidEsales\\Eshop\\Core\\Exception\\ArticleException $oEx */\n                $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ArticleException::class);\n                $oEx->setMessage('EXCEPTION_ARTICLE_NOPRODUCTID');\n                throw $oEx;\n            }\n\n            $this->_oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            // #M773 Do not use article lazy loading on order save\n            if ($blDisableLazyLoading) {\n                $this->_oArticle->modifyCacheKey('_allviews');\n                $this->_oArticle->disableLazyLoading();\n            }\n\n            // performance:\n            // - skipping variants loading\n            // - skipping 'ab' price info\n            // - load parent field\n            $this->_oArticle->setNoVariantLoading(true);\n            $this->_oArticle->setLoadParentData(true);\n            if (!$this->_oArticle->load($sProductId)) {\n                /** @var \\OxidEsales\\Eshop\\Core\\Exception\\NoArticleException $oEx */\n                $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\NoArticleException::class);\n                $oLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n                $oEx->setMessage(sprintf($oLang->translateString('ERROR_MESSAGE_ARTICLE_ARTICLE_DOES_NOT_EXIST', $oLang->getBaseLanguage()), $sProductId));\n                $oEx->setArticleNr($sProductId);\n                $oEx->setProductId($sProductId);\n                throw $oEx;\n            }\n\n            // cant put not visible product to basket (M:1286)\n            if ($blCheckProduct && !$this->_oArticle->isVisible()) {\n                /** @var \\OxidEsales\\Eshop\\Core\\Exception\\NoArticleException $oEx */\n                $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\NoArticleException::class);\n                $oLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n                $oEx->setMessage(sprintf($oLang->translateString('ERROR_MESSAGE_ARTICLE_ARTICLE_DOES_NOT_EXIST', $oLang->getBaseLanguage()), $this->_oArticle->oxarticles__oxartnum->value));\n                $oEx->setArticleNr($sProductId);\n                $oEx->setProductId($sProductId);\n                throw $oEx;\n            }\n\n            // cant put not buyable product to basket\n            if ($blCheckProduct && !$this->_oArticle->isBuyable()) {\n                /** @var \\OxidEsales\\Eshop\\Core\\Exception\\ArticleInputException $oEx */\n                $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ArticleInputException::class);\n                $oEx->setMessage('ERROR_MESSAGE_ARTICLE_ARTICLE_NOT_BUYABLE');\n                $oEx->setArticleNr($sProductId);\n                $oEx->setProductId($sProductId);\n                throw $oEx;\n            }\n        }\n\n        return $this->_oArticle;\n    }\n\n    /**\n     * Returns bundle amount\n     *\n     * @return double\n     */\n    public function getdBundledAmount()\n    {\n        return $this->isBundle() ? $this->_dAmount : 0;\n    }\n\n    /**\n     * Returns the price.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getPrice()\n    {\n        return $this->_oPrice;\n    }\n\n    /**\n     * Returns the price.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getUnitPrice()\n    {\n        return $this->_oUnitPrice;\n    }\n\n    /**\n     * Returns the amount of item.\n     *\n     * @return double\n     */\n    public function getAmount()\n    {\n        return $this->_dAmount;\n    }\n\n    /**\n     * returns the total weight.\n     *\n     * @return double\n     */\n    public function getWeight()\n    {\n        return $this->_dWeight;\n    }\n\n    /**\n     * Returns product title\n     *\n     * @return string\n     */\n    public function getTitle()\n    {\n        if ($this->_sTitle === null || $this->getLanguageId() != \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage()) {\n            $oArticle = $this->getArticle();\n            $this->_sTitle = $oArticle->oxarticles__oxtitle->value;\n\n            if ($oArticle->oxarticles__oxvarselect->value) {\n                $this->_sTitle = $this->_sTitle . ', ' . $this->getVarSelect();\n            }\n        }\n\n        return $this->_sTitle;\n    }\n\n    /**\n     * Returns product details URL\n     *\n     * @return string\n     */\n    public function getLink()\n    {\n        if ($this->_sLink === null || $this->getLanguageId() != \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage()) {\n            $this->_sLink = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsUrl()->cleanUrl($this->getArticle()->getLink(), ['force_sid']);\n        }\n\n        $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n        return $session->processUrl($this->_sLink);\n    }\n\n    /**\n     * Returns ID of shop from which this product was added into basket\n     *\n     * @return string\n     */\n    public function getShopId()\n    {\n        return $this->_sShopId;\n    }\n\n    /**\n     * Returns user passed select list information\n     *\n     * @return array\n     */\n    public function getSelList()\n    {\n        return $this->_aSelList;\n    }\n\n    /**\n     * Returns user chosen select list information\n     *\n     * @return array\n     */\n    public function getChosenSelList()\n    {\n        return $this->_aChosenSelectlist;\n    }\n\n    /**\n     * Returns true if product is bundle\n     *\n     * @return bool\n     */\n    public function isBundle()\n    {\n        return $this->_blBundle;\n    }\n\n    /**\n     * Returns true if product is given as discount\n     *\n     * @return bool\n     */\n    public function isDiscountArticle()\n    {\n        return $this->_blIsDiscountArticle;\n    }\n\n    /**\n     * Returns true if discount must be skipped for current product\n     *\n     * @return bool\n     */\n    public function isSkipDiscount()\n    {\n        return $this->_blSkipDiscounts;\n    }\n\n    /**\n     * Special getter function for backwards compatibility.\n     * Executes methods by rule \"get\".$sVariableName and returns\n     * result processed by executed function.\n     *\n     * @param string $sName parameter name\n     *\n     * @return mixed\n     */\n    public function __get($sName)\n    {\n        if ($sName == 'oProduct') {\n            return $this->getArticle();\n        }\n    }\n\n    /**\n     * Does not return _oArticle var on serialisation\n     *\n     * @return array\n     */\n    public function __sleep()\n    {\n        $aRet = [];\n        foreach (get_object_vars($this) as $sKey => $sVar) {\n            if ($sKey != '_oArticle') {\n                $aRet[] = $sKey;\n            }\n        }\n\n        return $aRet;\n    }\n\n    /**\n     * Assigns general product parameters to oxbasketitem object :\n     *  - sProduct    - oxarticle object ID;\n     *  - title       - products title;\n     *  - icon        - icon name;\n     *  - link        - details URL's;\n     *  - sShopId     - current shop ID;\n     *  - sNativeShopId  - article shop ID;\n     *  - _sDimageDirNoSsl - NON SSL mode image path;\n     *  - _sDimageDirSsl   - SSL mode image path;\n     *\n     * @param string $sProductId product id\n     *\n     * @throws oxNoArticleException exception\n     */\n    protected function setArticle($sProductId)\n    {\n        $oConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $oArticle = $this->getArticle(true, $sProductId);\n\n        // product ID\n        $this->_sProductId = $sProductId;\n\n        $this->_sTitle = null;\n        $this->_sVarSelect = null;\n        $this->getTitle();\n\n        // removing force_sid from the link (in case it'll change)\n        $this->_sLink = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsUrl()->cleanUrl($oArticle->getLink(), ['force_sid']);\n\n        // shop Ids\n        $this->_sShopId = $oConfig->getShopId();\n        $this->_sNativeShopId = $oArticle->oxarticles__oxshopid->value;\n\n        // SSL/NON SSL image paths\n        $this->_sDimageDirNoSsl = $oArticle->nossl_dimagedir;\n        $this->_sDimageDirSsl = $oArticle->ssl_dimagedir;\n    }\n\n    /**\n     * Assigns general product parameters to oxbasketitem object:\n     *  - sProduct    - oxarticle object ID;\n     *  - title       - products title;\n     *  - sShopId     - current shop ID;\n     *  - sNativeShopId  - article shop ID;\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\OrderArticle $oOrderArticle order article\n     */\n    protected function setFromOrderArticle($oOrderArticle)\n    {\n        // overriding whole article\n        $this->_oArticle = $oOrderArticle;\n\n        // product ID\n        $this->_sProductId = $oOrderArticle->getProductId();\n\n        // products title\n        $this->_sTitle = $oOrderArticle->oxarticles__oxtitle->value;\n\n        // shop Ids\n        $this->_sShopId = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId();\n        $this->_sNativeShopId = $oOrderArticle->oxarticles__oxshopid->value;\n    }\n\n    /**\n     * Stores item select lists ( oxbasketitem::aSelList )\n     *\n     * @param array $aSelList item select lists\n     */\n    protected function setSelectList($aSelList)\n    {\n        // checking for default select list\n        $aSelectLists = $this->getArticle()->getSelectLists();\n        if (!$aSelList || is_array($aSelList) && count($aSelList) == 0) {\n            if ($iSelCnt = count($aSelectLists)) {\n                $aSelList = array_fill(0, $iSelCnt, '0');\n            }\n        }\n\n        $this->_aSelList = $aSelList;\n\n        //\n        if (is_array($this->_aSelList) && count($this->_aSelList)) {\n            foreach ($this->_aSelList as $conkey => $iSel) {\n                $this->_aChosenSelectlist[$conkey] = new stdClass();\n                $this->_aChosenSelectlist[$conkey]->name = $aSelectLists[$conkey]['name'];\n                $this->_aChosenSelectlist[$conkey]->value = $aSelectLists[$conkey][$iSel]->name;\n            }\n        }\n    }\n\n    /**\n     * Get persistent parameters ( oxbasketitem::_aPersistentParameters )\n     *\n     * @return array\n     */\n    public function getPersParams()\n    {\n        return $this->_aPersistentParameters;\n    }\n\n    /**\n     * Stores items persistent parameters ( oxbasketitem::_aPersistentParameters )\n     *\n     * @param array $aPersParam items persistent parameters\n     */\n    public function setPersParams($aPersParam)\n    {\n        $this->_aPersistentParameters = $aPersParam;\n    }\n\n    /**\n     * Marks if item is bundle ( oxbasketitem::blBundle )\n     *\n     * @param bool $blBundle if item is bundle\n     */\n    public function setBundle($blBundle)\n    {\n        $this->_blBundle = $blBundle;\n    }\n\n    /**\n     * Used to set \"skip discounts\" status for basket item\n     *\n     * @param bool $blSkip set true to skip discounts\n     */\n    public function setSkipDiscounts($blSkip)\n    {\n        $this->_blSkipDiscounts = $blSkip;\n    }\n\n    /**\n     * Returns product Id\n     *\n     * @return string product id\n     */\n    public function getProductId()\n    {\n        return $this->_sProductId;\n    }\n\n    /**\n     * Product wrapping paper id setter\n     *\n     * @param string $sWrapId wrapping paper id\n     */\n    public function setWrapping($sWrapId)\n    {\n        $this->_sWrappingId = $sWrapId;\n    }\n\n    /**\n     * Returns wrapping paper ID (if such was applied)\n     *\n     * @return string\n     */\n    public function getWrappingId()\n    {\n        return $this->_sWrappingId;\n    }\n\n    /**\n     * Returns basket item wrapping object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Wrapping\n     */\n    public function getWrapping()\n    {\n        $oWrap = null;\n        if ($sWrapId = $this->getWrappingId()) {\n            $oWrap = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Wrapping::class);\n            $oWrap->load($sWrapId);\n        }\n\n        return $oWrap;\n    }\n\n    /**\n     * Returns wishlist user Id\n     *\n     * @return string\n     */\n    public function getWishId()\n    {\n        return $this->_sWishId;\n    }\n\n    /**\n     * Wish user id setter\n     *\n     * @param string $sWishId user id\n     */\n    public function setWishId($sWishId)\n    {\n        $this->_sWishId = $sWishId;\n    }\n\n    /**\n     * Wish article Id setter\n     *\n     * @param string $sArticleId wish article id\n     */\n    public function setWishArticleId($sArticleId)\n    {\n        $this->_sWishArticleId = $sArticleId;\n    }\n\n    /**\n     * Returns wish article Id\n     *\n     * @return string\n     */\n    public function getWishArticleId()\n    {\n        return $this->_sWishArticleId;\n    }\n\n    /**\n     * Returns formatted regular unit price\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-08; use oxPrice template engine formatter\n     *\n     * @return string\n     */\n    public function getFRegularUnitPrice()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getLang()->formatCurrency($this->getRegularUnitPrice()->getPrice());\n    }\n\n    /**\n     * Returns formatted unit price\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-08; use oxPrice template engine formatter\n     *\n     * @return string\n     */\n    public function getFUnitPrice()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getLang()->formatCurrency($this->getUnitPrice()->getPrice());\n    }\n\n    /**\n     * Returns formatted total price\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-08; use oxPrice template engine formatter\n     *\n     * @return string\n     */\n    public function getFTotalPrice()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getLang()->formatCurrency($this->getPrice()->getPrice());\n    }\n\n    /**\n     * Returns formatted total price\n     *\n     * @return string\n     */\n    public function getVatPercent()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getLang()->formatVat($this->getPrice()->getVat());\n    }\n\n    /**\n     * Returns varselect value\n     *\n     * @return string\n     */\n    public function getVarSelect()\n    {\n        if ($this->_sVarSelect === null || $this->getLanguageId() != \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage()) {\n            $oArticle = $this->getArticle();\n            $sVarSelectValue = $oArticle->oxarticles__oxvarselect->value;\n            $this->_sVarSelect = (!empty($sVarSelectValue) || $sVarSelectValue === '0') ? $sVarSelectValue : '';\n        }\n\n        return $this->_sVarSelect;\n    }\n\n    /**\n     * Get language id\n     *\n     * @return integer\n     */\n    public function getLanguageId()\n    {\n        return $this->_iLanguageId;\n    }\n\n    /**\n     * Set language Id, reload basket content on language change.\n     *\n     * @param integer $iLanguageId language id\n     */\n    public function setLanguageId($iLanguageId)\n    {\n        $iOldLang = $this->_iLanguageId;\n        $this->_iLanguageId = $iLanguageId;\n\n        // #0003777: reload content on language change\n        if ($iOldLang !== null && $iOldLang != $iLanguageId) {\n            try {\n                $this->setArticle($this->getProductId());\n            } catch (\\OxidEsales\\Eshop\\Core\\Exception\\NoArticleException $oEx) {\n                \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay($oEx);\n            } catch (\\OxidEsales\\Eshop\\Core\\Exception\\ArticleInputException $oEx) {\n                \\OxidEsales\\Eshop\\Core\\Registry::getUtilsView()->addErrorToDisplay($oEx);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/BasketReservation.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse Exception;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse oxRegistry;\nuse oxField;\nuse oxDb;\nuse oxuserbasket;\n\n/**\n * Basket reservations handler class\n */\nclass BasketReservation extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Reservations list\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\UserBasket\n     */\n    protected $_oReservations = null;\n\n    /**\n     * Currently reserved products array\n     *\n     * @var array\n     */\n    protected $_aCurrentlyReserved = null;\n\n    /**\n     * return the ID of active resevations user basket\n     *\n     * @return string\n     */\n    protected function getReservationsId()\n    {\n        $sId = \\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable('basketReservationToken');\n        if (!$sId) {\n            $utilsObject = $this->getUtilsObjectInstance();\n            $sId = $utilsObject->generateUId();\n            \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable('basketReservationToken', $sId);\n        }\n\n        return $sId;\n    }\n\n    /**\n     * load reservation or create new reservation user basket\n     *\n     * @param string $sBasketId basket id for this user basket\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\UserBasket\n     */\n    protected function loadReservations($sBasketId)\n    {\n        $oReservations = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\UserBasket::class);\n        $aWhere = ['oxuserbaskets.oxuserid' => $sBasketId, 'oxuserbaskets.oxtitle' => 'reservations'];\n        $query = $oReservations->buildSelectString($aWhere);\n\n        $record = DatabaseProvider::getDb()->select($query);\n        if ($record && $record->count() > 0) {\n            $oReservations->assign($record->fields);\n        } else {\n            // creating if it does not exist\n            $oReservations->oxuserbaskets__oxtitle = new \\OxidEsales\\Eshop\\Core\\Field('reservations');\n            $oReservations->oxuserbaskets__oxuserid = new \\OxidEsales\\Eshop\\Core\\Field($sBasketId);\n            // marking basket as new (it will not be saved in DB yet)\n            $oReservations->setIsNewBasket();\n        }\n\n        return $oReservations;\n    }\n\n    /**\n     * get reservations collection\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\UserBasket\n     */\n    public function getReservations()\n    {\n        if ($this->_oReservations) {\n            return $this->_oReservations;\n        }\n\n        if (!$sBasketId = $this->getReservationsId()) {\n            return null;\n        }\n\n        $this->_oReservations = $this->loadReservations($sBasketId);\n\n        return $this->_oReservations;\n    }\n\n    /**\n     * return currently reserved items in an array format array (artId => amount)\n     *\n     * @return array\n     */\n    protected function getReservedItems()\n    {\n        if (isset($this->_aCurrentlyReserved)) {\n            return $this->_aCurrentlyReserved;\n        }\n\n        $oReserved = $this->getReservations();\n        if (!$oReserved) {\n            return [];\n        }\n\n        $this->_aCurrentlyReserved = [];\n        foreach ($oReserved->getItems(false, false) as $oItem) {\n            if (!isset($this->_aCurrentlyReserved[$oItem->oxuserbasketitems__oxartid->value])) {\n                $this->_aCurrentlyReserved[$oItem->oxuserbasketitems__oxartid->value] = 0;\n            }\n            $this->_aCurrentlyReserved[$oItem->oxuserbasketitems__oxartid->value] += $oItem->oxuserbasketitems__oxamount->value;\n        }\n\n        return $this->_aCurrentlyReserved;\n    }\n\n    /**\n     * return currently reserved amount for an article\n     *\n     * @param string $sArticleId article id\n     *\n     * @return double\n     */\n    public function getReservedAmount($sArticleId)\n    {\n        $aCurrentlyReserved = $this->getReservedItems();\n        if (isset($aCurrentlyReserved[$sArticleId])) {\n            return $aCurrentlyReserved[$sArticleId];\n        }\n\n        return 0;\n    }\n\n    /**\n     * compute difference of reserved amounts vs basket items\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket basket object\n     *\n     * @return array\n     */\n    protected function basketDifference(\\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket)\n    {\n        $aDiff = $this->getReservedItems();\n        // refreshing history\n        foreach ($oBasket->getContents() as $oItem) {\n            $sProdId = $oItem->getProductId();\n            if (!isset($aDiff[$sProdId])) {\n                $aDiff[$sProdId] = -$oItem->getAmount();\n            } else {\n                $aDiff[$sProdId] -= $oItem->getAmount();\n            }\n        }\n\n        return $aDiff;\n    }\n\n    /**\n     * reserve articles given the basket difference array\n     *\n     * @param array $aBasketDiff basket difference array\n     *\n     * @see oxBasketReservation::_basketDifference\n     */\n    protected function reserveArticles($aBasketDiff)\n    {\n        $blAllowNegativeStock = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blAllowNegativeStock');\n\n        $oReserved = $this->getReservations();\n        foreach ($aBasketDiff as $sId => $dAmount) {\n            if ($dAmount != 0) {\n                $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n                if ($oArticle->load($sId)) {\n                    $oArticle->reduceStock(-$dAmount, $blAllowNegativeStock);\n                    $oReserved->addItemToBasket($sId, -$dAmount);\n                }\n            }\n        }\n        $this->_aCurrentlyReserved = null;\n    }\n\n    /**\n     * reserve given basket items, only when not in admin mode\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket basket object\n     */\n    public function reserveBasket(\\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket)\n    {\n        if (!$this->isAdmin()) {\n            $this->reserveArticles($this->basketDifference($oBasket));\n        }\n    }\n\n    /**\n     * commit reservation of given article amount\n     * deletes this amount from active reservations userBasket,\n     * update sold amount\n     *\n     * @param string $sArticleId article id\n     * @param double $dAmount    amount to use\n     */\n    public function commitArticleReservation($sArticleId, $dAmount)\n    {\n        $dReserved = $this->getReservedAmount($sArticleId);\n\n        if ($dReserved < $dAmount) {\n            $dAmount = $dReserved;\n        }\n\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $oArticle->load($sArticleId);\n\n        $this->getReservations()->addItemToBasket($sArticleId, -$dAmount);\n        $oArticle->beforeUpdate();\n        $oArticle->updateSoldAmount($dAmount);\n        $this->_aCurrentlyReserved = null;\n    }\n\n    /**\n     * discard one article reservation\n     * return the reserved stock to article\n     *\n     * @param string $sArticleId article id\n     */\n    public function discardArticleReservation($sArticleId)\n    {\n        $dReserved = $this->getReservedAmount($sArticleId);\n        if ($dReserved) {\n            $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            if ($oArticle->load($sArticleId)) {\n                $oArticle->reduceStock(-$dReserved, true);\n                $this->getReservations()->addItemToBasket($sArticleId, 0, null, true);\n                $this->_aCurrentlyReserved = null;\n            }\n        }\n    }\n\n    /**\n     * discard all reserved articles\n     */\n    public function discardReservations()\n    {\n        foreach (array_keys($this->getReservedItems()) as $sArticleId) {\n            $this->discardArticleReservation($sArticleId);\n        }\n        if ($this->_oReservations) {\n            $this->_oReservations->delete();\n            $this->_oReservations = null;\n            $this->_aCurrentlyReserved = null;\n        }\n    }\n\n    /**\n     * periodic cleanup: discards timed out reservations even if they are not\n     * for the current user\n     *\n     * @param int $iLimit limit for discarding (performance related)\n     *\n     * @throws Exception\n     *\n     * @return null\n     */\n    public function discardUnusedReservations($iLimit)\n    {\n        $database = DatabaseProvider::getMaster();\n\n        $psBasketReservationTimeout = (int)Registry::getConfig()->getConfigParam('iPsBasketReservationTimeout');\n        $startTime = Registry::getUtilsDate()->getTime() - $psBasketReservationTimeout;\n        $shopId = Registry::getConfig()->getShopId();\n\n        $reservations = $database->select(\n            \"SELECT oxid FROM oxuserbaskets\n            WHERE oxtitle = :oxtitle\n                AND oxupdate <= :oxupdate\n            LIMIT $iLimit\",\n            [\n                'oxtitle' => 'reservations',\n                'oxupdate' => $startTime,\n            ]\n        );\n\n        if ($reservations->EOF) {\n            return;\n        }\n\n        $finished = [];\n        while (!$reservations->EOF) {\n            $finished[] = $database->quote($reservations->fields['oxid']);\n            $reservations->fetchRow();\n        }\n        $finished = implode(',', $finished);\n\n        $database->startTransaction();\n        try {\n            // Restock articles from selected reservation baskets only\n            $items = $database->select(\n                \"SELECT oxartid, oxamount FROM oxuserbasketitems \n                WHERE oxbasketid IN ($finished)\"\n            );\n\n            while (!$items->EOF) {\n                $article = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n                if ($article->load($items->fields['oxartid'])) {\n                    $article->reduceStock(-$items->fields['oxamount'], true);\n                }\n                $items->fetchRow();\n            }\n\n            // Delete items of the selected reservations\n            $database->execute(\"DELETE FROM oxuserbasketitems WHERE oxbasketid IN ($finished)\");\n\n            // Delete items of expired savedbasket (shop-scoped) — no reservations here\n            $database->execute(\n                \"DELETE i FROM oxuserbasketitems i\n                JOIN oxuserbaskets b ON i.oxbasketid = b.oxid \n                WHERE b.oxupdate <= :startTime\n                    AND oxtitle = 'savedbasket'\n                    AND b.oxuserid IN (SELECT oxid FROM oxuser WHERE oxshopid = :oxshopid)\",\n                [\n                    'startTime' => $startTime,\n                    'oxshopid' => $shopId,\n                ]\n            );\n\n            // Delete the selected reservation baskets\n            $database->execute(\"DELETE FROM oxuserbaskets WHERE oxid IN ($finished)\");\n\n            // Delete expired savedbaskets (shop scoped)\n            $database->execute(\n                \"DELETE FROM oxuserbaskets\n                WHERE oxupdate <= :startTime \n                    AND oxtitle = 'savedbasket'\n                    AND oxuserid IN (SELECT oxid FROM oxuser WHERE oxshopid = :oxshopid)\",\n                [\n                    'startTime' => $startTime,\n                    'oxshopid' => $shopId,\n                ]\n            );\n\n            $database->commitTransaction();\n        } catch (Exception $exception) {\n            $database->rollbackTransaction();\n            throw $exception;\n        }\n\n        $this->_aCurrentlyReserved = null;\n    }\n\n    /**\n     * return time left (in seconds) for basket before expiration\n     *\n     * @return int\n     */\n    public function getTimeLeft()\n    {\n        $iTimeout = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iPsBasketReservationTimeout');\n        if ($iTimeout > 0) {\n            $oRev = $this->getReservations();\n            if ($oRev && $oRev->getId()) {\n                $iTimeout -= (\\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime() - (int) $oRev->oxuserbaskets__oxupdate->value);\n                \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable(\"iBasketReservationTimeout\", $oRev->oxuserbaskets__oxupdate->value);\n            } elseif (($iSessionTimeout = \\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable(\"iBasketReservationTimeout\"))) {\n                $iTimeout -= (\\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime() - (int) $iSessionTimeout);\n            }\n\n            return $iTimeout < 0 ? 0 : $iTimeout;\n        }\n\n        return 0;\n    }\n\n    /**\n     * renews expiration timer to maximum value\n     */\n    public function renewExpiration()\n    {\n        if ($oReserved = $this->getReservations()) {\n            $iTime = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime();\n            $oReserved->oxuserbaskets__oxupdate = new \\OxidEsales\\Eshop\\Core\\Field($iTime);\n            $oReserved->save();\n\n            \\OxidEsales\\Eshop\\Core\\Registry::getSession()->deleteVariable(\"iBasketReservationTimeout\");\n        }\n    }\n\n    /**\n     * @return \\OxidEsales\\Eshop\\Core\\UtilsObject\n     */\n    protected function getUtilsObjectInstance()\n    {\n        return Registry::getUtilsObject();\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Category.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Category manager.\n * Collects category information (articles, etc.), performs insertion/deletion\n * of categories nodes. By recursion methods are set structure of category.\n */\n#[\\AllowDynamicProperties]\nclass Category extends \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel implements \\OxidEsales\\Eshop\\Core\\Contract\\IUrl\n{\n    /**\n     * Subcategories array.\n     *\n     * @var array\n     */\n    protected $_aSubCats = [];\n\n    /**\n     * Content category array.\n     *\n     * @var array\n     */\n    protected $_aContentCats = [];\n\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxcategory';\n\n    /**\n     * number of articles in the current category\n     *\n     * @var int\n     */\n    protected $_iNrOfArticles;\n\n    /**\n     * visibility of a category\n     *\n     * @var int\n     */\n    protected $_blIsVisible;\n\n    /**\n     * expanded state of a category\n     *\n     * @var int\n     */\n    protected $_blExpanded;\n\n    /**\n     * visibility of a category\n     *\n     * @var int\n     */\n    protected $_blHasSubCats;\n\n    /**\n     * has visible sub categories state of a category\n     *\n     * @var int\n     */\n    protected $_blHasVisibleSubCats;\n\n    /**\n     * Marks that current object is managed by SEO\n     *\n     * @var bool\n     */\n    protected $_blIsSeoObject = true;\n\n    /**\n     * Set $_blUseLazyLoading to true if you want to load only actually used fields not full object, depending on views.\n     *\n     * @var bool\n     */\n    protected $_blUseLazyLoading = false;\n\n    /**\n     * Dyn image dir\n     *\n     * @var string\n     */\n    protected $_sDynImageDir = null;\n\n    /**\n     * Top category marker\n     *\n     * @var bool\n     */\n    protected $_blTopCategory = null;\n\n    /**\n     * Standard/dynamic article urls for languages\n     *\n     * @var array\n     */\n    protected $_aStdUrls = [];\n\n    /**\n     * Seo article urls for languages\n     *\n     * @var array\n     */\n    protected $_aSeoUrls = [];\n\n    /**\n     * Category attributes cache\n     *\n     * @var array\n     */\n    protected static $_aCatAttributes = [];\n\n    /**\n     * Parent category object container.\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Category\n     */\n    protected $_oParent = null;\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxI18n()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxcategories');\n    }\n\n    /**\n     * Gets default sorting value\n     */\n    public function getDefaultSorting()\n    {\n        return $this->getFieldData('oxdefsort');\n    }\n\n    /**\n     * Gets default sorting mode value\n     *\n     * @return string\n     */\n    public function getDefaultSortingMode()\n    {\n        return $this->oxcategories__oxdefsortmode->value;\n    }\n\n    /**\n     * Extra getter to guarantee compatibility with templates\n     *\n     * @param string $sName name of variable to get\n     *\n     * @return string\n     */\n    public function __get($sName)\n    {\n        switch ($sName) {\n            case 'aSubCats':\n                return $this->_aSubCats;\n                break;\n            case 'aContent':\n                return $this->_aContentCats;\n                break;\n            case 'iArtCnt':\n                return $this->getNrOfArticles();\n                break;\n            case 'isVisible':\n                return $this->getIsVisible();\n                break;\n            case 'expanded':\n                return $this->getExpanded();\n                break;\n            case 'hasSubCats':\n                return $this->getHasSubCats();\n                break;\n            case 'hasVisibleSubCats':\n                return $this->getHasVisibleSubCats();\n                break;\n            case 'openlink':\n            case 'closelink':\n            case 'link':\n                //case 'toListLink':\n                //case 'noparamlink':\n                return $this->getLink();\n                break;\n            case 'dimagedir':\n                return $this->getPictureUrl();\n                break;\n        }\n        return parent::__get($sName);\n    }\n\n    /**\n     * Get data from db\n     *\n     * @param string $sOXID id\n     *\n     * @return array\n     */\n    protected function loadFromDb($sOXID)\n    {\n        $sSelect = $this->buildSelectString([\"`{$this->getViewName()}`.`oxid`\" => $sOXID]);\n        $aData = DatabaseProvider::getDb()->getRow($sSelect);\n\n        return $aData;\n    }\n\n    /**\n     * Load category data\n     *\n     * @param string $sOXID id\n     *\n     * @return bool\n     */\n    public function load($sOXID)\n    {\n        $aData = $this->loadFromDb($sOXID);\n\n        if ($aData) {\n            $this->assign($aData);\n            $this->_isLoaded = true;\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Loads and assigns object data from DB.\n     *\n     * @param mixed $dbRecord database record array\n     *\n     * @return null\n     */\n    public function assign($dbRecord)\n    {\n        $this->_iNrOfArticles = null;\n\n        //clear seo urls\n        $this->_aSeoUrls = [];\n\n        return parent::assign($dbRecord);\n    }\n\n    /**\n     * Delete empty categories, returns true on success.\n     *\n     * @param string $sOXID Object ID\n     *\n     * @return bool\n     */\n    public function delete($sOXID = null)\n    {\n        if (!$this->getId()) {\n            $this->load($sOXID);\n        }\n\n        $sOXID = isset($sOXID) ? $sOXID : $this->getId();\n\n        $myConfig = Registry::getConfig();\n        $oDb = DatabaseProvider::getDb();\n        $blRet = false;\n\n        if ($this->oxcategories__oxright->value == ($this->oxcategories__oxleft->value + 1)) {\n            $myUtilsPic = Registry::getUtilsPic();\n            $sDir = $myConfig->getPictureDir(false);\n\n            // only delete empty categories\n            // #1173M - not all pic are deleted, after article is removed\n            $myUtilsPic->safePictureDelete(\n                $this->getFieldData('oxthumb'),\n                $sDir . Registry::getUtilsFile()->getImageDirByType('TC'),\n                'oxcategories',\n                'oxthumb'\n            );\n            $myUtilsPic->safePictureDelete(\n                $this->getFieldData('oxicon'),\n                $sDir . Registry::getUtilsFile()->getImageDirByType('CICO'),\n                'oxcategories',\n                'oxicon'\n            );\n            $myUtilsPic->safePictureDelete(\n                $this->getFieldData('oxpromoicon'),\n                $sDir . Registry::getUtilsFile()->getImageDirByType('PICO'),\n                'oxcategories',\n                'oxpromoicon'\n            );\n\n            $query = \"UPDATE oxcategories SET OXLEFT = OXLEFT - 2\n                      WHERE OXROOTID = :oxrootid AND\n                            OXLEFT > :oxleft AND\n                            OXSHOPID = :oxshopid\";\n            $oDb->execute($query, [\n                'oxrootid' => $this->oxcategories__oxrootid->value,\n                'oxleft' => (int) $this->oxcategories__oxleft->value,\n                'oxshopid' => $this->getShopId()\n            ]);\n\n            $query = \"UPDATE oxcategories SET OXRIGHT = OXRIGHT - 2\n                      WHERE OXROOTID = :oxrootid AND\n                            OXRIGHT > :oxright AND\n                            OXSHOPID = :oxshopid\";\n            $oDb->execute($query, [\n                'oxrootid' => $this->oxcategories__oxrootid->value,\n                'oxright' => (int) $this->oxcategories__oxright->value,\n                'oxshopid' => $this->getShopId()\n            ]);\n\n            // delete entry\n            $blRet = parent::delete($sOXID);\n\n            // delete links to articles\n            $oDb->execute(\"delete from oxobject2category where oxobject2category.oxcatnid = :oxid\", [\n                'oxid' => $sOXID\n            ]);\n\n            // #657 ADDITIONAL delete links to attributes\n            $oDb->execute(\"delete from oxcategory2attribute where oxcategory2attribute.oxobjectid = :oxid\", [\n                'oxid' => $sOXID\n            ]);\n\n            // A. removing assigned:\n            // - deliveries\n            $oDb->execute(\"delete from oxobject2delivery where oxobject2delivery.oxobjectid = :oxid\", [\n                'oxid' => $sOXID\n            ]);\n            // - discounts\n            $oDb->execute(\"delete from oxobject2discount where oxobject2discount.oxobjectid = :oxid\", [\n                'oxid' => $sOXID\n            ]);\n\n            Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderCategory::class)->onDeleteCategory($this);\n        }\n\n        return $blRet;\n    }\n\n    /**\n     * returns the sub category array\n     *\n     * @return array\n     */\n    public function getSubCats()\n    {\n        return $this->_aSubCats;\n    }\n\n    /**\n     * returns a specific sub category\n     *\n     * @param string $sKey the key of the category\n     *\n     * @return object\n     */\n    public function getSubCat($sKey)\n    {\n        return $this->_aSubCats[$sKey];\n    }\n\n    /**\n     * Sets an array of sub categories, also handles parent hasVisibleSubCats\n     *\n     * @param array $aCats array of categories\n     */\n    public function setSubCats($aCats)\n    {\n        $this->_aSubCats = $aCats;\n\n        foreach ($aCats as $oCat) {\n            // keeping ref. to parent\n            $oCat->setParentCategory($this);\n\n            if ($oCat->getIsVisible()) {\n                $this->setHasVisibleSubCats(true);\n            }\n        }\n    }\n\n    /**\n     * sets a single category, handles sorting and parent hasVisibleSubCats\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Category $oCat the category\n     * @param string                                       $sKey (optional, default=null)  the key for that category,\n     *                                                           without a key, the category is just added to the array\n     */\n    public function setSubCat($oCat, $sKey = null)\n    {\n        if ($sKey) {\n            $this->_aSubCats[$sKey] = $oCat;\n        } else {\n            $this->_aSubCats[] = $oCat;\n        }\n\n        // keeping ref. to parent\n        $oCat->setParentCategory($this);\n\n        if ($oCat->getIsVisible()) {\n            $this->setHasVisibleSubCats(true);\n        }\n    }\n\n    /**\n     * returns the content category array\n     *\n     * @return array\n     */\n    public function getContentCats()\n    {\n        return $this->_aContentCats;\n    }\n\n    /**\n     * Sets an array of content categories\n     *\n     * @param array $aContent array of content\n     */\n    public function setContentCats($aContent)\n    {\n        $this->_aContentCats = $aContent;\n    }\n\n    /**\n     * sets a single category\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Category $oContent the category\n     * @param string                                       $sKey     optional, the key for that category,\n     *                                                               without a key, the category is just added to the array\n     */\n    public function setContentCat($oContent, $sKey = null)\n    {\n        if ($sKey) {\n            $this->_aContentCats[$sKey] = $oContent;\n        } else {\n            $this->_aContentCats[] = $oContent;\n        }\n    }\n\n    /**\n     * returns number or articles in category\n     *\n     * @return integer\n     */\n    public function getNrOfArticles()\n    {\n        $myConfig = Registry::getConfig();\n\n        if (\n            !isset($this->_iNrOfArticles)\n            && !$this->isAdmin()\n            && (\n                $myConfig->getConfigParam('bl_perfShowActionCatArticleCnt')\n                || $myConfig->getConfigParam('blDontShowEmptyCategories')\n            )\n        ) {\n            if ($this->isPriceCategory()) {\n                $this->_iNrOfArticles = Registry::getUtilsCount()\n                    ->getPriceCatArticleCount(\n                        $this->getId(),\n                        $this->getFieldData('oxpricefrom'),\n                        $this->getFieldData('oxpriceto')\n                    );\n            } else {\n                $this->_iNrOfArticles = Registry::getUtilsCount()\n                    ->getCatArticleCount($this->getId());\n            }\n        }\n\n        return (int) $this->_iNrOfArticles;\n    }\n\n    /**\n     * sets the number or articles in category\n     *\n     * @param int $iNum category product count setter\n     */\n    public function setNrOfArticles($iNum)\n    {\n        $this->_iNrOfArticles = $iNum;\n    }\n\n    /**\n     * returns the visibility of a category, handles hidden and empty categories\n     *\n     * @return bool\n     */\n    public function getIsVisible()\n    {\n        if (!isset($this->_blIsVisible)) {\n            if (Registry::getConfig()->getConfigParam('blDontShowEmptyCategories')) {\n                $blEmpty = ($this->getNrOfArticles() < 1) && !$this->getHasVisibleSubCats();\n            } else {\n                $blEmpty = false;\n            }\n\n            $this->_blIsVisible = !($blEmpty || $this->oxcategories__oxhidden->value);\n        }\n\n        return $this->_blIsVisible;\n    }\n\n    /**\n     * sets the visibility of a category\n     *\n     * @param bool $blVisible category visibility status setter\n     */\n    public function setIsVisible($blVisible)\n    {\n        $this->_blIsVisible = $blVisible;\n    }\n\n    /**\n     * Returns dyn image dir\n     *\n     * @return string\n     */\n    public function getPictureUrl()\n    {\n        if ($this->_sDynImageDir === null) {\n            $sThisShop = $this->oxcategories__oxshopid->value;\n            $this->_sDynImageDir = Registry::getConfig()->getPictureUrl(null, false, null, null, $sThisShop);\n        }\n\n        return $this->_sDynImageDir;\n    }\n\n    /**\n     * Returns raw category seo url\n     *\n     * @param int $iLang language id\n     * @param int $iPage page number [optional]\n     *\n     * @return string\n     */\n    public function getBaseSeoLink($iLang, $iPage = 0)\n    {\n        $oEncoder = Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderCategory::class);\n        if (!$iPage) {\n            return $oEncoder->getCategoryUrl($this, $iLang);\n        }\n\n        return $oEncoder->getCategoryPageUrl($this, $iPage, $iLang);\n    }\n\n    /**\n     * returns the url of the category\n     *\n     * @param int $iLang language id\n     *\n     * @return string\n     */\n    public function getLink($iLang = null)\n    {\n        if (\n            !Registry::getUtils()->seoIsActive() ||\n            $this->getFieldData('oxextlink')\n        ) {\n            return $this->getStdLink($iLang);\n        }\n\n        if ($iLang === null) {\n            $iLang = $this->getLanguage();\n        }\n\n        if (!isset($this->_aSeoUrls[$iLang])) {\n            $this->_aSeoUrls[$iLang] = $this->getBaseSeoLink($iLang);\n        }\n\n        return $this->_aSeoUrls[$iLang];\n    }\n\n    /**\n     * sets the url of the category\n     *\n     * @param string $sLink category url\n     */\n    public function setLink($sLink)\n    {\n        $iLang = $this->getLanguage();\n        if (Registry::getUtils()->seoIsActive()) {\n            $this->_aSeoUrls[$iLang] = $sLink;\n        } else {\n            $this->_aStdUrls[$iLang] = $sLink;\n        }\n    }\n\n    /**\n     * Returns SQL select string with checks if items are available\n     *\n     * @param bool $blForceCoreTable forces core table usage (optional)\n     *\n     * @return string\n     */\n    public function getSqlActiveSnippet($blForceCoreTable = null)\n    {\n        $sQ = parent::getSqlActiveSnippet($blForceCoreTable);\n\n        $sTable = $this->getViewName($blForceCoreTable);\n        $sQ .= (strlen($sQ) ? ' and ' : '') . \" $sTable.oxhidden = '0' \";\n        $sQ .= $this->getAdditionalSqlFilter($blForceCoreTable);\n\n        return \"( $sQ ) \";\n    }\n\n    /**\n     * Additional SQL conditions for selecting articles snippet\n     *\n     * @param bool $forceCoreTable\n     * @return string\n     */\n    protected function getAdditionalSqlFilter($forceCoreTable)\n    {\n        return '';\n    }\n\n    /**\n     * Returns base dynamic url: shopUrl/index.php?cl=details\n     *\n     * @param int  $iLang   language id\n     * @param bool $blAddId add current object id to url or not\n     * @param bool $blFull  return full including domain name [optional]\n     *\n     * @return string\n     */\n    public function getBaseStdLink($iLang, $blAddId = true, $blFull = true)\n    {\n        $externalLink = $this->getFieldData('oxextlink');\n        if ($externalLink) {\n            return $externalLink;\n        }\n\n        $sUrl = '';\n        if ($blFull) {\n            //always returns shop url, not admin\n            $sUrl = Registry::getConfig()->getShopUrl($iLang, false);\n        }\n\n        //always returns shop url, not admin\n        return $sUrl . \"index.php?cl=alist\" . ($blAddId ? \"&amp;cnid=\" . $this->getId() : \"\");\n    }\n\n    /**\n     * Returns standard URL to category\n     *\n     * @param int   $iLang   language\n     * @param array $aParams additional params to use [optional]\n     *\n     * @return string\n     */\n    public function getStdLink($iLang = null, $aParams = [])\n    {\n        $externalLink = $this->getFieldData('oxextlink');\n        if ($externalLink) {\n            return Registry::getUtilsUrl()->processUrl($externalLink);\n        }\n\n        if ($iLang === null) {\n            $iLang = $this->getLanguage();\n        }\n\n        if (!isset($this->_aStdUrls[$iLang])) {\n            $this->_aStdUrls[$iLang] = $this->getBaseStdLink($iLang);\n        }\n\n        return Registry::getUtilsUrl()->processUrl($this->_aStdUrls[$iLang], true, $aParams, $iLang);\n    }\n\n    /**\n     * returns the expanded state of the category\n     *\n     * @return bool\n     */\n    public function getExpanded()\n    {\n        return $this->_blExpanded;\n    }\n\n    /**\n     * set the expanded state of the category\n     *\n     * @param bool $blExpanded expanded status setter\n     */\n    public function setExpanded($blExpanded)\n    {\n        $this->_blExpanded = $blExpanded;\n    }\n\n    /**\n     * returns if a category has sub categories\n     *\n     * @return bool\n     */\n    public function getHasSubCats()\n    {\n        if (!isset($this->_blHasSubCats)) {\n            $this->_blHasSubCats = $this->oxcategories__oxright->value > $this->oxcategories__oxleft->value + 1;\n        }\n\n        return $this->_blHasSubCats;\n    }\n\n    /**\n     * returns if a category has visible sub categories\n     *\n     * @return bool\n     */\n    public function getHasVisibleSubCats()\n    {\n        if (!isset($this->_blHasVisibleSubCats)) {\n            $this->_blHasVisibleSubCats = false;\n        }\n\n        return $this->_blHasVisibleSubCats;\n    }\n\n    /**\n     * sets the state of has visible sub categories for the category\n     *\n     * @param bool $blHasVisibleSubcats marker if category has visible subcategories\n     */\n    public function setHasVisibleSubCats($blHasVisibleSubcats)\n    {\n        if ($blHasVisibleSubcats && !$this->_blHasVisibleSubCats) {\n            unset($this->_blIsVisible);\n            if ($this->_oParent instanceof \\OxidEsales\\Eshop\\Application\\Model\\Category) {\n                $this->_oParent->setHasVisibleSubCats(true);\n            }\n        }\n        $this->_blHasVisibleSubCats = $blHasVisibleSubcats;\n    }\n\n    /**\n     * Loads and returns attribute list associated with this category\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\AttributeList\n     */\n    public function getAttributes()\n    {\n        $sActCat = $this->getId();\n\n        $sKey = md5($sActCat . serialize(Registry::getSession()->getVariable('session_attrfilter')));\n        if (!isset(self::$_aCatAttributes[$sKey])) {\n            $oAttrList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\AttributeList::class);\n            $oAttrList->getCategoryAttributes($sActCat, $this->getLanguage());\n            self::$_aCatAttributes[$sKey] = $oAttrList;\n        }\n\n        return self::$_aCatAttributes[$sKey];\n    }\n\n    /**\n     * Loads and returns category in base language\n     *\n     * @param object $oActCategory active category\n     *\n     * @return object\n     */\n    public function getCatInLang($oActCategory = null)\n    {\n        $oCategoryInDefaultLanguage = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n        if ($this->isPriceCategory()) {\n            // get it in base language\n            $oCategoryInDefaultLanguage->loadInLang(0, $this->getId());\n        } else {\n            $oCategoryInDefaultLanguage->loadInLang(0, $oActCategory->getId());\n        }\n\n        return $oCategoryInDefaultLanguage;\n    }\n\n    /**\n     * Set parent category object for internal usage only.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Category $oCategory parent category object\n     */\n    public function setParentCategory($oCategory)\n    {\n        $this->_oParent = $oCategory;\n    }\n\n    /**\n     * Returns parent category object for current category (if it is available).\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Category\n     */\n    public function getParentCategory()\n    {\n        $category = null;\n\n        $parentCategoryId = $this->getFieldData('oxparentid');\n        if ($parentCategoryId !== 'oxrootid') {\n            // checking if object itself has ref to parent\n            if ($this->_oParent) {\n                $category = $this->_oParent;\n            } else {\n                $category = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n                if (!$category->load($parentCategoryId)) {\n                    $category = null;\n                } else {\n                    $this->_oParent = $category;\n                }\n            }\n        }\n\n        return $category;\n    }\n\n    /**\n     * Returns root category id of a child category\n     *\n     * @param string $sCategoryId category id\n     *\n     * @return integer\n     */\n    public static function getRootId($sCategoryId)\n    {\n        if (!isset($sCategoryId)) {\n            return;\n        }\n        $oDb = DatabaseProvider::getDb();\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        return $oDb\n            ->getOne('select oxrootid from ' . $tableViewNameGenerator->getViewName('oxcategories') . ' where oxid = :oxid', [\n            'oxid' => $sCategoryId\n        ]);\n    }\n\n    /**\n     * Before assigning the record from SQL it checks for viewable rights\n     *\n     * @param string $sSelect SQL select\n     *\n     * @return bool\n     */\n    public function assignViewableRecord($sSelect)\n    {\n        if ($this->assignRecord($sSelect)) {\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Inserts new category (and updates existing node oxLeft amd oxRight accordingly). Returns true on success.\n     *\n     * @return bool\n     */\n    protected function insert()\n    {\n        $parentCategoryId = $this->getFieldData('oxparentid');\n        if ($parentCategoryId !== 'oxrootid') {\n            // load parent\n            $oParent = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n            //#M317 check if parent is loaded\n            if (!$oParent->load($parentCategoryId)) {\n                return false;\n            }\n\n            // update existing nodes\n            $oDb = DatabaseProvider::getDb();\n            $query = \"UPDATE oxcategories SET OXLEFT = OXLEFT + 2\n                      WHERE OXROOTID = :oxrootid AND\n                            OXLEFT > :oxleft AND\n                            OXRIGHT >= :oxright AND\n                            OXSHOPID = :oxshopid \";\n            $oDb->execute($query, [\n                'oxrootid' => $oParent->oxcategories__oxrootid->value,\n                'oxleft' => (int) $oParent->oxcategories__oxright->value,\n                'oxright' => (int) $oParent->oxcategories__oxright->value,\n                'oxshopid' => $this->getShopId()\n            ]);\n\n            $query = \"UPDATE oxcategories SET OXRIGHT = OXRIGHT + 2\n                      WHERE OXROOTID = :oxrootid AND\n                            OXRIGHT >= :oxright AND\n                            OXSHOPID = :oxshopid\";\n            $oDb->execute($query, [\n                'oxrootid' => $oParent->oxcategories__oxrootid->value,\n                'oxright' => (int) $oParent->oxcategories__oxright->value,\n                'oxshopid' => $this->getShopId()\n            ]);\n\n            if (!$this->getId()) {\n                $this->setId();\n            }\n\n            $this->oxcategories__oxrootid = new \\OxidEsales\\Eshop\\Core\\Field($oParent->oxcategories__oxrootid->value, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n            $this->oxcategories__oxleft = new \\OxidEsales\\Eshop\\Core\\Field($oParent->oxcategories__oxright->value, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n            $this->oxcategories__oxright = new \\OxidEsales\\Eshop\\Core\\Field($oParent->oxcategories__oxright->value + 1, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n\n            return parent::insert();\n        } else {\n            // root entry\n            if (!$this->getId()) {\n                $this->setId();\n            }\n\n            $this->oxcategories__oxrootid = new \\OxidEsales\\Eshop\\Core\\Field($this->getId(), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n            $this->oxcategories__oxleft = new \\OxidEsales\\Eshop\\Core\\Field(1, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n            $this->oxcategories__oxright = new \\OxidEsales\\Eshop\\Core\\Field(2, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n\n            return parent::insert();\n        }\n    }\n\n    /**\n     * Updates category tree, returns true on success.\n     *\n     * @return bool\n     */\n    protected function update()\n    {\n        $this->setUpdateSeo(true);\n        $this->setUpdateSeoOnFieldChange('oxtitle');\n\n        // Function is called from inside a transaction in Category::save (see ESDEV-3804 and ESDEV-3822).\n        // No need to explicitly force master here.\n        $database = DatabaseProvider::getDb();\n        $sOldParentID = $database->getOne(\"select oxparentid from oxcategories where oxid = :oxid\", [\n            'oxid' => $this->getId()\n        ]);\n\n        if ($this->_blIsSeoObject && $this->isAdmin()) {\n            Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderCategory::class)->markRelatedAsExpired($this);\n        }\n\n        $blRes = parent::update();\n\n        // #872C - need to update category tree oxleft and oxright values (nested sets),\n        // then sub trees are moved inside one root, or to another root.\n        // this is done in 3 basic steps\n        // 1. increase oxleft and oxright values of target root tree by $iTreeSize, where oxleft>=$iMoveAfter , oxright>=$iMoveAfter\n        // 2. modify current subtree, we want to move by adding $iDelta to it's oxleft and oxright,  where oxleft>=$sOldParentLeft and oxright<=$sOldParentRight values,\n        //    in this step we also modify rootid's if they were changed\n        // 3. decreasing oxleft and oxright values of current root tree, where oxleft >= $sOldParentRight+1 , oxright >= $sOldParentRight+1\n\n        // did we change position in tree ?\n        $parentCategoryId = $this->getFieldData('oxparentid');\n        if ($parentCategoryId != $sOldParentID) {\n            $sOldParentLeft = $this->oxcategories__oxleft->value;\n            $sOldParentRight = $this->oxcategories__oxright->value;\n\n            $iTreeSize = $sOldParentRight - $sOldParentLeft + 1;\n\n            $sNewRootID = $database->getOne(\"select oxrootid from oxcategories where oxid = :oxid\", [\n                'oxid' => $parentCategoryId\n            ]);\n\n            //If empty rootID, we set it to categorys oxid\n            if ($sNewRootID == \"\") {\n                $sNewRootID = $this->getId();\n            }\n            $sNewParentLeft = $database->getOne(\"select oxleft from oxcategories where oxid = :oxid\", [\n                'oxid' => $parentCategoryId\n            ]);\n\n            $iMoveAfter = $sNewParentLeft + 1;\n\n            //New parentid can not be set to it's child\n            if ($sNewParentLeft > $sOldParentLeft && $sNewParentLeft < $sOldParentRight && $this->oxcategories__oxrootid->value == $sNewRootID) {\n\n                //Restoring old parentid, stoping further actions\n                $sRestoreOld = \"UPDATE oxcategories SET OXPARENTID = :oxparentid WHERE oxid = :oxid\";\n                $database->execute($sRestoreOld, [\n                    'oxparentid' => $sOldParentID,\n                    'oxid' => $this->getId()\n                ]);\n\n                return false;\n            }\n\n            //Old parent will be shifted too, if it is in the same tree\n            if ($sOldParentLeft > $iMoveAfter && $this->oxcategories__oxrootid->value == $sNewRootID) {\n                $sOldParentLeft += $iTreeSize;\n                $sOldParentRight += $iTreeSize;\n            }\n\n            $iDelta = $iMoveAfter - $sOldParentLeft;\n            $sAddOld = \" and oxshopid = '\" . $this->getShopId() . \"' and OXROOTID = \" . $database->quote($this->oxcategories__oxrootid->value) . \";\";\n            $sAddNew = \" and oxshopid = '\" . $this->getShopId() . \"' and OXROOTID = \" . $database->quote($sNewRootID) . \";\";\n\n            //Updating everything after new position\n            $params = ['treeSize' => $iTreeSize, 'offset' => $iMoveAfter];\n            $database->execute(\"UPDATE oxcategories SET OXLEFT = (OXLEFT + :treeSize) WHERE OXLEFT >= :offset\" . $sAddNew, $params);\n            $database->execute(\"UPDATE oxcategories SET OXRIGHT = (OXRIGHT + :treeSize) WHERE OXRIGHT >= :offset\" . $sAddNew, $params);\n\n            $sChangeRootID = \"\";\n            if ($this->oxcategories__oxrootid->value != $sNewRootID) {\n                $sChangeRootID = \", OXROOTID=\" . $database->quote($sNewRootID);\n            }\n\n            //Updating subtree\n            $query = \"UPDATE oxcategories SET OXLEFT = (OXLEFT + :delta), OXRIGHT = (OXRIGHT + :delta) \" . $sChangeRootID .\n                     \"WHERE OXLEFT >= :oxleft AND OXRIGHT <= :oxright\" . $sAddOld;\n            $database->execute($query, [\n                'delta' => $iDelta,\n                'oxleft' => $sOldParentLeft,\n                'oxright' => $sOldParentRight\n            ]);\n\n            //Updating everything after old position\n            $params = ['treeSize' => $iTreeSize, 'offset' => $sOldParentRight + 1];\n            $database->execute(\"UPDATE oxcategories SET OXLEFT = (OXLEFT - :treeSize) WHERE OXLEFT >= :offset\" . $sAddOld, $params);\n            $database->execute(\"UPDATE oxcategories SET OXRIGHT = (OXRIGHT - :treeSize) WHERE OXRIGHT >= :offset\" . $sAddOld, $params);\n        }\n\n        if ($blRes && $this->_blIsSeoObject && $this->isAdmin()) {\n            Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderCategory::class)->markRelatedAsExpired($this);\n        }\n\n        return $blRes;\n    }\n\n    /**\n     * Sets data field value\n     *\n     * @param string $fieldName index OR name (eg. 'oxarticles__oxtitle') of a data field to set\n     * @param string $value     value of data field\n     * @param int    $dataType  field type\n     *\n     * @return null\n     */\n    protected function setFieldData($fieldName, $value, $dataType = \\OxidEsales\\Eshop\\Core\\Field::T_TEXT)\n    {\n        //preliminary quick check saves 3% of execution time in category lists by avoiding redundant strtolower() call\n        $fieldNameIndex2 = $fieldName[2];\n        if ($fieldNameIndex2 === 'l' || $fieldNameIndex2 === 'L' || (isset($fieldName[16]) && ($fieldName[16] == 'l' || $fieldName[16] == 'L'))) {\n            $loweredFieldName = strtolower($fieldName);\n            if ('oxlongdesc' === $loweredFieldName || 'oxcategories__oxlongdesc' === $loweredFieldName) {\n                $dataType = \\OxidEsales\\Eshop\\Core\\Field::T_RAW;\n            }\n        }\n\n        return parent::setFieldData($fieldName, $value, $dataType);\n    }\n\n    /**\n     * Returns category icon picture url if exist, false - if not\n     *\n     * @return mixed\n     */\n    public function getIconUrl()\n    {\n        if (($sIcon = $this->oxcategories__oxicon->value)) {\n            $oConfig = Registry::getConfig();\n            $sSize = $oConfig->getConfigParam('sCatIconsize');\n            if (!isset($sSize)) {\n                $sSize = $oConfig->getConfigParam('sIconsize');\n            }\n\n            return Registry::getPictureHandler()->getPicUrl('category/icon/', $sIcon, $sSize);\n        }\n    }\n\n    /**\n     * Returns category thumbnail picture url if exist, false - if not\n     *\n     * @return mixed\n     */\n    public function getThumbUrl()\n    {\n        if (($sIcon = $this->oxcategories__oxthumb->value)) {\n            $sSize = Registry::getConfig()->getConfigParam('sCatThumbnailsize');\n\n            return Registry::getPictureHandler()->getPicUrl('category/thumb/', $sIcon, $sSize);\n        }\n    }\n\n    /**\n     * Returns category promotion icon picture url if exist, false - if not\n     *\n     * @return mixed\n     */\n    public function getPromotionIconUrl()\n    {\n        if (($sIcon = $this->oxcategories__oxpromoicon->value)) {\n            $sSize = Registry::getConfig()->getConfigParam('sCatPromotionsize');\n\n            return Registry::getPictureHandler()->getPicUrl('category/promo_icon/', $sIcon, $sSize);\n        }\n    }\n\n    /**\n     * Returns category picture url if exist, false - if not\n     *\n     * @param string $sPicName picture name\n     * @param string $sPicType picture type related with picture dir: icon - icon; 0 - image\n     *\n     * @return mixed\n     */\n    public function getPictureUrlForType($sPicName, $sPicType)\n    {\n        if ($sPicName) {\n            return $this->getPictureUrl() . $sPicType . '/' . $sPicName;\n        } else {\n            return false;\n        }\n    }\n\n    /**\n     * Returns true if category parentid is 'oxrootid'\n     *\n     * @return bool\n     */\n    public function isTopCategory()\n    {\n        if ($this->_blTopCategory == null) {\n            $this->_blTopCategory = $this->getFieldData('oxparentid') === 'oxrootid';\n        }\n\n        return $this->_blTopCategory;\n    }\n\n    /**\n     * Returns true if current category is price type ( ( oxpricefrom || oxpriceto ) > 0 )\n     *\n     * @return bool\n     */\n    public function isPriceCategory()\n    {\n        return (bool) ($this->oxcategories__oxpricefrom->value || $this->oxcategories__oxpriceto->value);\n    }\n\n    /**\n     * Returns short description\n     *\n     * @return string\n     */\n    public function getShortDescription()\n    {\n        return $this->oxcategories__oxdesc->value;\n    }\n\n    /**\n     * Returns category title\n     *\n     * @return string\n     */\n    public function getTitle()\n    {\n        return $this->oxcategories__oxtitle->value;\n    }\n\n    /**\n     * Gets one field from all of subcategories.\n     * Default is set to 'OXID'\n     *\n     * @param string $sField field to be retrieved from each subcategory\n     * @param string $sOXID  Cetegory ID\n     *\n     * @return array\n     */\n    public function getFieldFromSubCategories($sField = 'OXID', $sOXID = null)\n    {\n        if (!$sOXID) {\n            $sOXID = $this->getId();\n        }\n        if (!$sOXID) {\n            return false;\n        }\n\n        $sTable = $this->getViewName();\n        $sField = \"`{$sTable}`.`{$sField}`\";\n        $sSql = \"SELECT $sField FROM `{$sTable}` WHERE `OXROOTID` = :oxrootid AND `OXPARENTID` != 'oxrootid'\";\n        $aResult = DatabaseProvider::getDb()->getCol($sSql, [\n            'oxrootid' => $sOXID\n        ]);\n\n        return $aResult;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/CategoryList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\nuse Exception;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\n\n/**\n * Category list manager.\n * Collects available categories, performs some SQL queries to create category\n * list structure.\n */\nclass CategoryList extends \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n{\n    /**\n     * List Object class name\n     *\n     * @var string\n     */\n    protected $_sObjectsInListName = 'oxcategory';\n\n    /**\n     * Performance option mapped to config option blDontShowEmptyCategories\n     *\n     * @var boolean\n     */\n    protected $_blHideEmpty = false;\n\n    /**\n     * Performance option used to force full tree loading\n     *\n     * @var boolean\n     */\n    protected $_blForceFull = false;\n\n    /**\n     * Levels count should be loaded available options 1 - only root and 2 - root and second level\n     *\n     * @var boolean\n     */\n    protected $_iForceLevel = 2;\n\n    /**\n     * Active category id, used in path building, and performance optimization\n     *\n     * @var string\n     */\n    protected $_sActCat = null;\n\n    /**\n     * Active category path array\n     *\n     * @var array\n     */\n    protected $_aPath = [];\n\n    /**\n     * Category update info array\n     *\n     * @var array\n     */\n    protected $_aUpdateInfo = [];\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxList()).\n     *\n     * @param string $sObjectsInListName optional parameter, the objects contained in the list, always oxCategory\n     */\n    public function __construct($sObjectsInListName = 'oxcategory')\n    {\n        $this->_blHideEmpty = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blDontShowEmptyCategories');\n        parent::__construct($sObjectsInListName);\n    }\n\n    /**\n     * Set how to load tree true - for full tree\n     *\n     * @param boolean $blForceFull - true to load full\n     */\n    public function setLoadFull($blForceFull)\n    {\n        $this->_blForceFull = $blForceFull;\n    }\n\n    /**\n     * Return true if load full tree\n     *\n     * @return boolean\n     */\n    public function getLoadFull()\n    {\n        return $this->_blForceFull;\n    }\n\n    /**\n     * Set tree level 1- load root or 2 - root and second level\n     *\n     * @param int $iForceLevel - level number\n     */\n    public function setLoadLevel($iForceLevel)\n    {\n        if ($iForceLevel > 2) {\n            $iForceLevel = 2;\n        } elseif ($iForceLevel < 1) {\n            $iForceLevel = 0;\n        }\n        $this->_iForceLevel = $iForceLevel;\n    }\n\n    /**\n     * Returns tree load level\n     *\n     * @return integer\n     */\n    public function getLoadLevel()\n    {\n        return $this->_iForceLevel;\n    }\n\n    /**\n     * return fields to select while loading category tree\n     *\n     * @param string $sTable   table name\n     * @param array  $aColumns required column names (optional)\n     *\n     * @return string return\n     */\n    protected function getSqlSelectFieldsForTree($sTable, $aColumns = null)\n    {\n        if ($aColumns && count($aColumns)) {\n            foreach ($aColumns as $key => $val) {\n                $aColumns[$key] .= ' as ' . $val;\n            }\n\n            return \"$sTable.\" . implode(\", $sTable.\", $aColumns);\n        }\n\n        $sFieldList = \"$sTable.oxid as oxid, $sTable.oxactive as oxactive,\"\n                      . \" $sTable.oxhidden as oxhidden, $sTable.oxparentid as oxparentid,\"\n                      . \" $sTable.oxdefsort as oxdefsort, $sTable.oxdefsortmode as oxdefsortmode,\"\n                      . \" $sTable.oxleft as oxleft, $sTable.oxright as oxright,\"\n                      . \" $sTable.oxrootid as oxrootid, $sTable.oxsort as oxsort,\"\n                      . \" $sTable.oxtitle as oxtitle, $sTable.oxdesc as oxdesc,\"\n                      . \" $sTable.oxpricefrom as oxpricefrom, $sTable.oxpriceto as oxpriceto,\"\n                      . \" $sTable.oxicon as oxicon, $sTable.oxextlink as oxextlink,\"\n                      . \" $sTable.oxthumb as oxthumb, $sTable.oxpromoicon as oxpromoicon\";\n\n        $sFieldList .= $this->getActivityFieldsSql($sTable);\n\n        return $sFieldList;\n    }\n\n    /**\n     * Get activity related fields\n     *\n     * @param string $tableName\n     *\n     * @return string SQL snippet\n     */\n    protected function getActivityFieldsSql($tableName)\n    {\n        return \",not $tableName.oxactive as oxppremove\";\n    }\n\n    /**\n     * constructs the sql string to get the category list\n     *\n     * @param bool   $blReverse list loading order, true for tree, false for simple list (optional, default false)\n     * @param array  $aColumns  required column names (optional)\n     * @param string $sOrder    order by string (optional)\n     *\n     * @return string\n     */\n    protected function getSelectString($blReverse = false, $aColumns = null, $sOrder = null)\n    {\n        $sViewName = $this->getBaseObject()->getViewName();\n        $sFieldList = $this->getSqlSelectFieldsForTree($sViewName, $aColumns);\n\n        //excluding long desc\n        if (!$this->isAdmin() && !$this->_blHideEmpty && !$this->getLoadFull()) {\n            $oCat = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n            if (!($this->_sActCat && $oCat->load($this->_sActCat) && $oCat->oxcategories__oxrootid->value)) {\n                $oCat = null;\n                $this->_sActCat = null;\n            }\n\n            $sUnion = $this->getDepthSqlUnion($oCat, $aColumns);\n            $sWhere = $this->getDepthSqlSnippet($oCat);\n        } else {\n            $sUnion = '';\n            $sWhere = '1';\n        }\n\n        if (!$sOrder) {\n            $sOrdDir = $blReverse ? 'desc' : 'asc';\n            $sOrder = \"oxrootid $sOrdDir, oxleft $sOrdDir\";\n        }\n\n        return \"select $sFieldList from $sViewName where $sWhere $sUnion order by $sOrder\";\n    }\n\n    /**\n     * constructs the sql snippet responsible for depth optimizations,\n     * loads only selected category's siblings\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Category $oCat selected category\n     *\n     * @return string\n     */\n    protected function getDepthSqlSnippet($oCat)\n    {\n        $sViewName = $this->getBaseObject()->getViewName();\n        $depthSnippet = ' ( 0';\n\n        // load complete tree of active category, if it exists\n        if ($oCat) {\n            // select children here, siblings will be selected from union\n            $depthSnippet .= \" or ($sViewName.oxparentid = \"\n                . DatabaseProvider::getDb()->quote($oCat->oxcategories__oxid->value) . \")\";\n        }\n\n        // load 1'st category level (roots)\n        if ($this->getLoadLevel() >= 1) {\n            $depthSnippet .= \" or $sViewName.oxparentid = 'oxrootid'\";\n        }\n\n        // load 2'nd category level ()\n        if ($this->getLoadLevel() >= 2) {\n            $depthSnippet .= \" or $sViewName.oxrootid = $sViewName.oxparentid or $sViewName.oxid = $sViewName.oxrootid\";\n        }\n\n        return $depthSnippet . ' ) ';\n    }\n\n    /**\n     * returns sql snippet for union of select category's and its upper level\n     * siblings of the same root (siblings of the category, and parents and\n     * grandparents etc)\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Category $oCat     current category object\n     * @param array                                        $aColumns required column names (optional)\n     *\n     * @return string\n     */\n    protected function getDepthSqlUnion($oCat, $aColumns = null)\n    {\n        if (!$oCat) {\n            return '';\n        }\n\n        $sViewName = $this->getBaseObject()->getViewName();\n\n        return \"UNION SELECT \" . $this->getSqlSelectFieldsForTree('maincats', $aColumns)\n               . \" FROM oxcategories AS subcats\"\n               . \" LEFT JOIN $sViewName AS maincats on maincats.oxparentid = subcats.oxparentid\"\n               . \" WHERE subcats.oxrootid = \" . DatabaseProvider::getDb()->quote($oCat->oxcategories__oxrootid->value)\n               . \" AND subcats.oxleft <= \" . (int) $oCat->oxcategories__oxleft->value\n               . \" AND subcats.oxright >= \" . (int) $oCat->oxcategories__oxright->value;\n    }\n\n    /**\n     * Get data from db\n     *\n     * @return array\n     */\n    protected function loadFromDb()\n    {\n        $sSql = $this->getSelectString(false, null, 'oxparentid, oxsort, oxtitle');\n        $aData = DatabaseProvider::getDb()->getAll($sSql);\n\n        return $aData;\n    }\n\n    /**\n     * Load category list data\n     */\n    public function load()\n    {\n        $aData = $this->loadFromDb();\n        $this->assignArray($aData);\n    }\n\n    /**\n     * Fetches reversed raw categories and does all necessary postprocessing for\n     * removing invisible or forbidden categories, building oc navigation path,\n     * adding content categories and building tree structure.\n     *\n     * @param string $sActCat Active category (default null)\n     */\n    public function buildTree($sActCat)\n    {\n        startProfile(\"buildTree\");\n\n        $this->_sActCat = $sActCat;\n        $this->load();\n\n        // PostProcessing\n        if (!$this->isAdmin()) {\n            // remove inactive categories\n            $this->ppRemoveInactiveCategories();\n\n            // add active cat as full object\n            $this->ppLoadFullCategory($sActCat);\n\n            // builds navigation path\n            $this->ppAddPathInfo();\n\n            // add content categories\n            $this->ppAddContentCategories();\n\n            // build tree structure\n            $this->ppBuildTree();\n        }\n\n        stopProfile(\"buildTree\");\n    }\n\n    /**\n     * set full category object in tree\n     *\n     * @param string $sId category id\n     */\n    protected function ppLoadFullCategory($sId)\n    {\n        if ($sId !== null && isset($this->_aArray[$sId])) {\n            $oNewCat = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n            if ($oNewCat->load($sId)) {\n                // replace aArray object with fully loaded category\n                $this->_aArray[$sId] = $oNewCat;\n            }\n        } else {\n            $this->_sActCat = null;\n        }\n    }\n\n    /**\n     * Fetches raw categories and does postprocessing for adding depth information\n     */\n    public function loadList()\n    {\n        startProfile('buildCategoryList');\n\n        $this->setLoadFull(true);\n        $this->selectString($this->getSelectString(false, null, 'oxparentid, oxsort, oxtitle'));\n\n        // build tree structure\n        $this->ppBuildTree();\n\n        // PostProcessing\n        // add tree depth info\n        $this->ppAddDepthInformation();\n        stopProfile('buildCategoryList');\n    }\n\n    /**\n     * setter for shopId\n     *\n     * @param int $sShopId ShopID\n     */\n    public function setShopID($sShopId)\n    {\n        $this->_sShopID = $sShopId;\n    }\n\n    /**\n     * Getter for active category path\n     *\n     * @return array\n     */\n    public function getPath()\n    {\n        return $this->_aPath;\n    }\n\n    /**\n     * Getter for active category\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Category\n     */\n    public function getClickCat()\n    {\n        if (count($this->_aPath)) {\n            return end($this->_aPath);\n        }\n    }\n\n    /**\n     * Getter for active root category\n     *\n     * @return array of oxCategory\n     */\n    public function getClickRoot()\n    {\n        if (count($this->_aPath)) {\n            return [reset($this->_aPath)];\n        }\n    }\n\n    /**\n     * Postprocess to remove inactive/forbidden categories and subcategories\n     */\n    protected function ppRemoveInactiveCategories()\n    {\n        // Collect all items which must be remove\n        $aRemoveList = [];\n        foreach ($this->_aArray as $sId => $oCat) {\n            if ($oCat->oxcategories__oxppremove->value) {\n                if (!isset($aRemoveList[$oCat->oxcategories__oxrootid->value])) {\n                    $aRemoveList[$oCat->oxcategories__oxrootid->value] = [];\n                }\n                $aRemoveList[$oCat->oxcategories__oxrootid->value][$oCat->oxcategories__oxleft->value]\n                    = $oCat->oxcategories__oxright->value;\n                unset($this->_aArray[$sId]);\n            } else {\n                unset($oCat->oxcategories__oxppremove);\n            }\n        }\n\n        // Remove collected item's children from the list too (in the ranges).\n        foreach ($this->_aArray as $sId => $oCat) {\n            if (\n                isset($aRemoveList[$oCat->oxcategories__oxrootid->value]) &&\n                is_array($aRemoveList[$oCat->oxcategories__oxrootid->value])\n            ) {\n                foreach ($aRemoveList[$oCat->oxcategories__oxrootid->value] as $iLeft => $iRight) {\n                    if (\n                        ($iLeft <= $oCat->oxcategories__oxleft->value)\n                        && ($iRight >= $oCat->oxcategories__oxleft->value)\n                    ) {\n                        // this is a child in an inactive range (parent already gone)\n                        unset($this->_aArray[$sId]);\n                        break 1;\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * Category list postprocessing routine, responsible for generation of active category path\n     *\n     * @return null\n     */\n    protected function ppAddPathInfo()\n    {\n        if (is_null($this->_sActCat)) {\n            return;\n        }\n\n        $aPath = [];\n        $sCurrentCat = $this->_sActCat;\n\n        while ($sCurrentCat != 'oxrootid' && isset($this[$sCurrentCat])) {\n            $oCat = $this[$sCurrentCat];\n            $oCat->setExpanded(true);\n            $aPath[$sCurrentCat] = $oCat;\n            $sCurrentCat = $oCat->oxcategories__oxparentid->value;\n        }\n\n        $this->_aPath = array_reverse($aPath);\n    }\n\n    /**\n     * Category list postprocessing routine, responsible adding of content categories\n     */\n    protected function ppAddContentCategories()\n    {\n        // load content pages for adding them into menu tree\n        $oContentList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ContentList::class);\n        $oContentList->loadCatMenues();\n\n        foreach ($oContentList as $sCatId => $aContent) {\n            if (array_key_exists($sCatId, $this->_aArray)) {\n                $this[$sCatId]->setContentCats($aContent);\n            }\n        }\n    }\n\n    /**\n     * Category list postprocessing routine, responsible building an sorting of hierarchical category tree\n     */\n    protected function ppBuildTree()\n    {\n        $aTree = [];\n        foreach ($this->_aArray as $oCat) {\n            $sParentId = $oCat->oxcategories__oxparentid->value;\n            if ($sParentId != 'oxrootid') {\n                if (isset($this->_aArray[$sParentId])) {\n                    $this->_aArray[$sParentId]->setSubCat($oCat, $oCat->getId());\n                }\n            } else {\n                $aTree[$oCat->getId()] = $oCat;\n            }\n        }\n\n        $this->assign($aTree);\n    }\n\n    /**\n     * Category list postprocessing routine, responsible for making flat category tree and adding depth information.\n     * Requires reversed category list!\n     */\n    protected function ppAddDepthInformation()\n    {\n        $aTree = [];\n        foreach ($this->_aArray as $oCat) {\n            $aTree[$oCat->getId()] = $oCat;\n            $aSubCats = $oCat->getSubCats();\n            if (count($aSubCats) > 0) {\n                foreach ($aSubCats as $oSubCat) {\n                    $aTree = $this->addDepthInfo($aTree, $oSubCat);\n                }\n            }\n        }\n        $this->assign($aTree);\n    }\n\n    /**\n     * Recursive function to add depth information\n     *\n     * @param array  $aTree  new category tree\n     * @param object $oCat   category object\n     * @param string $sDepth string to show category depth\n     *\n     * @return array $aTree\n     */\n    protected function addDepthInfo($aTree, $oCat, $sDepth = \"\")\n    {\n        $sDepth .= \"-\";\n        $oCat->oxcategories__oxtitle->setValue($sDepth . ' ' . $oCat->oxcategories__oxtitle->value);\n        $aTree[$oCat->getId()] = $oCat;\n        $aSubCats = $oCat->getSubCats();\n        if (count($aSubCats) > 0) {\n            foreach ($aSubCats as $oSubCat) {\n                $aTree = $this->addDepthInfo($aTree, $oSubCat, $sDepth);\n            }\n        }\n\n        return $aTree;\n    }\n\n    /**\n     * Rebuilds nested sets information by updating oxLeft and oxRight category attributes, from oxParentId\n     *\n     * @param bool   $blVerbose Set to true for output the update status for user,\n     * @param string $sShopID   the shop id\n     */\n    public function updateCategoryTree($blVerbose = true, $sShopID = null)\n    {\n        // Only called from admin and admin mode reads from master (see ESDEV-3804 and ESDEV-3822).\n        $database = DatabaseProvider::getDb();\n        $database->startTransaction();\n\n        try {\n            $sWhere = $this->getInitialUpdateCategoryTreeCondition($blVerbose);\n\n            $database->execute(\"update oxcategories set oxleft = 0, oxright = 0 where $sWhere\");\n            $database->execute(\n                \"update oxcategories set oxleft = 1, oxright = 2 where oxparentid = 'oxrootid' and $sWhere\"\n            );\n\n            // Get all root categories\n            $categories = $database->select(\n                \"select oxid, oxtitle from oxcategories where oxparentid = 'oxrootid'\"\n                . \" and $sWhere order by oxsort\"\n            );\n            if ($categories != false && $categories->count() > 0) {\n                while (!$categories->EOF) {\n                    $this->_aUpdateInfo[] = \"<b>Processing : \" . $categories->fields['oxtitle']\n                        . \"</b>(\" . $categories->fields['oxid'] . \")<br>\";\n                    if ($blVerbose) {\n                        echo next($this->_aUpdateInfo);\n                    }\n                    $oxRootId = $categories->fields['oxid'];\n\n                    $this->updateNodes($oxRootId, true, $oxRootId);\n                    $categories->fetchRow();\n                }\n            }\n            $database->commitTransaction();\n        } catch (Exception $exception) {\n            $database->rollbackTransaction();\n            throw $exception;\n        }\n\n        $this->onUpdateCategoryTree();\n    }\n\n    /**\n     * Triggering in the end of updateCategoryTree method\n     */\n    protected function onUpdateCategoryTree()\n    {\n    }\n\n    /**\n     * Get Initial updateCategoryTree sql condition\n     *\n     * @param bool $blVerbose\n     *\n     * @return string\n     */\n    protected function getInitialUpdateCategoryTreeCondition($blVerbose = false)\n    {\n        return '1';\n    }\n\n    /**\n     * Returns update log data array\n     *\n     * @return array\n     */\n    public function getUpdateInfo()\n    {\n        return $this->_aUpdateInfo;\n    }\n\n    /**\n     * Recursively updates root nodes, this method is used (only) in updateCategoryTree()\n     *\n     * @param string $oxRootId rootid of tree\n     * @param bool   $isRoot   is the current node root?\n     * @param string $thisRoot the id of the root\n     */\n    protected function updateNodes($oxRootId, $isRoot, $thisRoot)\n    {\n        // Called from inside a transaction so master is picked automatically (see ESDEV-3804 and ESDEV-3822).\n        $database = DatabaseProvider::getDb();\n\n        if ($isRoot) {\n            $thisRoot = $oxRootId;\n        }\n\n        $database->execute('update oxcategories set oxrootid = :oxrootid where oxparentid = :oxparentid', [\n            'oxrootid' => $thisRoot,\n            'oxparentid' => $oxRootId\n        ]);\n        $childCategories = $database->select(\n            'select oxid, oxparentid from oxcategories where oxparentid = :oxparentid order by oxsort',\n            [\n                'oxparentid' => $oxRootId\n            ]\n        );\n        if ($childCategories != false && $childCategories->count() > 0) {\n            while (!$childCategories->EOF) {\n                $parentId = $childCategories->fields['oxparentid'];\n                $actOxid = $childCategories->fields['oxid'];\n\n                $parentCategory = $database->select(\n                    'select oxrootid, oxright from oxcategories where oxid = :oxid',\n                    [\n                        'oxid' => $parentId\n                    ]\n                );\n                if ($parentCategory != false && $parentCategory->count() > 0) {\n                    while (!$parentCategory->EOF) {\n                        $parentOxRootId = $parentCategory->fields['oxrootid'];\n                        $parentRight = (int)$parentCategory->fields['oxright'];\n                        $parentCategory->fetchRow();\n                    }\n                }\n\n                $query = 'update oxcategories set oxleft = oxleft + 2 where oxrootid = :oxrootid and'\n                    . ' oxleft > :parentRight and oxright >= :parentRight and oxid != :oxid';\n                $database->execute($query, [\n                    'oxrootid' => $parentOxRootId,\n                    'parentRight' => $parentRight,\n                    'oxid' => $actOxid\n                ]);\n\n                $query = 'update oxcategories set oxright = oxright + 2 where oxrootid = :oxrootid and'\n                    . ' oxright >= :oxright and oxid != :oxid';\n                $database->execute($query, [\n                    'oxrootid' => $parentOxRootId,\n                    'oxright' => $parentRight,\n                    'oxid' => $actOxid\n                ]);\n\n                $query = 'update oxcategories set oxleft = :parentRight, oxright = (:parentRight + 1)'\n                    . ' where oxid = :oxid';\n                $database->execute($query, [\n                    'parentRight' => $parentRight,\n                    'oxid' => $actOxid\n                ]);\n                $this->updateNodes($actOxid, false, $thisRoot);\n                $childCategories->fetchRow();\n            }\n        }\n    }\n\n    /**\n     * Extra getter to guarantee compatibility with templates\n     *\n     * @param string $sName variable name\n     *\n     * @return string\n     */\n    public function __get($sName)\n    {\n        switch ($sName) {\n            case 'aPath':\n            case 'aFullPath':\n                return $this->getPath();\n                break;\n        }\n        return parent::__get($sName);\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/CompanyVatIn.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxStr;\n\n/**\n * Company VAT identification number (VATIN)\n */\nclass CompanyVatIn\n{\n    /**\n     * VAT identification number\n     *\n     * @var string\n     */\n    private $_sCompanyVatNumber;\n\n    /**\n     * Constructor\n     *\n     * @param string $sCompanyVatNumber - company vat identification number.\n     */\n    public function __construct($sCompanyVatNumber)\n    {\n        $this->_sCompanyVatNumber = $sCompanyVatNumber;\n    }\n\n    /**\n     * Returns country code from number.\n     *\n     * @return string\n     */\n    public function getCountryCode()\n    {\n        return (string) \\OxidEsales\\Eshop\\Core\\Str::getStr()->strtoupper(\\OxidEsales\\Eshop\\Core\\Str::getStr()->substr($this->cleanUp($this->_sCompanyVatNumber), 0, 2));\n    }\n\n    /**\n     * Returns country code from number.\n     *\n     * @return string\n     */\n    public function getNumbers()\n    {\n        return (string) \\OxidEsales\\Eshop\\Core\\Str::getStr()->substr($this->cleanUp($this->_sCompanyVatNumber), 2);\n    }\n\n    /**\n     * Removes spaces and symbols: '-',',','.' from string\n     *\n     * @param string $sValue Value.\n     *\n     * @return string\n     */\n    protected function cleanUp($sValue)\n    {\n        return (string) \\OxidEsales\\Eshop\\Core\\Str::getStr()->preg_replace(\"/\\s|-/\", '', $sValue);\n    }\n\n\n    /**\n     * Cast to string\n     *\n     * @return string\n     */\n    public function __toString()\n    {\n        return $this->_sCompanyVatNumber;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Content.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Content manager.\n * Base object for content pages\n */\nclass Content extends \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel implements \\OxidEsales\\Eshop\\Core\\Contract\\IUrl\n{\n    /**\n     * Current class name.\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxcontent';\n\n    /**\n     * Seo article urls for languages.\n     *\n     * @var array\n     */\n    protected $_aSeoUrls = [];\n\n    /**\n     * Content parent category id\n     *\n     * @var string\n     */\n    protected $_sParentCatId = null;\n\n    /**\n     * Expanded state of a content category.\n     *\n     * @var bool\n     */\n    protected $_blExpanded = null;\n\n    /**\n     * Marks that current object is managed by SEO.\n     *\n     * @var bool\n     */\n    protected $_blIsSeoObject = true;\n\n    /**\n     * Category id.\n     *\n     * @var string\n     */\n    protected $_sCategoryId;\n\n    /**\n     * Extra getter to guarantee compatibility with templates.\n     *\n     * @param string $sName parameter name\n     *\n     * @return mixed\n     */\n    public function __get($sName)\n    {\n        switch ($sName) {\n            case 'expanded':\n                return $this->getExpanded();\n                break;\n        }\n        return parent::__get($sName);\n    }\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxI18n()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxcontents');\n    }\n\n    /**\n     * Returns the expanded state of the content category.\n     *\n     * @return bool\n     */\n    public function getExpanded()\n    {\n        if (!isset($this->_blExpanded)) {\n            $this->_blExpanded = ($this->getId() == Registry::getRequest()->getRequestEscapedParameter('oxcid'));\n        }\n\n        return $this->_blExpanded;\n    }\n\n    /**\n     * Sets category id.\n     *\n     * @param string $sCategoryId\n     */\n    public function setCategoryId($sCategoryId)\n    {\n        $this->oxcontents__oxcatid = new Field($sCategoryId);\n    }\n\n    /**\n     * Returns category id.\n     *\n     * @return string\n     */\n    public function getCategoryId()\n    {\n        return $this->oxcontents__oxcatid->value;\n    }\n\n    /**\n     * Get data from db.\n     *\n     * @param string $sLoadId id\n     *\n     * @return array\n     */\n    protected function loadFromDb($sLoadId)\n    {\n        $sTable = $this->getViewName();\n        $sShopId = $this->getShopId();\n        $aParams = [$sTable . '.oxloadid' => $sLoadId, $sTable . '.oxshopid' => $sShopId];\n\n        $sSelect = $this->buildSelectString($aParams);\n\n        //Loads \"credits\" content object and its text (first available)\n        if ($sLoadId == 'oxcredits') {\n            // fetching column names\n            $sColQ = \"SHOW COLUMNS FROM oxcontents WHERE field LIKE  'oxcontent%'\";\n            $aCols = DatabaseProvider::getDb()->getAll($sColQ);\n\n            // building subquery\n            $sPattern = \"IF ( %s != '', %s, %s ) \";\n            $iCount = count($aCols) - 1;\n\n            $sContQ = \"SELECT {$sPattern}\";\n            foreach ($aCols as $iKey => $aCol) {\n                $sContQ = sprintf($sContQ, $aCol[0], $aCol[0], $iCount != $iKey ? $sPattern : \"''\");\n            }\n            $sContQ .= \" FROM oxcontents WHERE oxloadid = '{$sLoadId}' AND oxshopid = '{$sShopId}'\";\n\n            $sSelect = $this->buildSelectString($aParams);\n            $sSelect = str_replace(\"`{$sTable}`.`oxcontent`\", \"( $sContQ ) as oxcontent\", $sSelect);\n        }\n\n        $aData = DatabaseProvider::getDb()->getRow($sSelect);\n\n        return $aData;\n    }\n\n    /**\n     * Loads Content by using field oxloadid instead of oxid.\n     *\n     * @param string $loadId     content load ID\n     * @param string|false $onlyActive selection state - active/inactive\n     *\n     * @return bool\n     */\n    public function loadByIdent($loadId, $onlyActive = false)\n    {\n        return $this->assignContentData($this->loadFromDb($loadId), $onlyActive);\n    }\n\n    /**\n     * Assign content data, filter inactive if needed.\n     *\n     * @param array $fetchedContent Item data to assign\n     * @param bool  $onlyActive     Only assign if item is active\n     *\n     * @return bool\n     */\n    protected function assignContentData($fetchedContent, $onlyActive = false)\n    {\n        $filteredContent = $this->filterInactive($fetchedContent, $onlyActive);\n\n        if (!is_null($filteredContent)) {\n            $this->assign($filteredContent);\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Decide if content item can be loaded by checking item activity if needed\n     *\n     * @param array $data\n     * @param bool  $checkIfActive\n     *\n     * @return array|null\n     */\n    protected function filterInactive($data, $checkIfActive = false)\n    {\n        return $data && (!$checkIfActive || ($checkIfActive && $data['OXACTIVE']) == '1') ? $data : null;\n    }\n\n    /**\n     * Returns unique object id.\n     *\n     * @return string\n     */\n    public function getLoadId()\n    {\n        return $this->oxcontents__oxloadid->value;\n    }\n\n    /**\n     * Returns unique object id.\n     *\n     * @return string\n     */\n    public function isActive()\n    {\n        return $this->oxcontents__oxactive->value;\n    }\n\n    /**\n     * Replace the \"&amp;\" into \"&\" and call base class.\n     *\n     * @param array $dbRecord database record\n     */\n    public function assign($dbRecord)\n    {\n        parent::assign($dbRecord);\n        // workaround for firefox showing &lang= as &9001;= entity, mantis#0001272\n\n        if ($this->oxcontents__oxcontent) {\n            $this->oxcontents__oxcontent->setValue(\n                str_replace('&lang=', '&amp;lang=', $this->oxcontents__oxcontent->value),\n                Field::T_RAW\n            );\n        }\n    }\n\n    /**\n     * Returns raw content seo url\n     *\n     * @param int $iLang language id\n     *\n     * @return string\n     */\n    public function getBaseSeoLink($iLang)\n    {\n        return Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderContent::class)\n            ->getContentUrl($this, $iLang);\n    }\n\n    /**\n     * getLink returns link for this content in the frontend.\n     *\n     * @param int $iLang language id [optional]\n     *\n     * @return string\n     */\n    public function getLink($iLang = null)\n    {\n        if (!Registry::getUtils()->seoIsActive()) {\n            return $this->getStdLink($iLang);\n        }\n\n        if ($iLang === null) {\n            $iLang = $this->getLanguage();\n        }\n\n        if (!isset($this->_aSeoUrls[$iLang])) {\n            $this->_aSeoUrls[$iLang] = $this->getBaseSeoLink($iLang);\n        }\n\n        return $this->_aSeoUrls[$iLang];\n    }\n\n    /**\n     * Returns base dynamic url: shopurl/index.php?cl=details\n     *\n     * @param int  $iLang   language id\n     * @param bool $blAddId add current object id to url or not\n     * @param bool $blFull  return full including domain name [optional]\n     *\n     * @return string\n     */\n    public function getBaseStdLink($iLang, $blAddId = true, $blFull = true)\n    {\n        $sUrl = '';\n        if ($blFull) {\n            //always returns shop url, not admin\n            $sUrl = Registry::getConfig()->getShopUrl($iLang, false);\n        }\n\n        if ($this->oxcontents__oxloadid->value === 'oxcredits') {\n            $sUrl .= \"index.php?cl=credits\";\n        } else {\n            $sUrl .= \"index.php?cl=content\";\n        }\n        $sUrl .= '&amp;oxloadid=' . $this->getLoadId();\n\n        if ($blAddId) {\n            $sUrl .= \"&amp;oxcid=\" . $this->getId();\n            // adding parent category if if available\n            if (\n                $this->_sParentCatId !== false\n                && $this->oxcontents__oxcatid->value\n                && $this->oxcontents__oxcatid->value != 'oxrootid'\n            ) {\n                if ($this->_sParentCatId === null) {\n                    $this->_sParentCatId = false;\n                    $oDb = DatabaseProvider::getDb();\n                    $sParentId = $oDb->getOne(\"select oxparentid from oxcategories where oxid = :oxid\", [\n                        'oxid' => $this->oxcontents__oxcatid->value\n                    ]);\n                    if ($sParentId && 'oxrootid' != $sParentId) {\n                        $this->_sParentCatId = $sParentId;\n                    }\n                }\n\n                if ($this->_sParentCatId) {\n                    $sUrl .= \"&amp;cnid=\" . $this->_sParentCatId;\n                }\n            }\n        }\n\n        //always returns shop url, not admin\n        return $sUrl;\n    }\n\n    /**\n     * Returns standard URL to product.\n     *\n     * @param integer $iLang   language\n     * @param array   $aParams additional params to use [optional]\n     *\n     * @return string\n     */\n    public function getStdLink($iLang = null, $aParams = [])\n    {\n        if ($iLang === null) {\n            $iLang = $this->getLanguage();\n        }\n\n        return Registry::getUtilsUrl()->processUrl($this->getBaseStdLink($iLang), true, $aParams, $iLang);\n    }\n\n    /**\n     * Sets data field value.\n     *\n     * @param string $sFieldName index OR name (eg. 'oxarticles__oxtitle') of a data field to set\n     * @param string $sValue     value of data field\n     * @param int    $iDataType  field type\n     *\n     * @return null\n     */\n    protected function setFieldData($sFieldName, $sValue, $iDataType = Field::T_TEXT)\n    {\n        $sLoweredFieldName = strtolower($sFieldName);\n        if ('oxcontent' === $sLoweredFieldName || 'oxcontents__oxcontent' === $sLoweredFieldName) {\n            $iDataType = Field::T_RAW;\n        }\n\n        return parent::setFieldData($sFieldName, $sValue, $iDataType);\n    }\n\n    /**\n     * Delete this object from the database, returns true on success.\n     *\n     * @param string $sOXID Object ID(default null)\n     *\n     * @return bool\n     */\n    public function delete($sOXID = null)\n    {\n        if (!$sOXID) {\n            $sOXID = $this->getId();\n        }\n\n        if (parent::delete($sOXID)) {\n            Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderContent::class)->onDeleteContent($sOXID);\n\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Save this Object to database, insert or update as needed.\n     *\n     * @return mixed\n     */\n    public function save()\n    {\n        $blSaved = parent::save();\n        if ($blSaved && $this->getFieldData('oxloadid') === 'oxagb') {\n            $sShopId = Registry::getConfig()->getShopId();\n            $sVersion = $this->oxcontents__oxtermversion->value;\n\n            $oDb = DatabaseProvider::getDb();\n            // dropping expired..\n            $oDb->execute(\n                \"delete from oxacceptedterms where oxshopid = :oxshopid and oxtermversion != :notoxtermversion\",\n                [\n                    'oxshopid' => $sShopId,\n                    'notoxtermversion' => $sVersion\n                ]\n            );\n        }\n\n        return $blSaved;\n    }\n\n    /**\n     * Returns latest terms version id.\n     *\n     * @return string\n     */\n    public function getTermsVersion()\n    {\n        if ($this->loadByIdent('oxagb')) {\n            return $this->oxcontents__oxtermversion->value;\n        }\n    }\n\n    /**\n     * Set type of content.\n     *\n     * @param string $sValue type value\n     */\n    public function setType($sValue)\n    {\n        $this->setFieldData('oxcontents__oxtype', $sValue);\n    }\n\n    /**\n     * Return type of content\n     *\n     * @return integer\n     */\n    public function getType()\n    {\n        return (int) $this->getFieldData('oxcontents__oxtype');\n    }\n\n    /**\n     * Set title of content\n     *\n     * @param string $sValue title value\n     */\n    public function setTitle($sValue)\n    {\n        $this->setFieldData('oxcontents__oxtitle', $sValue);\n    }\n\n    /**\n     * Return title of content\n     *\n     * @return string\n     */\n    public function getTitle()\n    {\n        return (string) $this->getFieldData('oxcontents__oxtitle');\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/ContentList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\n\n/**\n * Content list manager.\n * Collects list of content\n */\nclass ContentList extends \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n{\n    /**\n     * Information content type\n     *\n     * @var int\n     */\n    const TYPE_INFORMATION_CONTENTS = 0;\n\n    /**\n     * Main menu list type\n     *\n     * @var int\n     */\n    const TYPE_MAIN_MENU_LIST = 1;\n\n    /**\n     * Main menu list type\n     *\n     * @var int\n     */\n    const TYPE_CATEGORY_MENU = 2;\n\n    /**\n     * Service list.\n     *\n     * @var int\n     */\n    const TYPE_SERVICE_LIST = 3;\n\n    /**\n     * List of services.\n     *\n     * @var array\n     */\n    protected $_aServiceKeys = ['oximpressum', 'oxagb', 'oxsecurityinfo', 'oxdeliveryinfo', 'oxrightofwithdrawal', 'oxorderinfo', 'oxcredits'];\n\n    /**\n     * Sets service keys.\n     *\n     * @param array $aServiceKeys\n     */\n    public function setServiceKeys($aServiceKeys)\n    {\n        $this->_aServiceKeys = $aServiceKeys;\n    }\n\n    /**\n     * Gets services keys.\n     *\n     * @return array\n     */\n    public function getServiceKeys()\n    {\n        return $this->_aServiceKeys;\n    }\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxList()).\n     */\n    public function __construct()\n    {\n        parent::__construct('oxcontent');\n    }\n\n    /**\n     * Loads main menue entries and generates list with links\n     */\n    public function loadMainMenulist()\n    {\n        $this->load(self::TYPE_MAIN_MENU_LIST);\n    }\n\n    /**\n     * Load Array of Menue items and change keys of aList to catid\n     */\n    public function loadCatMenues()\n    {\n        $this->load(self::TYPE_CATEGORY_MENU);\n        $aArray = [];\n\n        if ($this->count()) {\n            foreach ($this as $oContent) {\n                // add into category tree\n                if (!isset($aArray[$oContent->getCategoryId()])) {\n                    $aArray[$oContent->getCategoryId()] = [];\n                }\n\n                $aArray[$oContent->oxcontents__oxcatid->value][] = $oContent;\n            }\n        }\n\n        $this->_aArray = $aArray;\n    }\n\n    /**\n     * Get data from db\n     *\n     * @param integer $iType - type of content\n     *\n     * @return array\n     */\n    protected function loadFromDb($iType)\n    {\n        $sSql = $this->getSQLByType($iType);\n        return DatabaseProvider::getDb()->getAll($sSql);\n    }\n\n    /**\n     * Load category list data\n     *\n     * @param integer $type - type of content\n     */\n    protected function load($type)\n    {\n        $data = $this->loadFromDb($type);\n        $this->assignArray($data);\n    }\n\n    /**\n     * Load category list data.\n     */\n    public function loadServices()\n    {\n        $this->load(self::TYPE_SERVICE_LIST);\n        $this->extractListToArray();\n    }\n\n    /**\n     * Extract oxContentList object to associative array with oxloadid as keys.\n     */\n    protected function extractListToArray()\n    {\n        $aExtractedContents = [];\n        foreach ($this as $oContent) {\n            $aExtractedContents[$oContent->getLoadId()] = $oContent;\n        }\n\n        $this->_aArray = $aExtractedContents;\n    }\n\n    /**\n     * Creates SQL by type.\n     *\n     * @param integer $iType type.\n     *\n     * @return string\n     */\n    protected function getSQLByType($iType)\n    {\n        $sSQLAdd = '';\n        $oDb = DatabaseProvider::getDb();\n        $sSQLType = \" AND `oxtype` = \" . $oDb->quote($iType);\n\n        if ($iType == self::TYPE_CATEGORY_MENU) {\n            $sSQLAdd = \" AND `oxcatid` IS NOT NULL AND `oxsnippet` = '0'\";\n        }\n\n        if ($iType == self::TYPE_SERVICE_LIST) {\n            $sIdents = implode(\", \", DatabaseProvider::getDb()->quoteArray($this->getServiceKeys()));\n            $sSQLAdd = \" AND OXLOADID IN (\" . $sIdents . \")\";\n            $sSQLType = '';\n        }\n        $sViewName = $this->getBaseObject()->getViewName();\n        $sSql = \"SELECT * FROM {$sViewName} WHERE `oxactive` = '1' $sSQLType AND `oxshopid` = \" . $oDb->quote($this->_sShopID) . \" $sSQLAdd ORDER BY `oxloadid`\";\n\n        return $sSql;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Contract/ArticleInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model\\Contract;\n\n/**\n * Article interface\n */\ninterface ArticleInterface\n{\n\n    /**\n     * Checks if stock configuration allows to buy user chosen amount $dAmount\n     *\n     * @param double $dAmount         buyable amount\n     * @param double $dArtStockAmount stock amount\n     *\n     * @return mixed\n     */\n    public function checkForStock($dAmount, $dArtStockAmount = 0);\n\n    /**\n     * Returns all selectlists this article has.\n     *\n     * @param string $sKeyPrefix Optionall key prefix\n     *\n     * @return array\n     */\n    public function getSelectLists($sKeyPrefix = null);\n\n    /**\n     * Creates, calculates and returns oxprice object for basket product.\n     *\n     * @param double $dAmount  Amount\n     * @param string $aSelList Selection list\n     * @param object $oBasket  User shopping basket object\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getBasketPrice($dAmount, $aSelList, $oBasket);\n\n    /**\n     * Checks if discount should be skipped for this article in basket. Returns true if yes.\n     *\n     * @return bool\n     */\n    public function skipDiscounts();\n\n    /**\n     * Returns ID's of categories. where this article is assigned\n     *\n     * @param bool $blActCats   select categories if all parents are active\n     * @param bool $blSkipCache Whether to skip cache\n     *\n     * @return array\n     */\n    public function getCategoryIds($blActCats = false, $blSkipCache = false);\n\n    /**\n     * Calculates and returns price of article (adds taxes and discounts).\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getPrice();\n\n    /**\n     * Returns product id (oxid)\n     *\n     * @return string\n     */\n    public function getProductId();\n\n    /**\n     * Returns base article price from database\n     *\n     * @param double $dAmount article amount. Default is 1\n     *\n     * @return double\n     */\n    public function getBasePrice($dAmount = 1);\n\n    /**\n     * Returns true if object is derived from oxorderarticle class\n     *\n     * @return bool\n     */\n    public function isOrderArticle();\n}\n"
  },
  {
    "path": "source/Application/Model/Country.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Country manager\n */\nclass Country extends \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel\n{\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxcountry';\n\n    /**\n     * State list\n     *\n     * @var array\n     */\n    protected $_aStates = null;\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxI18n()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxcountry');\n    }\n\n    /**\n     * returns true if this country is a foreign country\n     *\n     * @return bool\n     */\n    public function isForeignCountry()\n    {\n        return !in_array($this->getId(), \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('aHomeCountry'));\n    }\n\n    /**\n     * returns true if this country is marked as EU\n     *\n     * @return bool\n     */\n    public function isInEU()\n    {\n        return (bool) ($this->oxcountry__oxvatstatus->value == 1);\n    }\n\n    /**\n     * Returns current state list\n     *\n     * @return array\n     */\n    public function getStates()\n    {\n        if (!is_null($this->_aStates)) {\n            return $this->_aStates;\n        }\n\n        $sCountryId = $this->getId();\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sViewName = $tableViewNameGenerator->getViewName(\"oxstates\", $this->getLanguage());\n        $sQ = \"select * from {$sViewName} where `oxcountryid` = :oxcountryid order by `oxtitle`  \";\n        $this->_aStates = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n        $this->_aStates->init(\"oxstate\");\n        $this->_aStates->selectString($sQ, [\n            'oxcountryid' => $sCountryId\n        ]);\n\n        return $this->_aStates;\n    }\n\n    /**\n     * Returns country id by code\n     *\n     * @param string $sCode country code\n     *\n     * @return string\n     */\n    public function getIdByCode($sCode)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        return $oDb->getOne(\"select oxid from oxcountry where oxisoalpha2 = :oxisoalpha2\", [\n            'oxisoalpha2' => $sCode\n        ]);\n    }\n\n    /**\n     * Method returns VAT identification number prefix.\n     *\n     * @return string\n     */\n    public function getVATIdentificationNumberPrefix()\n    {\n        return $this->oxcountry__oxvatinprefix->value;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/CountryList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Country list manager class.\n * Collects a list of countries according to collection rules (active).\n */\nclass CountryList extends \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n{\n    /**\n     * Call parent class constructor\n     */\n    public function __construct()\n    {\n        parent::__construct('oxcountry');\n    }\n\n    /**\n     * Selects and loads all active countries\n     *\n     * @param integer $iLang language\n     */\n    public function loadActiveCountries($iLang = null)\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sViewName = $tableViewNameGenerator->getViewName('oxcountry', $iLang);\n        $sSelect = \"SELECT oxid, oxtitle, oxisoalpha2 FROM {$sViewName} WHERE oxactive = '1' ORDER BY oxorder, oxtitle \";\n        $this->selectString($sSelect);\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Delivery.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Order delivery manager.\n * Currently calculates price/costs.\n */\nclass Delivery extends \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel\n{\n    /**\n     * Calculation rule\n     */\n    const CALCULATION_RULE_ONCE_PER_CART = 0;\n    const CALCULATION_RULE_FOR_EACH_DIFFERENT_PRODUCT = 1;\n    const CALCULATION_RULE_FOR_EACH_PRODUCT = 2;\n\n    /**\n     * Condition type\n     */\n    const CONDITION_TYPE_PRICE = 'p';\n    const CONDITION_TYPE_AMOUNT = 'a';\n    const CONDITION_TYPE_SIZE = 's';\n    const CONDITION_TYPE_WEIGHT = 'w';\n\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxdelivery';\n\n    /**\n     * Total count of product items which are covered by current delivery\n     * (used for caching purposes across several methods)\n     *\n     * @var double\n     */\n    protected $_iItemCnt = 0;\n\n    /**\n     * Total count of products which are covered by current delivery\n     * (used for caching purposes across several methods)\n     *\n     * @var double\n     */\n    protected $_iProdCnt = 0;\n\n    /**\n     * Total price of products which are covered by current delivery\n     * (used for caching purposes across several methods)\n     *\n     * @var double\n     */\n    protected $_dPrice = 0;\n\n    /**\n     * Current delivery price object which keeps price info\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Price\n     */\n    protected $_oPrice = null;\n\n    /**\n     * Article Ids which are assigned to current delivery\n     *\n     * @var array\n     */\n    protected $_aArtIds = null;\n\n    /**\n     * Category Ids which are assigned to current delivery\n     *\n     * @var array\n     */\n    protected $_aCatIds = null;\n\n    /**\n     * If article has free shipping\n     *\n     * @var bool\n     */\n    protected $_blFreeShipping = true;\n\n    /**\n     * Product list storage\n     *\n     * @var array\n     */\n    protected static $_aProductList = [];\n\n    /**\n     * Delivery VAT config\n     *\n     * @var bool\n     */\n    protected $_blDelVatOnTop = false;\n\n    /**\n     * Countries ISO assigned to current delivery.\n     *\n     * @var array\n     */\n    protected $_aCountriesISO = null;\n\n    /**\n     * RDFa delivery sets assigned to current delivery.\n     *\n     * @var array\n     */\n    protected $_aRDFaDeliverySet = null;\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxdelivery');\n        $this->setDelVatOnTop(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blDeliveryVatOnTop'));\n    }\n\n    /**\n     * Delivery VAT config setter\n     *\n     * @param bool $blOnTop delivery vat config\n     */\n    public function setDelVatOnTop($blOnTop)\n    {\n        $this->_blDelVatOnTop = $blOnTop;\n    }\n\n    /**\n     * Collects article Ids which are assigned to current delivery\n     *\n     * @return array\n     */\n    public function getArticles()\n    {\n        if (is_null($this->_aArtIds)) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $sQ = \"select oxobjectid from oxobject2delivery \n                where oxdeliveryid = :oxdeliveryid and oxtype = :oxtype\";\n            $aArtIds = $oDb->getCol($sQ, [\n                'oxdeliveryid' => $this->getId(),\n                'oxtype' => 'oxarticles'\n            ]);\n            $this->_aArtIds = $aArtIds;\n        }\n\n        return $this->_aArtIds;\n    }\n\n    /**\n     * Collects category Ids which are assigned to current delivery\n     *\n     * @return array\n     */\n    public function getCategories()\n    {\n        if (is_null($this->_aCatIds)) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $sQ = \"select oxobjectid from oxobject2delivery \n                where oxdeliveryid = :oxdeliveryid and oxtype = :oxtype\";\n            $aCatIds = $oDb->getCol($sQ, [\n                'oxdeliveryid' => $this->getId(),\n                'oxtype' => 'oxcategories'\n            ]);\n            $this->_aCatIds = $aCatIds;\n        }\n\n        return $this->_aCatIds;\n    }\n\n    /**\n     * Checks if delivery has assigned articles\n     *\n     * @return bool\n     */\n    public function hasArticles()\n    {\n        return (bool) count($this->getArticles());\n    }\n\n    /**\n     * Checks if delivery has assigned categories\n     *\n     * @return bool\n     */\n    public function hasCategories()\n    {\n        return (bool) count($this->getCategories());\n    }\n\n    /**\n     * Returns amount (total net price/weight/volume/Amount) on which delivery price is applied\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\BasketItem $oBasketItem basket item object\n     *\n     * @return double\n     */\n    public function getDeliveryAmount($oBasketItem)\n    {\n        $dAmount = 0;\n        $oProduct = $oBasketItem->getArticle(false);\n\n        if ($oProduct->isOrderArticle()) {\n            $oProduct = $oProduct->getArticle();\n        }\n\n        $blExclNonMaterial = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blExclNonMaterialFromDelivery');\n\n        // mark free shipping products\n        if ($oProduct->oxarticles__oxfreeshipping->value || ($oProduct->oxarticles__oxnonmaterial->value && $blExclNonMaterial)) {\n            if ($this->_blFreeShipping !== false) {\n                $this->_blFreeShipping = true;\n            }\n        } else {\n            $this->_blFreeShipping = false;\n\n            switch ($this->getConditionType()) {\n                case self::CONDITION_TYPE_PRICE: // price\n                    if ($this->getCalculationRule() == self::CALCULATION_RULE_FOR_EACH_PRODUCT) {\n                        $dAmount += $oProduct->getPrice()->getPrice();\n                    } elseif ($oBasketItem->getPrice()) {\n                        $dAmount += $oBasketItem->getPrice()->getPrice(); // price// currency conversion must already be done in price class / $oCur->rate; // $oBasketItem->oPrice->getPrice() / $oCur->rate;\n                    }\n                    break;\n                case self::CONDITION_TYPE_WEIGHT: // weight\n                    if ($this->getCalculationRule() == self::CALCULATION_RULE_FOR_EACH_PRODUCT) {\n                        $dAmount += $oProduct->getWeight();\n                    } else {\n                        $dAmount += $oBasketItem->getWeight();\n                    }\n                    break;\n                case self::CONDITION_TYPE_SIZE: // size\n                    $dAmount += $oProduct->getSize();\n                    if ($this->getCalculationRule() != self::CALCULATION_RULE_FOR_EACH_PRODUCT) {\n                        $dAmount *= $oBasketItem->getAmount();\n                    }\n                    break;\n                case self::CONDITION_TYPE_AMOUNT: // amount\n                    $dAmount += $oBasketItem->getAmount();\n                    break;\n            }\n\n            if ($oBasketItem->getPrice()) {\n                $this->_dPrice += $oBasketItem->getPrice()->getPrice();\n            }\n        }\n\n        return $dAmount;\n    }\n\n    /**\n     * Delivery price setter\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Price $oPrice delivery price to set\n     */\n    public function setDeliveryPrice($oPrice)\n    {\n        $this->_oPrice = $oPrice;\n    }\n\n    /**\n     * Returns oxPrice object for delivery costs\n     *\n     * @param double $dVat delivery vat\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getDeliveryPrice($dVat = null)\n    {\n        if ($this->_oPrice === null) {\n            // loading oxPrice object for final price calculation\n            $oPrice = oxNew(\\OxidEsales\\Eshop\\Core\\Price::class);\n            $oPrice->setNettoMode($this->_blDelVatOnTop);\n            $oPrice->setVat($dVat);\n\n            // if article is free shipping, price for delivery will be not calculated\n            if (!$this->_blFreeShipping) {\n                $oPrice->add($this->getCostSum());\n            }\n            $this->setDeliveryPrice($oPrice);\n        }\n\n        return $this->_oPrice;\n    }\n\n    /**\n     * Delete this object from the database, returns true on success.\n     *\n     * @param string $sOxId Object ID (default null)\n     *\n     * @return bool\n     */\n    public function delete($sOxId = null)\n    {\n        if (!$sOxId) {\n            $sOxId = $this->getId();\n        }\n        if (!$sOxId) {\n            return false;\n        }\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sQ = \"delete from `oxobject2delivery` where `oxobject2delivery`.`oxdeliveryid` = :oxdeliveryid\";\n        $oDb->execute($sQ, [\n            'oxdeliveryid' => $sOxId\n        ]);\n\n        return parent::delete($sOxId);\n    }\n\n    /**\n     * Checks if delivery fits for current basket\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket shop basket\n     *\n     * @return bool\n     */\n    public function isForBasket($oBasket)\n    {\n        $this->_iItemCnt = 0;\n        $this->_iProdCnt = 0;\n        $this->_dPrice = 0;\n\n        // amount for conditional check\n        $blHasArticles = $this->hasArticles();\n        $blHasCategories = $this->hasCategories();\n        $blUse = true;\n        $aggregatedDeliveryAmount = 0;\n        $blForBasket = false;\n\n        // category & article check\n        if ($blHasCategories || $blHasArticles) {\n            $blUse = false;\n\n            $aDeliveryArticles = $this->getArticles();\n            $aDeliveryCategories = $this->getCategories();\n\n            foreach ($oBasket->getContents() as $oContent) {\n                //V FS#1954 - load delivery for variants from parent article\n                $oArticle = $oContent->getArticle(false);\n                $sProductId = $oArticle->getProductId();\n                $sParentId = $oArticle->getParentId();\n\n                if ($blHasArticles && (in_array($sProductId, $aDeliveryArticles) || ($sParentId && in_array($sParentId, $aDeliveryArticles)))) {\n                    $blUse = true;\n                    $artAmount = $this->getDeliveryAmount($oContent);\n                    if ($this->isDeliveryRuleFitByArticle($artAmount)) {\n                        $blForBasket = true;\n                        $this->updateItemCount($oContent);\n                        $this->increaseProductCount();\n                    }\n                    if (!$blForBasket) {\n                        $aggregatedDeliveryAmount += $artAmount;\n                    }\n                } elseif ($blHasCategories) {\n                    if (isset(self::$_aProductList[$sProductId])) {\n                        $oProduct = self::$_aProductList[$sProductId];\n                    } else {\n                        $oProduct = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n                        $oProduct->setSkipAssign(true);\n\n                        if (!$oProduct->load($sProductId)) {\n                            continue;\n                        }\n\n                        $oProduct->setId($sProductId);\n                        self::$_aProductList[$sProductId] = $oProduct;\n                    }\n\n                    foreach ($aDeliveryCategories as $sCatId) {\n                        if ($oProduct->inCategory($sCatId)) {\n                            $artAmount = $this->getDeliveryAmount($oContent);\n\n                            $blUse = true;\n                            if ($this->isDeliveryRuleFitByArticle($artAmount)) {\n                                $blForBasket = true;\n                                $this->updateItemCount($oContent);\n                                $this->increaseProductCount();\n                            }\n                            if (!$blForBasket) {\n                                $aggregatedDeliveryAmount += $artAmount;\n                            }\n\n                            //HR#5650 product might be in multiple rule categories, counting it once is enough\n                            break;\n                        }\n                    }\n                }\n            }\n        } else {\n            // regular amounts check\n            foreach ($oBasket->getContents() as $oContent) {\n                $artAmount = $this->getDeliveryAmount($oContent);\n                if ($this->isDeliveryRuleFitByArticle($artAmount)) {\n                    $blForBasket = true;\n                    $this->updateItemCount($oContent);\n                    $this->increaseProductCount();\n                }\n                if (!$blForBasket) {\n                    $aggregatedDeliveryAmount += $artAmount;\n                }\n            }\n        }\n\n        //#M1130: Single article in Basket, checked as free shipping, is not buyable (step 3 no payments found)\n        if (!$blForBasket && $blUse && ($this->checkDeliveryAmount($aggregatedDeliveryAmount) || $this->_blFreeShipping)) {\n            $blForBasket = true;\n        }\n\n        return $blForBasket;\n    }\n\n    /**\n     * Update total count of product items are covered by current delivery.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\BasketItem $content\n     */\n    protected function updateItemCount($content)\n    {\n        $this->_iItemCnt += $content->getAmount();\n    }\n\n    /**\n     * Increase count of products which are covered by current delivery.\n     */\n    protected function increaseProductCount()\n    {\n        $this->_iProdCnt += 1;\n    }\n\n    /**\n     * checks if amount param is ok for this delivery\n     *\n     * @param double $iAmount amount\n     *\n     * @return boolean\n     */\n    protected function checkDeliveryAmount($iAmount)\n    {\n        $blResult = false;\n\n        if ($this->getConditionType() == self::CONDITION_TYPE_PRICE) {\n            $oCur = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActShopCurrencyObject();\n            $iAmount /= $oCur->rate;\n        }\n\n        if ($iAmount >= $this->getConditionFrom() && $iAmount <= $this->getConditionTo()) {\n            $blResult = true;\n        }\n\n        return $blResult;\n    }\n\n    /**\n     * returns delivery id\n     *\n     * @param string $sTitle delivery name\n     *\n     * @return string\n     */\n    public function getIdByName($sTitle)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sQ = \"SELECT `oxid` FROM `\" . $tableViewNameGenerator->getViewName('oxdelivery') . \"` \n            WHERE `oxtitle` = :oxtitle\";\n        $sId = $oDb->getOne($sQ, [\n            'oxtitle' => $sTitle\n        ]);\n\n        return $sId;\n    }\n\n    /**\n     * Returns array of country ISO's which are assigned to current delivery\n     *\n     * @return array\n     */\n    public function getCountriesISO()\n    {\n        if ($this->_aCountriesISO === null) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $this->_aCountriesISO = [];\n\n            $sSelect = \"\n                SELECT\n                    `oxcountry`.`oxisoalpha2`\n                FROM `oxcountry`\n                    LEFT JOIN `oxobject2delivery` ON `oxobject2delivery`.`oxobjectid` = `oxcountry`.`oxid`\n                WHERE `oxobject2delivery`.`oxdeliveryid` = :oxdeliveryid\n                    AND `oxobject2delivery`.`oxtype` = :oxtype\";\n\n            $rs = $oDb->getCol($sSelect, [\n                'oxdeliveryid' => $this->getId(),\n                'oxtype' => 'oxcountry'\n            ]);\n            $this->_aCountriesISO = $rs;\n        }\n\n        return $this->_aCountriesISO;\n    }\n\n    /**\n     * Returns condition type (type >= from <= to) : a - amount, s - size, w -weight, p - price\n     *\n     * @return string\n     */\n    public function getConditionType()\n    {\n        return $this->oxdelivery__oxdeltype->value;\n    }\n\n    /**\n     * Returns condition from value (type >= from <= to)\n     *\n     * @return string\n     */\n    public function getConditionFrom()\n    {\n        return $this->oxdelivery__oxparam->value;\n    }\n\n    /**\n     * Returns condition to value (type >= from <= to)\n     *\n     * @return string\n     */\n    public function getConditionTo()\n    {\n        return $this->oxdelivery__oxparamend->value;\n    }\n\n    /**\n     * Returns calculation rule: 0 - Once per Cart; 1 - Once for each different product 2 - For each product\n     *\n     * @return int\n     */\n    public function getCalculationRule()\n    {\n        return $this->oxdelivery__oxfixed->value;\n    }\n\n    /**\n     * Returns amount cost\n     *\n     * @return float\n     */\n    public function getAddSum()\n    {\n        return $this->oxdelivery__oxaddsum->value;\n    }\n\n    /**\n     * Returns type of cost: % - percentage; abs - absolute value\n     *\n     * @return string\n     */\n    public function getAddSumType()\n    {\n        return $this->oxdelivery__oxaddsumtype->value;\n    }\n\n    /**\n     * Calculate multiplier for price calculation\n     *\n     * @return float|int\n     */\n    protected function getMultiplier()\n    {\n        $dAmount = 0;\n\n        if ($this->getCalculationRule() == self::CALCULATION_RULE_ONCE_PER_CART) {\n            $dAmount = 1;\n        } elseif ($this->getCalculationRule() == self::CALCULATION_RULE_FOR_EACH_DIFFERENT_PRODUCT) {\n            $dAmount = $this->_iProdCnt;\n        } elseif ($this->getCalculationRule() == self::CALCULATION_RULE_FOR_EACH_PRODUCT) {\n            $dAmount = $this->_iItemCnt;\n        }\n\n        return $dAmount;\n    }\n\n    /**\n     * Calculate cost sum\n     *\n     * @return float\n     */\n    protected function getCostSum()\n    {\n        if ($this->getAddSumType() == 'abs') {\n            $oCur = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActShopCurrencyObject();\n            $dPrice = $this->getAddSum() * $oCur->rate * $this->getMultiplier();\n        } else {\n            $dPrice = $this->_dPrice / 100 * $this->getAddSum();\n        }\n\n        return $dPrice;\n    }\n\n    /**\n     * Checks if delivery rule applies for basket because of one article's amount.\n     * Delivery rules that are to be applied once per cart can be ruled out here.\n     *\n     * @param integer $artAmount product amount\n     *\n     * @return bool\n     */\n    protected function isDeliveryRuleFitByArticle($artAmount)\n    {\n        $result = false;\n        if ($this->getCalculationRule() != self::CALCULATION_RULE_ONCE_PER_CART) {\n            if (!$this->_blFreeShipping && $this->checkDeliveryAmount($artAmount)) {\n                $result = true;\n            }\n        }\n\n        return $result;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/DeliveryList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse oxRegistry;\n\n/**\n * Delivery list manager.\n */\nclass DeliveryList extends \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n{\n    /**\n     * Session user Id\n     *\n     * @var string\n     */\n    protected $_sUserId = null;\n\n    /**\n     * Performance - load or not delivery list\n     *\n     * @var bool\n     */\n    protected $_blPerfLoadDelivery = null;\n\n    /**\n     * Deliveries list\n     *\n     * @var array\n     */\n    protected $_aDeliveries = [];\n\n    /**\n     * User object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\User\n     */\n    protected $_oUser = null;\n\n    /**\n     * Home country info array\n     *\n     * @var array\n     */\n    protected $_sHomeCountry = null;\n\n    /**\n     * Collect fitting deliveries sets instead of fitting deliveries\n     * Default is false\n     *\n     * @var bool\n     */\n    protected $_blCollectFittingDeliveriesSets = false;\n\n\n    /**\n     * Calls parent constructor and sets home country\n     */\n    public function __construct()\n    {\n        parent::__construct('oxdelivery');\n\n        // load or not delivery list\n        $this->setHomeCountry(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('aHomeCountry'));\n    }\n\n    /**\n     * Home country setter\n     *\n     * @param string $sHomeCountry home country id\n     */\n    public function setHomeCountry($sHomeCountry)\n    {\n        if (is_array($sHomeCountry)) {\n            $this->_sHomeCountry = current($sHomeCountry);\n        } else {\n            $this->_sHomeCountry = $sHomeCountry;\n        }\n    }\n\n    /**\n     * Returns active delivery list\n     *\n     * Loads all active delivery in list. Additionally\n     * checks if set has user customized parameters like\n     * assigned users, countries or user groups. Performs\n     * additional filtering according to these parameters\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser      session user object\n     * @param string                                   $sCountryId user country id\n     * @param string                                   $sDelSet    user chosen delivery set\n     *\n     * @return array\n     */\n    protected function getActiveDeliveryList($oUser = null, $sCountryId = null, $sDelSet = null)\n    {\n        // checking for current session user which gives additional restrictions for user itself, users group and country\n        if ($oUser === null) {\n            $oUser = $this->getUser();\n        } else {\n            //set user\n            $this->setUser($oUser);\n        }\n\n        $sUserId = $oUser ? $oUser->getId() : '';\n\n        // choosing delivery country if it is not set yet\n        if (!$sCountryId) {\n            if ($oUser) {\n                $sCountryId = $oUser->getActiveCountry();\n            } else {\n                $sCountryId = $this->_sHomeCountry;\n            }\n        }\n\n        if (($sUserId . $sCountryId . $sDelSet) !== $this->_sUserId) {\n            $this->selectString($this->getFilterSelect($oUser, $sCountryId, $sDelSet));\n            $this->_sUserId = $sUserId . $sCountryId . $sDelSet;\n        }\n\n        $this->rewind();\n\n        return $this;\n    }\n\n    /**\n     * Creates delivery list filter SQL to load current state delivery list\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser      session user object\n     * @param string                                   $sCountryId user country id\n     * @param string                                   $sDelSet    user chosen delivery set\n     *\n     * @return string\n     */\n    protected function getFilterSelect($oUser, $sCountryId, $sDelSet)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sTable = $tableViewNameGenerator->getViewName('oxdelivery');\n        $sQ = \"select $sTable.* from ( select distinct $sTable.* from $sTable left join oxdel2delset on oxdel2delset.oxdelid=$sTable.oxid \";\n        $sQ .= \"where \" . $this->getBaseObject()->getSqlActiveSnippet() . \" and oxdel2delset.oxdelsetid = \" . $oDb->quote($sDelSet) . \" \";\n\n        // defining initial filter parameters\n        $sUserId = null;\n        $aGroupIds = [];\n\n        // checking for current session user which gives additional restrictions for user itself, users group and country\n        if ($oUser) {\n            // user ID\n            $sUserId = $oUser->getId();\n\n            // user groups ( maybe would be better to fetch by function \\OxidEsales\\Eshop\\Application\\Model\\User::getUserGroups() ? )\n            $aGroupIds = $oUser->getUserGroups();\n        }\n\n        $aIds = [];\n        if (count($aGroupIds)) {\n            foreach ($aGroupIds as $oGroup) {\n                $aIds[] = $oGroup->getId();\n            }\n        }\n\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sUserTable = $tableViewNameGenerator->getViewName('oxuser');\n        $sGroupTable = $tableViewNameGenerator->getViewName('oxgroups');\n        $sCountryTable = $tableViewNameGenerator->getViewName('oxcountry');\n\n        $sCountrySql = $sCountryId ? \"EXISTS(select oxobject2delivery.oxid from oxobject2delivery where oxobject2delivery.oxdeliveryid=$sTable.OXID and oxobject2delivery.oxtype='oxcountry' and oxobject2delivery.OXOBJECTID=\" . $oDb->quote($sCountryId) . \")\" : '0';\n        $sUserSql = $sUserId ? \"EXISTS(select oxobject2delivery.oxid from oxobject2delivery where oxobject2delivery.oxdeliveryid=$sTable.OXID and oxobject2delivery.oxtype='oxuser' and oxobject2delivery.OXOBJECTID=\" . $oDb->quote($sUserId) . \")\" : '0';\n        $sGroupSql = count($aIds) ? \"EXISTS(select oxobject2delivery.oxid from oxobject2delivery where oxobject2delivery.oxdeliveryid=$sTable.OXID and oxobject2delivery.oxtype='oxgroups' and oxobject2delivery.OXOBJECTID in (\" . implode(', ', \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aIds)) . \") )\" : '0';\n\n        $sQ .= \" order by $sTable.oxsort asc ) as $sTable where (\n                if(EXISTS(select 1 from oxobject2delivery, $sCountryTable where $sCountryTable.oxid=oxobject2delivery.oxobjectid and oxobject2delivery.oxdeliveryid=$sTable.OXID and oxobject2delivery.oxtype='oxcountry' LIMIT 1),\n                    $sCountrySql,\n                    1) &&\n                if(EXISTS(select 1 from oxobject2delivery, $sUserTable where $sUserTable.oxid=oxobject2delivery.oxobjectid and oxobject2delivery.oxdeliveryid=$sTable.OXID and oxobject2delivery.oxtype='oxuser' LIMIT 1),\n                    $sUserSql,\n                    1) &&\n                if(EXISTS(select 1 from oxobject2delivery, $sGroupTable where $sGroupTable.oxid=oxobject2delivery.oxobjectid and oxobject2delivery.oxdeliveryid=$sTable.OXID and oxobject2delivery.oxtype='oxgroups' LIMIT 1),\n                    $sGroupSql,\n                    1)\n            )\";\n\n        $sQ .= \" order by $sTable.oxsort asc \";\n\n        return $sQ;\n    }\n\n    /**\n     * Loads and returns list of deliveries.\n     *\n     * Process:\n     *\n     *  - first checks if delivery loading is enabled in config -\n     *    $myConfig->bl_perfLoadDelivery is TRUE;\n     *  - loads delivery set list by calling this::GetDeliverySetList(...);\n     *  - checks if there is any active (eg. chosen delivery set in order\n     *    process etc) delivery set defined and if its set - rearranges\n     *    delivery set list by storing active set at the beginning in the\n     *    list.\n     *  - goes through delivery sets and loads its deliveries, checks if any\n     *    delivery fits. By checking calculates and stores conditional\n     *    amounts:\n     *\n     *       oDelivery->iItemCnt - items in basket that fits this delivery\n     *       oDelivery->iProdCnt - products in basket that fits this delivery\n     *       oDelivery->dPrice   - price of products that fits this delivery\n     *\n     *  - returns a list of deliveries.\n     *    NOTICE: for performance reasons deliveries is cached in\n     *    $myConfig->aDeliveryList.\n     *\n     * @param object                                   $oBasket     basket object\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser       session user\n     * @param string                                   $sDelCountry user country id\n     * @param string                                   $sDelSet     delivery set id\n     *\n     * @return array\n     */\n    public function getDeliveryList($oBasket, $oUser = null, $sDelCountry = null, $sDelSet = null)\n    {\n        // ids of deliveries that does not fit for us to skip double check\n        $aSkipDeliveries = [];\n        $aFittingDelSets = [];\n        $this->_aDeliveries = [];\n\n        $aDelSetList = \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\DeliverySetList::class)->getDeliverySetList($oUser, $sDelCountry, $sDelSet);\n\n        // must choose right delivery set to use its delivery list\n        foreach ($aDelSetList as $sDeliverySetId => $oDeliverySet) {\n            // loading delivery list to check if some of them fits\n            $aDeliveries = $this->getActiveDeliveryList($oUser, $sDelCountry, $sDeliverySetId);\n            $blDelFound = false;\n\n            foreach ($aDeliveries as $sDeliveryId => $oDelivery) {\n                // skipping that was checked and didn't fit before\n                if (in_array($sDeliveryId, $aSkipDeliveries)) {\n                    continue;\n                }\n\n                $aSkipDeliveries[] = $sDeliveryId;\n\n                if ($oDelivery->isForBasket($oBasket)) {\n                    // delivery fits conditions\n                    $this->_aDeliveries[$sDeliveryId] = $aDeliveries[$sDeliveryId];\n                    $blDelFound = true;\n\n                    // removing from unfitting list\n                    array_pop($aSkipDeliveries);\n\n                    // maybe checked \"Stop processing after first match\" ?\n                    if ($oDelivery->oxdelivery__oxfinalize->value) {\n                        break;\n                    }\n                }\n            }\n\n            // found delivery set and deliveries that fits\n            if ($blDelFound) {\n                if ($this->_blCollectFittingDeliveriesSets) {\n                    // collect only deliveries sets that fits deliveries\n                    $aFittingDelSets[$sDeliverySetId] = $oDeliverySet;\n                } else {\n                    // return collected fitting deliveries\n                    \\OxidEsales\\Eshop\\Core\\Registry::getSession()->setVariable('sShipSet', $sDeliverySetId);\n\n                    return $this->_aDeliveries;\n                }\n            }\n        }\n\n        //return deliveries sets if found\n        if ($this->_blCollectFittingDeliveriesSets && count($aFittingDelSets)) {\n            //resetting getting delivery sets list instead of deliveries before return\n            $this->_blCollectFittingDeliveriesSets = false;\n\n            //reset cache and list\n            $this->setUser(null);\n            $this->clear();\n\n            return $aFittingDelSets;\n        }\n\n        // nothing what fits was found\n        return [];\n    }\n\n    /**\n     * Checks if deliveries in list fits for current basket and delivery set\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket        shop basket\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User   $oUser          session user\n     * @param string                                     $sDelCountry    delivery country\n     * @param string                                     $sDeliverySetId delivery set id to check its relation to delivery list\n     *\n     * @return bool\n     */\n    public function hasDeliveries($oBasket, $oUser, $sDelCountry, $sDeliverySetId)\n    {\n        $blHas = false;\n\n        // loading delivery list to check if some of them fits\n        $this->getActiveDeliveryList($oUser, $sDelCountry, $sDeliverySetId);\n        foreach ($this as $oDelivery) {\n            if ($oDelivery->isForBasket($oBasket)) {\n                $blHas = true;\n                break;\n            }\n        }\n\n        return $blHas;\n    }\n\n    /**/\n\n    /**\n     * Get current user object. If user is not set, try to get current user.\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\User\n     */\n    public function getUser()\n    {\n        if (!$this->_oUser) {\n            $this->_oUser = parent::getUser();\n        }\n\n        return $this->_oUser;\n    }\n\n    /**\n     * Set current user object\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser user object\n     */\n    public function setUser($oUser)\n    {\n        $this->_oUser = $oUser;\n    }\n\n    /**\n     * Force or not to collect deliveries sets instead of deliveries when\n     * getting deliveries list in getDeliveryList()\n     *\n     * @param bool $blCollectFittingDeliveriesSets collect deliveries sets or not\n     */\n    public function setCollectFittingDeliveriesSets($blCollectFittingDeliveriesSets = false)\n    {\n        $this->_blCollectFittingDeliveriesSets = $blCollectFittingDeliveriesSets;\n    }\n\n    /**\n     * Load oxDeliveryList for product\n     *\n     * @param object $oProduct oxArticle object\n     */\n    public function loadDeliveryListForProduct($oProduct)\n    {\n        $dPrice = $oProduct->getPrice()->getBruttoPrice();\n        $dSize = $oProduct->getSize();\n        $dWeight = $oProduct->getWeight();\n\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sTable = $tableViewNameGenerator->getViewName('oxdelivery');\n        $params = [];\n\n        $sQ = \"select $sTable.* from $sTable\";\n        $sQ .= \" where \" . $this->getBaseObject()->getSqlActiveSnippet();\n        $sQ .= \" and ($sTable.oxdeltype != 'a' || ( $sTable.oxparam <= 1 && $sTable.oxparamend >= 1))\";\n        if ($dPrice) {\n            $sQ .= \" and ($sTable.oxdeltype != 'p' || ( $sTable.oxparam <= :dprice && $sTable.oxparamend >= :dprice))\";\n            $params['dprice'] = $dPrice;\n        }\n        if ($dSize) {\n            $sQ .= \" and ($sTable.oxdeltype != 's' || ( $sTable.oxparam <= :dsize && $sTable.oxparamend >= :dsize))\";\n            $params['dsize'] = $dSize;\n        }\n        if ($dWeight) {\n            $sQ .= \" and ($sTable.oxdeltype != 'w' || ( $sTable.oxparam <= :dweight && $sTable.oxparamend >= :dweight))\";\n            $params['dweight'] = $dWeight;\n        }\n        $this->selectString($sQ, $params);\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/DeliverySet.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Order delivery set manager.\n */\nclass DeliverySet extends \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel\n{\n    /**\n     * Current object class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxdeliveryset';\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxdeliveryset');\n    }\n\n    /**\n     * Delete this object from the database, returns true on success.\n     *\n     * @param string $sOxId Object ID(default null)\n     *\n     * @return bool\n     */\n    public function delete($sOxId = null)\n    {\n        if (!$sOxId) {\n            $sOxId = $this->getId();\n        }\n        if (!$sOxId) {\n            return false;\n        }\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $oDb->execute('delete from oxobject2payment where oxobjectid = :oxid', [\n            'oxid' => $sOxId\n        ]);\n        $oDb->execute('delete from oxobject2delivery where oxdeliveryid = :oxid', [\n            'oxid' => $sOxId\n        ]);\n        $oDb->execute('delete from oxdel2delset where oxdelsetid = :oxid', [\n            'oxid' => $sOxId\n        ]);\n\n        return parent::delete($sOxId);\n    }\n\n    /**\n     * returns delivery set id\n     *\n     * @param string $sTitle delivery name\n     *\n     * @return string\n     */\n    public function getIdByName($sTitle)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sQ = \"SELECT `oxid` FROM `\" . $tableViewNameGenerator->getViewName('oxdeliveryset') . \"` \n            WHERE  `oxtitle` = :oxtitle\";\n        $sId = $oDb->getOne($sQ, [\n            'oxtitle' => $sTitle\n        ]);\n\n        return $sId;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/DeliverySetList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse oxRegistry;\nuse oxDb;\n\n/**\n * DeliverySet list manager.\n */\nclass DeliverySetList extends \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n{\n    /**\n     * Session user Id\n     *\n     * @var string\n     */\n    protected $_sUserId = null;\n\n    /**\n     * Country Id\n     *\n     * @var string\n     */\n    protected $_sCountryId = null;\n\n    /**\n     * User object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\User\n     */\n    protected $_oUser = null;\n\n    /**\n     * Home country info id\n     *\n     * @var array\n     */\n    protected $_sHomeCountry = null;\n\n    /**\n     * Calls parent constructor and sets home country\n     */\n    public function __construct()\n    {\n        $this->setHomeCountry(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('aHomeCountry'));\n        parent::__construct('oxdeliveryset');\n    }\n\n    /**\n     * Home country setter\n     *\n     * @param string $sHomeCountry home country id\n     */\n    public function setHomeCountry($sHomeCountry)\n    {\n        if (is_array($sHomeCountry)) {\n            $this->_sHomeCountry = current($sHomeCountry);\n        } else {\n            $this->_sHomeCountry = $sHomeCountry;\n        }\n    }\n\n    /**\n     * Returns active delivery set list\n     *\n     * Loads all active delivery sets in list. Additionally\n     * checks if set has user customized parameters like\n     * assigned users, countries or user groups. Performs\n     * additional filtering according to these parameters\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser      user object\n     * @param string                                   $sCountryId user country id\n     *\n     * @return array\n     */\n    protected function getActiveDeliverySetList($oUser = null, $sCountryId = null)\n    {\n        // checking for current session user which gives additional restrictions for user itself, users group and country\n        if ($oUser === null) {\n            $oUser = $this->getUser();\n        } else {\n            //set user\n            $this->setUser($oUser);\n        }\n\n        $sUserId = $oUser ? $oUser->getId() : '';\n\n        if ($sUserId !== $this->_sUserId || $sCountryId !== $this->_sCountryId) {\n            // choosing delivery country if it is not set yet\n            if (!$sCountryId) {\n                if ($oUser) {\n                    $sCountryId = $oUser->getActiveCountry();\n                } else {\n                    $sCountryId = $this->_sHomeCountry;\n                }\n            }\n\n            $this->selectString($this->getFilterSelect($oUser, $sCountryId));\n            $this->_sUserId = $sUserId;\n            $this->_sCountryId = $sCountryId;\n        }\n\n        $this->rewind();\n\n        return $this;\n    }\n\n\n    /**\n     * Creates delivery set list filter SQL to load current state delivery set list\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser      user object\n     * @param string                                   $sCountryId user country id\n     *\n     * @return string\n     */\n    protected function getFilterSelect($oUser, $sCountryId)\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sTable = $tableViewNameGenerator->getViewName('oxdeliveryset');\n        $sQ = \"select $sTable.* from $sTable \";\n        $sQ .= \"where \" . $this->getBaseObject()->getSqlActiveSnippet() . ' ';\n\n        // defining initial filter parameters\n        $sUserId = null;\n        $aGroupIds = [];\n\n        // checking for current session user which gives additional restrictions for user itself, users group and country\n        if ($oUser) {\n            // user ID\n            $sUserId = $oUser->getId();\n\n            // user groups ( maybe would be better to fetch by function \\OxidEsales\\Eshop\\Application\\Model\\User::getUserGroups() ? )\n            $aGroupIds = $oUser->getUserGroups();\n        }\n\n        $aIds = [];\n        if (count($aGroupIds)) {\n            foreach ($aGroupIds as $oGroup) {\n                $aIds[] = $oGroup->getId();\n            }\n        }\n\n        $sUserTable = $tableViewNameGenerator->getViewName('oxuser');\n        $sGroupTable = $tableViewNameGenerator->getViewName('oxgroups');\n        $sCountryTable = $tableViewNameGenerator->getViewName('oxcountry');\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $sCountrySql = $sCountryId ? \"EXISTS(select oxobject2delivery.oxid from oxobject2delivery where oxobject2delivery.oxdeliveryid=$sTable.OXID and oxobject2delivery.oxtype='oxdelset' and oxobject2delivery.OXOBJECTID=\" . $oDb->quote($sCountryId) . \")\" : '0';\n        $sUserSql = $sUserId ? \"EXISTS(select oxobject2delivery.oxid from oxobject2delivery where oxobject2delivery.oxdeliveryid=$sTable.OXID and oxobject2delivery.oxtype='oxdelsetu' and oxobject2delivery.OXOBJECTID=\" . $oDb->quote($sUserId) . \")\" : '0';\n        $sGroupSql = count($aIds) ? \"EXISTS(select oxobject2delivery.oxid from oxobject2delivery where oxobject2delivery.oxdeliveryid=$sTable.OXID and oxobject2delivery.oxtype='oxdelsetg' and oxobject2delivery.OXOBJECTID in (\" . implode(', ', \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aIds)) . \") )\" : '0';\n\n        $sQ .= \"and (\n                if(EXISTS(select 1 from oxobject2delivery, $sCountryTable where $sCountryTable.oxid=oxobject2delivery.oxobjectid and oxobject2delivery.oxdeliveryid=$sTable.OXID and oxobject2delivery.oxtype='oxdelset' LIMIT 1),\n                    $sCountrySql,\n                    1) &&\n                if(EXISTS(select 1 from oxobject2delivery, $sUserTable where $sUserTable.oxid=oxobject2delivery.oxobjectid and oxobject2delivery.oxdeliveryid=$sTable.OXID and oxobject2delivery.oxtype='oxdelsetu' LIMIT 1),\n                    $sUserSql,\n                    1) &&\n                if(EXISTS(select 1 from oxobject2delivery, $sGroupTable where $sGroupTable.oxid=oxobject2delivery.oxobjectid and oxobject2delivery.oxdeliveryid=$sTable.OXID and oxobject2delivery.oxtype='oxdelsetg' LIMIT 1),\n                    $sGroupSql,\n                    1)\n            )\";\n\n        //order by\n        $sQ .= \" order by $sTable.oxpos\";\n\n        return $sQ;\n    }\n\n    /**\n     * Creates current state delivery set list\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser      user object\n     * @param string                                   $sCountryId user country id\n     * @param string                                   $sDelSet    preferred delivery set ID (optional)\n     *\n     * @return array\n     */\n    public function getDeliverySetList($oUser, $sCountryId, $sDelSet = null)\n    {\n        $this->getActiveDeliverySetList($oUser, $sCountryId);\n\n        // if there is already chosen delivery set we must start checking from it\n        $aList = $this->_aArray;\n        if ($sDelSet && isset($aList[$sDelSet])) {\n            //set it as first element\n            $oDelSet = $aList[$sDelSet];\n            unset($aList[$sDelSet]);\n\n            $aList = array_merge([$sDelSet => $oDelSet], $aList);\n        }\n\n        return $aList;\n    }\n\n    /**\n     * Loads delivery set data, checks if it has payments assigned. If active delivery set id\n     * is passed - checks if it can be used, if not - takes first ship set id from list which\n     * fits. For active ship set collects payment list info. Returns array containing:\n     *   1. all ship sets that has payment (array)\n     *   2. active ship set id (string)\n     *   3. payment list for active ship set (array)\n     *\n     * @param string                                   $sShipSet current ship set id (can be null if not set yet)\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser    active user\n     * @param double                                   $oBasket  basket object\n     *\n     * @return array\n     */\n    public function getDeliverySetData($sShipSet, $oUser, $oBasket)\n    {\n        $sActShipSet = null;\n        $aActSets = [];\n        $aActPaymentList = [];\n\n        if (!$oUser) {\n            return;\n        }\n\n        $this->getActiveDeliverySetList($oUser, $oUser->getActiveCountry());\n\n        // if there are no shipping sets we don't need to load payments\n        if ($this->count()) {\n            // one selected ?\n            if ($sShipSet && !isset($this->_aArray[$sShipSet])) {\n                $sShipSet = null;\n            }\n\n            $oPayList = \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\PaymentList::class);\n            $oDelList = \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\DeliveryList::class);\n\n            $oCur = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActShopCurrencyObject();\n            $dBasketPrice = $oBasket->getPriceForPayment() / $oCur->rate;\n\n            // checking if these ship sets available (number of possible payment methods > 0)\n            foreach ($this as $sShipSetId => $oShipSet) {\n                $aPaymentList = $oPayList->getPaymentList($sShipSetId, $dBasketPrice, $oUser);\n                if (count($aPaymentList)) {\n                    // now checking for deliveries\n                    if ($oDelList->hasDeliveries($oBasket, $oUser, $oUser->getActiveCountry(), $sShipSetId)) {\n                        $aActSets[$sShipSetId] = $oShipSet;\n\n                        if (!$sShipSet || ($sShipSetId == $sShipSet)) {\n                            $sActShipSet = $sShipSet = $sShipSetId;\n                            $aActPaymentList = $aPaymentList;\n                            $oShipSet->blSelected = true;\n                        }\n                    }\n                }\n            }\n        }\n\n        return [$aActSets, $sActShipSet, $aActPaymentList];\n    }\n\n    /**\n     * Get current user object. If user is not set, try to get current user.\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\User\n     */\n    public function getUser()\n    {\n        if (!$this->_oUser) {\n            $this->_oUser = parent::getUser();\n        }\n\n        return $this->_oUser;\n    }\n\n    /**\n     * Set current user object\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser user object\n     */\n    public function setUser($oUser)\n    {\n        $this->_oUser = $oUser;\n    }\n\n    /**\n     * Loads an object including all delivery sets which are not mapped to a\n     * predefined GoodRelations delivery method.\n     */\n    public function loadNonRDFaDeliverySetList()\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sTable = $tableViewNameGenerator->getViewName('oxdeliveryset');\n        $sSubSql = \"SELECT * FROM oxobject2delivery WHERE oxobject2delivery.OXDELIVERYID = $sTable.OXID AND oxobject2delivery.OXTYPE = 'rdfadeliveryset'\";\n        $this->selectString(\"SELECT $sTable.* FROM $sTable WHERE NOT EXISTS($sSubSql) AND $sTable.OXACTIVE = 1\");\n    }\n\n    /**\n     * Loads delivery set mapped to a\n     * predefined GoodRelations delivery method.\n     *\n     * @param string $sDelId delivery set id\n     */\n    public function loadRDFaDeliverySetList($sDelId = null)\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sTable = $tableViewNameGenerator->getViewName('oxdeliveryset');\n        if ($sDelId) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $sSubSql = \"( select $sTable.* from $sTable left join oxdel2delset on oxdel2delset.oxdelsetid=$sTable.oxid where \" . $this->getBaseObject()->getSqlActiveSnippet() . \" and oxdel2delset.oxdelid = :oxdelid ) as $sTable\";\n        } else {\n            $sSubSql = $sTable;\n        }\n        $sQ = \"select $sTable.*, oxobject2delivery.oxobjectid from $sSubSql left join (select oxobject2delivery.* from oxobject2delivery where oxobject2delivery.oxtype = 'rdfadeliveryset' ) as oxobject2delivery on oxobject2delivery.oxdeliveryid=$sTable.oxid where \" . $this->getBaseObject()->getSqlActiveSnippet() . \" \";\n        $this->selectString($sQ, [\n            'oxdelid' => $sDelId\n        ]);\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Diagnostics.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\n\n/**\n * Diagnostic tool model\n * Stores configuration and public diagnostic methods for shop diagnostics\n */\nclass Diagnostics\n{\n    /**\n     * Edition of THIS OXID eShop\n     *\n     * @var string\n     */\n    protected $_sEdition = \"\";\n\n    /**\n     * Version of THIS OXID eShop\n     *\n     * @var string\n     */\n    protected $_sVersion = \"\";\n\n    /**\n     * Revision of THIS OXID eShop\n     *\n     * @var string\n     */\n    protected $_sShopLink = \"\";\n\n    /**\n     * Version setter\n     *\n     * @param string $sVersion Version.\n     */\n    public function setVersion($sVersion)\n    {\n        if (!empty($sVersion)) {\n            $this->_sVersion = $sVersion;\n        }\n    }\n\n    /**\n     * Version getter\n     *\n     * @return string\n     */\n    public function getVersion()\n    {\n        return $this->_sVersion;\n    }\n\n    /**\n     * Edition setter\n     *\n     * @param string $sEdition Edition\n     */\n    public function setEdition($sEdition)\n    {\n        if (!empty($sEdition)) {\n            $this->_sEdition = $sEdition;\n        }\n    }\n\n    /**\n     * Edition getter\n     *\n     * @return string\n     */\n    public function getEdition()\n    {\n        return $this->_sEdition;\n    }\n\n    /**\n     * ShopLink setter\n     *\n     * @param string $sShopLink Shop link.\n     */\n    public function setShopLink($sShopLink)\n    {\n        if (!empty($sShopLink)) {\n            $this->_sShopLink = $sShopLink;\n        }\n    }\n\n    /**\n     * ShopLink getter\n     *\n     * @return string\n     */\n    public function getShopLink()\n    {\n        return $this->_sShopLink;\n    }\n\n    /**\n     * Collects information on the shop, like amount of categories, articles, users\n     *\n     * @return array\n     */\n    public function getShopDetails()\n    {\n        $aShopDetails = [\n            'Date'                => date(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('fullDateFormat'), time()),\n            'URL'                 => $this->getShopLink(),\n            'Edition'             => $this->getEdition(),\n            'Version'             => $this->getVersion(),\n            'Subshops (Total)'    => $this->countRows('oxshops', true),\n            'Subshops (Active)'   => $this->countRows('oxshops', false),\n            'Categories (Total)'  => $this->countRows('oxcategories', true),\n            'Categories (Active)' => $this->countRows('oxcategories', false),\n            'Articles (Total)'    => $this->countRows('oxarticles', true),\n            'Articles (Active)'   => $this->countRows('oxarticles', false),\n            'Users (Total)'       => $this->countRows('oxuser', true),\n        ];\n\n        return $aShopDetails;\n    }\n\n    /**\n     * counts result Rows\n     *\n     * @param string  $sTable table\n     * @param boolean $blMode mode\n     *\n     * @return integer\n     */\n    protected function countRows($table, $mode)\n    {\n        $query = sprintf(\"SELECT COUNT(*) FROM %s\", $table);\n\n        if ($mode == false) {\n            $query .= ' WHERE oxactive = 1';\n        }\n\n        return (int) DatabaseProvider::getDb()->getOne($query);\n    }\n\n\n    /**\n     * Picks some pre-selected PHP configuration settings and returns them.\n     *\n     * @return array\n     */\n    public function getPhpSelection()\n    {\n        $aPhpIniParams = [\n            'allow_url_fopen',\n            'display_errors',\n            'file_uploads',\n            'max_execution_time',\n            'memory_limit',\n            'post_max_size',\n            'register_globals',\n            'upload_max_filesize',\n        ];\n\n        $aPhpIniConf = [];\n\n        foreach ($aPhpIniParams as $sParam) {\n            $sValue = ini_get($sParam);\n            $aPhpIniConf[$sParam] = $sValue;\n        }\n\n        return $aPhpIniConf;\n    }\n\n\n    /**\n     * Returns the installed PHP devoder (like Zend Optimizer, Guard Loader)\n     *\n     * @return string\n     */\n    public function getPhpDecoder()\n    {\n        $sReturn = 'Zend ';\n\n        if (function_exists('zend_optimizer_version')) {\n            $sReturn .= 'Optimizer';\n        }\n\n        if (function_exists('zend_loader_enabled')) {\n            $sReturn .= 'Guard Loader';\n        }\n\n        return $sReturn;\n    }\n\n\n    /**\n     * General server information\n     * We will use the exec command here several times. In order tro prevent stop on failure, use $this->isExecAllowed().\n     *\n     * @return array\n     */\n    public function getServerInfo()\n    {\n        // init empty variables (can be filled if exec is allowed)\n        $iMemTotal = $iMemFree = $sCpuModelName = $sCpuModel = $sCpuFreq = $iCpuCores = null;\n\n        // fill, if exec is allowed\n        if ($this->isExecAllowed()) {\n            $iCpuAmnt = $this->getCpuAmount();\n            $iCpuMhz = $this->getCpuMhz();\n            $iBogo = $this->getBogoMips();\n            $iMemTotal = $this->getMemoryTotal();\n            $iMemFree = $this->getMemoryFree();\n            $sCpuModelName = $this->getCpuModel();\n            $sCpuModel = $iCpuAmnt . 'x ' . $sCpuModelName;\n            $sCpuFreq = $iCpuMhz . ' MHz';\n\n            // prevent \"division by zero\" error\n            if ($iBogo && $iCpuMhz) {\n                $iCpuCores = $iBogo / $iCpuMhz;\n            }\n        }\n\n        $aServerInfo = [\n            'Server OS'     => @php_uname('s'),\n            'VM'            => $this->getVirtualizationSystem(),\n            'PHP'           => $this->getPhpVersion(),\n            'MySQL'         => $this->getMySqlServerInfo(),\n            'Apache'        => $this->getApacheVersion(),\n            'Disk total'    => $this->getDiskTotalSpace(),\n            'Disk free'     => $this->getDiskFreeSpace(),\n            'Memory total'  => $iMemTotal,\n            'Memory free'   => $iMemFree,\n            'CPU Model'     => $sCpuModel,\n            'CPU frequency' => $sCpuFreq,\n            'CPU cores'     => round($iCpuCores, 0),\n        ];\n\n        return $aServerInfo;\n    }\n\n    /**\n     * Returns Apache version\n     *\n     * @return string\n     */\n    protected function getApacheVersion()\n    {\n        if (function_exists('apache_get_version')) {\n            $sReturn = apache_get_version();\n        } else {\n            $sReturn = $_SERVER['SERVER_SOFTWARE'];\n        }\n\n        return $sReturn;\n    }\n\n    /**\n     * Tries to find out which VM is used\n     *\n     * @return string\n     */\n    protected function getVirtualizationSystem()\n    {\n        $sSystemType = '';\n\n        if ($this->isExecAllowed()) {\n            //VMWare\n            @$sDeviceList = $this->getDeviceList('vmware');\n            if ($sDeviceList) {\n                $sSystemType = 'VMWare';\n                unset($sDeviceList);\n            }\n\n            //VirtualBox\n            @$sDeviceList = $this->getDeviceList('VirtualBox');\n            if ($sDeviceList) {\n                $sSystemType = 'VirtualBox';\n                unset($sDeviceList);\n            }\n        }\n\n        return $sSystemType;\n    }\n\n    /**\n     * Determines, whether the exec() command is allowed or not.\n     *\n     * @return boolean\n     */\n    public function isExecAllowed()\n    {\n        return function_exists('exec');\n    }\n\n    /**\n     * Finds the list of system devices for given system type\n     *\n     * @param string $sSystemType System type.\n     *\n     * @return string\n     */\n    protected function getDeviceList($sSystemType)\n    {\n        return exec('lspci | grep -i ' . $sSystemType);\n    }\n\n    /**\n     * Returns amount of CPU units.\n     *\n     * @return string\n     */\n    protected function getCpuAmount()\n    {\n        // cat /proc/cpuinfo | grep \"processor\" | sort -u | cut -d: -f2');\n        return exec('cat /proc/cpuinfo | grep \"physical id\" | sort | uniq | wc -l');\n    }\n\n    /**\n     * Returns CPU speed in Mhz\n     *\n     * @return float\n     */\n    protected function getCpuMhz()\n    {\n        return round(exec('cat /proc/cpuinfo | grep \"MHz\" | sort -u | cut -d: -f2'), 0);\n    }\n\n    /**\n     * Returns BogoMIPS evaluation of processor\n     *\n     * @return string\n     */\n    protected function getBogoMips()\n    {\n        return exec('cat /proc/cpuinfo | grep \"bogomips\" | sort -u | cut -d: -f2');\n    }\n\n    /**\n     * Returns total amount of memory\n     *\n     * @return string\n     */\n    protected function getMemoryTotal()\n    {\n        return exec('cat /proc/meminfo | grep \"MemTotal\" | sort -u | cut -d: -f2');\n    }\n\n    /**\n     * Returns amount of free memory\n     *\n     * @return string\n     */\n    protected function getMemoryFree()\n    {\n        return exec('cat /proc/meminfo | grep \"MemFree\" | sort -u | cut -d: -f2');\n    }\n\n    /**\n     * Returns CPU model information\n     *\n     * @return string\n     */\n    protected function getCpuModel()\n    {\n        return exec('cat /proc/cpuinfo | grep \"model name\" | sort -u | cut -d: -f2');\n    }\n\n    /**\n     * Returns total disk space\n     *\n     * @return string\n     */\n    protected function getDiskTotalSpace()\n    {\n        return round(disk_total_space('/') / 1024 / 1024, 0) . ' GiB';\n    }\n\n    /**\n     * Returns free disk space\n     *\n     * @return string\n     */\n    protected function getDiskFreeSpace()\n    {\n        return round(disk_free_space('/') / 1024 / 1024, 0) . ' GiB';\n    }\n\n    /**\n     * Returns PHP version\n     *\n     * @return string\n     */\n    protected function getPhpVersion()\n    {\n        return phpversion();\n    }\n\n    /**\n     * Returns MySQL server Information\n     *\n     * @return string\n     */\n    protected function getMySqlServerInfo()\n    {\n        $aResult = DatabaseProvider::getDb()->getRow(\"SHOW VARIABLES LIKE 'version'\");\n\n        return $aResult['Value'];\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/DiagnosticsOutput.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\n/**\n * Diagnostic tool result outputer\n * Performs OutputKey check of shop files and generates report file.\n */\nclass DiagnosticsOutput\n{\n    /**\n     * result key\n     *\n     * @var string\n     */\n    protected $_sOutputKey = \"diagnostic_tool_result\";\n\n\n    /**\n     * Result file path\n     *\n     * @var string\n     */\n    protected $_sOutputFileName = \"diagnostic_tool_result.html\";\n\n    /**\n     * Utils object\n     *\n     * @var mixed\n     */\n    protected $_oUtils = null;\n\n    /**\n     * Object constructor\n     */\n    public function __construct()\n    {\n        $this->_oUtils = \\OxidEsales\\Eshop\\Core\\Registry::getUtils();\n    }\n\n    /**\n     * OutputKey setter\n     *\n     * @param string $sOutputKey Output key.\n     */\n    public function setOutputKey($sOutputKey)\n    {\n        if (!empty($sOutputKey)) {\n            $this->_sOutputKey = $sOutputKey;\n        }\n    }\n\n    /**\n     * OutputKey getter\n     *\n     * @return string\n     */\n    public function getOutputKey()\n    {\n        return $this->_sOutputKey;\n    }\n\n    /**\n     * OutputFileName setter\n     *\n     * @param string $sOutputFileName Output file name.\n     */\n    public function setOutputFileName($sOutputFileName)\n    {\n        if (!empty($sOutputFileName)) {\n            $this->_sOutputFileName = $sOutputFileName;\n        }\n    }\n\n    /**\n     * OutputKey getter\n     *\n     * @return string\n     */\n    public function getOutputFileName()\n    {\n        return $this->_sOutputFileName;\n    }\n\n    /**\n     * Stores result file in file cache\n     *\n     * @param string $sResult Result.\n     */\n    public function storeResult($sResult)\n    {\n        $this->_oUtils->toFileCache($this->_sOutputKey, $sResult);\n    }\n\n    /**\n     * Reads exported result file contents\n     *\n     * @param string $sOutputKey Output key.\n     *\n     * @return string\n     */\n    public function readResultFile($sOutputKey = null)\n    {\n        $sCurrentKey = (empty($sOutputKey)) ? $this->_sOutputKey : $sOutputKey;\n\n        return $this->_oUtils->fromFileCache($sCurrentKey);\n    }\n\n    /**\n     * Sends generated file for download\n     *\n     * @param string $sOutputKey Output key.\n     */\n    public function downloadResultFile($sOutputKey = null)\n    {\n        $sCurrentKey = (empty($sOutputKey)) ? $this->_sOutputKey : $sOutputKey;\n\n        $this->_oUtils = \\OxidEsales\\Eshop\\Core\\Registry::getUtils();\n        $content = $this->_oUtils->fromFileCache($sCurrentKey);\n        $contentLength = strlen($content);\n\n        $this->_oUtils->setHeader(\"Pragma: public\");\n        $this->_oUtils->setHeader(\"Expires: 0\");\n        $this->_oUtils->setHeader(\"Cache-Control: must-revalidate, post-check=0, pre-check=0, private\");\n        $this->_oUtils->setHeader('Content-Disposition: attachment;filename=' . $this->_sOutputFileName);\n        $this->_oUtils->setHeader(\"Content-Type:text/html;charset=utf-8\");\n        if ($contentLength) {\n            $this->_oUtils->setHeader(\"Content-Length: \" . $contentLength);\n        }\n\n        echo $content;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Discount.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\nuse OxidEsales\\Eshop\\Core\\Database\\Adapter\\Doctrine\\Database;\nuse OxidEsales\\Eshop\\Core\\Exception\\InputException;\nuse OxidEsales\\Eshop\\Core\\Exception\\StandardException;\nuse stdClass;\n\n/**\n * Discounts manager.\n */\nclass Discount extends \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel\n{\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxdiscount';\n\n    /**\n     * Stores amount of articles which are applied for current discount\n     *\n     * @var double\n     */\n    protected $_dAmount = null;\n\n    /**\n     * Basket ident\n     *\n     * @var string\n     */\n    protected $_sBasketIdent = null;\n\n    /**\n     * Is discount for article or For category\n     *\n     * @var bool\n     */\n    protected $_blIsForArticleOrForCategory = null;\n\n    /**\n     * Is discount set for article, array index article id\n     *\n     * @var array\n     */\n    protected $_aHasArticleDiscounts = [];\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxdiscount');\n    }\n\n    /**\n     * Delete this object from the database, returns true on success.\n     *\n     * @param string $sOXID Object ID(default null)\n     *\n     * @return bool\n     */\n    public function delete($sOXID = null)\n    {\n        if (!$sOXID) {\n            $sOXID = $this->getId();\n        }\n        if (!$sOXID) {\n            return false;\n        }\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $oDb->execute('delete from oxobject2discount where oxobject2discount.oxdiscountid = :oxdiscountid', [\n            'oxdiscountid' => $sOXID\n        ]);\n\n        return parent::delete($sOXID);\n    }\n\n    /**\n     * Save the discount.\n     * Assigns a value to oxsort, if it was null\n     * Does input validation before saving the discount.\n     *\n     * Returns saving status\n     *\n     * @throws InputException\n     * @throws StandardException\n     *\n     * @return bool\n     */\n    public function save()\n    {\n        // Auto assign oxsort, if it is null\n        $oxsort = $this->oxdiscount__oxsort->value;\n        if (is_null($oxsort)) {\n            $shopId = $this->oxdiscount__oxshopid->value;\n            $newSort = $this->getNextOxsort($shopId);\n            $this->oxdiscount__oxsort = new \\oxField($newSort, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        }\n\n        // Validate oxsort before saving\n        if (!is_numeric($this->oxdiscount__oxsort->value)) {\n            /** @var InputException $exception */\n            $exception = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\InputException::class);\n            $exception->setMessage('DISCOUNT_ERROR_OXSORT_NOT_A_NUMBER');\n\n            throw $exception;\n        }\n\n        try {\n            $saveStatus = parent::save();\n        } catch (\\OxidEsales\\Eshop\\Core\\Exception\\StandardException $exception) {\n            if ($exception->getCode() == \\OxidEsales\\Eshop\\Core\\Database\\Adapter\\Doctrine\\Database::DUPLICATE_KEY_ERROR_CODE && false !== strpos($exception->getMessage(), 'UNIQ_OXSORT')) {\n                $exception = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\InputException::class);\n                $exception->setMessage('DISCOUNT_ERROR_OXSORT_NOT_UNIQUE');\n            }\n\n            throw $exception;\n        }\n\n        return $saveStatus;\n    }\n    /**\n     * Check for global discount (no articles, no categories)\n     *\n     * @return bool\n     */\n    public function isGlobalDiscount()\n    {\n        if (is_null($this->_blIsForArticleOrForCategory)) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n            $sQuery = \"select 1\n                        from oxobject2discount\n                        where oxdiscountid = :oxdiscountid and (oxtype = :oxtypearticles or oxtype = :oxtypecategories)\";\n            $params = [\n                'oxdiscountid' => $this->oxdiscount__oxid->value,\n                'oxtypearticles' => 'oxarticles',\n                'oxtypecategories' => 'oxcategories'\n            ];\n\n            $this->_blIsForArticleOrForCategory = $oDb->getOne($sQuery, $params) ? false : true;\n        }\n\n        return $this->_blIsForArticleOrForCategory;\n    }\n\n    /**\n     * Checks if discount applies for article\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle article object\n     *\n     * @return bool\n     */\n    public function isForArticle($oArticle)\n    {\n        // item discounts may only be applied for basket\n        if ($this->oxdiscount__oxaddsumtype->value == 'itm') {\n            return false;\n        }\n\n        if ($this->oxdiscount__oxamount->value || $this->oxdiscount__oxprice->value) {\n            return false;\n        }\n\n        if ($this->oxdiscount__oxpriceto->value && ($this->oxdiscount__oxpriceto->value < $oArticle->getBasePrice())) {\n            return false;\n        }\n\n        if ($this->isGlobalDiscount()) {\n            return true;\n        }\n\n        $sArticleId = $oArticle->getProductId();\n\n        if (!isset($this->_aHasArticleDiscounts[$sArticleId])) {\n            $blResult = $this->isArticleAssigned($oArticle) || $this->isCategoriesAssigned($oArticle->getCategoryIds());\n\n            $this->_aHasArticleDiscounts[$sArticleId] = $blResult;\n        }\n\n        return $this->_aHasArticleDiscounts[$sArticleId];\n    }\n\n    /**\n     * Checks if discount is setup for some basket item\n     *\n     * @param object $oArticle basket item\n     *\n     * @return bool\n     */\n    public function isForBasketItem($oArticle)\n    {\n        if ($this->oxdiscount__oxamount->value == 0 && $this->oxdiscount__oxprice->value == 0) {\n            return false;\n        }\n\n        // skipping bundle discounts\n        if ($this->oxdiscount__oxaddsumtype->value == 'itm') {\n            return false;\n        }\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        // check if this article is assigned\n        $sQ = \"select 1 from oxobject2discount \n            where oxdiscountid = :oxdiscountid and oxtype = :oxtype \";\n        $sQ .= $this->getProductCheckQuery($oArticle);\n        $params = [\n            'oxdiscountid' => $this->oxdiscount__oxid->value,\n            'oxtype' => 'oxarticles'\n        ];\n\n        if (!($blOk = (bool)$oDb->getOne($sQ, $params))) {\n            // checking article category\n            $blOk = $this->checkForArticleCategories($oArticle);\n        }\n\n        return $blOk;\n    }\n\n    /**\n     * Tests if total amount or price (price priority) of articles that can be applied to current discount fits to discount configuration\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket basket\n     *\n     * @return bool\n     */\n    public function isForBasketAmount($oBasket)\n    {\n        $dAmount = 0;\n        $aBasketItems = $oBasket->getContents();\n        foreach ($aBasketItems as $oBasketItem) {\n            $oBasketArticle = $oBasketItem->getArticle(false);\n\n            if ($this->oxdiscount__oxaddsumtype->value != 'itm') {\n                $blForBasketItem = $this->isForBasketItem($oBasketArticle);\n            } else {\n                $blForBasketItem = $this->isForBundleItem($oBasketArticle);\n            }\n\n            if ($blForBasketItem) {\n                $dRate = $oBasket->getBasketCurrency()->rate;\n                if ($this->oxdiscount__oxprice->value) {\n                    if (($oPrice = $oBasketArticle->getPrice())) {\n                        $dAmount += ($oPrice->getPrice() * $oBasketItem->getAmount()) / $dRate;\n                    }\n                } elseif ($this->oxdiscount__oxamount->value) {\n                    $dAmount += $oBasketItem->getAmount();\n                }\n            }\n        }\n\n        return $this->isForAmount($dAmount);\n    }\n\n    /**\n     * Tests if passed amount or price fits current discount (price priority)\n     *\n     * @param double $dAmount amount or price to check (price priority)\n     *\n     * @return bool\n     */\n    public function isForAmount($dAmount)\n    {\n        $blIs = true;\n\n        if (\n            $this->oxdiscount__oxprice->value &&\n            ($dAmount < $this->oxdiscount__oxprice->value || $dAmount > $this->oxdiscount__oxpriceto->value)\n        ) {\n            $blIs = false;\n        } elseif (\n            $this->oxdiscount__oxamount->value &&\n                  ($dAmount < $this->oxdiscount__oxamount->value || $dAmount > $this->oxdiscount__oxamountto->value)\n        ) {\n            $blIs = false;\n        }\n\n        return $blIs;\n    }\n\n    /**\n     * Checks if discount is setup for whole basket\n     *\n     * @param object $oBasket basket object\n     *\n     * @return bool\n     */\n    public function isForBasket($oBasket)\n    {\n        // initial configuration check\n        if ($this->oxdiscount__oxamount->value == 0 && $this->oxdiscount__oxprice->value == 0) {\n            return false;\n        }\n\n        $oSummary = $oBasket->getBasketSummary();\n        // amounts check\n        if ($this->oxdiscount__oxamount->value && ($oSummary->iArticleCount < $this->oxdiscount__oxamount->value || $oSummary->iArticleCount > $this->oxdiscount__oxamountto->value)) {\n            return false;\n        // price check\n        } elseif ($this->oxdiscount__oxprice->value) {\n            $dRate = $oBasket->getBasketCurrency()->rate;\n            if ($oSummary->dArticleDiscountablePrice < $this->oxdiscount__oxprice->value * $dRate || $oSummary->dArticleDiscountablePrice > $this->oxdiscount__oxpriceto->value * $dRate) {\n                return false;\n            }\n        }\n\n        // oxobject2discount configuration check\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sQ = 'select 1 from oxobject2discount \n            where oxdiscountid = :oxdiscountid and oxtype in (\"oxarticles\", \"oxcategories\" ) ';\n        $params = [\n            'oxdiscountid' => $this->oxdiscount__oxid->value\n        ];\n\n        return !((bool)$oDb->getOne($sQ, $params));\n    }\n\n    /**\n     * Checks if discount type is bundle discount\n     *\n     * @param object $oArticle article object\n     *\n     * @return bool\n     */\n    public function isForBundleItem($oArticle)\n    {\n        if ($this->oxdiscount__oxaddsumtype->value != 'itm') {\n            return false;\n        }\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sQ = \"select 1 from oxobject2discount where oxdiscountid = :oxdiscountid\";\n        $sQ .= $this->getProductCheckQuery($oArticle);\n        $params = [\n            'oxdiscountid' => $this->getId()\n        ];\n\n        if (!($blOk = (bool)$oDb->getOne($sQ, $params))) {\n            // additional checks for amounts and other dependencies\n            $blOk = $this->checkForArticleCategories($oArticle);\n        }\n\n        return $blOk;\n    }\n\n    /**\n     * Checks if discount type is whole basket bundle discount\n     *\n     * @param object $oBasket basket object\n     *\n     * @return bool\n     */\n    public function isForBundleBasket($oBasket)\n    {\n        if ($this->oxdiscount__oxaddsumtype->value != 'itm') {\n            return false;\n        }\n\n        return $this->isForBasket($oBasket);\n    }\n\n    /**\n     * Returns absolute discount value\n     *\n     * @param float     $dPrice  item price\n     * @param float|int $dAmount item amount, interpretted only when discount is absolute (default 1)\n     *\n     * @return float\n     */\n    public function getAbsValue($dPrice, $dAmount = 1)\n    {\n        if ($this->oxdiscount__oxaddsumtype->value == '%') {\n            return $dPrice * ($this->oxdiscount__oxaddsum->value / 100);\n        } else {\n            $oCur = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActShopCurrencyObject();\n\n            return $this->oxdiscount__oxaddsum->value * $dAmount * $oCur->rate;\n        }\n    }\n\n    /**\n     * Return discount percent\n     *\n     * @param double $dPrice - price from which calculates discount\n     *\n     * @return double\n     */\n    public function getPercentage($dPrice)\n    {\n        if ($this->getAddSumType() == 'abs' && $dPrice > 0) {\n            return $this->getAddSum() / $dPrice * 100;\n        } else {\n            return $this->getAddSum();\n        }\n    }\n\n    /**\n     * Return add sum in abs type discount with efected currency rate;\n     * Return discount percent value in other way;\n     *\n     * @return double\n     */\n    public function getAddSum()\n    {\n        if ($this->oxdiscount__oxaddsumtype->value == 'abs') {\n            $oCur = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActShopCurrencyObject();\n\n            return $this->oxdiscount__oxaddsum->value * $oCur->rate;\n        } else {\n            return $this->oxdiscount__oxaddsum->value;\n        }\n    }\n\n    /**\n     * Return addsum type\n     *\n     * @return string\n     */\n    public function getAddSumType()\n    {\n        return $this->oxdiscount__oxaddsumtype->value;\n    }\n\n    /**\n     * Returns amount of items to bundle\n     *\n     * @param double $dAmount item amount\n     *\n     * @return double\n     */\n    public function getBundleAmount($dAmount)\n    {\n        $dItemAmount = $this->oxdiscount__oxitmamount->value;\n\n        // Multiplying bundled articles count, if allowed\n        if ($this->oxdiscount__oxitmmultiple->value && $this->oxdiscount__oxamount->value > 0) {\n            $dItemAmount = floor($dAmount / $this->oxdiscount__oxamount->value) * $this->oxdiscount__oxitmamount->value;\n        }\n\n        return $dItemAmount;\n    }\n\n    /**\n     * Returns compact discount object which is used in oxbasket\n     *\n     * @return stdClass\n     */\n    public function getSimpleDiscount()\n    {\n        $oDiscount = new stdClass();\n        $oDiscount->sOXID = $this->getId();\n        $oDiscount->sDiscount = $this->oxdiscount__oxtitle->value;\n        $oDiscount->sType = $this->oxdiscount__oxaddsumtype->value;\n\n        return $oDiscount;\n    }\n\n    /**\n     * Returns article ids assigned to discount\n     *\n     * @return array\n     */\n    public function getArticleIds()\n    {\n        $db = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $params = [\n            'oxdiscountid' => $this->getId(),\n            'oxtype' => 'oxarticles'\n        ];\n\n        return $db->getCol(\"select `oxobjectid` from oxobject2discount \n            where oxdiscountid = :oxdiscountid and oxtype = :oxtype\", $params);\n    }\n\n    /**\n     * Returns category ids asigned to discount\n     *\n     * @return array\n     */\n    public function getCategoryIds()\n    {\n        $db = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $params = [\n            'oxdiscountid' => $this->getId(),\n            'oxtype' => 'oxcategories'\n        ];\n\n        return $db->getCol(\"select `oxobjectid` from oxobject2discount \n            where oxdiscountid = :oxdiscountid and oxtype = :oxtype\", $params);\n    }\n\n    /**\n     * Increment the maximum value of oxsort found in the database by certain amount and return it.\n     *\n     * @param int $shopId The id of the current shop\n     *\n     * @return int The incremented oxsort\n     */\n    public function getNextOxsort($shopId)\n    {\n        $query = \"SELECT MAX(`oxsort`)+10 FROM `oxdiscount` WHERE `oxshopid` = :oxshopid\";\n        $nextSort = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->getOne($query, [\n            'oxshopid' => $shopId\n        ]);\n\n        return (int) $nextSort;\n    }\n\n    /**\n     * Checks if discount may be applied according amounts info\n     *\n     * @param object $oArticle article object to chesk\n     *\n     * @return bool\n     */\n    protected function checkForArticleCategories($oArticle)\n    {\n        // check if article is in some assigned category\n        $aCatIds = $oArticle->getCategoryIds();\n        if (!$aCatIds || !count($aCatIds)) {\n            // no categories are set for article, so no discounts from categories..\n            return false;\n        }\n\n        $sCatIds = \"(\" . implode(\",\", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aCatIds)) . \")\";\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        // getOne appends limit 1, so this one should be fast enough\n        $sQ = \"select oxobjectid from oxobject2discount \n            where oxdiscountid = :oxdiscountid \n                and oxobjectid in $sCatIds \n                and oxtype = :oxtype\";\n\n        return $oDb->getOne($sQ, [\n            'oxdiscountid' => $this->oxdiscount__oxid->value,\n            'oxtype' => 'oxcategories'\n        ]);\n    }\n\n    /**\n     * Returns part of query for discount check. If product is variant - query contains both id check e.g.\n     * \"and (oxobjectid = '...' or oxobjectid = '...')\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oProduct product used for discount check\n     *\n     * @return string\n     */\n    protected function getProductCheckQuery($oProduct)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        // check if this article is assigned\n        if (($sParentId = $oProduct->getParentId())) {\n            $sArticleId = \" and ( oxobjectid = \" . $oDb->quote($oProduct->getProductId()) . \" or oxobjectid = \" . $oDb->quote($sParentId) . \" )\";\n        } else {\n            $sArticleId = \" and oxobjectid = \" . $oDb->quote($oProduct->getProductId());\n        }\n\n        return $sArticleId;\n    }\n\n    /**\n     * Checks whether this article is assigned to discount\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle\n     *\n     * @return bool\n     */\n    protected function isArticleAssigned($oArticle)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $sQ = \"select 1\n                from oxobject2discount\n                where oxdiscountid = :oxdiscountid \n                    and oxtype = :oxtype \";\n        $sQ .= $this->getProductCheckQuery($oArticle);\n        $params = [\n            'oxdiscountid' => $this->oxdiscount__oxid->value,\n            'oxtype' => 'oxarticles'\n        ];\n\n        return $oDb->getOne($sQ, $params) ? true : false;\n    }\n\n    /**\n     * Checks whether categories are assigned to discount\n     *\n     * @param array $aCategoryIds\n     *\n     * @return bool\n     */\n    protected function isCategoriesAssigned($aCategoryIds)\n    {\n        if (empty($aCategoryIds)) {\n            return false;\n        }\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $sCategoryIds = \"(\" . implode(\",\", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aCategoryIds)) . \")\";\n        $sQ = \"select 1\n                from oxobject2discount\n                where oxdiscountid = :oxdiscountid and oxobjectid in {$sCategoryIds} and oxtype = :oxtype\";\n        $params = [\n            'oxdiscountid' => $this->oxdiscount__oxid->value,\n            'oxtype' => 'oxcategories'\n        ];\n\n        return $oDb->getOne($sQ, $params) ? true : false;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/DiscountList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Discount list manager.\n * Organizes list of discount objects.\n */\nclass DiscountList extends \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n{\n    /**\n     * Discount user id\n     *\n     * @var string User ID\n     */\n    protected $_sUserId = null;\n\n    /**\n     * Forced list reload marker\n     *\n     * @var bool\n     */\n    protected $_blReload = true;\n\n\n    /**\n     * If any shops category has \"skip discounts\" status this parameter value will be true\n     *\n     * @var bool\n     */\n    protected $_hasSkipDiscountCategories = null;\n\n    /**\n     * Class Constructor\n     */\n    public function __construct()\n    {\n        parent::__construct('oxdiscount');\n    }\n\n    /**\n     * Initializes current state discount list\n     * For iterating through the list, use getArray() on the list,\n     * as iterating on object itself can cause concurrency problems.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser user object (optional)\n     *\n     * @return array\n     */\n    protected function getDiscountList($oUser = null)\n    {\n        $sUserId = $oUser ? $oUser->getId() : '';\n\n        if ($this->_blReload || $sUserId !== $this->_sUserId) {\n            // loading list\n            $this->selectString($this->getFilterSelect($oUser));\n\n            // setting list proterties\n            $this->_blReload = false; // reload marker\n            $this->_sUserId = $sUserId; // discount list user id\n        }\n\n        // resetting array pointer\n        $this->rewind();\n\n        return $this;\n    }\n\n    /**\n     * Returns user country id for for discount selection\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser oxuser object\n     *\n     * @return string\n     */\n    public function getCountryId($oUser)\n    {\n        $sCountryId = null;\n        if ($oUser) {\n            $sCountryId = $oUser->getActiveCountry();\n        }\n\n        return $sCountryId;\n    }\n\n    /**\n     * Used to force discount list reload\n     */\n    public function forceReload()\n    {\n        $this->_blReload = true;\n    }\n\n    /**\n     * Creates discount list filter SQL to load current state discount list\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser user object\n     *\n     * @return string\n     */\n    protected function getFilterSelect($oUser)\n    {\n        $oBaseObject = $this->getBaseObject();\n\n        $sTable = $oBaseObject->getViewName();\n        $sQ = \"select \" . $oBaseObject->getSelectFields() . \" from $sTable \";\n        $sQ .= \"where \" . $oBaseObject->getSqlActiveSnippet() . ' ';\n\n\n        // defining initial filter parameters\n        $sUserId = null;\n        $sGroupIds = null;\n        $sCountryId = $this->getCountryId($oUser);\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        // checking for current session user which gives additional restrictions for user itself, users group and country\n        if ($oUser) {\n            // user ID\n            $sUserId = $oUser->getId();\n\n            // user group ids\n            foreach ($oUser->getUserGroups() as $oGroup) {\n                if ($sGroupIds) {\n                    $sGroupIds .= ', ';\n                }\n                $sGroupIds .= $oDb->quote($oGroup->getId());\n            }\n        }\n\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sUserTable = $tableViewNameGenerator->getViewName('oxuser');\n        $sGroupTable = $tableViewNameGenerator->getViewName('oxgroups');\n        $sCountryTable = $tableViewNameGenerator->getViewName('oxcountry');\n\n        $sCountrySql = $sCountryId ? \"EXISTS(select oxobject2discount.oxid from oxobject2discount where oxobject2discount.OXDISCOUNTID=$sTable.OXID and oxobject2discount.oxtype='oxcountry' and oxobject2discount.OXOBJECTID=\" . $oDb->quote($sCountryId) . \")\" : '0';\n        $sUserSql = $sUserId ? \"EXISTS(select oxobject2discount.oxid from oxobject2discount where oxobject2discount.OXDISCOUNTID=$sTable.OXID and oxobject2discount.oxtype='oxuser' and oxobject2discount.OXOBJECTID=\" . $oDb->quote($sUserId) . \")\" : '0';\n        $sGroupSql = $sGroupIds ? \"EXISTS(select oxobject2discount.oxid from oxobject2discount where oxobject2discount.OXDISCOUNTID=$sTable.OXID and oxobject2discount.oxtype='oxgroups' and oxobject2discount.OXOBJECTID in ($sGroupIds) )\" : '0';\n\n        $sQ .= \"and (\n                if(EXISTS(select 1 from oxobject2discount, $sCountryTable where $sCountryTable.oxid=oxobject2discount.oxobjectid and oxobject2discount.OXDISCOUNTID=$sTable.OXID and oxobject2discount.oxtype='oxcountry' LIMIT 1),\n                        $sCountrySql,\n                        1) &&\n                if(EXISTS(select 1 from oxobject2discount, $sUserTable where $sUserTable.oxid=oxobject2discount.oxobjectid and oxobject2discount.OXDISCOUNTID=$sTable.OXID and oxobject2discount.oxtype='oxuser' LIMIT 1),\n                        $sUserSql,\n                        1) &&\n                if(EXISTS(select 1 from oxobject2discount, $sGroupTable where $sGroupTable.oxid=oxobject2discount.oxobjectid and oxobject2discount.OXDISCOUNTID=$sTable.OXID and oxobject2discount.oxtype='oxgroups' LIMIT 1),\n                        $sGroupSql,\n                        1)\n            )\";\n\n        $sQ .= \" order by $sTable.oxsort \";\n\n        return $sQ;\n    }\n\n    /**\n     * Returns array of discounts that can be globally (transparently) applied\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle article object\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User    $oUser    oxuser object (optional)\n     *\n     * @return array\n     */\n    public function getArticleDiscounts($oArticle, $oUser = null)\n    {\n        $aList = [];\n        $aDiscList = $this->getDiscountList($oUser)->getArray();\n        foreach ($aDiscList as $oDiscount) {\n            if ($oDiscount->isForArticle($oArticle)) {\n                $aList[$oDiscount->getId()] = $oDiscount;\n            }\n        }\n\n        return $aList;\n    }\n\n    /**\n     * Returns array of discounts that can be applied for individual basket item\n     *\n     * @param mixed                                      $oArticle article object or article id (according to needs)\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket  array of basket items containing article id, amount and price\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User   $oUser    user object (optional)\n     *\n     * @return array\n     */\n    public function getBasketItemDiscounts($oArticle, $oBasket, $oUser = null)\n    {\n        $aList = [];\n        $aDiscList = $this->getDiscountList($oUser)->getArray();\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\Discount $oDiscount */\n        foreach ($aDiscList as $oDiscount) {\n            if ($oDiscount->isForBasketItem($oArticle) && $oDiscount->isForBasketAmount($oBasket)) {\n                $aList[$oDiscount->getId()] = $oDiscount;\n            }\n        }\n\n        return $aList;\n    }\n\n    /**\n     * Returns array of discounts that can be applied for whole basket\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket basket\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User   $oUser   user object (optional)\n     *\n     * @return array\n     */\n    public function getBasketDiscounts($oBasket, $oUser = null)\n    {\n        $aList = [];\n        $aDiscList = $this->getDiscountList($oUser)->getArray();\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\Discount $oDiscount */\n        foreach ($aDiscList as $oDiscount) {\n            if ($oDiscount->isForBasket($oBasket)) {\n                $aList[$oDiscount->getId()] = $oDiscount;\n            }\n        }\n\n        return $aList;\n    }\n\n    /**\n     * Returns array of bundle discounts that can be applied for whole basket\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle article object\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket  $oBasket  basket\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User    $oUser    user object (optional)\n     *\n     * @return array\n     */\n    public function getBasketItemBundleDiscounts($oArticle, $oBasket, $oUser = null)\n    {\n        $aList = [];\n        $aDiscList = $this->getDiscountList($oUser)->getArray();\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\Discount $oDiscount */\n        foreach ($aDiscList as $oDiscount) {\n            if ($oDiscount->isForBundleItem($oArticle, $oBasket) && $oDiscount->isForBasketAmount($oBasket)) {\n                $aList[$oDiscount->getId()] = $oDiscount;\n            }\n        }\n\n        return $aList;\n    }\n\n    /**\n     * Returns array of basket bundle discounts\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket oxbasket object\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User   $oUser   oxuser object (optional)\n     *\n     * @return array\n     */\n    public function getBasketBundleDiscounts($oBasket, $oUser = null)\n    {\n        $aList = [];\n        $aDiscList = $this->getDiscountList($oUser)->getArray();\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\Discount $oDiscount */\n        foreach ($aDiscList as $oDiscount) {\n            if ($oDiscount->isForBundleBasket($oBasket)) {\n                $aList[$oDiscount->getId()] = $oDiscount;\n            }\n        }\n\n        return $aList;\n    }\n\n    /**\n     * Checks if any category has \"skip discounts\" status\n     *\n     * @return bool\n     */\n    public function hasSkipDiscountCategories()\n    {\n        if ($this->_hasSkipDiscountCategories === null || $this->_blReload) {\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $sViewName = $tableViewNameGenerator->getViewName('oxcategories');\n            $sQ = \"select 1 from {$sViewName} where {$sViewName}.oxactive = 1 and {$sViewName}.oxskipdiscounts = '1' \";\n\n            $this->_hasSkipDiscountCategories = (bool) \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->getOne($sQ);\n        }\n\n        return $this->_hasSkipDiscountCategories;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/File.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxField;\nuse oxRegistry;\nuse oxDb;\nuse oxException;\n\n/**\n * Article files manager.\n */\nclass File extends \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n{\n    /**\n     * No active user exception code.\n     */\n    const NO_USER = 2;\n\n    /**\n     * Object core table name\n     *\n     * @var string\n     */\n    protected $_sCoreTable = 'oxfiles';\n\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxfile';\n\n    /**\n     * Stores relative oxFile path from configs 'sDownloadsDir'\n     *\n     * @var string\n     */\n    protected $_sRelativeFilePath = null;\n\n    /**\n     * Paid order indicator\n     *\n     * @var bool\n     */\n    protected $_blIsPaid = null;\n\n    /**\n     * Full URL where article could be downloaded from.\n     * Is set to false in case download is not available for current user\n     *\n     * @var string|bool\n     */\n    protected $_sDownloadLink = null;\n\n    /**\n     * Has valid downloads indicator\n     *\n     * @var bool\n     */\n    protected $_blHasValidDownloads = null;\n\n    /**\n     * Default manual upload dir located within general file dir\n     *\n     * @var string\n     */\n    protected $_sManualUploadDir = \"uploads\";\n\n    /**\n     * Initialises the instance\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init();\n    }\n\n    /**\n     * Sets oxefile__oxstorehash with file hash.\n     * Moves file to desired location and change its access rights.\n     *\n     * @param int $sFileIndex File index\n     *\n     * @throws oxException Throws exception if file wasn't moved or if rights wasn't changed.\n     */\n    public function processFile($sFileIndex)\n    {\n        $aFileInfo = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getUploadedFile($sFileIndex);\n\n        $this->checkArticleFile($aFileInfo);\n\n        $sFileHash = $this->getFileHash($aFileInfo['tmp_name']);\n        $this->oxfiles__oxstorehash = new \\OxidEsales\\Eshop\\Core\\Field($sFileHash, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        $sUploadTo = $this->getStoreLocation();\n\n        if (!$this->uploadFile($aFileInfo['tmp_name'], $sUploadTo)) {\n            throw new \\OxidEsales\\Eshop\\Core\\Exception\\StandardException('EXCEPTION_COULDNOTWRITETOFILE');\n        }\n    }\n\n    /**\n     * Checks if given file is valid upload file\n     *\n     * @param array $aFileInfo File info array\n     *\n     * @throws oxException Throws exception if file wasn't uploaded successfully.\n     */\n    protected function checkArticleFile($aFileInfo)\n    {\n        //checking params\n        if (!isset($aFileInfo['name']) || !isset($aFileInfo['tmp_name'])) {\n            throw new \\OxidEsales\\Eshop\\Core\\Exception\\StandardException('EXCEPTION_NOFILE');\n        }\n\n        // error uploading file ?\n        if (isset($aFileInfo['error']) && $aFileInfo['error']) {\n            throw new \\OxidEsales\\Eshop\\Core\\Exception\\StandardException('EXCEPTION_FILEUPLOADERROR_' . ((int) $aFileInfo['error']));\n        }\n    }\n\n    /**\n     * Return full path of root dir where download files are stored\n     *\n     * @return string\n     */\n    protected function getBaseDownloadDirPath()\n    {\n        $sConfigValue = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('sDownloadsDir');\n\n        //Unix full path is set\n        if ($sConfigValue && $sConfigValue[0] == DIRECTORY_SEPARATOR) {\n            return $sConfigValue;\n        }\n\n        //relative path is set\n        if ($sConfigValue) {\n            $sPath = getShopBasePath() . DIRECTORY_SEPARATOR . $sConfigValue;\n\n            return $sPath;\n        }\n\n        //no path is set\n        $sPath = getShopBasePath() . \"/out/downloads/\";\n\n        return $sPath;\n    }\n\n    /**\n     * Returns full filesystem path where files are stored.\n     * Make sure that object oxfiles__oxstorehash or oxfiles__oxfilename\n     * attribute is set before calling this method\n     *\n     * @return string\n     */\n    public function getStoreLocation()\n    {\n        $sPath = $this->getBaseDownloadDirPath();\n        $sPath .= DIRECTORY_SEPARATOR . $this->getFileLocation();\n\n        return $sPath;\n    }\n\n    /**\n     * Return true if file is under download folder.\n     * Return false if file is above download folder or if file does not exist.\n     *\n     * @return bool\n     */\n    public function isUnderDownloadFolder()\n    {\n        $storageLocation = realpath($this->getStoreLocation());\n\n        if ($storageLocation === false) {\n            return false;\n        }\n\n        $downloadFolder = realpath($this->getBaseDownloadDirPath());\n\n        return strpos($storageLocation, $downloadFolder) !== false;\n    }\n\n    /**\n     * Returns relative file path from oxConfig 'sDownloadsDir' variable.\n     *\n     * @return string\n     */\n    protected function getFileLocation()\n    {\n        $this->_sRelativeFilePath = '';\n        $sFileHash = $this->oxfiles__oxstorehash->value;\n        $sFileName = $this->oxfiles__oxfilename->value;\n\n        //security check for demo shops\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->isDemoShop()) {\n            $sFileName = basename($sFileName);\n        }\n\n        if ($this->isUploaded()) {\n            $this->_sRelativeFilePath = $this->getHashedFileDir($sFileHash);\n            $this->_sRelativeFilePath .= DIRECTORY_SEPARATOR . $sFileHash;\n        } else {\n            $this->_sRelativeFilePath = DIRECTORY_SEPARATOR . $this->_sManualUploadDir . DIRECTORY_SEPARATOR . $sFileName;\n        }\n\n        return $this->_sRelativeFilePath;\n    }\n\n    /**\n     * Returns relative sub dir of oxconfig 'sDownloadsDir' of\n     * required file from supplied $sFileHash parameter.\n     * Creates dir in case it does not exist.\n     *\n     * @param string $sFileHash File hash value\n     *\n     * @return string\n     */\n    protected function getHashedFileDir($sFileHash)\n    {\n        $sDir = substr($sFileHash, 0, 2);\n        $sAbsDir = $this->getBaseDownloadDirPath() . DIRECTORY_SEPARATOR . $sDir;\n\n        if (!is_dir($sAbsDir)) {\n            mkdir($sAbsDir, 0755);\n        }\n\n        return $sDir;\n    }\n\n    /**\n     * Calculates file hash.\n     * Currently MD5 is used.\n     *\n     * @param string $sFileName File name values\n     *\n     * @return string\n     */\n    protected function getFileHash($sFileName)\n    {\n        return md5_file($sFileName);\n    }\n\n    /**\n     * Moves file from source to target and changes file mode.\n     * Returns true on success.\n     *\n     * @param string $sSource Source filename\n     * @param string $sTarget Target filename\n     *\n     * @return bool\n     */\n    protected function uploadFile($sSource, $sTarget)\n    {\n        $blDone = move_uploaded_file($sSource, $sTarget);\n\n        if ($blDone) {\n            $blDone = @chmod($sTarget, 0644);\n        }\n\n        return $blDone;\n    }\n\n    /**\n     * Checks whether the file has been uploaded over admin area.\n     * Returns true in case file is uploaded (and hashed) over admin area.\n     * Returns false in case file is placed manually (ftp) to \"out/downloads/uploads\" dir.\n     * It's similar so don't get confused here.\n     *\n     * @return bool\n     */\n    public function isUploaded()\n    {\n        $blHashed = false;\n        if ($this->oxfiles__oxstorehash->value) {\n            $blHashed = true;\n        }\n\n        return $blHashed;\n    }\n\n    /**\n     * Deletes oxFile record from DB, removes orphan files.\n     *\n     * @param string $sOxId default null\n     *\n     * @return bool\n     */\n    public function delete($sOxId = null)\n    {\n        $sOxId = $sOxId ? $sOxId : $this->getId();\n\n        $this->load($sOxId);\n        // if record cannot be delete, abort deletion\n        if ($blDeleted = parent::delete($sOxId)) {\n            $this->deleteFile();\n        }\n\n        return $blDeleted;\n    }\n\n    /**\n     * Checks if file is not used for  other objects.\n     * If not used, unlink the file.\n     *\n     * @return null|false\n     */\n    protected function deleteFile()\n    {\n        if (!$this->isUploaded()) {\n            return false;\n        }\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $iCount = $oDb->getOne(\n            'SELECT COUNT(*) FROM `oxfiles` WHERE `OXSTOREHASH` = :oxstorehash',\n            [\n                'oxstorehash' => $this->oxfiles__oxstorehash->value\n            ]\n        );\n        if (!$iCount) {\n            $sPath = $this->getStoreLocation();\n            unlink($sPath);\n        }\n    }\n\n    /**\n     * returns oxfile__oxfilename for URL usage\n     * converts spec symbols to %xx combination\n     *\n     * @return string\n     */\n    protected function getFilenameForUrl()\n    {\n        return rawurlencode($this->oxfiles__oxfilename->value);\n    }\n\n    /**\n     * Supplies the downloadable file for client and exits\n     */\n    public function download()\n    {\n        $oUtils = \\OxidEsales\\Eshop\\Core\\Registry::getUtils();\n        $sFileName = $this->getFilenameForUrl();\n        $sFileLocations = $this->getStoreLocation();\n\n        if (!$this->exist() || !$this->isUnderDownloadFolder()) {\n            throw new \\OxidEsales\\Eshop\\Core\\Exception\\StandardException('EXCEPTION_NOFILE');\n        }\n\n        $oUtils->setHeader(\"Pragma: public\");\n        $oUtils->setHeader(\"Expires: 0\");\n        $oUtils->setHeader(\"Cache-Control: must-revalidate, post-check=0, pre-check=0, private\");\n        $oUtils->setHeader('Content-Disposition: attachment;filename=' . $sFileName);\n        $oUtils->setHeader(\"Content-Type: application/octet-stream\");\n        if ($iFileSize = $this->getSize()) {\n            $oUtils->setHeader(\"Content-Length: \" . $iFileSize);\n        }\n        readfile($sFileLocations);\n        $oUtils->showMessageAndExit(null);\n    }\n\n    /**\n     * Check if file exist\n     *\n     * @return bool\n     */\n    public function exist()\n    {\n        return file_exists($this->getStoreLocation());\n    }\n\n    /**\n     * Checks if this file has valid ordered downloads\n     *\n     * @return bool\n     */\n    public function hasValidDownloads()\n    {\n        if ($this->_blHasValidDownloads == null) {\n            $this->_blHasValidDownloads = false;\n\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n            $sSql = \"SELECT\n                        `oxorderfiles`.`oxid`\n                     FROM `oxorderfiles`\n                        LEFT JOIN `oxorderarticles` ON `oxorderarticles`.`oxid` = `oxorderfiles`.`oxorderarticleid`\n                        LEFT JOIN `oxorder` ON `oxorder`.`oxid` = `oxorderfiles`.`oxorderid`\n                     WHERE `oxorderfiles`.`oxfileid` = :oxfileid\n                        AND ( ! `oxorderfiles`.`oxmaxdownloadcount` OR `oxorderfiles`.`oxmaxdownloadcount` > `oxorderfiles`.`oxdownloadcount`)\n                        AND ( `oxorderfiles`.`oxvaliduntil` = '0000-00-00 00:00:00' OR `oxorderfiles`.`oxvaliduntil` > :oxvaliduntil )\n                        AND `oxorder`.`oxstorno` = 0\n                        AND `oxorderarticles`.`oxstorno` = 0\";\n            $params = [\n                'oxfileid' => $this->getId(),\n                'oxvaliduntil' => date('Y-m-d H:i:s', \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime())\n            ];\n\n            if ($oDb->getOne($sSql, $params)) {\n                $this->_blHasValidDownloads = true;\n            }\n        }\n\n        return $this->_blHasValidDownloads;\n    }\n\n    /**\n     * Returns max download count of file\n     *\n     * @return int\n     */\n    public function getMaxDownloadsCount()\n    {\n        $iMaxCount = $this->oxfiles__oxmaxdownloads->value;\n        //if value is -1, takes global options\n        if ($iMaxCount < 0) {\n            $iMaxCount = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam(\"iMaxDownloadsCount\");\n        }\n\n        return $iMaxCount;\n    }\n\n    /**\n     * Returns max download count of file, if user is not registered\n     *\n     * @return int\n     */\n    public function getMaxUnregisteredDownloadsCount()\n    {\n        $iMaxCount = $this->oxfiles__oxmaxunregdownloads->value;\n        //if value is -1, takes global options\n        if ($iMaxCount < 0) {\n            $iMaxCount = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam(\"iMaxDownloadsCountUnregistered\");\n        }\n\n        return $iMaxCount;\n    }\n\n    /**\n     * Returns ordered file link expiration time in hours\n     *\n     * @return int\n     */\n    public function getLinkExpirationTime()\n    {\n        $iExpTime = $this->oxfiles__oxlinkexptime->value;\n        //if value is -1, takes global options\n        if ($iExpTime < 0) {\n            $iExpTime = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam(\"iLinkExpirationTime\");\n        }\n\n        return $iExpTime;\n    }\n\n    /**\n     * Returns download link expiration time in hours, after the first download\n     *\n     * @return int\n     */\n    public function getDownloadExpirationTime()\n    {\n        $iExpTime = $this->oxfiles__oxdownloadexptime->value;\n        //if value is -1, takes global options\n        if ($iExpTime < 0) {\n            $iExpTime = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam(\"iDownloadExpirationTime\");\n        }\n\n        return $iExpTime;\n    }\n\n    /**\n     * Returns file size in bytes\n     *\n     * @return int\n     */\n    public function getSize()\n    {\n        $iSize = 0;\n        if ($this->exist()) {\n            $iSize = filesize($this->getStoreLocation());\n        }\n\n        return $iSize;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/FileCollector.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse Exception;\n\n/**\n * Directory reader.\n * Performs reading of file list of one shop directory\n */\nclass FileCollector\n{\n    /**\n     * base directory\n     *\n     * @var string\n     */\n    protected $_sBaseDirectory;\n\n    /**\n     * array of collected files\n     *\n     * @var array\n     */\n    protected $_aFiles;\n\n    /**\n     * Setter for working directory\n     *\n     * @param string $sDir Directory\n     */\n    public function setBaseDirectory($sDir)\n    {\n        if (!empty($sDir)) {\n            $this->_sBaseDirectory = $sDir;\n        }\n    }\n\n    /**\n     * get collection files\n     *\n     * @return mixed\n     */\n    public function getFiles()\n    {\n        return $this->_aFiles;\n    }\n\n    /**\n     * Add one file to collection if it exists\n     *\n     * @param string $sFile file name to add to collection\n     *\n     * @throws Exception\n     * @return null\n     */\n    public function addFile($sFile)\n    {\n        if (empty($sFile)) {\n            throw new Exception('Parameter $sFile is empty!');\n        }\n\n        if (empty($this->_sBaseDirectory)) {\n            throw new Exception('Base directory is not set, please use setter setBaseDirectory!');\n        }\n\n        if (is_file($this->_sBaseDirectory . $sFile)) {\n            $this->_aFiles[] = $sFile;\n\n            return true;\n        }\n\n        return false;\n    }\n\n\n    /**\n     * browse all folders and sub-folders after files which have given extensions\n     *\n     * @param string  $sFolder     which is explored\n     * @param array   $aExtensions list of extensions to scan - if empty all files are taken\n     * @param boolean $blRecursive should directories be checked in recursive manner\n     *\n     * @throws exception\n     * @return null\n     */\n    public function addDirectoryFiles($sFolder, $aExtensions = [], $blRecursive = false)\n    {\n        if (empty($sFolder)) {\n            throw new Exception('Parameter $sFolder is empty!');\n        }\n\n        if (empty($this->_sBaseDirectory)) {\n            throw new Exception('Base directory is not set, please use setter setBaseDirectory!');\n        }\n\n        $aCurrentList = [];\n\n        if (!is_dir($this->_sBaseDirectory . $sFolder)) {\n            return;\n        }\n\n        $handle = opendir($this->_sBaseDirectory . $sFolder);\n\n        while ($sFile = readdir($handle)) {\n            if ($sFile != \".\" && $sFile != \"..\") {\n                if (is_dir($this->_sBaseDirectory . $sFolder . $sFile)) {\n                    if ($blRecursive) {\n                        $aResultList = $this->addDirectoryFiles($sFolder . $sFile . '/', $aExtensions, $blRecursive);\n\n                        if (is_array($aResultList)) {\n                            $aCurrentList = array_merge($aCurrentList, $aResultList);\n                        }\n                    }\n                } else {\n                    $sExt = substr(strrchr($sFile, '.'), 1);\n\n                    if (\n                        (!empty($aExtensions) && is_array($aExtensions) && in_array($sExt, $aExtensions)) ||\n                        (empty($aExtensions))\n                    ) {\n                        $this->addFile($sFolder . $sFile);\n                    }\n                }\n            }\n        }\n        closedir($handle);\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Groups.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\n\n/**\n * Group manager.\n * Base class for user groups. Does nothing special yet.\n */\nclass Groups extends \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel\n{\n    /**\n     * Name of current class\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxgroups';\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxgroups');\n    }\n\n    /**\n     * Deletes user group from database. Returns true/false, according to deleting status.\n     *\n     * @param string $sOXID Object ID (default null)\n     *\n     * @return bool\n     */\n    public function delete($sOXID = null)\n    {\n        if (!$sOXID) {\n            $sOXID = $this->getId();\n        }\n        if (!$sOXID) {\n            return false;\n        }\n\n        $parentResult = parent::delete($sOXID);\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        // deleting related data records\n        $sDelete = 'delete from oxobject2group where oxobject2group.oxgroupsid = :oxid';\n        $oDb->execute($sDelete, [\n            'oxid' => $sOXID\n        ]);\n\n        $sDelete = 'delete from oxobject2delivery where oxobject2delivery.oxobjectid = :oxid';\n        $oDb->execute($sDelete, [\n            'oxid' => $sOXID\n        ]);\n\n        $sDelete = 'delete from oxobject2discount where oxobject2discount.oxobjectid = :oxid';\n        $oDb->execute($sDelete, [\n            'oxid' => $sOXID\n        ]);\n\n        $sDelete = 'delete from oxobject2payment where oxobject2payment.oxobjectid = :oxid';\n        $oDb->execute($sDelete, [\n            'oxid' => $sOXID\n        ]);\n\n        return $parentResult;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Links.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxField;\n\n/**\n * Links manager.\n * Collects stored in DB links data (URL, description).\n */\nclass Links extends \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel\n{\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxlinks';\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxI18n()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxlinks');\n    }\n\n    /**\n     * Sets data field value\n     *\n     * @param string $sFieldName index OR name (eg. 'oxarticles__oxtitle') of a data field to set\n     * @param string $sValue     value of data field\n     * @param int    $iDataType  field type\n     *\n     * @return null\n     */\n    protected function setFieldData($sFieldName, $sValue, $iDataType = \\OxidEsales\\Eshop\\Core\\Field::T_TEXT)\n    {\n        if ('oxurldesc' === strtolower($sFieldName) || 'oxlinks__oxurldesc' === strtolower($sFieldName)) {\n            $iDataType = \\OxidEsales\\Eshop\\Core\\Field::T_RAW;\n        }\n\n        return parent::setFieldData($sFieldName, $sValue, $iDataType);\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/ListObject.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxField;\n\n/**\n * Simple list object\n */\nclass ListObject\n{\n    /**\n     * @var string\n     */\n    private $_sTableName = '';\n\n    /**\n     * Class constructor\n     *\n     * @param string $sTableName Table name\n     */\n    public function __construct($sTableName)\n    {\n        $this->_sTableName = $sTableName;\n    }\n\n    /**\n     * Assigns database record to object\n     *\n     * @param object $aData Database record\n     *\n     * @return null\n     */\n    public function assign($aData)\n    {\n        if (!is_array($aData)) {\n            return;\n        }\n        foreach ($aData as $sKey => $sValue) {\n            $sFieldName = strtolower($this->_sTableName . '__' . $sKey);\n            $this->$sFieldName = new \\OxidEsales\\Eshop\\Core\\Field($sValue);\n        }\n    }\n\n    /**\n     * Returns object id\n     *\n     * @return int\n     */\n    public function getId()\n    {\n        $sFieldName = strtolower($this->_sTableName . '__oxid');\n        return $this->$sFieldName->value;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Maintenance.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\n/**\n * Maintenance task handler. Maintenance tasks are called periodically, by cronTab (configure on your needs)\n */\nclass Maintenance\n{\n    /**\n     * Executes maintenance tasks. Currently calls oxArticleList::updateUpcomingPrices()\n     */\n    public function execute()\n    {\n        // updating upcoming prices\n        oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class)->updateUpcomingPrices(true);\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Manufacturer.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\Contract\\IUrl;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\UtilsFile;\nuse OxidEsales\\Eshop\\Core\\UtilsPic;\nuse Symfony\\Component\\Filesystem\\Path;\n\n/**\n * Manufacturer manager\n */\nclass Manufacturer extends MultiLanguageModel implements IUrl\n{\n    protected static $_aRootManufacturer = [];\n\n    /**\n     * @var string Name of current class\n     */\n    protected $_sClassName = 'oxmanufacturer';\n\n    /**\n     * Marker to load manufacturer article count info\n     *\n     * @var bool\n     */\n    protected $_blShowArticleCnt = false;\n\n    /**\n     * Manufacturer article count (default is -1, which means not calculated)\n     *\n     * @var int\n     */\n    protected $_iNrOfArticles = -1;\n\n    /**\n     * Marks that current object is managed by SEO\n     *\n     * @var bool\n     */\n    protected $_blIsSeoObject = true;\n\n    /**\n     * Visibility of a manufacturer\n     *\n     * @var int\n     */\n    protected $_blIsVisible;\n\n    /**\n     * has visible endors state of a category\n     *\n     * @var int\n     */\n    protected $_blHasVisibleSubCats;\n\n    /**\n     * Seo article urls for languages\n     *\n     * @var array\n     */\n    protected $_aSeoUrls = [];\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxI18n()).\n     */\n    public function __construct()\n    {\n        $this->setShowArticleCnt(Registry::getConfig()->getConfigParam('bl_perfShowActionCatArticleCnt'));\n        parent::__construct();\n        $this->init('oxmanufacturers');\n    }\n\n    /**\n     * Extra getter to guarantee compatibility with templates\n     *\n     * @param string $sName name of variable to return\n     *\n     * @return mixed\n     */\n    public function __get($sName)\n    {\n        switch ($sName) {\n            case 'oxurl':\n            case 'openlink':\n            case 'closelink':\n            case 'link':\n                $sValue = $this->getLink();\n                break;\n            case 'iArtCnt':\n                $sValue = $this->getNrOfArticles();\n                break;\n            case 'isVisible':\n                $sValue = $this->getIsVisible();\n                break;\n            case 'hasVisibleSubCats':\n                $sValue = $this->getHasVisibleSubCats();\n                break;\n            default:\n                $sValue = parent::__get($sName);\n                break;\n        }\n        return $sValue;\n    }\n\n    /**\n     * Marker to load manufacturer article count info setter\n     *\n     * @param bool $blShowArticleCount Marker to load manufacturer article count\n     */\n    public function setShowArticleCnt($blShowArticleCount = false)\n    {\n        $this->_blShowArticleCnt = $blShowArticleCount;\n    }\n\n    /**\n     * Assigns to $this object some base parameters/values.\n     *\n     * @param array $dbRecord parameters/values\n     */\n    public function assign($dbRecord)\n    {\n        parent::assign($dbRecord);\n\n        // manufacturer article count is stored in cache\n        if ($this->_blShowArticleCnt && !$this->isAdmin()) {\n            $this->_iNrOfArticles = Registry::getUtilsCount()->getManufacturerArticleCount($this->getId());\n        }\n\n        $this->oxmanufacturers__oxnrofarticles = new Field($this->_iNrOfArticles, Field::T_RAW);\n    }\n\n    /**\n     * Loads object data from DB (object data ID is passed to method). Returns\n     * true on success.\n     *\n     * @param string $sOxid object id\n     *\n     * @return bool\n     */\n    public function load($sOxid)\n    {\n        if ($sOxid == 'root') {\n            return $this->setRootObjectData();\n        }\n\n        return parent::load($sOxid);\n    }\n\n    /**\n     * Sets root manufacturer data. Returns true\n     *\n     * @return bool\n     */\n    protected function setRootObjectData()\n    {\n        $this->setId('root');\n        $this->oxmanufacturers__oxtitle = new Field(\n            Registry::getLang()->translateString('BY_MANUFACTURER', $this->getLanguage(), false),\n            Field::T_RAW\n        );\n        $this->oxmanufacturers__oxshortdesc = new Field('', Field::T_RAW);\n        $this->oxmanufacturers__oxicon = new Field('', Field::T_RAW);\n\n        return true;\n    }\n\n    /**\n     * Returns raw manufacturer seo url\n     *\n     * @param int $iLang language id\n     * @param int $iPage page number [optional]\n     *\n     * @return string\n     */\n    public function getBaseSeoLink($iLang, $iPage = 0)\n    {\n        $oEncoder = Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderManufacturer::class);\n        if (!$iPage) {\n            return $oEncoder->getManufacturerUrl($this, $iLang);\n        }\n\n        return $oEncoder->getManufacturerPageUrl($this, $iPage, $iLang);\n    }\n\n    /**\n     * Returns manufacturer link Url\n     *\n     * @param int $iLang language id [optional]\n     *\n     * @return string\n     */\n    public function getLink($iLang = null)\n    {\n        if (!Registry::getUtils()->seoIsActive()) {\n            return $this->getStdLink($iLang);\n        }\n\n        if ($iLang === null) {\n            $iLang = $this->getLanguage();\n        }\n\n        if (!isset($this->_aSeoUrls[$iLang])) {\n            $this->_aSeoUrls[$iLang] = $this->getBaseSeoLink($iLang);\n        }\n\n        return $this->_aSeoUrls[$iLang];\n    }\n\n    /**\n     * Returns base dynamic url: shopurl/index.php?cl=details\n     *\n     * @param int  $iLang   language id\n     * @param bool $blAddId add current object id to url or not\n     * @param bool $blFull  return full including domain name [optional]\n     *\n     * @return string\n     */\n    public function getBaseStdLink($iLang, $blAddId = true, $blFull = true)\n    {\n        $sUrl = '';\n        if ($blFull) {\n            //always returns shop url, not admin\n            $sUrl = Registry::getConfig()->getShopUrl($iLang, false);\n        }\n\n        return $sUrl . \"index.php?cl=manufacturerlist\" . ($blAddId ? \"&amp;mnid=\" . $this->getId() : \"\");\n    }\n\n    /**\n     * Returns standard URL to manufacturer\n     *\n     * @param int   $iLang   language\n     * @param array $aParams additional params to use [optional]\n     *\n     * @return string\n     */\n    public function getStdLink($iLang = null, $aParams = [])\n    {\n        if ($iLang === null) {\n            $iLang = $this->getLanguage();\n        }\n\n        return Registry::getUtilsUrl()->processUrl($this->getBaseStdLink($iLang), true, $aParams, $iLang);\n    }\n\n    /**\n     * returns number or articles of this manufacturer\n     *\n     * @return integer\n     */\n    public function getNrOfArticles()\n    {\n        if (!$this->_blShowArticleCnt || $this->isAdmin()) {\n            return -1;\n        }\n\n        return $this->_iNrOfArticles;\n    }\n\n    /**\n     * returns the sub category array\n     */\n    public function getSubCats()\n    {\n    }\n\n    /**\n     * returns the visibility of a manufacturer\n     *\n     * @return bool\n     */\n    public function getIsVisible()\n    {\n        return $this->_blIsVisible;\n    }\n\n    /**\n     * sets the visibilty of a category\n     *\n     * @param bool $blVisible manufacturers visibility status setter\n     */\n    public function setIsVisible($blVisible)\n    {\n        $this->_blIsVisible = $blVisible;\n    }\n\n    /**\n     * returns if a manufacturer has visible sub categories\n     *\n     * @return bool\n     */\n    public function getHasVisibleSubCats()\n    {\n        if (!isset($this->_blHasVisibleSubCats)) {\n            $this->_blHasVisibleSubCats = false;\n        }\n\n        return $this->_blHasVisibleSubCats;\n    }\n\n    /**\n     * sets the state of has visible sub manufacturers\n     *\n     * @param bool $blHasVisibleSubcats marker if manufacturer has visible subcategories\n     */\n    public function setHasVisibleSubCats($blHasVisibleSubcats)\n    {\n        $this->_blHasVisibleSubCats = $blHasVisibleSubcats;\n    }\n\n    /**\n     * Empty method, called in templates when manufacturer is used in same code like category\n     */\n    public function getContentCats()\n    {\n    }\n\n    /**\n     * Delete this object from the database, returns true on success.\n     *\n     * @param string $oxid Object ID(default null)\n     *\n     * @return bool\n     */\n    public function delete($oxid = null)\n    {\n        if (!$oxid) {\n            $oxid = $this->getId();\n        }\n\n        $this->load($oxid);\n\n        if (parent::delete($oxid)) {\n            Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderManufacturer::class)\n                ->onDeleteManufacturer($this);\n\n            $this->deletePicture($this->oxmanufacturers__oxicon->value, 'MICO', 'oxicon');\n            $this->deletePicture($this->oxmanufacturers__oxicon_alt->value, 'MICO', 'oxicon_alt');\n            $this->deletePicture($this->oxmanufacturers__oxpicture->value, 'MPIC', 'oxpicture');\n            $this->deletePicture($this->oxmanufacturers__oxthumbnail->value, 'MTHU', 'oxthumbnail');\n            $this->deletePicture($this->oxmanufacturers__oxpromotion_icon->value, 'MPICO', 'oxpromotion_icon');\n\n            return true;\n        }\n\n        return false;\n    }\n\n    public function getImageType(string $fieldName): ?string\n    {\n        return match ($fieldName) {\n            'oxicon', 'oxicon_alt' => 'MICO',\n            'oxpicture' => 'MPIC',\n            'oxthumbnail' => 'MTHU',\n            'oxpromotion_icon' => 'MPICO',\n            default => null,\n        };\n    }\n\n    public function getIconUrl(): string\n    {\n        $imageName = $this->oxmanufacturers__oxicon->value;\n\n        return $this->getImageUrl($imageName, 'sManufacturerIconsize', 'icon') ?? '';\n    }\n\n    public function getIconAltUrl(): string\n    {\n        $imageName = $this->oxmanufacturers__oxicon_alt->value;\n\n        return $this->getImageUrl($imageName, 'sManufacturerIconsize', 'icon') ?? '';\n    }\n\n    public function getPictureUrl(): string\n    {\n        $imageName = $this->oxmanufacturers__oxpicture->value;\n\n        return $this->getImageUrl($imageName, 'sManufacturerPicturesize', 'picture') ?? '';\n    }\n\n    public function getThumbnailUrl(): string\n    {\n        $imageName = $this->oxmanufacturers__oxthumbnail->value;\n\n        return $this->getImageUrl($imageName, 'sManufacturerThumbnailsize', 'thumb') ?? '';\n    }\n\n    public function getPromotionIconUrl(): string\n    {\n        $imageName = $this->oxmanufacturers__oxpromotion_icon->value;\n\n        return $this->getImageUrl($imageName, 'sManufacturerPromotionsize', 'promo_icon') ?? '';\n    }\n\n    /**\n     * Returns false, because manufacturer has not thumbnail\n     * @return false\n     * @deprecated since v7.0.0 (2023-03-16). Please use Manufacturer::getThumbnailUrl() instead.\n     */\n    public function getThumbUrl()\n    {\n        return false;\n    }\n\n    /**\n     * Returns manufacturer title\n     *\n     * @return string\n     */\n    public function getTitle()\n    {\n        return $this->oxmanufacturers__oxtitle->value;\n    }\n\n    /**\n     * Returns short description\n     *\n     * @return string\n     */\n    public function getShortDescription()\n    {\n        return $this->oxmanufacturers__oxshortdesc->value;\n    }\n\n    public function deletePicture(string $pictureName, string $pictureType, string $pictureFieldName): void\n    {\n        /** @var UtilsPic $utilsPic */\n        $utilsPic = Registry::getUtilsPic();\n        /** @var UtilsFile $utilsFile */\n        $utilsFile = Registry::getUtilsFile();\n        $pictureDirectory = Registry::getConfig()->getPictureDir(false);\n\n        $utilsPic->safePictureDelete(\n            $pictureName,\n            Path::join($pictureDirectory, $utilsFile->getImageDirByType($pictureType)),\n            'oxmanufacturers',\n            $pictureFieldName\n        );\n    }\n\n    private function getImageUrl(mixed $imageName, string $paramName, string $directoryName): string\n    {\n        $config = Registry::getConfig();\n        if (empty($size = $config->getConfigParam($paramName))) {\n            $size = $config->getConfigParam('sIconsize');\n        }\n        $path = 'manufacturer' . DIRECTORY_SEPARATOR . $directoryName . DIRECTORY_SEPARATOR;\n\n        if ($url = Registry::getPictureHandler()->getPicUrl($path, $imageName, $size)) {\n            return $url;\n        }\n\n        return '';\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/ManufacturerList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxRegistry;\nuse oxField;\n\n/**\n * Manufacturer list manager.\n * Collects list of manufacturers according to collection rules (activ, etc.).\n */\nclass ManufacturerList extends \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n{\n    /**\n     * Manufacturer root.\n     *\n     * @var \\stdClass\n     */\n    protected $_oRoot = null;\n\n    /**\n     * Manufacturer tree path.\n     *\n     * @var array\n     */\n    protected $_aPath = [];\n\n    /**\n     * To show manufacturer article count or not\n     *\n     * @var bool\n     */\n    protected $_blShowManufacturerArticleCnt = false;\n\n    /**\n     * Active manufacturer object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Manufacturer\n     */\n    protected $_oClickedManufacturer = null;\n\n    /**\n     * Calls parent constructor and defines if Article vendor count is shown\n     */\n    public function __construct()\n    {\n        $this->setShowManufacturerArticleCnt(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('bl_perfShowActionCatArticleCnt'));\n        parent::__construct('oxmanufacturer');\n    }\n\n    /**\n     * Enables/disables manufacturer article count calculation\n     *\n     * @param bool $blShowManufacturerArticleCnt to show article count or not\n     */\n    public function setShowManufacturerArticleCnt($blShowManufacturerArticleCnt = false)\n    {\n        $this->_blShowManufacturerArticleCnt = $blShowManufacturerArticleCnt;\n    }\n\n    /**\n     * Loads simple manufacturer list\n     */\n    public function loadManufacturerList()\n    {\n        $oBaseObject = $this->getBaseObject();\n\n        $sFieldList = $oBaseObject->getSelectFields();\n        $sViewName = $oBaseObject->getViewName();\n        $this->getBaseObject()->setShowArticleCnt($this->_blShowManufacturerArticleCnt);\n\n        $sWhere = '';\n        if (!$this->isAdmin()) {\n            $sWhere = $oBaseObject->getSqlActiveSnippet();\n            $sWhere = $sWhere ? \" where $sWhere and \" : ' where ';\n            $sWhere .= \"{$sViewName}.oxtitle != '' \";\n        }\n\n        $sSelect = \"select {$sFieldList} from {$sViewName} {$sWhere} order by {$sViewName}.oxtitle\";\n        $this->selectString($sSelect);\n    }\n\n    /**\n     * Creates fake root for manufacturer tree, and ads category list fileds for each manufacturer item\n     *\n     * @param string $sLinkTarget  Name of class, responsible for category rendering\n     * @param string $sActCat      Active category\n     * @param string $sShopHomeUrl base shop url ($myConfig->getShopHomeUrl())\n     */\n    public function buildManufacturerTree($sLinkTarget, $sActCat, $sShopHomeUrl)\n    {\n        //Load manufacturer list\n        $this->loadManufacturerList();\n\n\n        //Create fake manufacturer root category\n        $this->_oRoot = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Manufacturer::class);\n        $this->_oRoot->load(\"root\");\n\n        //category fields\n        $this->addCategoryFields($this->_oRoot);\n        $this->_aPath[] = $this->_oRoot;\n\n        foreach ($this as $sVndId => $oManufacturer) {\n            // storing active manufacturer object\n            if ((string)$sVndId === $sActCat) {\n                $this->setClickManufacturer($oManufacturer);\n            }\n\n            $this->addCategoryFields($oManufacturer);\n            if ($sActCat == $oManufacturer->oxmanufacturers__oxid->value) {\n                $this->_aPath[] = $oManufacturer;\n            }\n        }\n\n        $this->seoSetManufacturerData();\n    }\n\n    /**\n     * Root manufacturer list node (which usually is a manually prefilled object) getter\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Manufacturer\n     */\n    public function getRootCat()\n    {\n        return $this->_oRoot;\n    }\n\n    /**\n     * Returns manufacturer path array\n     *\n     * @return array\n     */\n    public function getPath()\n    {\n        return $this->_aPath;\n    }\n\n    /**\n     * Adds category specific fields to manufacturer object\n     *\n     * @param object $oManufacturer manufacturer object\n     */\n    protected function addCategoryFields($oManufacturer)\n    {\n        $oManufacturer->oxcategories__oxid = new \\OxidEsales\\Eshop\\Core\\Field($oManufacturer->oxmanufacturers__oxid->value);\n        $oManufacturer->oxcategories__oxicon = $oManufacturer->oxmanufacturers__oxicon;\n        $oManufacturer->oxcategories__oxtitle = $oManufacturer->oxmanufacturers__oxtitle;\n        $oManufacturer->oxcategories__oxdesc = $oManufacturer->oxmanufacturers__oxshortdesc;\n\n        $oManufacturer->setIsVisible(true);\n        $oManufacturer->setHasVisibleSubCats(false);\n    }\n\n    /**\n     * Sets active (open) manufacturer object\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Manufacturer $oManufacturer active manufacturer\n     */\n    public function setClickManufacturer($oManufacturer)\n    {\n        $this->_oClickedManufacturer = $oManufacturer;\n    }\n\n    /**\n     * returns active (open) manufacturer object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Manufacturer\n     */\n    public function getClickManufacturer()\n    {\n        return $this->_oClickedManufacturer;\n    }\n\n    /**\n     * Processes manufacturer category URLs\n     */\n    protected function seoSetManufacturerData()\n    {\n        // only when SEO id on and in front end\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getUtils()->seoIsActive() && !$this->isAdmin()) {\n            $oEncoder = \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderManufacturer::class);\n\n            // preparing root manufacturer category\n            if ($this->_oRoot) {\n                $oEncoder->getManufacturerUrl($this->_oRoot);\n            }\n\n            // encoding manufacturer category\n            foreach ($this as $sVndId => $value) {\n                $oEncoder->getManufacturerUrl($this->_aArray[$sVndId]);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/MdVariant.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxRegistry;\n\n/**\n * Defines an element of multidimentional variant name tree structure. Contains article id, variant name, URL, price, price text, and a subset of MD variants.\n */\nclass MdVariant extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * MD variant identifier\n     *\n     * @var string\n     */\n    protected $_sId;\n\n    /**\n     * Parent ID\n     *\n     * @var string\n     */\n    protected $_sParentId;\n\n    /**\n     * Corresponding article id\n     *\n     * @var string\n     */\n    protected $_sArticleId;\n\n    /**\n     * Variant name\n     *\n     * @var string\n     */\n    protected $_sName;\n\n    /**\n     * Variant URL\n     *\n     * @var string\n     */\n    protected $_sUrl;\n\n    /**\n     * Variant price\n     *\n     * @var double\n     */\n    protected $_dPrice = null;\n\n    /**\n     * Variant Price text represenatation. Eg. \"10,00 EUR\" or \"from 8,00 EUR\"\n     *\n     * @var string\n     */\n    protected $_sFPrice;\n\n    /**\n     * Subvariant array\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\MdVariant[]\n     */\n    protected $_aSubvariants = [];\n\n    /**\n     * Sets MD variant identifier\n     *\n     * @param string $sId New id\n     */\n    public function setId($sId)\n    {\n        $this->_sId = $sId;\n    }\n\n    /**\n     * Returns MD variant identifier\n     *\n     * @return string\n     */\n    public function getId()\n    {\n        return $this->_sId;\n    }\n\n    /**\n     * Sets parent id\n     *\n     * @param string $sParentId Parent id\n     */\n    public function setParentId($sParentId)\n    {\n        $this->_sParentId = $sParentId;\n    }\n\n    /**\n     * Returns parent id\n     *\n     * @return string\n     */\n    public function getParentId()\n    {\n        return $this->_sParentId;\n    }\n\n    /**\n     * Sets MD subvariants\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\MdVariant[] $aSubvariants Subvariants\n     */\n    public function setMdSubvariants($aSubvariants)\n    {\n        $this->_aSubvariants = $aSubvariants;\n    }\n\n    /**\n     * Returns full array of subvariants\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\MdVariant[]\n     */\n    public function getMdSubvariants()\n    {\n        return $this->_aSubvariants;\n    }\n\n    /**\n     * Returns first MD subvariant from subvariant set or null in case variant has no subvariants.\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\MdVariant\n     */\n    public function getFirstMdSubvariant()\n    {\n        $aMdSubvariants = $this->getMdSubvariants();\n        if (count($aMdSubvariants)) {\n            return reset($aMdSubvariants);\n        }\n\n        return null;\n    }\n\n    /**\n     * Checks for existing MD subvariant by name. Returns existing one or in case $sName has not been found creates an empty OxMdVariant instance.\n     *\n     * @param string $sName Subvariant name\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\MdVariant\n     */\n    public function getMdSubvariantByName($sName)\n    {\n        $aSubvariants = $this->getMdSubvariants();\n        foreach ($aSubvariants as $oMdSubvariant) {\n            if (strcasecmp($oMdSubvariant->getName(), $sName) == 0) {\n                return $oMdSubvariant;\n            }\n        }\n\n        $oNewSubvariant = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\MdVariant::class);\n        $oNewSubvariant->setName($sName);\n        $oNewSubvariant->setId(md5($sName . $this->getId()));\n        $oNewSubvariant->setParentId($this->getId());\n        $this->addMdSubvariant($oNewSubvariant);\n\n        return $oNewSubvariant;\n    }\n\n    /**\n     * Returns corresponding article URL or recusively first variant URL from subvariant set\n     *\n     * @return string\n     */\n    public function getLink()\n    {\n        $oFirstSubvariant = $this->getFirstMdSubvariant();\n        if ($oFirstSubvariant) {\n            return $oFirstSubvariant->getLink();\n        }\n\n        return $this->_sUrl;\n    }\n\n    /**\n     * Name setter\n     *\n     * @param string $sName New name\n     */\n    public function setName($sName)\n    {\n        $this->_sName = $sName;\n    }\n\n    /**\n     * Returns MD variant name\n     *\n     * @return string\n     */\n    public function getName()\n    {\n        return $this->_sName;\n    }\n\n    /**\n     * Returns price\n     *\n     * @return double\n     */\n    public function getDPrice()\n    {\n        return $this->_dPrice;\n    }\n\n    /**\n     * Returns min price recursively selected from full subvariant tree.\n     *\n     * @return double\n     */\n    public function getMinDPrice()\n    {\n        $dMinPrice = $this->getDPrice();\n        $aVariants = $this->getMdSubvariants();\n        foreach ($aVariants as $oVariant) {\n            $dMinVariantPrice = $oVariant->getMinDPrice();\n            if (is_null($dMinPrice)) {\n                $dMinPrice = $dMinVariantPrice;\n            }\n            if (!is_null($dMinVariantPrice) && $dMinVariantPrice < $dMinPrice) {\n                $dMinPrice = $dMinVariantPrice;\n            }\n        }\n\n        return $dMinPrice;\n    }\n\n    /**\n     * Gets max subvariant depth. 0 means no deeper subvariants.\n     *\n     * @return int\n     */\n    public function getMaxDepth()\n    {\n        $aSubvariants = $this->getMdSubvariants();\n\n        if (!count($aSubvariants)) {\n            return 0;\n        }\n\n        $iMaxDepth = 0;\n        foreach ($aSubvariants as $oSubvariant) {\n            if ($oSubvariant->getMaxDepth() > $iMaxDepth) {\n                $iMaxDepth = $oSubvariant->getMaxDepth();\n            }\n        }\n\n        return $iMaxDepth + 1;\n    }\n\n    /**\n     * Returns MD variant price as a text.\n     *\n     * @return string\n     */\n    public function getFPrice()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        // 0002030 No need to return price if it disabled for better performance.\n        if (!$myConfig->getConfigParam('bl_perfLoadPrice')) {\n            return;\n        }\n\n        if ($this->_sFPrice) {\n            return $this->_sFPrice;\n        }\n\n        $sFromPrefix = '';\n\n        if (!$this->isFixedPrice()) {\n            $sFromPrefix = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('PRICE_FROM') . ' ';\n        }\n\n        $dMinPrice = $this->getMinDPrice();\n        $sFMinPrice = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->formatCurrency($dMinPrice);\n        $sCurrency = ' ' . \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActShopCurrencyObject()->sign;\n        $this->_sFPrice = $sFromPrefix . $sFMinPrice . $sCurrency;\n\n        return $this->_sFPrice;\n    }\n\n    /**\n     * Inits MD variant by name. In case $aNames parameter has more than one element addNames recursively adds names for subvariants.\n     *\n     * @param string $sArtId Article ID\n     * @param array  $aNames Expected array of $sKey=>$sName pairs.\n     * @param double $dPrice Price as double\n     * @param string $sUrl   Article URL\n     */\n    public function addNames($sArtId, $aNames, $dPrice, $sUrl)\n    {\n        $iCount = count($aNames);\n        $sName = array_shift($aNames);\n\n        if ($iCount) {\n            //get required subvariant\n            $oVariant = $this->getMdSubvariantByName($sName);\n            //add remaining names\n            $oVariant->addNames($sArtId, $aNames, $dPrice, $sUrl);\n        } else {\n            //means we have the deepest element and assign other attributes\n            $this->_sArticleId = $sArtId;\n            $this->_dPrice = $dPrice;\n            $this->_sUrl = $sUrl;\n        }\n    }\n\n    /**\n     * Returns corresponding article id or recusively first variant id from subvariant set\n     *\n     * @return string\n     */\n    public function getArticleId()\n    {\n        $oFirstSubvariant = $this->getFirstMdSubvariant();\n\n        if ($oFirstSubvariant) {\n            return $oFirstSubvariant->getArticleId();\n        }\n\n        return $this->_sArticleId;\n    }\n\n    /**\n     * Checks whether $sArtId is one of subtree article ids.\n     *\n     * @param string $sArtId Article ID\n     *\n     * @return bool\n     */\n    public function hasArticleId($sArtId)\n    {\n        if ($this->getArticleId() == $sArtId) {\n            return true;\n        }\n\n        $aSubvariants = $this->getMdSubvariants();\n        foreach ($aSubvariants as $oSubvariant) {\n            if ($oSubvariant->hasArticleId($sArtId)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Adds one subvariant to subvariant set\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\MdVariant $oSubvariant Subvariant\n     */\n    protected function addMdSubvariant($oSubvariant)\n    {\n        $this->_aSubvariants[$oSubvariant->getId()] = $oSubvariant;\n    }\n\n    /**\n     * Checks if variant price is fixed or not (\"from\" price)\n     *\n     * @return bool\n     */\n    protected function isFixedPrice()\n    {\n        $dPrice = $this->getDPrice();\n        $aVariants = $this->getMdSubvariants();\n        foreach ($aVariants as $oVariant) {\n            $dVariantPrice = $oVariant->getDPrice();\n            if (is_null($dPrice)) {\n                $dPrice = $dVariantPrice;\n            }\n            if (!is_null($dVariantPrice) && $dVariantPrice != $dPrice) {\n                return false;\n            }\n            if (!$oVariant->isFixedPrice()) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/MediaUrl.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass MediaUrl extends \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel\n{\n    protected $_sClassName = 'oxmediaurls';\n\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxmediaurls');\n    }\n\n    /**\n     * Return HTML code depending on current URL\n     *\n     * @return string\n     */\n    public function getHtml()\n    {\n        $sUrl = $this->oxmediaurls__oxurl->value;\n        //youtube link\n        if (strpos($sUrl, 'youtube.com') || strpos($sUrl, 'youtu.be')) {\n            return $this->getYoutubeHtml();\n        }\n\n        //simple link\n        return $this->getHtmlLink();\n    }\n\n    /**\n     * Returns simple HTML link\n     *\n     * @param bool $blNewPage Whether to open link in new window (adds target=_blank to link)\n     *\n     * @return string\n     */\n    public function getHtmlLink($blNewPage = true)\n    {\n        $sForceBlank = $blNewPage ? ' target=\"_blank\"' : '';\n        $sDesc = $this->oxmediaurls__oxdesc->value;\n        $sUrl = $this->getLink();\n\n        $sHtmlLink = \"<a href=\\\"$sUrl\\\"{$sForceBlank}>$sDesc</a>\";\n\n        return $sHtmlLink;\n    }\n\n    public function getLink()\n    {\n        if ($this->oxmediaurls__oxisuploaded->value) {\n            $url = Registry::getConfig()->getShopUrl() . 'out/media/' . basename($this->oxmediaurls__oxurl->value);\n        } else {\n            $url = $this->oxmediaurls__oxurl->value;\n        }\n\n        return $url;\n    }\n\n    /**\n     * Returns  object id\n     *\n     * @return string\n     */\n    public function getObjectId()\n    {\n        return $this->oxmediaurls__oxobjectid->value;\n    }\n\n    /**\n     * Deletes record and unlinks the file\n     *\n     * @param string $sOXID Object ID(default null)\n     *\n     * @return bool\n     */\n    public function delete($sOXID = null)\n    {\n        $sFilePath = Path::join(\n            ContainerFacade::getParameter('oxid_esales.shop_source_directory'),\n            'out',\n            'media',\n            basename($this->oxmediaurls__oxurl->value)\n        );\n\n        if ($this->oxmediaurls__oxisuploaded->value) {\n            if (file_exists($sFilePath)) {\n                unlink($sFilePath);\n            }\n        }\n\n        return parent::delete($sOXID);\n    }\n\n    /**\n     * @return string\n     */\n    protected function getYoutubeHtml()\n    {\n        $url = $this->oxmediaurls__oxurl->value;\n        $youTubeUrl = '';\n\n        if (strpos($url, 'youtube.com')) {\n            $youTubeUrl = str_replace('www.youtube.com/watch?v=', 'www.youtube.com/embed/', $url);\n            $youTubeUrl = preg_replace(\"/&/\", '?', $youTubeUrl, 1);\n        }\n        if (strpos($url, 'youtu.be')) {\n            $youTubeUrl = str_replace('youtu.be/', 'www.youtube.com/embed/', $url);\n        }\n\n        return sprintf(\n            '%s<br><iframe width=\"425\" height=\"344\" src=\"%s\" frameborder=\"0\" allowfullscreen></iframe>',\n            $this->oxmediaurls__oxdesc->value,\n            $youTubeUrl\n        );\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/NewsSubscribed.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\nuse oxField;\n\n/**\n * Newsletter Subscriptions manager\n * Performs user managing function\n * information, deletion and other.\n */\nclass NewsSubscribed extends \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n{\n    /**\n     * Subscription marker\n     *\n     * @var bool\n     */\n    protected $_blWasSubscribed = false;\n\n    /**\n     * Subscription marker. Marks that newsletter was subscribed but wasn't confirmed.\n     *\n     * @var bool\n     */\n    protected $_blWasPreSubscribed = false;\n\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxnewssubscribed';\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n\n        $this->init('oxnewssubscribed');\n    }\n\n    /**\n     * Loads object (newssubscription) details from DB. Returns true on success.\n     *\n     * @param string $oxId oxnewssubscribed ID\n     *\n     * @return bool\n     */\n    public function load($oxId)\n    {\n        $blRet = parent::load($oxId);\n\n        if ($this->getFieldData('oxnewssubscribed__oxdboptin') == 1) {\n            $this->_blWasSubscribed = true;\n        } elseif ($this->getFieldData('oxnewssubscribed__oxdboptin') == 2) {\n            $this->_blWasPreSubscribed = true;\n        }\n\n        return $blRet;\n    }\n\n    /**\n     * Loader which loads news subscription according to subscribers email address\n     *\n     * @param string $sEmailAddress subscribers email address\n     *\n     * @return bool\n     */\n    public function loadFromEmail($sEmailAddress)\n    {\n        $userOxid = $this->getSubscribedUserIdByEmail($sEmailAddress);\n        return $this->load($userOxid);\n    }\n\n    /**\n     * Get subscribed user id by email.\n     *\n     * @param string $email\n     *\n     * @return string\n     */\n    protected function getSubscribedUserIdByEmail($email)\n    {\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $params = [\n            'oxemail' => (string) $email\n        ];\n\n        $userOxid = $database->getOne(\"select oxid from oxnewssubscribed \n            where oxemail = :oxemail \", $params);\n\n        return $userOxid;\n    }\n\n    /**\n     * Loader which loads news subscription according to subscribers oxid\n     *\n     * @param string $sOxUserId subscribers oxid\n     *\n     * @return bool\n     */\n    public function loadFromUserId($sOxUserId)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $params = [\n            'oxuserid' => $sOxUserId,\n            'oxshopid' => \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId()\n        ];\n\n        $sOxId = $oDb->getOne(\"select oxid from oxnewssubscribed \n            where oxuserid = :oxuserid and oxshopid = :oxshopid\", $params);\n\n        return $this->load($sOxId);\n    }\n\n    /**\n     * Inserts nbews object data to DB. Returns true on success.\n     *\n     * @return mixed oxid on success or false on failure\n     */\n    protected function insert()\n    {\n        // set subscription date\n        $this->oxnewssubscribed__oxsubscribed = new \\OxidEsales\\Eshop\\Core\\Field(date('Y-m-d H:i:s'), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n\n        return parent::insert();\n    }\n\n    /**\n     * We need to check if we unsubscribe here\n     *\n     * @return mixed oxid on success or false on failure\n     */\n    protected function update()\n    {\n        if (($this->_blWasSubscribed || $this->_blWasPreSubscribed) && !$this->oxnewssubscribed__oxdboptin->value) {\n            // set unsubscription date\n            $this->oxnewssubscribed__oxunsubscribed->setValue(date('Y-m-d H:i:s'));\n            // 0001974 Same object can be called many times without requiring to renew date.\n            // If so happens, it would have _aSkipSaveFields set to skip date field. So need to check and\n            // release if _aSkipSaveFields are set for field oxunsubscribed.\n            $aSkipSaveFieldsKeys = array_keys($this->_aSkipSaveFields, 'oxunsubscribed');\n            foreach ($aSkipSaveFieldsKeys as $iSkipSaveFieldKey) {\n                unset($this->_aSkipSaveFields[$iSkipSaveFieldKey]);\n            }\n        } else {\n            // don't update date\n            $this->_aSkipSaveFields[] = 'oxunsubscribed';\n        }\n\n        return parent::update();\n    }\n\n    /**\n     * Newsletter subscription status getter\n     *\n     * @return int\n     */\n    public function getOptInStatus()\n    {\n        return (int) $this->getFieldData('oxdboptin');\n    }\n\n    /**\n     * Newsletter subscription status setter\n     *\n     * @param int $iStatus subscription status\n     */\n    public function setOptInStatus($iStatus)\n    {\n        $this->oxnewssubscribed__oxdboptin = new \\OxidEsales\\Eshop\\Core\\Field($iStatus, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        $this->save();\n    }\n\n    /**\n     * Newsletter subscription email sending status getter\n     *\n     * @return int\n     */\n    public function getOptInEmailStatus()\n    {\n        return $this->oxnewssubscribed__oxemailfailed->value;\n    }\n\n    /**\n     * Newsletter subscription email sending status setter\n     *\n     * @param int $iStatus subscription status\n     */\n    public function setOptInEmailStatus($iStatus)\n    {\n        $this->oxnewssubscribed__oxemailfailed = new \\OxidEsales\\Eshop\\Core\\Field($iStatus, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        $this->save();\n    }\n\n    /**\n     * Check if was ever unsubscribed by unsubscribed field.\n     *\n     * @return bool\n     */\n    public function wasUnsubscribed()\n    {\n        if ('0000-00-00 00:00:00' != $this->oxnewssubscribed__oxunsubscribed->value) {\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * This method is called from \\OxidEsales\\Eshop\\Application\\Model\\User::update. Currently it updates user\n     * information kept in db\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser subscription user object\n     *\n     * @return bool\n     */\n    public function updateSubscription($oUser)\n    {\n        // user email changed ?\n        if ($oUser->oxuser__oxusername->value && $this->oxnewssubscribed__oxemail->value != $oUser->oxuser__oxusername->value) {\n            $this->oxnewssubscribed__oxemail = new \\OxidEsales\\Eshop\\Core\\Field($oUser->oxuser__oxusername->value, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        }\n\n        // updating some other fields\n        $this->oxnewssubscribed__oxsal = new \\OxidEsales\\Eshop\\Core\\Field($oUser->getFieldData('oxsal'), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        $this->oxnewssubscribed__oxfname = new \\OxidEsales\\Eshop\\Core\\Field($oUser->getFieldData('oxfname'), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        $this->oxnewssubscribed__oxlname = new \\OxidEsales\\Eshop\\Core\\Field($oUser->getFieldData('oxlname'), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n\n        return (bool) $this->save();\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Object2Category.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxField;\n\n/**\n * Manages product assignment to category.\n */\nclass Object2Category extends \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n{\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxobject2category';\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()) and sets table name.\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxobject2category');\n    }\n\n    /**\n     * Returns assigned product id\n     *\n     * @return string\n     */\n    public function getProductId()\n    {\n        return $this->oxobject2category__oxobjectid->value;\n    }\n\n    /**\n     * Sets assigned product id\n     *\n     * @param string $sId assigned product id\n     */\n    public function setProductId($sId)\n    {\n        $this->oxobject2category__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($sId);\n    }\n\n    /**\n     * Returns assigned category id\n     *\n     * @return string\n     */\n    public function getCategoryId()\n    {\n        return $this->oxobject2category__oxcatnid->value;\n    }\n\n    /**\n     * Sets assigned category id\n     *\n     * @param string $sId assigned category id\n     */\n    public function setCategoryId($sId)\n    {\n        $this->oxobject2category__oxcatnid = new \\OxidEsales\\Eshop\\Core\\Field($sId);\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Object2Group.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxField;\nuse OxidEsales\\Eshop\\Core\\Exception\\DatabaseErrorException;\nuse OxidEsales\\Eshop\\Core\\Database\\Adapter\\Doctrine\\Database;\n\n/**\n * Manages object (users, discounts, deliveries...) assignment to groups.\n */\nclass Object2Group extends \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n{\n    /** @var boolean Load the relation even if from other shop */\n    protected $_blDisableShopCheck = true;\n\n    /** @var string Current class name */\n    protected $_sClassName = 'oxobject2group';\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxobject2group');\n        $this->oxobject2group__oxshopid = new \\OxidEsales\\Eshop\\Core\\Field(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId(), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n    }\n\n    /**\n     * Extends the default save method\n     * to prevent from exception if same relationship already exist.\n     * The table oxobject2group has an UNIQUE index on (OXGROUPSID, OXOBJECTID, OXSHOPID)\n     * which ensures that a relationship would not be duplicated.\n     *\n     * @throws DatabaseErrorException\n     *\n     * @return bool\n     */\n    public function save()\n    {\n        try {\n            return parent::save();\n        } catch (\\OxidEsales\\Eshop\\Core\\Exception\\DatabaseErrorException $exception) {\n            if ($exception->getCode() !== \\OxidEsales\\Eshop\\Core\\Database\\Adapter\\Doctrine\\Database::DUPLICATE_KEY_ERROR_CODE) {\n                throw $exception;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Order.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse Exception;\nuse OxidEsales\\Eshop\\Core\\Counter;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Price as ShopPrice;\nuse OxidEsales\\Eshop\\Application\\Model\\Payment as EshopPayment;\nuse OxidEsales\\Eshop\\Application\\Model\\Voucher as EshopVoucherModel;\n\n/**\n * Order manager.\n * Performs creation assigning, updating, deleting and other order functions.\n */\nclass Order extends \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n{\n    // defining order state constants\n    /**\n     * Error while sending order notification mail to customer\n     *\n     * @var int\n     */\n    const ORDER_STATE_MAILINGERROR = 0;\n\n    /**\n     * Order finalization was completed without errors\n     *\n     * @var int\n     */\n    const ORDER_STATE_OK = 1;\n\n    /**\n     * Error during payment execution\n     *\n     * @var int\n     */\n    const ORDER_STATE_PAYMENTERROR = 2;\n\n    /**\n     * Order with such id already exist\n     *\n     * @var int\n     */\n    const ORDER_STATE_ORDEREXISTS = 3;\n\n    /**\n     * Delivery parameters used for order are invalid\n     *\n     * @var int\n     */\n    const ORDER_STATE_INVALIDDELIVERY = 4;\n\n    /**\n     * Payment parameters used for order are invalid\n     *\n     * @var int\n     */\n    const ORDER_STATE_INVALIDPAYMENT = 5;\n\n    /**\n     * Protection parameters used for some data in order are invalid\n     *\n     * @var int\n     */\n    const ORDER_STATE_INVALIDDELADDRESSCHANGED = 7;\n\n    /**\n     * Basket price < minimum order price\n     *\n     * @var int\n     */\n    const ORDER_STATE_BELOWMINPRICE = 8;\n\n    /**\n     * Voucher cannot be applied\n     *\n     * @var int\n     */\n    const ORDER_STATE_VOUCHERERROR = 9;\n\n    /**\n     * Skip update fields\n     *\n     * @var array\n     */\n    protected $_aSkipSaveFields = ['oxtimestamp'];\n\n    /**\n     * oxList of oxarticle objects\n     *\n     * @var \\oxlist\n     */\n    protected $_oArticles = null;\n\n    /**\n     * Oxdeliveryset object\n     *\n     * @var \\oxdeliveryset\n     */\n    protected $_oDelSet = null;\n\n    /**\n     * Gift card\n     *\n     * @var \\oxWrapping\n     */\n    protected $_oGiftCard = null;\n\n    /**\n     * Payment type\n     *\n     * @var \\oxpayment\n     */\n    protected $_oPaymentType = null;\n\n    /**\n     * User payment\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\UserPayment\n     */\n    protected $_oPayment = null;\n\n    /**\n     * Order vouchers marked as used\n     *\n     * @var array\n     */\n    protected $_aVoucherList = null;\n\n    /**\n     * Order delivery costs price object\n     *\n     * @var ShopPrice\n     */\n    protected $_oDelPrice = null;\n\n    /**\n     * Order user\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\User\n     */\n    protected $_oUser = null;\n\n    /**\n     * Order basket\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Basket\n     */\n    protected $_oBasket = null;\n\n    /**\n     * Order wrapping costs price object\n     *\n     * @var ShopPrice\n     */\n    protected $_oWrappingPrice = null;\n\n    /**\n     * Order gift card price object\n     *\n     * @var ShopPrice\n     */\n    protected $_oGiftCardPrice = null;\n\n    /**\n     * Order payment costs price object\n     *\n     * @var ShopPrice\n     */\n    protected $_oPaymentPrice = null;\n\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxorder';\n\n    /**\n     * Useage of seperate orders numbering for different shops\n     *\n     * @var bool\n     */\n    protected $_blSeparateNumbering = null;\n\n    /**\n     * Order language id\n     *\n     * @var int\n     */\n    protected $_iOrderLang = null;\n\n    /**\n     * If true delivery will be recalculated while recalculating order\n     *\n     * @var bool\n     */\n    protected $_blReloadDelivery = true;\n\n    /**\n     * If true discount will be recalculated while recalculating order\n     *\n     * @var bool\n     */\n    protected $_blReloadDiscount = true;\n\n    /**\n     * Current order currency object\n     *\n     * @var \\stdClass\n     */\n    protected $_oOrderCurrency = null;\n\n    /**\n     * Current order files object\n     *\n     * @var object\n     */\n    protected $_oOrderFiles = null;\n\n    /**\n     * Shipment tracking url\n     *\n     * @var string\n     */\n    protected $_sShipTrackUrl = null;\n\n    /**\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Basket\n     */\n    protected $_oOrderBasket = null;\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxorder');\n\n        // set usage of separate orders numbering for different shops\n        $this->setSeparateNumbering(Registry::getConfig()->getConfigParam('blSeparateNumbering'));\n    }\n\n    /**\n     * Getter made for order delivery set object access\n     *\n     * @param string $sName parameter name\n     *\n     * @return mixed\n     */\n    public function __get($sName)\n    {\n        if ($sName == 'oDelSet') {\n            return $this->getDelSet();\n        }\n\n        if ($sName == 'oxorder__oxbillcountry') {\n            return $this->getBillCountry();\n        }\n\n        if ($sName == 'oxorder__oxdelcountry') {\n            return $this->getDelCountry();\n        }\n    }\n\n    /**\n     * Assigns data, stored in DB to oxorder object\n     *\n     * @param mixed $dbRecord DB record\n     */\n    public function assign($dbRecord)\n    {\n        parent::assign($dbRecord);\n\n        $oUtilsDate = Registry::getUtilsDate();\n\n        // convert date's to international format\n        $this->oxorder__oxorderdate = new Field($oUtilsDate->formatDBDate($this->oxorder__oxorderdate->value));\n        $this->oxorder__oxsenddate = new Field($oUtilsDate->formatDBDate($this->oxorder__oxsenddate->value));\n    }\n\n    /**\n     * Gets country title by country id.\n     *\n     * @param string $sCountryId country ID\n     *\n     * @return string\n     */\n    protected function getCountryTitle($sCountryId)\n    {\n        $sTitle = null;\n        if ($sCountryId && $sCountryId != '-1') {\n            $oCountry = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Country::class);\n            $oCountry->loadInLang($this->getOrderLanguage(), $sCountryId);\n            $sTitle = $oCountry->oxcountry__oxtitle->value;\n        }\n\n        return $sTitle;\n    }\n\n    /**\n     * returned assigned orderarticles from order\n     *\n     * @param bool $blExcludeCanceled excludes canceled items from list\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    protected function getArticles($blExcludeCanceled = false)\n    {\n        $sSelect = \"SELECT `oxorderarticles`.* FROM `oxorderarticles`\n             WHERE `oxorderarticles`.`oxorderid` = :oxorderid\" .\n            ($blExcludeCanceled ? \" AND `oxorderarticles`.`oxstorno` != 1 \" : \" \")\n            . \" ORDER BY `oxorderarticles`.`oxartid`, `oxorderarticles`.`oxselvariant`,\"\n            . \" `oxorderarticles`.`oxpersparam` \";\n\n        // order articles\n        $oArticles = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n        $oArticles->init('oxorderarticle');\n        $oArticles->selectString($sSelect, [\n            'oxorderid' => (string) $this->getId()\n        ]);\n\n        return $oArticles;\n    }\n\n    /**\n     * Assigns data, stored in oxorderarticles to oxorder object .\n     *\n     * @param bool $blExcludeCanceled excludes canceled items from list\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    public function getOrderArticles($blExcludeCanceled = false)\n    {\n        // checking set value\n        if ($blExcludeCanceled) {\n            return $this->getArticles(true);\n        } elseif ($this->_oArticles === null) {\n            $this->_oArticles = $this->getArticles();\n        }\n\n        return $this->_oArticles;\n    }\n\n    /**\n     * Order article list setter\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\OrderArticleList $oOrderArticleList\n     */\n    public function setOrderArticleList($oOrderArticleList)\n    {\n        $this->_oArticles = $oOrderArticleList;\n    }\n\n    /**\n     * Returns order delivery expenses price object\n     *\n     * @return ShopPrice\n     */\n    public function getOrderDeliveryPrice()\n    {\n        if ($this->_oDelPrice != null) {\n            return $this->_oDelPrice;\n        }\n\n        $this->_oDelPrice = oxNew(\\OxidEsales\\Eshop\\Core\\Price::class);\n        $this->_oDelPrice->setBruttoPriceMode();\n        $this->_oDelPrice->setPrice($this->oxorder__oxdelcost->value, $this->oxorder__oxdelvat->value);\n\n        return $this->_oDelPrice;\n    }\n\n    /**\n     * Returns order wrapping expenses price object\n     *\n     * @return ShopPrice\n     */\n    public function getOrderWrappingPrice()\n    {\n        if ($this->_oWrappingPrice != null) {\n            return $this->_oWrappingPrice;\n        }\n\n        $this->_oWrappingPrice = oxNew(\\OxidEsales\\Eshop\\Core\\Price::class);\n        $this->_oWrappingPrice->setBruttoPriceMode();\n        $this->_oWrappingPrice->setPrice($this->oxorder__oxwrapcost->value, $this->oxorder__oxwrapvat->value);\n\n        return $this->_oWrappingPrice;\n    }\n\n    /**\n     * Returns order wrapping expenses price object\n     *\n     * @return ShopPrice\n     */\n    public function getOrderGiftCardPrice()\n    {\n        if ($this->_oGidtCardPrice != null) {\n            return $this->_oGidtCardPrice;\n        }\n\n        $this->_oGidtCardPrice = oxNew(\\OxidEsales\\Eshop\\Core\\Price::class);\n        $this->_oGidtCardPrice->setBruttoPriceMode();\n        $this->_oGidtCardPrice->setPrice($this->oxorder__oxgiftcardcost->value, $this->oxorder__oxgiftcardvat->value);\n\n        return $this->_oGidtCardPrice;\n    }\n\n\n    /**\n     * Returns order payment expenses price object\n     *\n     * @return ShopPrice\n     */\n    public function getOrderPaymentPrice()\n    {\n        if ($this->_oPaymentPrice != null) {\n            return $this->_oPaymentPrice;\n        }\n\n        $this->_oPaymentPrice = oxNew(\\OxidEsales\\Eshop\\Core\\Price::class);\n        $this->_oPaymentPrice->setBruttoPriceMode();\n        $this->_oPaymentPrice->setPrice($this->oxorder__oxpaycost->value, $this->oxorder__oxpayvat->value);\n\n        return $this->_oPaymentPrice;\n    }\n\n    /**\n     * Returns order netto sum (total order price - VAT)\n     *\n     * @return double\n     */\n    public function getOrderNetSum()\n    {\n        $dTotalNetSum = 0;\n\n        $dTotalNetSum += $this->oxorder__oxtotalnetsum->value;\n        $dTotalNetSum += $this->getOrderDeliveryPrice()->getNettoPrice();\n        $dTotalNetSum += $this->getOrderWrappingPrice()->getNettoPrice();\n        $dTotalNetSum += $this->getOrderPaymentPrice()->getNettoPrice();\n\n        return $dTotalNetSum;\n    }\n\n    /**\n     * Order checking, processing and saving method.\n     * Before saving performed checking if order is still not executed (checks in\n     * database oxorder table for order with know ID), if yes - returns error code 3,\n     * if not - loads payment data, assigns all info from basket to new Order object\n     * and saves full order with error status. Then executes payment.\n     * On failure - deletes order and returns error code 2.\n     * On success - saves order (\\OxidEsales\\Eshop\\Application\\Model\\Order::save()),\n     * removes article from wishlist (\\OxidEsales\\Eshop\\Application\\Model\\Order::_updateWishlist()),\n     * updates voucher data (\\OxidEsales\\Eshop\\Application\\Model\\Order::_markVouchers()).\n     * Finally sends order confirmation email to customer (\\OxidEsales\\Eshop\\Core\\Email::SendOrderEMailToUser())\n     * and shop owner (\\OxidEsales\\Eshop\\Core\\Email::SendOrderEMailToOwner()).\n     * If this is order recalculation, skipping payment execution, marking vouchers as used\n     * and sending order by email to shop owner and user\n     * Mailing status (1 if OK, 0 on error) is returned.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket              Basket object\n     * @param object                                     $oUser                Current User object\n     * @param bool                                       $blRecalculatingOrder Order recalculation\n     *\n     * @return integer\n     */\n    public function finalizeOrder(\n        \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket,\n        $oUser,\n        $blRecalculatingOrder = false\n    ) {\n        // check if this order is already stored\n        $orderId = Registry::getSession()->getVariable('sess_challenge');\n        if ($this->checkOrderExist($orderId)) {\n            Registry::getLogger()->debug('finalizeOrder: Order already exists: ' . $orderId, [$oBasket, $oUser]);\n            // we might use this later, this means that somebody clicked like mad on order button\n            return self::ORDER_STATE_ORDEREXISTS;\n        }\n\n        // if not recalculating order, use sess_challenge id, else leave old order id\n        if (!$blRecalculatingOrder) {\n            // use this ID\n            $this->setId($orderId);\n\n            // validating various order/basket parameters before finalizing\n            if ($iOrderState = $this->validateOrder($oBasket, $oUser)) {\n                return $iOrderState;\n            }\n        }\n\n        // copies user info\n        $this->assignUserInformation($oUser);\n\n        // copies basket info\n        $this->loadFromBasket($oBasket);\n\n        // payment information\n        $oUserPayment = $this->setPayment($oBasket->getPaymentId());\n\n        // set folder information, if order is new\n        // #M575 in recalculating order case folder must be the same as it was\n        if (!$blRecalculatingOrder) {\n            $this->setFolder();\n        }\n\n        // marking as not finished\n        $this->setOrderStatus('NOT_FINISHED');\n\n        //saving all order data to DB\n        $this->save();\n\n        // executing payment (on failure deletes order and returns error code)\n        // in case when recalculating order, payment execution is skipped\n        if (!$blRecalculatingOrder) {\n            $blRet = $this->executePayment($oBasket, $oUserPayment);\n            if ($blRet !== true) {\n                return $blRet;\n            }\n        }\n\n        if (!$this->getFieldData('oxordernr')) {\n            $this->setNumber();\n        } else {\n            oxNew(Counter::class)->update($this->getCounterIdent(), $this->oxorder__oxordernr->value);\n        }\n\n        // deleting remark info only when order is finished\n        Registry::getSession()->deleteVariable('ordrem');\n\n        //#4005: Order creation time is not updated when order processing is complete\n        if (!$blRecalculatingOrder) {\n            $this->updateOrderDate();\n        }\n\n        // updating order trans status (success status)\n        $this->setOrderStatus('OK');\n\n        // store orderid\n        $oBasket->setOrderId($this->getId());\n\n        // updating wish lists\n        $this->updateWishlist($oBasket->getContents(), $oUser);\n\n        // updating users notice list\n        $this->updateNoticeList($oBasket->getContents(), $oUser);\n\n        // marking vouchers as used and sets them to $this->_aVoucherList (will be used in order email)\n        // skipping this action in case of order recalculation\n        if (!$blRecalculatingOrder) {\n            $this->markVouchers($oBasket, $oUser);\n        }\n\n        // send order by email to shop owner and current user\n        // skipping this action in case of order recalculation\n        if (!$blRecalculatingOrder) {\n            $iRet = $this->sendOrderByEmail($oUser, $oBasket, $oUserPayment);\n        } else {\n            $iRet = self::ORDER_STATE_OK;\n        }\n\n        return $iRet;\n    }\n\n    /**\n     * Return true if order store in netto mode\n     *\n     * @return bool\n     */\n    public function isNettoMode()\n    {\n        return (bool) $this->oxorder__oxisnettomode->value;\n    }\n\n\n    /**\n     * Updates order transaction status. Faster than saving whole object\n     *\n     * @param string $sStatus order transaction status\n     */\n    protected function setOrderStatus($sStatus)\n    {\n        $oDb = DatabaseProvider::getDb();\n        $sQ = 'update oxorder set oxtransstatus = :oxtransstatus where oxid = :oxid';\n        $oDb->execute($sQ, [\n            'oxtransstatus' => $sStatus,\n            'oxid' => $this->getId()\n        ]);\n\n        //updating order object\n        $this->oxorder__oxtransstatus = new Field($sStatus, Field::T_RAW);\n    }\n\n    /**\n     * Converts string VAT representation into float e.g. 7,6 to 7.6\n     *\n     * @param string $sVat vat value\n     *\n     * @return float\n     */\n    protected function convertVat($sVat)\n    {\n        if (strpos($sVat, '.') < strpos($sVat, ',')) {\n            $sVat = str_replace(['.', ','], ['', '.'], $sVat);\n        } else {\n            $sVat = str_replace(',', '', $sVat);\n        }\n\n        return (float) $sVat;\n    }\n\n    /**\n     * Reset Vat info\n     */\n    protected function resetVats()\n    {\n        $this->oxorder__oxartvat1 = new Field(null);\n        $this->oxorder__oxartvatprice1 = new Field(null);\n        $this->oxorder__oxartvat2 = new Field(null);\n        $this->oxorder__oxartvatprice2 = new Field(null);\n    }\n\n    /**\n     * Gathers and assigns to new oxOrder object customer data, payment, delivery\n     * and shipping info, customer order remark, currency, voucher, language data.\n     * Additionally stores general discount and wrapping. Sets order status to \"error\"\n     * and creates oxOrderArticle objects and assigns to them basket articles.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket Shopping basket object\n     */\n    protected function loadFromBasket(\\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket)\n    {\n        $myConfig = Registry::getConfig();\n\n        // store IP Address - default must be FALSE as it is illegal to store\n        if ($myConfig->getConfigParam('blStoreIPs') && $this->oxorder__oxip->value === null) {\n            $this->oxorder__oxip = new Field(Registry::getUtilsServer()->getRemoteAddress(), Field::T_RAW);\n        }\n\n        //setting view mode\n        $this->oxorder__oxisnettomode = new Field($oBasket->isCalculationModeNetto());\n\n        // copying main price info\n        $this->oxorder__oxtotalnetsum = new Field($oBasket->getNettoSum());\n        $this->oxorder__oxtotalbrutsum = new Field($oBasket->getBruttoSum());\n        $this->oxorder__oxtotalordersum = new Field($oBasket->getPrice()->getBruttoPrice(), Field::T_RAW);\n\n        // copying discounted VAT info\n        $this->resetVats();\n        $iVatIndex = 1;\n        foreach ($oBasket->getProductVats(false) as $iVat => $dPrice) {\n            $this->{\"oxorder__oxartvat$iVatIndex\"} = new Field($this->convertVat($iVat), Field::T_RAW);\n            $this->{\"oxorder__oxartvatprice$iVatIndex\"} = new Field($dPrice, Field::T_RAW);\n            $iVatIndex++;\n        }\n\n        // payment costs if available\n        if (($oPaymentCost = $oBasket->getCosts('oxpayment'))) {\n            $this->oxorder__oxpaycost = new Field($oPaymentCost->getBruttoPrice(), Field::T_RAW);\n            $this->oxorder__oxpayvat = new Field($oPaymentCost->getVAT(), Field::T_RAW);\n        }\n\n        // delivery info\n        if (($oDeliveryCost = $oBasket->getCosts('oxdelivery'))) {\n            $this->oxorder__oxdelcost = new Field($oDeliveryCost->getBruttoPrice(), Field::T_RAW);\n            //V #M382: Save VAT, not VAT value for delivery costs\n            $this->oxorder__oxdelvat = new Field($oDeliveryCost->getVAT(), Field::T_RAW); //V #M382\n            $this->oxorder__oxdeltype = new Field($oBasket->getShippingId(), Field::T_RAW);\n        }\n\n        // user remark\n        if (!isset($this->oxorder__oxremark) || !isset($this->oxorder__oxremark->value)) {\n            $this->oxorder__oxremark = new Field(Registry::getSession()->getVariable('ordrem'), Field::T_RAW);\n        }\n\n        // currency\n        $oCur = $myConfig->getActShopCurrencyObject();\n        $this->oxorder__oxcurrency = new Field($oCur->name);\n        $this->oxorder__oxcurrate = new Field($oCur->rate, Field::T_RAW);\n\n        // store voucher discount\n        if (($oVoucherDiscount = $oBasket->getVoucherDiscount())) {\n            $this->oxorder__oxvoucherdiscount = new Field($oVoucherDiscount->getBruttoPrice(), Field::T_RAW);\n        }\n\n        // general discount\n        if ($this->_blReloadDiscount) {\n            $dDiscount = 0;\n            $aDiscounts = $oBasket->getDiscounts();\n            if (is_array($aDiscounts) && count($aDiscounts) > 0) {\n                foreach ($aDiscounts as $oDiscount) {\n                    $dDiscount += $oDiscount->dDiscount;\n                }\n            }\n            $this->oxorder__oxdiscount = new Field($dDiscount, Field::T_RAW);\n        }\n\n        //order language\n        $this->oxorder__oxlang = new Field($this->getOrderLanguage());\n\n\n        // initial status - 'ERROR'\n        $this->oxorder__oxtransstatus = new Field('ERROR', Field::T_RAW);\n\n        // copies basket product info ...\n        $this->setOrderArticles($oBasket->getContents());\n\n        // copies wrapping info\n        $this->setWrapping($oBasket);\n    }\n\n    /**\n     * Returns language id of current order object. If order already has\n     * language defined - checks if this language is defined in shops config\n     *\n     * @return int\n     */\n    public function getOrderLanguage()\n    {\n        if ($this->_iOrderLang === null) {\n            if (isset($this->oxorder__oxlang->value)) {\n                $this->_iOrderLang = Registry::getLang()->validateLanguage($this->oxorder__oxlang->value);\n            } else {\n                $this->_iOrderLang = Registry::getLang()->getBaseLanguage();\n            }\n        }\n\n        return $this->_iOrderLang;\n    }\n\n    /**\n     * Assigns to new oxorder object customer delivery and shipping info\n     *\n     * @param object $oUser user object\n     */\n    protected function assignUserInformation($oUser)\n    {\n        $this->oxorder__oxuserid = new Field($oUser->getId());\n\n        // bill address\n        $this->oxorder__oxbillcompany = clone $oUser->oxuser__oxcompany;\n        $this->oxorder__oxbillemail = clone $oUser->oxuser__oxusername;\n        $this->oxorder__oxbillfname = clone $oUser->oxuser__oxfname;\n        $this->oxorder__oxbilllname = clone $oUser->oxuser__oxlname;\n        $this->oxorder__oxbillstreet = clone $oUser->oxuser__oxstreet;\n        $this->oxorder__oxbillstreetnr = clone $oUser->oxuser__oxstreetnr;\n        $this->oxorder__oxbilladdinfo = clone $oUser->oxuser__oxaddinfo;\n        $this->oxorder__oxbillustid = clone $oUser->oxuser__oxustid;\n        $this->oxorder__oxbillcity = clone $oUser->oxuser__oxcity;\n        $this->oxorder__oxbillcountryid = clone $oUser->oxuser__oxcountryid;\n        $this->oxorder__oxbillstateid = clone $oUser->oxuser__oxstateid;\n        $this->oxorder__oxbillzip = clone $oUser->oxuser__oxzip;\n        $this->oxorder__oxbillfon = clone $oUser->oxuser__oxfon;\n        $this->oxorder__oxbillfax = clone $oUser->oxuser__oxfax;\n        $this->oxorder__oxbillsal = clone $oUser->oxuser__oxsal;\n\n\n        // delivery address\n        if (($oDelAdress = $this->getDelAddressInfo())) {\n            // set delivery address\n            $this->oxorder__oxdelcompany = clone $oDelAdress->oxaddress__oxcompany;\n            $this->oxorder__oxdelfname = clone $oDelAdress->oxaddress__oxfname;\n            $this->oxorder__oxdellname = clone $oDelAdress->oxaddress__oxlname;\n            $this->oxorder__oxdelstreet = clone $oDelAdress->oxaddress__oxstreet;\n            $this->oxorder__oxdelstreetnr = clone $oDelAdress->oxaddress__oxstreetnr;\n            $this->oxorder__oxdeladdinfo = clone $oDelAdress->oxaddress__oxaddinfo;\n            $this->oxorder__oxdelcity = clone $oDelAdress->oxaddress__oxcity;\n            $this->oxorder__oxdelcountryid = clone $oDelAdress->oxaddress__oxcountryid;\n            $this->oxorder__oxdelstateid = clone $oDelAdress->oxaddress__oxstateid;\n            $this->oxorder__oxdelzip = clone $oDelAdress->oxaddress__oxzip;\n            $this->oxorder__oxdelfon = clone $oDelAdress->oxaddress__oxfon;\n            $this->oxorder__oxdelfax = clone $oDelAdress->oxaddress__oxfax;\n            $this->oxorder__oxdelsal = clone $oDelAdress->oxaddress__oxsal;\n        }\n    }\n\n    /**\n     * Assigns wrapping VAT and card price + card message info\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket basket object\n     */\n    protected function setWrapping(\\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket)\n    {\n        // wrapping price\n        if (($oWrappingCost = $oBasket->getCosts('oxwrapping'))) {\n            $this->oxorder__oxwrapcost = new Field($oWrappingCost->getBruttoPrice(), Field::T_RAW);\n            // wrapping VAT will be always calculated (#3757)\n            $this->oxorder__oxwrapvat = new Field($oWrappingCost->getVAT(), Field::T_RAW);\n        }\n\n        if (($oGiftCardCost = $oBasket->getCosts('oxgiftcard'))) {\n            $this->oxorder__oxgiftcardcost = new Field($oGiftCardCost->getBruttoPrice(), Field::T_RAW);\n            $this->oxorder__oxgiftcardvat = new Field($oGiftCardCost->getVAT(), Field::T_RAW);\n        }\n\n        // greetings card\n        $this->oxorder__oxcardid = new Field($oBasket->getCardId(), Field::T_RAW);\n\n        // card text will be stored in database\n        $this->oxorder__oxcardtext = new Field($oBasket->getCardMessage(), Field::T_RAW);\n    }\n\n    /**\n     * Creates OrderArticle objects and assigns to them basket articles.\n     * Updates quantity of sold articles (\\OxidEsales\\Eshop\\Application\\Model\\Article::updateSoldAmount()).\n     *\n     * @param array $aArticleList article list\n     */\n    protected function setOrderArticles($aArticleList)\n    {\n        // reset articles list\n        $this->_oArticles = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n        $iCurrLang = $this->getOrderLanguage();\n\n        // add all the products we have on basket to the order\n        foreach ($aArticleList as $oContent) {\n            //$oContent->oProduct = $oContent->getArticle();\n            // #M773 Do not use article lazy loading on order save\n            $oProduct = $oContent->getArticle(true, null, true);\n\n            // copy only if object is oxarticle type\n            if ($oProduct->isOrderArticle()) {\n                $oOrderArticle = $oProduct;\n            } else {\n                // if order language does not match product language article must be reloaded in order language\n                if ($iCurrLang != $oProduct->getLanguage()) {\n                    $oProduct->loadInLang($iCurrLang, $oProduct->getProductId());\n                }\n\n                // set chosen select list\n                $sSelList = '';\n                if (count($aChosenSelList = $oContent->getChosenSelList())) {\n                    foreach ($aChosenSelList as $oItem) {\n                        if ($sSelList) {\n                            $sSelList .= \", \";\n                        }\n                        $sSelList .= \"{$oItem->name} : {$oItem->value}\";\n                    }\n                    if ($sSelList !== '' && $oContent->getVarSelect() !== '') {\n                        $sSelList .= ' ||';\n                    }\n                }\n\n                $oOrderArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\OrderArticle::class);\n                $oOrderArticle->setIsNewOrderItem(true);\n                $oOrderArticle->copyThis($oProduct);\n                $oOrderArticle->setId();\n\n                $oOrderArticle->oxorderarticles__oxartnum = clone $oProduct->oxarticles__oxartnum;\n                $oOrderArticle->oxorderarticles__oxselvariant = new Field(\n                    trim($sSelList . ' ' . $oContent->getVarSelect()),\n                    Field::T_RAW\n                );\n                $oOrderArticle->oxorderarticles__oxshortdesc = new Field(\n                    $oProduct->oxarticles__oxshortdesc->getRawValue(),\n                    Field::T_RAW\n                );\n                // #M974: duplicated entries for the name of variants in orders\n                $oOrderArticle->oxorderarticles__oxtitle = new Field(\n                    trim($oProduct->oxarticles__oxtitle->getRawValue()),\n                    Field::T_RAW\n                );\n\n                // copying persistent parameters ...\n                $aPersParams = $oContent->getPersParams();\n                if (is_array($aPersParams) && count($aPersParams)) {\n                    $oOrderArticle->oxorderarticles__oxpersparam = new Field(serialize($aPersParams), Field::T_RAW);\n                }\n            }\n\n            // ids, titles, numbers ...\n            $oOrderArticle->oxorderarticles__oxorderid = new Field($this->getId());\n            $oOrderArticle->oxorderarticles__oxartid = new Field($oContent->getProductId());\n            $oOrderArticle->oxorderarticles__oxamount = new Field($oContent->getAmount());\n\n            // prices\n            $oPrice = $oContent->getPrice();\n            $oOrderArticle->oxorderarticles__oxnetprice = new Field($oPrice->getNettoPrice(), Field::T_RAW);\n            $oOrderArticle->oxorderarticles__oxvatprice = new Field($oPrice->getVatValue(), Field::T_RAW);\n            $oOrderArticle->oxorderarticles__oxbrutprice = new Field($oPrice->getBruttoPrice(), Field::T_RAW);\n            $oOrderArticle->oxorderarticles__oxvat = new Field($oPrice->getVat(), Field::T_RAW);\n\n            $oUnitPrice = $oContent->getUnitPrice();\n            $oOrderArticle->oxorderarticles__oxnprice = new Field($oUnitPrice->getNettoPrice(), Field::T_RAW);\n            $oOrderArticle->oxorderarticles__oxbprice = new Field($oUnitPrice->getBruttoPrice(), Field::T_RAW);\n\n            // wrap id\n            $oOrderArticle->oxorderarticles__oxwrapid = new Field($oContent->getWrappingId(), Field::T_RAW);\n\n            // items shop id\n            $oOrderArticle->oxorderarticles__oxordershopid = new Field($oContent->getShopId(), Field::T_RAW);\n\n            // bundle?\n            $oOrderArticle->oxorderarticles__oxisbundle = new Field($oContent->isBundle());\n\n            // add information for eMail\n            //P\n            //TODO: check if this assign is needed at all\n            $oOrderArticle->oProduct = $oProduct;\n\n            $oOrderArticle->setArticle($oProduct);\n\n            // simulation order article list\n            $this->_oArticles->offsetSet($oOrderArticle->getId(), $oOrderArticle);\n        }\n    }\n\n    /**\n     * Executes payment. Additionally loads oxPaymentGateway object, initiates\n     * it by adding payment parameters (oxPaymentGateway::setPaymentParams())\n     * and finally executes it (oxPaymentGateway::executePayment()). On failure -\n     * deletes order and returns * error code 2.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket      basket object\n     * @param object                                     $oUserpayment user payment object\n     *\n     * @return  integer 2 or an error code\n     */\n    protected function executePayment(\\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket, $oUserpayment)\n    {\n        $oPayTransaction = $this->getGateway();\n        $oPayTransaction->setPaymentParams($oUserpayment);\n\n        if (!$oPayTransaction->executePayment($oBasket->getPrice()->getBruttoPrice(), $this)) {\n            $this->delete();\n\n            // checking for error messages\n            if (method_exists($oPayTransaction, 'getLastError')) {\n                if (($sLastError = $oPayTransaction->getLastError())) {\n                    return $sLastError;\n                }\n            }\n\n            // checking for error codes\n            if (method_exists($oPayTransaction, 'getLastErrorNo')) {\n                if (($iLastErrorNo = $oPayTransaction->getLastErrorNo())) {\n                    return $iLastErrorNo;\n                }\n            }\n\n            return self::ORDER_STATE_PAYMENTERROR; // means no authentication\n        }\n\n        return true; // everything fine\n    }\n\n    /**\n     * Returns the correct gateway. At the moment only switch between default\n     * and IPayment, can be extended later.\n     *\n     * @return object $oPayTransaction payment gateway object\n     */\n    protected function getGateway()\n    {\n        return oxNew(\\OxidEsales\\Eshop\\Application\\Model\\PaymentGateway::class);\n    }\n\n    /**\n     * Creates and returns user payment.\n     *\n     * @param string $sPaymentid used payment id\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\UserPayment\n     */\n    protected function setPayment($sPaymentid)\n    {\n        $oPayment = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Payment::class);\n\n        if (!$oPayment->load($sPaymentid)) {\n            return null;\n        }\n\n        $aDynvalue = $this->getDynamicValues();\n\n        $oPayment->setDynValues(Registry::getUtils()->assignValuesFromText($oPayment->oxpayments__oxvaldesc->value));\n\n        // collecting dynamic values\n        $aDynVal = [];\n\n        if (is_array($aPaymentDynValues = $oPayment->getDynValues())) {\n            foreach ($aPaymentDynValues as $key => $oVal) {\n                if (isset($aDynvalue[$oVal->name])) {\n                    $oVal->value = $aDynvalue[$oVal->name];\n                }\n\n                //$oPayment->setDynValue($key, $oVal);\n                $aPaymentDynValues[$key] = $oVal;\n                $aDynVal[$oVal->name] = $oVal->value;\n            }\n        }\n\n        // Store this payment information, we might allow users later to\n        // reactivate already give payment information\n\n        $oUserpayment = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\UserPayment::class);\n        $oUserpayment->oxuserpayments__oxuserid = clone $this->oxorder__oxuserid;\n        $oUserpayment->oxuserpayments__oxpaymentsid = new Field($sPaymentid, Field::T_RAW);\n        $oUserpayment->oxuserpayments__oxvalue = new Field(\n            Registry::getUtils()->assignValuesToText($aDynVal),\n            Field::T_RAW\n        );\n        $oUserpayment->oxpayments__oxdesc = clone $oPayment->oxpayments__oxdesc;\n        $oUserpayment->oxpayments__oxlongdesc = clone $oPayment->oxpayments__oxlongdesc;\n        $oUserpayment->setDynValues($aPaymentDynValues);\n        $oUserpayment->save();\n\n        // storing payment information to order\n        $this->oxorder__oxpaymentid = new Field($oUserpayment->getId(), Field::T_RAW);\n        $this->oxorder__oxpaymenttype = clone $oUserpayment->oxuserpayments__oxpaymentsid;\n\n        // returning user payment object which will be used later in code ...\n        return $oUserpayment;\n    }\n\n    /**\n     * Assigns oxfolder as new\n     */\n    protected function setFolder()\n    {\n        $myConfig = Registry::getConfig();\n        $this->oxorder__oxfolder = new Field(\n            key($myConfig->getShopConfVar('aOrderfolder', $myConfig->getShopId())),\n            Field::T_RAW\n        );\n    }\n\n    /**\n     * aAdds/removes user chosen article to/from his noticelist\n     * or wishlist (oxuserbasket::addItemToBasket()).\n     *\n     * @param array  $aArticleList basket products\n     * @param object $oUser        user object\n     */\n    protected function updateWishlist($aArticleList, $oUser)\n    {\n        foreach ($aArticleList as $oContent) {\n            if (($sWishId = $oContent->getWishId())) {\n                // checking which wishlist user uses ..\n                if ($sWishId == $oUser->getId()) {\n                    $oUserBasket = $oUser->getBasket('wishlist');\n                } else {\n                    $aWhere = ['oxuserbaskets.oxuserid' => $sWishId, 'oxuserbaskets.oxtitle' => 'wishlist'];\n                    $oUserBasket = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\UserBasket::class);\n\n                    $query = $oUserBasket->buildSelectString($aWhere);\n                    $record = DatabaseProvider::getDb()->select($query);\n                    if ($record && $record->count() > 0) {\n                        $oUserBasket->assign($record->fields);\n                    }\n                }\n\n                // updating users wish list\n                if ($oUserBasket) {\n                    if (!($sProdId = $oContent->getWishArticleId())) {\n                        $sProdId = $oContent->getProductId();\n                    }\n                    $oUserBasketItem = $oUserBasket->getItem($sProdId, $oContent->getSelList());\n                    $dNewAmount = $oUserBasketItem->oxuserbasketitems__oxamount->value - $oContent->getAmount();\n                    if ($dNewAmount < 0) {\n                        $dNewAmount = 0;\n                    }\n                    $oUserBasket->addItemToBasket($sProdId, $dNewAmount, $oContent->getSelList(), true);\n                }\n            }\n        }\n    }\n\n    /**\n     * After order is finished this method cleans up users notice list, by\n     * removing bought items from users notice list\n     *\n     * @param array                                    $aArticleList array of basket products\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser        basket user object\n     *\n     * @return null\n     */\n    protected function updateNoticeList($aArticleList, $oUser)\n    {\n        /*\n         * #6141\n         * If there is no noticelist, don't create an empty one.\n         * Because loading the list via $user->getBasket('noticelist') will create it if there isn't one, but it will\n         * only exists in the session for now. So it is possible to check if it has an oxid. If yes then we had a list.\n         * If not, it's newly created and adds a row in oxuserbaskets without content in oxuserbasketitems.\n         * Also it will prevent creating a row for guests.\n         */\n        if (!isset($oUser->getBasket('noticelist')->oxuserbaskets__oxid->value)) {\n            return;\n        }\n\n        // loading users notice list ..\n        if ($oUserBasket = $oUser->getBasket('noticelist')) {\n            // only if wishlist is enabled\n            foreach ($aArticleList as $oContent) {\n                $sProdId = $oContent->getProductId();\n\n                // updating users notice list\n                /** @var \\OxidEsales\\EshopCommunity\\Application\\Model\\BasketItem $oUserBasketItem */\n                $oUserBasketItem = $oUserBasket->getItem($sProdId, $oContent->getSelList(), $oContent->getPersParams());\n\n                if (\n                    is_object($oUserBasketItem->oxuserbasketitems__oxamount)\n                    && $oUserBasketItem->oxuserbasketitems__oxamount->value\n                ) {\n                    $dNewAmount = $oUserBasketItem->oxuserbasketitems__oxamount->value - $oContent->getAmount();\n                } else {\n                    $dNewAmount = -1 * $oContent->getAmount();\n                }\n\n                if ($dNewAmount < 0) {\n                    $dNewAmount = 0;\n                }\n                $oUserBasket->addItemToBasket(\n                    $sProdId,\n                    $dNewAmount,\n                    $oContent->getSelList(),\n                    true,\n                    $oContent->getPersParams()\n                );\n            }\n        }\n    }\n\n    /**\n     * Updates order date to current date\n     */\n    protected function updateOrderDate()\n    {\n        $oDb = DatabaseProvider::getDb();\n        $sDate = date('Y-m-d H:i:s', Registry::getUtilsDate()->getTime());\n        $sQ = 'update oxorder set oxorderdate = :oxorderdate where oxid = :oxid';\n        $this->oxorder__oxorderdate = new Field($sDate, Field::T_RAW);\n        $oDb->execute($sQ, [\n            'oxorderdate' => $sDate,\n            'oxid' => $this->getId()\n        ]);\n    }\n\n    /**\n     * Marks voucher as used (oxvoucher::markAsUsed())\n     * and sets them to $this->_aVoucherList.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket basket object\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User   $oUser   user object\n     */\n    protected function markVouchers($oBasket, $oUser)\n    {\n        $this->_aVoucherList = $oBasket->getVouchers();\n\n        if (is_array($this->_aVoucherList)) {\n            foreach ($this->_aVoucherList as $sVoucherId => $oSimpleVoucher) {\n                $oVoucher = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Voucher::class);\n                $oVoucher->load($sVoucherId);\n                $oVoucher->markAsUsed(\n                    $this->oxorder__oxid->value,\n                    $oUser->oxuser__oxid->value,\n                    $oSimpleVoucher->dVoucherdiscount\n                );\n\n                $this->_aVoucherList[$sVoucherId] = $oVoucher;\n            }\n        }\n    }\n\n    /**\n     * Updates/inserts order object and related info to DB\n     *\n     * @return null\n     */\n    public function save()\n    {\n        if (($blSave = parent::save())) {\n            // saving order articles\n            $oOrderArticles = $this->getOrderArticles();\n            if ($oOrderArticles && count($oOrderArticles) > 0) {\n                foreach ($oOrderArticles as $oOrderArticle) {\n                    $oOrderArticle->save();\n                }\n            }\n        }\n\n        return $blSave;\n    }\n\n    /**\n     * Loads and returns delivery address object or null\n     * if deladrid is not configured, or object was not loaded\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Address|null\n     */\n    public function getDelAddressInfo()\n    {\n        $oDelAdress = null;\n        if (!($soxAddressId = Registry::getRequest()->getRequestEscapedParameter('deladrid'))) {\n            $soxAddressId = Registry::getSession()->getVariable('deladrid');\n        }\n        if ($soxAddressId) {\n            $oDelAdress = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Address::class);\n            $oDelAdress->load($soxAddressId);\n\n            //get delivery country name from delivery country id\n            if ($oDelAdress->oxaddress__oxcountryid->value && $oDelAdress->oxaddress__oxcountryid->value != -1) {\n                $oCountry = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Country::class);\n                $oCountry->load($oDelAdress->oxaddress__oxcountryid->value);\n                $oDelAdress->oxaddress__oxcountry = clone $oCountry->oxcountry__oxtitle;\n            }\n        }\n\n        return $oDelAdress;\n    }\n\n    /**\n     * Function which checks if article stock is valid.\n     * If not displays error and returns false.\n     *\n     * @param object $oBasket basket object\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\NoArticleException\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\ArticleInputException\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\OutOfStockException\n     */\n    public function validateStock($oBasket)\n    {\n        foreach ($oBasket->getContents() as $key => $oContent) {\n            try {\n                $oProd = $oContent->getArticle(true, null, true);\n            } catch (\\OxidEsales\\Eshop\\Core\\Exception\\NoArticleException $oEx) {\n                $oBasket->removeItem($key);\n                throw $oEx;\n            } catch (\\OxidEsales\\Eshop\\Core\\Exception\\ArticleInputException $oEx) {\n                $oBasket->removeItem($key);\n                throw $oEx;\n            }\n\n            // check if its still available\n            $dArtStockAmount = $oBasket->getArtStockInBasket($oProd->getId(), $key);\n            $iOnStock = $oProd->checkForStock($oContent->getAmount(), $dArtStockAmount);\n            if ($iOnStock !== true) {\n                /** @var \\OxidEsales\\Eshop\\Core\\Exception\\OutOfStockException $oEx */\n                $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\OutOfStockException::class);\n                $oEx->setMessage('ERROR_MESSAGE_OUTOFSTOCK_OUTOFSTOCK');\n                $oEx->setArticleNr($oProd->oxarticles__oxartnum->value);\n                $oEx->setProductId($oProd->getId());\n                $oEx->setBasketIndex($key);\n\n                if (!is_numeric($iOnStock)) {\n                    $iOnStock = 0;\n                }\n                $oEx->setRemainingAmount($iOnStock);\n                throw $oEx;\n            }\n        }\n    }\n\n    /**\n     * Inserts order object information in DB. Returns true on success.\n     *\n     * @return bool\n     */\n    protected function insert()\n    {\n        $myConfig = Registry::getConfig();\n        $oUtilsDate = Registry::getUtilsDate();\n\n        //V #M525 orderdate must be the same as it was\n        if (!$this->oxorder__oxorderdate || !$this->oxorder__oxorderdate->value) {\n            $this->oxorder__oxorderdate = new Field(date('Y-m-d H:i:s', $oUtilsDate->getTime()), Field::T_RAW);\n        } else {\n            $this->oxorder__oxorderdate = new Field(\n                $oUtilsDate->formatDBDate(\n                    $this->oxorder__oxorderdate ? $this->oxorder__oxorderdate->value : null,\n                    true\n                )\n            );\n        }\n\n        $this->oxorder__oxshopid = new Field($myConfig->getShopId(), Field::T_RAW);\n        $this->oxorder__oxsenddate = new Field(\n            $oUtilsDate->formatDBDate(\n                $this->oxorder__oxsenddate ? $this->oxorder__oxsenddate->value : null,\n                true\n            )\n        );\n\n        $blInsert = parent::insert();\n\n        return $blInsert;\n    }\n\n    /**\n     * creates counter ident\n     *\n     * @return String\n     */\n    protected function getCounterIdent()\n    {\n        $sCounterIdent = ($this->_blSeparateNumbering) ? 'oxOrder_' . Registry::getConfig()->getShopId() : 'oxOrder';\n\n        return $sCounterIdent;\n    }\n\n\n    /**\n     * Tries to fetch and set next record number in DB. Returns true on success\n     *\n     * @return bool\n     */\n    protected function setNumber()\n    {\n        $oDb = DatabaseProvider::getDb();\n\n        $iCnt = oxNew(Counter::class)->getNext($this->getCounterIdent());\n        $sQ = \"update oxorder set oxordernr = :oxordernr where oxid = :oxid\";\n        $blUpdate = (bool) $oDb->execute($sQ, [\n            'oxordernr' => $iCnt,\n            'oxid' => $this->getId()\n        ]);\n\n        if ($blUpdate) {\n            $this->oxorder__oxordernr = new Field($iCnt);\n        }\n\n        return $blUpdate;\n    }\n\n    /**\n     * Updates object parameters to DB.\n     *\n     * @return null\n     */\n    protected function update()\n    {\n        $this->_aSkipSaveFields = ['oxtimestamp', 'oxorderdate'];\n        $this->oxorder__oxsenddate = new Field(Registry::getUtilsDate()\n            ->formatDBDate($this->oxorder__oxsenddate->value, true));\n\n        return parent::update();\n    }\n\n    /**\n     * Updates stock information, deletes current ordering details from DB,\n     * returns true on success.\n     *\n     * @param string $sOxId Ordering ID (default null)\n     *\n     * @return bool\n     */\n    public function delete($sOxId = null)\n    {\n        if ($sOxId) {\n            if (!$this->load($sOxId)) {\n                // such order does not exist\n                return false;\n            }\n        } elseif (!$sOxId) {\n            $sOxId = $this->getId();\n        }\n\n        // no order id is passed\n        if (!$sOxId) {\n            return false;\n        }\n\n        // delete order articles\n        $oOrderArticles = $this->getOrderArticles(false);\n        foreach ($oOrderArticles as $oOrderArticle) {\n            $oOrderArticle->delete();\n        }\n\n        // #440 - deleting user payment info\n        if ($oPaymentType = $this->getPaymentType()) {\n            $oPaymentType->delete();\n        }\n\n        return parent::delete($sOxId);\n    }\n\n    /**\n     * Recalculates order. Starts transactions, deletes current order and order articles from DB,\n     * adds current order articles to virtual basket and finally recalculates order by calling Order::finalizeOrder()\n     * If no errors, finishing transaction.\n     *\n     * @param array $aNewArticles article list of new order\n     *\n     * @throws Exception\n     */\n    public function recalculateOrder($aNewArticles = [])\n    {\n        DatabaseProvider::getDb()->startTransaction();\n        try {\n            $oBasket = $this->getOrderBasket();\n\n            // add this order articles to virtual basket and recalculates basket\n            $this->addOrderArticlesToBasket($oBasket, $this->getOrderArticles(true));\n\n            // adding new articles to existing order\n            $this->addArticlesToBasket($oBasket, $aNewArticles);\n\n            // recalculating basket\n            $oBasket->calculateBasket(true);\n\n            //finalizing order (skipping payment execution, vouchers marking and mail sending)\n            $iRet = $this->finalizeOrder($oBasket, $this->getOrderUser(), true);\n\n            //if finalizing order failed, rollback transaction\n            if ($iRet !== 1) {\n                DatabaseProvider::getDb()->rollbackTransaction();\n            } else {\n                DatabaseProvider::getDb()->commitTransaction();\n            }\n        } catch (Exception $exception) {\n            DatabaseProvider::getDb()->rollbackTransaction();\n\n            throw $exception;\n        }\n    }\n\n    /**\n     * Returns basket object filled up with discount, delivery, wrapping and all other info\n     *\n     * @param bool $blStockCheck perform stock check or not (default true)\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Basket\n     */\n    protected function getOrderBasket($blStockCheck = true)\n    {\n        $this->_oOrderBasket = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Basket::class);\n        $this->_oOrderBasket->enableSaveToDataBase(false);\n\n        //setting recalculation mode\n        $this->_oOrderBasket->setCalculationModeNetto($this->isNettoMode());\n\n        // setting stock check mode\n        $this->_oOrderBasket->setStockCheckMode($blStockCheck);\n\n        // setting virtual basket user\n        $this->_oOrderBasket->setBasketUser($this->getOrderUser());\n\n        // transferring order id\n        $this->_oOrderBasket->setOrderId($this->getId());\n\n        // setting basket currency order uses\n        $aCurrencies = Registry::getConfig()->getCurrencyArray();\n        foreach ($aCurrencies as $oCur) {\n            if ($oCur->name == $this->oxorder__oxcurrency->value) {\n                $oBasketCur = $oCur;\n                break;\n            }\n        }\n\n        // setting currency\n        $this->_oOrderBasket->setBasketCurrency($oBasketCur);\n\n        // set basket card id and message\n        $this->_oOrderBasket->setCardId($this->oxorder__oxcardid->value);\n        $this->_oOrderBasket->setCardMessage($this->oxorder__oxcardtext->value);\n\n        if ($this->_blReloadDiscount) {\n            $oDb = DatabaseProvider::getDb();\n            // disabling availability check\n            $this->_oOrderBasket->setSkipVouchersChecking(true);\n\n            // add previously used vouchers\n            $sQ = 'select oxid from oxvouchers where oxorderid = :oxorderid';\n            $aVouchers = $oDb->getAll($sQ, [\n                'oxorderid' => $this->getId()\n            ]);\n            foreach ($aVouchers as $aVoucher) {\n                $this->_oOrderBasket->addVoucher($aVoucher['oxid']);\n            }\n        } else {\n            $this->_oOrderBasket->setDiscountCalcMode(false);\n            $this->_oOrderBasket->setVoucherDiscount($this->oxorder__oxvoucherdiscount->value);\n            $this->_oOrderBasket->setTotalDiscount($this->oxorder__oxdiscount->value);\n        }\n\n        // must be kept old delivery?\n        if (!$this->_blReloadDelivery) {\n            $this->_oOrderBasket->setDeliveryPrice($this->getOrderDeliveryPrice());\n        } else {\n            //  set shipping\n            $this->_oOrderBasket->setShipping($this->oxorder__oxdeltype->value);\n            $this->_oOrderBasket->setDeliveryPrice(null);\n        }\n\n        //set basket payment\n        $this->_oOrderBasket->setPayment($this->oxorder__oxpaymenttype->value);\n\n        return $this->_oOrderBasket;\n    }\n\n    /**\n     * Sets new delivery id for order and forces order to recalculate using new delivery type.\n     * Order is not recalculated automatically, to do this Order::recalculateOrder() must be called ;\n     *\n     * @param string $sDeliveryId new delivery id\n     */\n    public function setDelivery($sDeliveryId)\n    {\n        $this->reloadDelivery(true);\n        $this->oxorder__oxdeltype = new Field($sDeliveryId);\n    }\n\n    /**\n     * Returns current order user object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\User\n     */\n    public function getOrderUser()\n    {\n        if ($this->_oUser === null) {\n            $this->_oUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n            $this->_oUser->load($this->oxorder__oxuserid->value);\n\n            // if object is loaded then reusing its order info\n            if ($this->_isLoaded) {\n                // bill address\n                $this->_oUser->oxuser__oxcompany = clone $this->oxorder__oxbillcompany;\n                $this->_oUser->oxuser__oxusername = clone $this->oxorder__oxbillemail;\n                $this->_oUser->oxuser__oxfname = clone $this->oxorder__oxbillfname;\n                $this->_oUser->oxuser__oxlname = clone $this->oxorder__oxbilllname;\n                $this->_oUser->oxuser__oxstreet = clone $this->oxorder__oxbillstreet;\n                $this->_oUser->oxuser__oxstreetnr = clone $this->oxorder__oxbillstreetnr;\n                $this->_oUser->oxuser__oxaddinfo = clone $this->oxorder__oxbilladdinfo;\n                $this->_oUser->oxuser__oxustid = clone $this->oxorder__oxbillustid;\n\n                $this->_oUser->oxuser__oxcity = clone $this->oxorder__oxbillcity;\n                $this->_oUser->oxuser__oxcountryid = clone $this->oxorder__oxbillcountryid;\n                $this->_oUser->oxuser__oxstateid = clone $this->oxorder__oxbillstateid;\n                $this->_oUser->oxuser__oxzip = clone $this->oxorder__oxbillzip;\n                $this->_oUser->oxuser__oxfon = clone $this->oxorder__oxbillfon;\n                $this->_oUser->oxuser__oxfax = clone $this->oxorder__oxbillfax;\n                $this->_oUser->oxuser__oxsal = clone $this->oxorder__oxbillsal;\n            }\n        }\n\n        return $this->_oUser;\n    }\n\n    /**\n     * Fake entries, pdf is generated in modules.. myorder.\n     *\n     * @param mixed $oPdf pdf object\n     */\n    public function pdfFooter($oPdf)\n    {\n    }\n\n    /**\n     * Fake entries, pdf is generated in modules.. myorder.\n     *\n     * @param mixed $oPdf pdf object\n     */\n    public function pdfHeaderplus($oPdf)\n    {\n    }\n\n    /**\n     * Fake entries, pdf is generated in modules.. myorder.\n     *\n     * @param mixed $oPdf pdf object\n     */\n    public function pdfHeader($oPdf)\n    {\n    }\n\n    /**\n     * Fake entries, pdf is generated in modules.. myorder.\n     *\n     * @param string $sFilename file name\n     * @param int    $iSelLang  selected language\n     */\n    public function genPdf($sFilename, $iSelLang = 0)\n    {\n    }\n\n    /**\n     * Returns order invoice number.\n     *\n     * @return integer\n     */\n    public function getInvoiceNum()\n    {\n        $sQ = 'select max(oxorder.oxinvoicenr) from oxorder \n            where oxorder.oxshopid = :oxshopid ';\n        $params = [\n            'oxshopid' => Registry::getConfig()->getShopId()\n        ];\n\n        return ((int) DatabaseProvider::getDb()->getOne($sQ, $params) + 1);\n    }\n\n    /**\n     * Returns next possible (free) order bill number.\n     *\n     * @return integer\n     */\n    public function getNextBillNum()\n    {\n        $sQ = 'select max(cast(oxorder.oxbillnr as unsigned)) from oxorder \n            where oxorder.oxshopid = :oxshopid ';\n        $params = [\n            'oxshopid' => Registry::getConfig()->getShopId()\n        ];\n\n        return ((int) DatabaseProvider::getDb()->getOne($sQ, $params) + 1);\n    }\n\n    /**\n     * Loads possible shipping sets for this order\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\DeliveryList\n     */\n    public function getShippingSetList()\n    {\n        // in which country we deliver\n        if (!($sShipId = $this->oxorder__oxdelcountryid->value)) {\n            $sShipId = $this->oxorder__oxbillcountryid->value;\n        }\n\n        $oBasket = $this->getOrderBasket(false);\n\n        // unsetting bundles\n        $oOrderArticles = $this->getOrderArticles();\n        foreach ($oOrderArticles as $sItemId => $oItem) {\n            if ($oItem->isBundle()) {\n                $oOrderArticles->offsetUnset($sItemId);\n            }\n        }\n\n        // add this order articles to basket and recalculate basket\n        $this->addOrderArticlesToBasket($oBasket, $oOrderArticles);\n\n        // recalculating basket\n        $oBasket->calculateBasket(true);\n\n        // load fitting deliveries list\n        $oDeliveryList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\DeliveryList::class, \"core\");\n        $oDeliveryList->setCollectFittingDeliveriesSets(true);\n\n        return $oDeliveryList->getDeliveryList($oBasket, $this->getOrderUser(), $sShipId);\n    }\n\n    /**\n     * Get vouchers numbers list which were used with this order\n     *\n     * @return array\n     */\n    public function getVoucherNrList()\n    {\n        return DatabaseProvider::getDb()->getCol(\n            \"select oxvouchernr from oxvouchers where oxorderid = :oxorderid\",\n            [\n                'oxorderid' => $this->oxorder__oxid->value\n            ]\n        );\n    }\n\n    /**\n     * Returns orders total price\n     *\n     * @param bool $blToday if true calculates only current day orders\n     *\n     * @return double\n     */\n    public function getOrderSum($blToday = false)\n    {\n        $sSelect = 'select sum(oxtotalordersum / oxcurrate) from oxorder where ';\n        $sSelect .= 'oxshopid = :oxshopid and oxorder.oxstorno != \"1\" ';\n\n        if ($blToday) {\n            $sSelect .= 'and oxorderdate like \"' . date('Y-m-d') . '%\" ';\n        }\n\n        $params = [\n            'oxshopid' => Registry::getConfig()->getShopId()\n        ];\n\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        return (float) DatabaseProvider::getMaster()->getOne($sSelect, $params);\n    }\n\n    /**\n     * Returns orders count\n     *\n     * @param bool $blToday if true calculates only current day orders\n     *\n     * @return int\n     */\n    public function getOrderCnt($blToday = false)\n    {\n        $sSelect = 'select count(*) from oxorder where ';\n        $sSelect .= 'oxshopid = :oxshopid  and oxorder.oxstorno != \"1\" ';\n\n        if ($blToday) {\n            $sSelect .= 'and oxorderdate like \"' . date('Y-m-d') . '%\" ';\n        }\n\n        $params = [\n            'oxshopid' => Registry::getConfig()->getShopId()\n        ];\n\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        return (int) DatabaseProvider::getMaster()->getOne($sSelect, $params);\n    }\n\n\n    /**\n     * Checking if this order is already stored.\n     *\n     * @param string $sOxId order ID\n     *\n     * @return bool\n     */\n    protected function checkOrderExist($sOxId = null)\n    {\n        if (!$sOxId) {\n            return false;\n        }\n\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        $masterDb = DatabaseProvider::getMaster();\n        $params = [\n            'oxid' => $sOxId\n        ];\n        if ($masterDb->getOne('select oxid from oxorder where oxid = :oxid', $params)) {\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Send order to shop owner and user\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User        $oUser    order user\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket      $oBasket  current order basket\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\UserPayment $oPayment order payment\n     *\n     * @return bool\n     */\n    protected function sendOrderByEmail($oUser = null, $oBasket = null, $oPayment = null)\n    {\n        $iRet = self::ORDER_STATE_MAILINGERROR;\n\n        // add user, basket and payment to order\n        $this->_oUser = $oUser;\n        $this->_oBasket = $oBasket;\n        $this->_oPayment = $oPayment;\n\n        $oxEmail = oxNew(\\OxidEsales\\Eshop\\Core\\Email::class);\n\n        // send order email to user\n        if ($oxEmail->sendOrderEMailToUser($this)) {\n            // mail to user was successfully sent\n            $iRet = self::ORDER_STATE_OK;\n        }\n\n        // send order email to shop owner\n        $oxEmail->sendOrderEMailToOwner($this);\n\n        return $iRet;\n    }\n\n    /**\n     * Returns order basket\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Basket\n     */\n    public function getBasket()\n    {\n        return $this->_oBasket;\n    }\n\n    /**\n     * Returns order payment\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\UserPayment\n     */\n    public function getPayment()\n    {\n        return $this->_oPayment;\n    }\n\n    /**\n     * Returns order vouchers marked as used\n     *\n     * @return array\n     */\n    public function getVoucherList()\n    {\n        return $this->_aVoucherList;\n    }\n\n    /**\n     * Returns order deliveryset object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\DeliverySet\n     */\n    public function getDelSet()\n    {\n        if ($this->_oDelSet == null) {\n            // load deliveryset info\n            $this->_oDelSet = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\DeliverySet::class);\n            $this->_oDelSet->load($this->oxorder__oxdeltype->value);\n        }\n\n        return $this->_oDelSet;\n    }\n\n    /**\n     * Get payment type\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\UserPayment|false\n     */\n    public function getPaymentType()\n    {\n        if ($this->getFieldData('oxpaymentid') && $this->_oPaymentType === null) {\n            $this->_oPaymentType = false;\n            $oPaymentType = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\UserPayment::class);\n            if ($oPaymentType->load($this->getFieldData('oxpaymentid'))) {\n                $this->_oPaymentType = $oPaymentType;\n            }\n        }\n\n        return $this->_oPaymentType;\n    }\n\n    /**\n     * Get gift card\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Wrapping|null\n     */\n    public function getGiftCard()\n    {\n        if ($this->oxorder__oxcardid->value && $this->_oGiftCard == null) {\n            $this->_oGiftCard = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Wrapping::class);\n            $this->_oGiftCard->load($this->oxorder__oxcardid->value);\n        }\n\n        return $this->_oGiftCard;\n    }\n\n    /**\n     * Set usage of separate orders numbering for different shops\n     *\n     * @param bool $blSeparateNumbering use or not separate orders numbering\n     */\n    public function setSeparateNumbering($blSeparateNumbering = null)\n    {\n        $this->_blSeparateNumbering = $blSeparateNumbering;\n    }\n\n    /**\n     * Get users payment type from last order\n     *\n     * @param string $sUserId order user id\n     *\n     * @return string $sLastPaymentId payment id\n     */\n    public function getLastUserPaymentType($sUserId)\n    {\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        $masterDb = DatabaseProvider::getMaster();\n        $sQ = 'select oxorder.oxpaymenttype from oxorder \n            where oxorder.oxshopid = :oxshopid \n                and oxorder.oxuserid = :oxuserid \n            order by oxorder.oxorderdate desc ';\n\n        $sLastPaymentId = $masterDb->getOne($sQ, [\n            'oxshopid' => Registry::getConfig()->getShopId(),\n            'oxuserid' => $sUserId\n        ]);\n\n        return $sLastPaymentId;\n    }\n\n    /**\n     * Adds order articles back to virtual basket. Needed for recalculating order.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket        basket object\n     * @param array                                      $aOrderArticles order articles\n     */\n    protected function addOrderArticlesToBasket($oBasket, $aOrderArticles)\n    {\n        // if no order articles, return empty basket\n        if (count($aOrderArticles) > 0) {\n            //adding order articles to basket\n            foreach ($aOrderArticles as $oOrderArticle) {\n                $oBasket->addOrderArticleToBasket($oOrderArticle);\n            }\n        }\n    }\n\n    /**\n     * Adds new products to basket/order\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket   basket to add articles\n     * @param array                                      $aArticles article array\n     */\n    protected function addArticlesToBasket($oBasket, $aArticles)\n    {\n        // if no order articles\n        if (count($aArticles) > 0) {\n            //adding order articles to basket\n            foreach ($aArticles as $oArticle) {\n                $aSel = isset($oArticle->oxorderarticles__oxselvariant)\n                    ? $oArticle->oxorderarticles__oxselvariant->value\n                    : null;\n                $aPersParam = isset($oArticle->oxorderarticles__oxpersparam) ? $oArticle->getPersParams() : null;\n                $oBasket->addToBasket(\n                    $oArticle->oxorderarticles__oxartid->value,\n                    $oArticle->oxorderarticles__oxamount->value,\n                    $aSel,\n                    $aPersParam\n                );\n            }\n        }\n    }\n\n    /**\n     * Get total sum from last order\n     *\n     * @return string\n     */\n    public function getTotalOrderSum()\n    {\n        $oCur = Registry::getConfig()->getActShopCurrencyObject();\n\n        return number_format((float) $this->oxorder__oxtotalordersum->value, $oCur->decimal, '.', '');\n    }\n\n    /**\n     * Returns array of plain formatted VATs stored in order\n     *\n     * @param bool $blFormatCurrency enables currency formatting\n     *\n     * @return array\n     */\n    public function getProductVats($blFormatCurrency = true)\n    {\n        $aVats = [];\n        if ($this->oxorder__oxartvat1->value) {\n            $aVats[(int)$this->oxorder__oxartvat1->value] = $this->oxorder__oxartvatprice1->value;\n        }\n        if ($this->oxorder__oxartvat2->value) {\n            $aVats[(int)$this->oxorder__oxartvat2->value] = $this->oxorder__oxartvatprice2->value;\n        }\n\n        if ($blFormatCurrency) {\n            $oLang = Registry::getLang();\n            $oCur = Registry::getConfig()->getActShopCurrencyObject();\n            foreach ($aVats as $sKey => $dVat) {\n                $aVats[$sKey] = $oLang->formatCurrency($dVat, $oCur);\n            }\n        }\n\n        return $aVats;\n    }\n\n    /**\n     * Get billing country name from billing country id\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Field\n     */\n    public function getBillCountry()\n    {\n        if (!property_exists($this, 'oxorder__oxbillcountry')) {\n            $this->oxorder__oxbillcountry = new Field($this->getCountryTitle($this->oxorder__oxbillcountryid->value));\n        }\n\n        return $this->oxorder__oxbillcountry;\n    }\n\n    /**\n     * Get delivery country name from delivery country id\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Field\n     */\n    public function getDelCountry()\n    {\n        if (!property_exists($this, 'oxorder__oxdelcountry')) {\n            $this->oxorder__oxdelcountry = new Field($this->getCountryTitle($this->oxorder__oxdelcountryid->value));\n        }\n\n        return $this->oxorder__oxdelcountry;\n    }\n\n    /**\n     * Tells to keep old or reload delivery costs while recalculating order\n     *\n     * @param bool $blReload reload state marker\n     */\n    public function reloadDelivery($blReload)\n    {\n        $this->_blReloadDelivery = $blReload;\n    }\n\n    /**\n     * Tells to keep old or reload discount while recalculating order\n     *\n     * @param bool $blReload reload state marker\n     */\n    public function reloadDiscount($blReload)\n    {\n        $this->_blReloadDiscount = $blReload;\n    }\n\n    /**\n     * Performs order cancel process\n     */\n    public function cancelOrder()\n    {\n        $this->oxorder__oxstorno = new Field(1);\n        if ($this->save()) {\n            // canceling ordered products\n            foreach ($this->getOrderArticles() as $oOrderArticle) {\n                $oOrderArticle->cancelOrderArticle();\n            }\n        }\n    }\n\n    /**\n     * Returns actual order currency object. In case currency was not recognized\n     * due to changed name returns first shop currency object\n     *\n     * @return \\stdClass\n     */\n    public function getOrderCurrency()\n    {\n        if ($this->_oOrderCurrency === null) {\n            // setting default in case unrecognized currency was set during order\n            $aCurrencies = Registry::getConfig()->getCurrencyArray();\n            $this->_oOrderCurrency = current($aCurrencies);\n\n            foreach ($aCurrencies as $oCurr) {\n                if ($oCurr->name == $this->oxorder__oxcurrency->value) {\n                    $this->_oOrderCurrency = $oCurr;\n                    break;\n                }\n            }\n        }\n\n        return $this->_oOrderCurrency;\n    }\n\n    /**\n     * Validates order parameters like stock, delivery and payment\n     * parameters\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket basket object\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User   $oUser   order user\n     *\n     * @return null\n     */\n    public function validateOrder($oBasket, $oUser)\n    {\n        // validating stock\n        $iValidState = $this->validateStock($oBasket);\n\n        if (!$iValidState) {\n            // validating delivery\n            $iValidState = $this->validateDelivery($oBasket);\n        }\n\n        if (!$iValidState) {\n            // validating payment\n            $iValidState = $this->validatePayment($oBasket, $oUser);\n        }\n\n        if (!$iValidState) {\n            //0003110 validating delivery address, it is not be changed during checkout process\n            $iValidState = $this->validateDeliveryAddress($oUser);\n        }\n\n        if (!$iValidState) {\n            // validating minimum price\n            $iValidState = $this->validateBasket($oBasket);\n        }\n\n        if (!$iValidState) {\n            // validating vouchers\n            $iValidState = $this->validateVouchers($oBasket);\n        }\n\n        return $iValidState;\n    }\n\n    public function validateVouchers($basket)\n    {\n        $voucherIds = array_keys($basket->getVouchers());\n        foreach ($voucherIds as $voucherId) {\n            $voucher = oxNew(EshopVoucherModel::class);\n            $voucher->load($voucherId);\n            if ($voucher->getFieldData('oxorderid')) {\n                return self::ORDER_STATE_VOUCHERERROR;\n            }\n        }\n    }\n\n    /**\n     * Validates basket. Currently checks if minimum order price > basket price\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket basket object\n     *\n     * @return bool\n     */\n    public function validateBasket($oBasket)\n    {\n        return $oBasket->isBelowMinOrderPrice() ? self::ORDER_STATE_BELOWMINPRICE : null;\n    }\n\n    /**\n     * Checks if delivery address (billing or shipping) was not changed during checkout\n     * Throws exception if not available\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser user object\n     *\n     * @return int\n     */\n    public function validateDeliveryAddress($oUser)\n    {\n        $sDelAddressMD5 = Registry::getRequest()->getRequestEscapedParameter('sDeliveryAddressMD5');\n\n        $sDeliveryAddress = $oUser->getEncodedDeliveryAddress();\n\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\RequiredAddressFields $oRequiredAddressFields */\n        $oRequiredAddressFields = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\RequiredAddressFields::class);\n\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\RequiredFieldsValidator $oFieldsValidator */\n        $oFieldsValidator = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\RequiredFieldsValidator::class);\n        $oFieldsValidator->setRequiredFields($oRequiredAddressFields->getBillingFields());\n        $blFieldsValid = $oFieldsValidator->validateFields($oUser);\n\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\Address $oDeliveryAddress */\n        $oDeliveryAddress = $this->getDelAddressInfo();\n        if ($blFieldsValid && $oDeliveryAddress) {\n            $sDeliveryAddress .= $oDeliveryAddress->getEncodedDeliveryAddress();\n\n            $oFieldsValidator->setRequiredFields($oRequiredAddressFields->getDeliveryFields());\n            $blFieldsValid = $oFieldsValidator->validateFields($oDeliveryAddress);\n        }\n\n        $iState = 0;\n        if ($sDelAddressMD5 != $sDeliveryAddress || !$blFieldsValid) {\n            $iState = self::ORDER_STATE_INVALIDDELADDRESSCHANGED;\n        }\n\n        return $iState;\n    }\n\n\n    /**\n     * Checks if delivery set used for current order is available and active.\n     * Throws exception if not available\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket basket object\n     *\n     * @return null\n     */\n    public function validateDelivery($oBasket)\n    {\n        // proceed with no delivery\n        // used for other countries\n        if ($oBasket->getPaymentId() == 'oxempty') {\n            return;\n        }\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        $masterDb = DatabaseProvider::getMaster();\n\n        $oDelSet = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\DeliverySet::class);\n        $sTable = $oDelSet->getViewName();\n\n        $sQ = \"select 1 from {$sTable} where {$sTable}.oxid = :oxid and \" . $oDelSet->getSqlActiveSnippet();\n        $params = [\n            'oxid' => $oBasket->getShippingId()\n        ];\n\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        if (!$masterDb->getOne($sQ, $params)) {\n            // throwing exception\n            return self::ORDER_STATE_INVALIDDELIVERY;\n        }\n    }\n\n    /**\n     * Checks if payment used for current order is available and active.\n     * Throws exception if not available\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket    $oBasket basket object\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User|null $oUser   user object\n     *\n     * @return null\n     */\n    public function validatePayment($oBasket, $oUser = null)\n    {\n        $paymentId = $oBasket->getPaymentId();\n\n        if (!$this->isValidPaymentId($paymentId) || !$this->isValidPayment($oBasket, $oUser)) {\n            return self::ORDER_STATE_INVALIDPAYMENT;\n        }\n    }\n\n    /**\n     * Get total net sum formatted\n     *\n     * @return string\n     */\n    public function getFormattedTotalNetSum()\n    {\n        return Registry::getLang()->formatCurrency($this->oxorder__oxtotalnetsum->value, $this->getOrderCurrency());\n    }\n\n    /**\n     * Get total brut sum formatted\n     *\n     * @return string\n     */\n    public function getFormattedTotalBrutSum()\n    {\n        return Registry::getLang()->formatCurrency($this->oxorder__oxtotalbrutsum->value, $this->getOrderCurrency());\n    }\n\n    /**\n     * Get Delivery cost sum formatted\n     *\n     * @return string\n     */\n    public function getFormattedDeliveryCost()\n    {\n        return Registry::getLang()->formatCurrency($this->oxorder__oxdelcost->value, $this->getOrderCurrency());\n    }\n\n    /**\n     * Get pay cost sum formatted\n     *\n     * @return string\n     */\n    public function getFormattedPayCost()\n    {\n        return Registry::getLang()->formatCurrency($this->oxorder__oxpaycost->value, $this->getOrderCurrency());\n    }\n\n    /**\n     * Get wrap cost sum formatted\n     *\n     * @return string\n     */\n    public function getFormattedWrapCost()\n    {\n        return Registry::getLang()->formatCurrency($this->oxorder__oxwrapcost->value, $this->getOrderCurrency());\n    }\n\n    /**\n     * Get wrap cost sum formatted\n     *\n     * @return string\n     */\n    public function getFormattedGiftCardCost()\n    {\n        return Registry::getLang()->formatCurrency($this->oxorder__oxgiftcardcost->value, $this->getOrderCurrency());\n    }\n\n    /**\n     * Get total vouchers formatted\n     *\n     * @return string\n     */\n    public function getFormattedTotalVouchers()\n    {\n        return Registry::getLang()->formatCurrency($this->oxorder__oxvoucherdiscount->value, $this->getOrderCurrency());\n    }\n\n    /**\n     * Get Discount formatted\n     *\n     * @return string\n     */\n    public function getFormattedDiscount()\n    {\n        return Registry::getLang()->formatCurrency($this->oxorder__oxdiscount->value, $this->getOrderCurrency());\n    }\n\n    /**\n     * Get formatted total sum from last order\n     *\n     * @return string\n     */\n    public function getFormattedTotalOrderSum()\n    {\n        return Registry::getLang()->formatCurrency($this->oxorder__oxtotalordersum->value, $this->getOrderCurrency());\n    }\n\n    /**\n     * Returns shipment tracking code\n     *\n     * @return string\n     */\n    public function getTrackCode()\n    {\n        return $this->oxorder__oxtrackcode->value;\n    }\n\n    /**\n     * Returns shipment tracking url if oxtrackcode and shipment tracking url are supplied\n     *\n     * @return string\n     */\n    public function getShipmentTrackingUrl()\n    {\n        if ($this->_sShipTrackUrl === null) {\n            $trackingUrl = $this->getTrackingUrl();\n            $trackingCode = $this->getTrackCode();\n            if ($trackingUrl && $trackingCode) {\n                $this->_sShipTrackUrl = str_replace('##ID##', $trackingCode, $trackingUrl);\n            }\n        }\n\n        return $this->_sShipTrackUrl;\n    }\n\n    private function getTrackingUrl(): string\n    {\n        $deliverySetTrackingUrl = $this->getDelSet()->getFieldData('oxtrackingurl');\n        return (string) ($deliverySetTrackingUrl ?: Registry::getConfig()->getConfigParam('sParcelService'));\n    }\n\n    /**\n     * Returns true if paymentId is valid.\n     *\n     * @param int $paymentId\n     *\n     * @return bool\n     */\n    private function isValidPaymentId($paymentId)\n    {\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        $masterDb = DatabaseProvider::getMaster();\n\n        $paymentModel = oxNew(EshopPayment::class);\n        $tableName = $paymentModel->getViewName();\n\n        $sql = \"\n            select\n                1 \n            from \n                {$tableName}\n            where \n                {$tableName}.oxid = :oxid\n                and {$paymentModel->getSqlActiveSnippet()}\n        \";\n\n        return (bool) $masterDb->getOne($sql, [\n            'oxid' => $paymentId\n        ]);\n    }\n\n    /**\n     * Returns true if payment is valid.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket    $basket\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User|null $oUser  user object\n     *\n     * @return bool\n     */\n    private function isValidPayment($basket, $oUser = null)\n    {\n        $paymentId = $basket->getPaymentId();\n        $paymentModel = oxNew(EshopPayment::class);\n        $paymentModel->load($paymentId);\n\n        $dynamicValues = $this->getDynamicValues();\n        $shopId = Registry::getConfig()->getShopId();\n\n        if (!$oUser) {\n            $oUser = $this->getUser();\n        }\n\n        return $paymentModel->isValidPayment(\n            $dynamicValues,\n            $shopId,\n            $oUser,\n            $basket->getPriceForPayment(),\n            $basket->getShippingId()\n        );\n    }\n\n    /**\n     * @return mixed\n     */\n    private function getDynamicValues()\n    {\n        $session = Registry::getSession();\n        $dynamicValues = $session->getVariable('dynvalue');\n\n        if (!$dynamicValues) {\n            $dynamicValues = Registry::getRequest()->getRequestParameter('dynvalue');\n        }\n\n        if (!$dynamicValues && $this->getPaymentType()) {\n            $dynamicValues = $this->getDynamicValuesFromPaymentType();\n        }\n\n        return $dynamicValues;\n    }\n\n    /**\n     * @return mixed\n     */\n    private function getDynamicValuesFromPaymentType()\n    {\n        $dynamicValues = null;\n        $dynamicValuesList = $this->getPaymentType()->getDynValues();\n\n        if (is_array($dynamicValuesList)) {\n            foreach ($dynamicValuesList as $value) {\n                $dynamicValues[$value->name] = $value->value;\n            }\n        }\n\n        return $dynamicValues;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/OrderArticle.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\Model\\BaseModel;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Application\\Model\\Contract\\ArticleInterface;\nuse OxidEsales\\Eshop\\Core\\Str;\n\n/**\n * Order article manager.\n * Performs copying of article.\n */\nclass OrderArticle extends BaseModel implements ArticleInterface\n{\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxorderarticle';\n\n    /**\n     * Persisten info\n     *\n     * @var array\n     */\n    protected $_aPersParam = null;\n\n    /**\n     * ERP status info\n     *\n     * @var array\n     */\n    protected $_aStatuses = null;\n\n    /**\n     * Order article selection list\n     *\n     * @var array\n     */\n    protected $_aOrderArticleSelList = null;\n\n    /**\n     * Order article instance\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected $_oOrderArticle = null;\n\n    /**\n     * Article instance\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected $_oArticle = null;\n\n    /**\n     * New order article marker\n     *\n     * @var bool\n     */\n    protected $_blIsNewOrderItem = false;\n\n    /**\n     * Array of fields to skip when saving\n     * Overrids oxBase variable\n     *\n     * @var array\n     */\n    protected $_aSkipSaveFields = ['oxtimestamp'];\n\n    /** @var \\OxidEsales\\Eshop\\Application\\Model\\Order */\n    private $order;\n\n    /**\n     * Class constructor, initiates class constructor (parent::oxbase()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxorderarticles');\n    }\n\n    /**\n     * Copies passed to method product into $this.\n     *\n     * @param object $oProduct product to copy\n     */\n    public function copyThis($oProduct)\n    {\n        $aObjectVars = get_object_vars($oProduct);\n\n        foreach ($aObjectVars as $sName => $sValue) {\n            if (isset($oProduct->$sName->value)) {\n                $sFieldName = preg_replace('/oxarticles__/', 'oxorderarticles__', $sName);\n                if ($sFieldName != \"oxorderarticles__oxtimestamp\") {\n                    $this->$sFieldName = $oProduct->$sName;\n                }\n                // formatting view\n                if (!Registry::getConfig()->getConfigParam('blSkipFormatConversion')) {\n                    if ($sFieldName == \"oxorderarticles__oxinsert\") {\n                        Registry::getUtilsDate()->convertDBDate($this->$sFieldName, true);\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * Assigns DB field values to object fields.\n     */\n    public function assign($dbRecord)\n    {\n        parent::assign($dbRecord);\n        $this->setArticleParams();\n    }\n\n    /**\n     * Performs stock modification for current order article. Additionally\n     * executes changeable article onChange/updateSoldAmount methods to\n     * update chained data\n     *\n     * @param double $dAddAmount           amount which will be substracled from value in db\n     * @param bool   $blAllowNegativeStock amount allow or not negative stock value\n     */\n    public function updateArticleStock($dAddAmount, $blAllowNegativeStock = false)\n    {\n        // TODO: use oxarticle reduceStock\n        // decrement stock if there is any\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $oArticle->load($this->oxorderarticles__oxartid->value);\n        $oArticle->beforeUpdate();\n\n        if (Registry::getConfig()->getConfigParam('blUseStock')) {\n            // get real article stock count\n            $iStockCount = $this->getArtStock($dAddAmount, $blAllowNegativeStock);\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n            $oArticle->oxarticles__oxstock = new \\OxidEsales\\Eshop\\Core\\Field($iStockCount);\n            $oDb->execute('update oxarticles set oxarticles.oxstock = :oxstock where oxarticles.oxid = :oxid', [\n                'oxstock' => $iStockCount,\n                'oxid' => $this->oxorderarticles__oxartid->value\n            ]);\n            $oArticle->onChange(ACTION_UPDATE_STOCK);\n        }\n\n        //update article sold amount\n        $oArticle->updateSoldAmount($dAddAmount * (-1));\n    }\n\n    /**\n     * Adds or substracts defined amount passed by param from arcticle stock\n     *\n     * @param double $dAddAmount           amount which will be added/substracled from value in db\n     * @param bool   $blAllowNegativeStock allow/disallow negative stock value\n     *\n     * @return double\n     */\n    protected function getArtStock($dAddAmount = 0, $blAllowNegativeStock = false)\n    {\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        $masterDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster();\n\n        // #1592A. must take real value\n        $sQ = 'select oxstock from oxarticles \n            where oxid = :oxid';\n        $iStockCount = (float) $masterDb->getOne($sQ, [\n            'oxid' => $this->oxorderarticles__oxartid->value\n        ]);\n\n        $iStockCount += $dAddAmount;\n\n        // #1592A. calculating according new stock option\n        if (!$blAllowNegativeStock && $iStockCount < 0) {\n            $iStockCount = 0;\n        }\n\n        return $iStockCount;\n    }\n\n    /**\n     * Order persistent data getter\n     *\n     * @return array\n     */\n    public function getPersParams()\n    {\n        if ($this->_aPersParam != null) {\n            return $this->_aPersParam;\n        }\n\n        if ($this->getFieldData('oxpersparam')) {\n            $this->_aPersParam = unserialize($this->getFieldData('oxpersparam'));\n        }\n\n        return $this->_aPersParam;\n    }\n\n    /**\n     * Order persistent params setter\n     *\n     * @param array $aParams array of params\n     */\n    public function setPersParams($aParams)\n    {\n        $this->_aPersParam = $aParams;\n\n        // serializing persisten info stored while ordering\n        $this->oxorderarticles__oxpersparam = new \\OxidEsales\\Eshop\\Core\\Field(serialize($aParams), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n    }\n\n    /**\n     * Sets data field value\n     *\n     * @param string $sFieldName index OR name (eg. 'oxarticles__oxtitle') of a data field to set\n     * @param string $sValue     value of data field\n     * @param int    $iDataType  field type\n     *\n     * @return null\n     */\n    protected function setFieldData($sFieldName, $sValue, $iDataType = \\OxidEsales\\Eshop\\Core\\Field::T_TEXT)\n    {\n        $sFieldName = strtolower($sFieldName);\n        switch ($sFieldName) {\n            case 'oxpersparam':\n            case 'oxorderarticles__oxpersparam':\n            case 'oxerpstatus':\n            case 'oxorderarticles__oxerpstatus':\n            case 'oxtitle':\n            case 'oxorderarticles__oxtitle':\n                $iDataType = \\OxidEsales\\Eshop\\Core\\Field::T_RAW;\n                break;\n        }\n\n        return parent::setFieldData($sFieldName, $sValue, $iDataType);\n    }\n\n    /**\n     * Executes \\OxidEsales\\Eshop\\Application\\Model\\OrderArticle::load() and returns its result\n     *\n     * @param int    $iLanguage language id\n     * @param string $sOxid     order article id\n     *\n     * @return bool\n     */\n    public function loadInLang($iLanguage, $sOxid)\n    {\n        return $this->load($sOxid);\n    }\n\n    /**\n     * Returns ordered article id, implements iBaseArticle interface getter method\n     *\n     * @return string\n     */\n    public function getProductId()\n    {\n        return $this->oxorderarticles__oxartid->value;\n    }\n\n    /**\n     * Returns product parent id\n     *\n     * @return string\n     */\n    public function getParentId()\n    {\n        // when this field will be introduced there will be no need to load from real article\n        if (isset($this->oxorderarticles__oxartparentid) && $this->oxorderarticles__oxartparentid->value !== false) {\n            return $this->oxorderarticles__oxartparentid->value;\n        }\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $sQ = \"select oxparentid from \" . $oArticle->getViewName() . \" \n            where oxid = :oxid\";\n        $this->oxarticles__oxparentid = new \\OxidEsales\\Eshop\\Core\\Field($oDb->getOne($sQ, [\n            'oxid' => $this->getProductId()\n        ]));\n\n        return $this->oxarticles__oxparentid->value;\n    }\n\n    /**\n     * Sets article parameters to current object, so this object can be used for basket calculation\n     */\n    protected function setArticleParams()\n    {\n        // creating needed fields\n        $this->oxarticles__oxstock = $this->oxorderarticles__oxamount;\n        $this->oxarticles__oxtitle = $this->oxorderarticles__oxtitle;\n        $this->oxarticles__oxwidth = $this->oxorderarticles__oxwidth;\n        $this->oxarticles__oxlength = $this->oxorderarticles__oxlength;\n        $this->oxarticles__oxheight = $this->oxorderarticles__oxheight;\n        $this->oxarticles__oxweight = $this->oxorderarticles__oxweight;\n        $this->oxarticles__oxsubclass = $this->oxorderarticles__oxsubclass;\n        $this->oxarticles__oxartnum = $this->oxorderarticles__oxartnum;\n        $this->oxarticles__oxshortdesc = $this->oxorderarticles__oxshortdesc;\n\n        $this->oxarticles__oxvat = $this->oxorderarticles__oxvat;\n        $this->oxarticles__oxprice = $this->oxorderarticles__oxprice;\n        $this->oxarticles__oxbprice = $this->oxorderarticles__oxbprice;\n\n        $this->oxarticles__oxthumb = $this->oxorderarticles__oxthumb;\n        $this->oxarticles__oxpic1 = $this->oxorderarticles__oxpic1;\n        $this->oxarticles__oxpic2 = $this->oxorderarticles__oxpic2;\n        $this->oxarticles__oxpic3 = $this->oxorderarticles__oxpic3;\n        $this->oxarticles__oxpic4 = $this->oxorderarticles__oxpic4;\n        $this->oxarticles__oxpic5 = $this->oxorderarticles__oxpic5;\n\n        $this->oxarticles__oxfile = $this->oxorderarticles__oxfile;\n        $this->oxarticles__oxdelivery = $this->oxorderarticles__oxdelivery;\n        $this->oxarticles__oxissearch = $this->oxorderarticles__oxissearch;\n        $this->oxarticles__oxfolder = $this->oxorderarticles__oxfolder;\n        $this->oxarticles__oxtemplate = $this->oxorderarticles__oxtemplate;\n        $this->oxarticles__oxexturl = $this->oxorderarticles__oxexturl;\n        $this->oxarticles__oxurlimg = $this->oxorderarticles__oxurlimg;\n        $this->oxarticles__oxurldesc = $this->oxorderarticles__oxurldesc;\n        $this->oxarticles__oxshopid = $this->oxorderarticles__oxordershopid;\n        $this->oxarticles__oxquestionemail = $this->oxorderarticles__oxquestionemail;\n        $this->oxarticles__oxsearchkeys = $this->oxorderarticles__oxsearchkeys;\n    }\n\n    /**\n     * Returns true, implements iBaseArticle interface method\n     *\n     * @param double $dAmount         stock to check\n     * @param double $dArtStockAmount stock amount\n     *\n     * @return bool\n     */\n    public function checkForStock($dAmount, $dArtStockAmount = 0)\n    {\n        return true;\n    }\n\n    /**\n     * Loads, caches and returns real order article instance. If article is not\n     * available (deleted from db or so) false is returned\n     *\n     * @param string $sArticleId article id (optional, is not passed oxorderarticles__oxartid will be used)\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article|false\n     */\n    protected function getOrderArticle($sArticleId = null)\n    {\n        if ($this->_oOrderArticle === null) {\n            $this->_oOrderArticle = false;\n\n            $sArticleId = $sArticleId ? $sArticleId : $this->getProductId();\n            $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            $oArticle->setLoadParentData(true);\n            if ($oArticle->load($sArticleId)) {\n                $this->_oOrderArticle = $oArticle;\n            }\n        }\n\n        return $this->_oOrderArticle;\n    }\n\n    /**\n     * Returns article select lists, implements iBaseArticle interface method\n     *\n     * @param string $sKeyPrefix prefix (not used)\n     *\n     * @return array\n     */\n    public function getSelectLists($sKeyPrefix = null)\n    {\n        $aSelLists = [];\n        if ($oArticle = $this->getOrderArticle()) {\n            $aSelLists = $oArticle->getSelectLists();\n        }\n\n        return $aSelLists;\n    }\n\n    /**\n     * Returns order article selection list array\n     *\n     * @param string $sArtId           ordered article id [optional]\n     * @param string $sOrderArtSelList order article selection list [optional]\n     *\n     * @return array\n     */\n    public function getOrderArticleSelectList($sArtId = null, $sOrderArtSelList = null)\n    {\n        if ($this->_aOrderArticleSelList === null) {\n            $sOrderArtSelList = $sOrderArtSelList ? $sOrderArtSelList : $this->oxorderarticles__oxselvariant->value;\n\n            $sOrderArtSelList = explode(' || ', $sOrderArtSelList)[0];\n\n            $aRet = [];\n\n            if ($oArticle = $this->getOrderArticle($sArtId)) {\n                $aList = explode(\", \", $sOrderArtSelList);\n                $oStr = Str::getStr();\n\n                $aArticleSelList = $oArticle->getSelectLists();\n\n                //formatting temporary list array from string\n                if (count($aArticleSelList) > 0) {\n                    foreach ($aList as $sList) {\n                        if ($sList) {\n                            $aVal = explode(\":\", $sList);\n                            if (isset($aVal[0]) && isset($aVal[1])) {\n                                $sOrderArtListTitle = $oStr->strtolower(trim($aVal[0]));\n                                $sOrderArtSelValue = $oStr->strtolower(trim($aVal[1]));\n\n                                //checking article list for matches with article list stored in oxOrderItem\n                                $iSelListNum = 0;\n                                foreach ($aArticleSelList as $aSelect) {\n                                    //check if selects titles are equal\n\n                                    if ($oStr->strtolower($aSelect['name']) == $sOrderArtListTitle) {\n                                        //try to find matching select items value\n                                        $iSelValueNum = 0;\n                                        foreach ($aSelect as $oSel) {\n                                            if ($oStr->strtolower($oSel->name) == $sOrderArtSelValue) {\n                                                // found, adding to return array\n                                                $aRet[$iSelListNum] = $iSelValueNum;\n                                                break;\n                                            }\n                                            //next article list item\n                                            $iSelValueNum++;\n                                        }\n                                    }\n                                    //next article list\n                                    $iSelListNum++;\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n\n            $this->_aOrderArticleSelList = $aRet;\n        }\n\n        return $this->_aOrderArticleSelList;\n    }\n\n    /**\n     * Returns basket order article price\n     *\n     * @param double                                     $dAmount  basket item amount\n     * @param array                                      $aSelList chosen selection list\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket  basket\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getBasketPrice($dAmount, $aSelList, $oBasket)\n    {\n        $oArticle = $this->getOrderArticle();\n\n        if ($oArticle) {\n            return $oArticle->getBasketPrice($dAmount, $aSelList, $oBasket);\n        } else {\n            return $this->getPrice();\n        }\n    }\n\n    /**\n     * Returns false, implements iBaseArticle interface method\n     *\n     * @return bool\n     */\n    public function skipDiscounts()\n    {\n        return false;\n    }\n\n    /**\n     * Returns empty array, implements iBaseArticle interface getter method\n     *\n     * @param bool $blActCats   select categories if all parents are active\n     * @param bool $blSkipCache force reload or not (default false - no reload)\n     *\n     * @return array\n     */\n    public function getCategoryIds($blActCats = false, $blSkipCache = false)\n    {\n        $aCatIds = [];\n        if ($oOrderArticle = $this->getOrderArticle()) {\n            $aCatIds = $oOrderArticle->getCategoryIds($blActCats, $blSkipCache);\n        }\n\n        return $aCatIds;\n    }\n\n    /**\n     * Returns current session language id\n     *\n     * @return int\n     */\n    public function getLanguage()\n    {\n        return Registry::getLang()->getBaseLanguage();\n    }\n\n    /**\n     * Returns base article price from database\n     *\n     * @param double $dAmount article amount. Default is 1\n     *\n     * @return object\n     */\n    public function getBasePrice($dAmount = 1)\n    {\n        return $this->getPrice();\n    }\n\n    /**\n     * Returns order article unit price\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getPrice()\n    {\n        $oBasePrice = oxNew(\\OxidEsales\\Eshop\\Core\\Price::class);\n        // prices in db are ONLY brutto\n        $oBasePrice->setBruttoPriceMode();\n        $oBasePrice->setVat($this->oxorderarticles__oxvat->value);\n        $oBasePrice->setPrice($this->oxorderarticles__oxbprice->value);\n\n        return $oBasePrice;\n    }\n\n    /**\n     * Marks object as new order item (this marker useful when recalculating stocks after order recalculation)\n     *\n     * @param bool $blIsNew marker value - TRUE if this item is newy added to order\n     */\n    public function setIsNewOrderItem($blIsNew)\n    {\n        $this->_blIsNewOrderItem = $blIsNew;\n    }\n\n    /**\n     * Returns TRUE if current order article is newly added to order\n     *\n     * @return bool\n     */\n    public function isNewOrderItem()\n    {\n        return $this->_blIsNewOrderItem;\n    }\n\n    /**\n     * Ordered article stock setter. Before setting new stock value additionally checks for\n     * original article stock value. Is stock values <= preferred, adjusts order stock according\n     * to it\n     *\n     * @param int $iNewAmount new ordered items amount\n     */\n    public function setNewAmount($iNewAmount)\n    {\n        if ($iNewAmount >= 0) {\n            // to update stock we must first check if it is possible - article exists?\n            $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            if ($oArticle->load($this->oxorderarticles__oxartid->value)) {\n                // updating stock info\n                $iStockChange = $iNewAmount - $this->oxorderarticles__oxamount->value;\n                if ($iStockChange > 0 && ($iOnStock = $oArticle->checkForStock($iStockChange)) !== false) {\n                    if ($iOnStock !== true) {\n                        $iStockChange = $iOnStock;\n                        $iNewAmount = $this->oxorderarticles__oxamount->value + $iStockChange;\n                    }\n                }\n\n                $this->updateArticleStock($iStockChange * -1, Registry::getConfig()->getConfigParam('blAllowNegativeStock'));\n\n                // updating self\n                $this->oxorderarticles__oxamount = new \\OxidEsales\\Eshop\\Core\\Field($iNewAmount, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n                $this->save();\n            }\n        }\n    }\n\n    /**\n     * Returns true if object is derived from oxorderarticle class\n     *\n     * @return bool\n     */\n    public function isOrderArticle()\n    {\n        return true;\n    }\n\n    /**\n     * Sets order article storno value to 1 and if stock control is on -\n     * restores previous oxarticle stock state\n     */\n    public function cancelOrderArticle()\n    {\n        if ($this->oxorderarticles__oxstorno->value == 0) {\n            $myConfig = Registry::getConfig();\n            $this->oxorderarticles__oxstorno = new \\OxidEsales\\Eshop\\Core\\Field(1);\n            if ($this->save()) {\n                $this->updateArticleStock($this->oxorderarticles__oxamount->value, $myConfig->getConfigParam('blAllowNegativeStock'));\n            }\n        }\n    }\n\n    /**\n     * Deletes order article object. If deletion succeded - updates\n     * article stock information. Returns deletion status\n     *\n     * @param string $oxid Article id\n     *\n     * @return bool\n     */\n    public function delete($oxid = null)\n    {\n        $isDeleted = parent::delete($oxid);\n        if ($isDeleted && (int)$this->getFieldData('oxstorno') !== 1) {\n            $this->updateArticleStock(\n                $this->getFieldData('oxamount'),\n                Registry::getConfig()->getConfigParam('blAllowNegativeStock')\n            );\n        }\n\n        return $isDeleted;\n    }\n\n    /**\n     * Saves order article object. If saving succeded - updates\n     * article stock information if \\OxidEsales\\Eshop\\Application\\Model\\OrderArticle::isNewOrderItem()\n     * returns TRUE. Returns saving status\n     *\n     * @return bool\n     */\n    public function save()\n    {\n        // ordered articles\n        if (($blSave = parent::save()) && $this->isNewOrderItem()) {\n            $myConfig = Registry::getConfig();\n            if (\n                $myConfig->getConfigParam('blUseStock') &&\n                $myConfig->getConfigParam('blPsBasketReservationEnabled')\n            ) {\n                $session = Registry::getSession();\n                $session->getBasketReservations()\n                    ->commitArticleReservation(\n                        $this->oxorderarticles__oxartid->value,\n                        $this->oxorderarticles__oxamount->value\n                    );\n            } else {\n                $this->updateArticleStock($this->oxorderarticles__oxamount->value * (-1), $myConfig->getConfigParam('blAllowNegativeStock'));\n            }\n\n            // seting downloadable products article files\n            $this->setOrderFiles();\n\n            // marking object as \"non new\" disable further stock changes\n            $this->setIsNewOrderItem(false);\n        }\n\n        return $blSave;\n    }\n\n    /**\n     * get used wrapping\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Wrapping\n     */\n    public function getWrapping()\n    {\n        if ($this->oxorderarticles__oxwrapid->value) {\n            $oWrapping = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Wrapping::class);\n            if ($oWrapping->load($this->oxorderarticles__oxwrapid->value)) {\n                return $oWrapping;\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * Returns true if ordered product is bundle\n     *\n     * @return bool\n     */\n    public function isBundle()\n    {\n        return (bool) $this->oxorderarticles__oxisbundle->value;\n    }\n\n    /**\n     * Get Total brut price formated\n     *\n     * @return string\n     */\n    public function getTotalBrutPriceFormated()\n    {\n        $oLang = Registry::getLang();\n        $oOrder = $this->getOrder();\n        $oCurrency = Registry::getConfig()->getCurrencyObject($oOrder->oxorder__oxcurrency->value);\n\n        return $oLang->formatCurrency($this->oxorderarticles__oxbrutprice->value, $oCurrency);\n    }\n\n    /**\n     * Get  brut price formated\n     *\n     * @return string\n     */\n    public function getBrutPriceFormated()\n    {\n        $oLang = Registry::getLang();\n        $oOrder = $this->getOrder();\n        $oCurrency = Registry::getConfig()->getCurrencyObject($oOrder->oxorder__oxcurrency->value);\n\n        return $oLang->formatCurrency($this->oxorderarticles__oxbprice->value, $oCurrency);\n    }\n\n    /**\n     * Get Net price formated\n     *\n     * @return string\n     */\n    public function getNetPriceFormated()\n    {\n        $oLang = Registry::getLang();\n        $oOrder = $this->getOrder();\n        $oCurrency = Registry::getConfig()->getCurrencyObject($oOrder->oxorder__oxcurrency->value);\n\n        return $oLang->formatCurrency($this->oxorderarticles__oxnprice->value, $oCurrency);\n    }\n\n    /**\n     * Returns Order object that the article belongs to.\n     *\n     * @return null|\\OxidEsales\\Eshop\\Application\\Model\\Order\n     */\n    public function getOrder()\n    {\n        if ($this->oxorderarticles__oxorderid->value) {\n            if ($this->order !== null && $this->order->getId() === $this->oxorderarticles__oxorderid->value) {\n                return $this->order;\n            }\n\n            $oOrder = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Order::class);\n            if ($oOrder->load($this->oxorderarticles__oxorderid->value)) {\n                return $this->order = $oOrder;\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * Sets article creation date\n     * (\\OxidEsales\\Eshop\\Application\\Model\\OrderArticle::oxorderarticles__oxtimestamp). Then executes parent method\n     * parent::_insert() and returns insertion status.\n     *\n     * @return bool\n     */\n    protected function insert()\n    {\n        $iInsertTime = time();\n        $now = date('Y-m-d H:i:s', $iInsertTime);\n        $this->oxorderarticles__oxtimestamp = new \\OxidEsales\\Eshop\\Core\\Field($now);\n\n        return parent::insert();\n    }\n\n\n    /**\n     * Set article\n     *\n     * @param object $oArticle - article object\n     */\n    public function setArticle($oArticle)\n    {\n        $this->_oArticle = $oArticle;\n    }\n\n    /**\n     * Get article\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    public function getArticle()\n    {\n        if ($this->_oArticle === null) {\n            $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            $oArticle->load($this->oxorderarticles__oxartid->value);\n            $this->_oArticle = $oArticle;\n        }\n\n        return $this->_oArticle;\n    }\n\n\n    /**\n     * Set order files\n     */\n    public function setOrderFiles()\n    {\n        $oArticle = $this->getArticle();\n\n        if ($oArticle->oxarticles__oxisdownloadable->value) {\n            $oConfig = Registry::getConfig();\n            $sOrderId = $this->oxorderarticles__oxorderid->value;\n            $sOrderArticleId = $this->getId();\n            $sShopId = $oConfig->getShopId();\n\n            $oUser = $oConfig->getUser();\n\n            $oFiles = $oArticle->getArticleFiles(true);\n\n            if ($oFiles) {\n                foreach ($oFiles as $oFile) {\n                    $oOrderFile = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\OrderFile::class);\n                    $oOrderFile->setOrderId($sOrderId);\n                    $oOrderFile->setOrderArticleId($sOrderArticleId);\n                    $oOrderFile->setShopId($sShopId);\n                    $iMaxDownloadCount = (!empty($oUser) && !$oUser->hasAccount()) ? $oFile->getMaxUnregisteredDownloadsCount() : $oFile->getMaxDownloadsCount();\n                    $oOrderFile->setFile(\n                        $oFile->oxfiles__oxfilename->value,\n                        $oFile->getId(),\n                        $iMaxDownloadCount * $this->oxorderarticles__oxamount->value,\n                        $oFile->getLinkExpirationTime(),\n                        $oFile->getDownloadExpirationTime()\n                    );\n\n                    $oOrderFile->save();\n                }\n            }\n        }\n    }\n\n    /**\n     * Get Total brut price formated\n     *\n     * @return string\n     */\n    public function getTotalNetPriceFormated()\n    {\n        $oLang = Registry::getLang();\n        $oOrder = $this->getOrder();\n        $oCurrency = Registry::getConfig()->getCurrencyObject($oOrder->oxorder__oxcurrency->value);\n\n        return $oLang->formatCurrency($this->oxorderarticles__oxnetprice->value, $oCurrency);\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/OrderArticleList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\n\n/**\n * Order article list manager.\n */\nclass OrderArticleList extends \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n{\n    /**\n     * Class constructor, initiates class constructor (parent::oxbase()).\n     */\n    public function __construct()\n    {\n        parent::__construct('oxorderarticle');\n    }\n\n    /**\n     * Copies passed to method product into $this.\n     *\n     * @param string $sOxId object id\n     *\n     * @return null\n     */\n    public function loadOrderArticlesForUser($sOxId)\n    {\n        if (!$sOxId) {\n            $this->clear();\n\n            return;\n        }\n\n        $sSelect = \"SELECT oxorderarticles.* FROM oxorder \";\n        $sSelect .= \"left join oxorderarticles on oxorderarticles.oxorderid = oxorder.oxid \";\n        $sSelect .= \"left join oxarticles on oxorderarticles.oxartid = oxarticles.oxid \";\n        $sSelect .= \"WHERE oxorder.oxuserid = :oxuserid\";\n\n        $this->selectString($sSelect, [\n            'oxuserid' => $sOxId\n        ]);\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/OrderFile.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxRegistry;\nuse oxField;\n\n/**\n * Article file link manager.\n */\nclass OrderFile extends \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n{\n    /**\n     * Object core table name\n     *\n     * @var string\n     */\n    protected $_sCoreTable = 'oxorderfiles';\n\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxorderfile';\n\n\n    /**\n     * Initialises the instance\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxorderfiles');\n    }\n\n    /**\n     * reset order files downloadcount and / or expration times\n     */\n    public function reset()\n    {\n        $oArticleFile = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\File::class);\n        $oArticleFile->load($this->oxorderfiles__oxfileid->value);\n        if (file_exists($oArticleFile->getStoreLocation())) {\n            $this->oxorderfiles__oxdownloadcount = new \\OxidEsales\\Eshop\\Core\\Field(0);\n            $this->oxorderfiles__oxfirstdownload = new \\OxidEsales\\Eshop\\Core\\Field('0000-00-00 00:00:00');\n            $this->oxorderfiles__oxlastdownload = new \\OxidEsales\\Eshop\\Core\\Field('0000-00-00 00:00:00');\n            $iExpirationTime = $this->oxorderfiles__oxlinkexpirationtime->value * 3600;\n            $sNow = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime();\n            $sDate = date('Y-m-d H:i:s', $sNow + $iExpirationTime);\n            $this->oxorderfiles__oxvaliduntil = new \\OxidEsales\\Eshop\\Core\\Field($sDate);\n            $this->oxorderfiles__oxresetcount = new \\OxidEsales\\Eshop\\Core\\Field($this->oxorderfiles__oxresetcount->value + 1);\n        }\n    }\n\n    /**\n     * set order id\n     *\n     * @param string $sOrderId - order id\n     */\n    public function setOrderId($sOrderId)\n    {\n        $this->oxorderfiles__oxorderid = new \\OxidEsales\\Eshop\\Core\\Field($sOrderId);\n    }\n\n    /**\n     * set order article id\n     *\n     * @param string $sOrderArticleId - order article id\n     */\n    public function setOrderArticleId($sOrderArticleId)\n    {\n        $this->oxorderfiles__oxorderarticleid = new \\OxidEsales\\Eshop\\Core\\Field($sOrderArticleId);\n    }\n\n    /**\n     * set shop id\n     *\n     * @param string $sShopId - shop id\n     */\n    public function setShopId($sShopId)\n    {\n        $this->oxorderfiles__oxshopid = new \\OxidEsales\\Eshop\\Core\\Field($sShopId);\n    }\n\n    /**\n     * Set file and download options\n     *\n     * @param string $sFileName               file name\n     * @param string $sFileId                 file id\n     * @param int    $iMaxDownloadCounts      max download count\n     * @param int    $iExpirationTime         main download time after order in times\n     * @param int    $iExpirationDownloadTime download time after first download in hours\n     */\n    public function setFile($sFileName, $sFileId, $iMaxDownloadCounts, $iExpirationTime, $iExpirationDownloadTime)\n    {\n        $sNow = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime();\n        $sDate = date('Y-m-d G:i', $sNow + $iExpirationTime * 3600);\n\n        $this->oxorderfiles__oxfileid = new \\OxidEsales\\Eshop\\Core\\Field($sFileId);\n        $this->oxorderfiles__oxfilename = new \\OxidEsales\\Eshop\\Core\\Field($sFileName);\n        $this->oxorderfiles__oxmaxdownloadcount = new \\OxidEsales\\Eshop\\Core\\Field($iMaxDownloadCounts);\n        $this->oxorderfiles__oxlinkexpirationtime = new \\OxidEsales\\Eshop\\Core\\Field($iExpirationTime);\n        $this->oxorderfiles__oxdownloadexpirationtime = new \\OxidEsales\\Eshop\\Core\\Field($iExpirationDownloadTime);\n        $this->oxorderfiles__oxvaliduntil = new \\OxidEsales\\Eshop\\Core\\Field($sDate);\n    }\n\n    /**\n     * Returns downloadable file size in bytes.\n     *\n     * @return int\n     */\n    public function getFileSize()\n    {\n        $oFile = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\File::class);\n        $oFile->load($this->oxorderfiles__oxfileid->value);\n\n        return $oFile->getSize();\n    }\n\n    /**\n     * returns long name\n     *\n     * @param string $sFieldName - field name\n     *\n     * @return string\n     */\n    protected function getFieldLongName($sFieldName)\n    {\n        $aFieldNames = [\n            'oxorderfiles__oxarticletitle',\n            'oxorderfiles__oxarticleartnum',\n            'oxorderfiles__oxordernr',\n            'oxorderfiles__oxorderdate',\n            'oxorderfiles__oxispaid',\n            'oxorderfiles__oxpurchasedonly'\n        ];\n\n        if (in_array($sFieldName, $aFieldNames)) {\n            return $sFieldName;\n        }\n\n        return parent::getFieldLongName($sFieldName);\n    }\n\n    /**\n     * Checks if order file is still available to download\n     *\n     * @return bool\n     */\n    public function isValid()\n    {\n        if (!$this->oxorderfiles__oxmaxdownloadcount->value || ($this->oxorderfiles__oxdownloadcount->value < $this->oxorderfiles__oxmaxdownloadcount->value)) {\n            if (!$this->oxorderfiles__oxlinkexpirationtime->value && !$this->oxorderfiles__oxdownloadxpirationtime->value) {\n                return true;\n            } else {\n                $sNow = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime();\n                $iTimestamp = strtotime($this->oxorderfiles__oxvaliduntil->value);\n                if (!$iTimestamp || ($iTimestamp > $sNow)) {\n                    return true;\n                }\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * returns state payed or not the order\n     *\n     * @return bool\n     */\n    public function isPaid()\n    {\n        return $this->oxorderfiles__oxispaid->value;\n    }\n\n    /**\n     * returns date ant time\n     *\n     * @return bool\n     */\n    public function getValidUntil()\n    {\n        return substr($this->oxorderfiles__oxvaliduntil->value, 0, 16);\n    }\n\n    /**\n     * returns date ant time\n     *\n     * @return bool\n     */\n    public function getLeftDownloadCount()\n    {\n        $iLeft = $this->oxorderfiles__oxmaxdownloadcount->value - $this->oxorderfiles__oxdownloadcount->value;\n        if ($iLeft < 0) {\n            $iLeft = 0;\n        }\n\n        return $iLeft;\n    }\n\n    /**\n     * Checks if download link is valid, changes count, if first download changes valid until\n     *\n     * @return bool\n     */\n    public function processOrderFile()\n    {\n        if ($this->isValid()) {\n            //first download\n            if (!$this->oxorderfiles__oxdownloadcount->value) {\n                $this->oxorderfiles__oxdownloadcount = new \\OxidEsales\\Eshop\\Core\\Field(1);\n\n                $iExpirationTime = $this->oxorderfiles__oxdownloadexpirationtime->value * 3600;\n                $iTime = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime();\n                $this->oxorderfiles__oxvaliduntil = new \\OxidEsales\\Eshop\\Core\\Field(date('Y-m-d H:i:s', $iTime + $iExpirationTime));\n\n                $this->oxorderfiles__oxfirstdownload = new \\OxidEsales\\Eshop\\Core\\Field(date('Y-m-d H:i:s', $iTime));\n                $this->oxorderfiles__oxlastdownload = new \\OxidEsales\\Eshop\\Core\\Field(date('Y-m-d H:i:s', $iTime));\n            } else {\n                $this->oxorderfiles__oxdownloadcount = new \\OxidEsales\\Eshop\\Core\\Field($this->oxorderfiles__oxdownloadcount->value + 1);\n\n                $iTime = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime();\n                $this->oxorderfiles__oxlastdownload = new \\OxidEsales\\Eshop\\Core\\Field(date('Y-m-d H:i:s', $iTime));\n            }\n            $this->save();\n\n            return $this->oxorderfiles__oxfileid->value;\n        }\n\n        return false;\n    }\n\n    /**\n     * Gets field id.\n     *\n     * @return mixed\n     */\n    public function getFileId()\n    {\n        return $this->oxorderfiles__oxfileid->value;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/OrderFileList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\n/**\n * Article file link manager.\n */\nclass OrderFileList extends \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n{\n    /**\n     * List Object class name\n     *\n     * @var string\n     */\n    protected $_sObjectsInListName = 'oxorderfile';\n\n    /**\n     * Returns orders\n     *\n     * @param string $sUserId - user id\n     */\n    public function loadUserFiles($sUserId)\n    {\n        $oOrderFile = $this->getBaseObject();\n        $sFields = $oOrderFile->getSelectFields();\n\n        $oOrderFile->addFieldName('oxorderfiles__oxarticletitle');\n        $oOrderFile->addFieldName('oxorderfiles__oxarticleartnum');\n        $oOrderFile->addFieldName('oxorderfiles__oxordernr');\n        $oOrderFile->addFieldName('oxorderfiles__oxorderdate');\n\n        $sSql = \"SELECT \" . $sFields . \" ,\n                      `oxorderarticles`.`oxtitle` AS `oxorderfiles__oxarticletitle`,\n                      `oxorderarticles`.`oxartnum` AS `oxorderfiles__oxarticleartnum`,\n                      `oxfiles`.`oxpurchasedonly` AS `oxorderfiles__oxpurchasedonly`,\n                      `oxorder`.`oxordernr` AS `oxorderfiles__oxordernr`,\n                      `oxorder`.`oxorderdate` AS `oxorderfiles__oxorderdate`,\n                      IF( `oxorder`.`oxpaid` != '0000-00-00 00:00:00', 1, 0 ) AS `oxorderfiles__oxispaid`\n                    FROM `oxorderfiles`\n                        LEFT JOIN `oxorderarticles` ON `oxorderarticles`.`oxid` = `oxorderfiles`.`oxorderarticleid`\n                        LEFT JOIN `oxfiles` ON `oxfiles`.`oxid` = `oxorderfiles`.`oxfileid`\n                        LEFT JOIN `oxorder` ON `oxorder`.`oxid` = `oxorderfiles`.`oxorderid`\n                    WHERE `oxorder`.`oxuserid` = :oxuserid\n                        AND `oxorderfiles`.`oxshopid` = :oxshopid\n                        AND `oxorder`.`oxstorno` = 0\n                        AND `oxorderarticles`.`oxstorno` = 0\n                    ORDER BY `oxorder`.`oxordernr`\";\n\n        $this->selectString($sSql, [\n            'oxuserid' => $sUserId,\n            'oxshopid' => \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId()\n        ]);\n    }\n\n    /**\n     * Returns oxorderfiles list\n     *\n     * @param string $sOrderId - order id\n     */\n    public function loadOrderFiles($sOrderId)\n    {\n        $oOrderFile = $this->getBaseObject();\n        $sFields = $oOrderFile->getSelectFields();\n\n        $oOrderFile->addFieldName('oxorderfiles__oxarticletitle');\n        $oOrderFile->addFieldName('oxorderfiles__oxarticleartnum');\n\n        $sSql = \"SELECT \" . $sFields . \" ,\n                      `oxorderarticles`.`oxtitle` AS `oxorderfiles__oxarticletitle`,\n                      `oxorderarticles`.`oxartnum` AS `oxorderfiles__oxarticleartnum`,\n                      `oxfiles`.`oxpurchasedonly` AS `oxorderfiles__oxpurchasedonly`\n                    FROM `oxorderfiles`\n                        LEFT JOIN `oxorderarticles` ON `oxorderarticles`.`oxid` = `oxorderfiles`.`oxorderarticleid`\n                        LEFT JOIN `oxfiles` ON `oxfiles`.`oxid` = `oxorderfiles`.`oxfileid`\n                    WHERE `oxorderfiles`.`oxorderid` = :oxorderid AND `oxorderfiles`.`oxshopid` = :oxshopid\n                        AND `oxorderarticles`.`oxstorno` = 0\";\n\n        $this->selectString($sSql, [\n            'oxorderid' => $sOrderId,\n            'oxshopid' => \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId()\n        ]);\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Payment.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Model\\ListModel;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse oxRegistry;\nuse oxDb;\n\n/**\n * Payment manager.\n * Performs payment methods, such as assigning to someone, returning value etc.\n */\nclass Payment extends \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel\n{\n    /**\n     * Consider for calculation of base sum - Value of all goods in basket\n     *\n     * @var int\n     */\n    const PAYMENT_ADDSUMRULE_ALLGOODS = 1;\n\n    /**\n     * Consider for calculation of base sum - Discounts\n     *\n     * @var int\n     */\n    const PAYMENT_ADDSUMRULE_DISCOUNTS = 2;\n\n    /**\n     * Consider for calculation of base sum - Vouchers\n     *\n     * @var int\n     */\n    const PAYMENT_ADDSUMRULE_VOUCHERS = 4;\n\n    /**\n     * Consider for calculation of base sum - Shipping costs\n     *\n     * @var int\n     */\n    const PAYMENT_ADDSUMRULE_SHIPCOSTS = 8;\n\n    /**\n     * Consider for calculation of base sum - Gift Wrapping/Greeting Card\n     *\n     * @var int\n     */\n    const PAYMENT_ADDSUMRULE_GIFTS = 16;\n\n    /**\n     * User groups object (default null).\n     *\n     * @var object\n     */\n    protected $_oGroups = null;\n\n    /**\n     * Countries assigned to current payment. Value from outside accessible\n     * by calling \\OxidEsales\\Eshop\\Application\\Model\\Payment::getCountries\n     *\n     * @var array\n     */\n    protected $_aCountries = null;\n\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxpayment';\n\n    /**\n     * current dyn values\n     *\n     * @var array\n     */\n    protected $_aDynValues = null;\n\n    /**\n     * payment error type\n     *\n     * @var int\n     */\n    protected $_iPaymentError = null;\n\n    /**\n     * Payment VAT config\n     *\n     * @var bool\n     */\n    protected $_blPaymentVatOnTop = false;\n\n    /**\n     * Payment price\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Price\n     */\n    protected $_oPrice = null;\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxI18n()).\n     */\n    public function __construct()\n    {\n        $this->setPaymentVatOnTop(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blPaymentVatOnTop'));\n        parent::__construct();\n        $this->init('oxpayments');\n    }\n\n    /**\n     * Payment VAT config setter\n     *\n     * @param bool $blOnTop Payment vat config\n     */\n    public function setPaymentVatOnTop($blOnTop)\n    {\n        $this->_blPaymentVatOnTop = $blOnTop;\n    }\n\n    /**\n     * Payment groups getter. Returns groups list\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    public function getGroups()\n    {\n        if ($this->_oGroups == null && ($sOxid = $this->getId())) {\n            // user groups\n            $this->_oGroups = oxNew(ListModel::class, 'oxgroups');\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $sViewName = $tableViewNameGenerator->getViewName(\"oxgroups\", $this->getLanguage());\n\n            // performance\n            $sSelect = \"select {$sViewName}.* from {$sViewName}, oxobject2group\n                        where oxobject2group.oxobjectid = :oxobjectid\n                        and oxobject2group.oxgroupsid = {$sViewName}.oxid \";\n            $this->_oGroups->selectString($sSelect, [\n                'oxobjectid' => $sOxid\n            ]);\n        }\n\n        return $this->_oGroups;\n    }\n\n    /**\n     * sets the dyn values\n     *\n     * @param array $aDynValues the array of dy values\n     */\n    public function setDynValues($aDynValues)\n    {\n        $this->_aDynValues = $aDynValues;\n    }\n\n    /**\n     * Sets a single dyn value\n     *\n     * @param mixed $oKey the key\n     * @param mixed $oVal the value\n     */\n    public function setDynValue($oKey, $oVal)\n    {\n        $this->_aDynValues[$oKey] = $oVal;\n    }\n\n    /**\n     * Returns an array of dyn payment values\n     *\n     * @return array\n     */\n    public function getDynValues()\n    {\n        if (!$this->_aDynValues) {\n            $sRawDynValue = null;\n            if (is_object($this->oxpayments__oxvaldesc)) {\n                $sRawDynValue = $this->oxpayments__oxvaldesc->getRawValue();\n            }\n\n            $this->_aDynValues = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->assignValuesFromText($sRawDynValue);\n        }\n\n        return $this->_aDynValues;\n    }\n\n    /**\n     * Returns additional taxes to base article price.\n     *\n     * @param double $dBasePrice Base article price\n     *\n     * @return double\n     */\n    public function getPaymentValue($dBasePrice)\n    {\n        if ($this->oxpayments__oxaddsumtype->value == \"%\") {\n            $dRet = $dBasePrice * $this->oxpayments__oxaddsum->value / 100;\n        } else {\n            $oCur = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActShopCurrencyObject();\n            $dRet = $this->oxpayments__oxaddsum->value * $oCur->rate;\n        }\n\n        if (($dRet * -1) > $dBasePrice) {\n            $dRet = $dBasePrice;\n        }\n\n        return $dRet;\n    }\n\n    /**\n     * Returns base basket price for payment cost calculations. Price depends on\n     * payment setup (payment administration)\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket oxBasket object\n     *\n     * @return double\n     */\n    public function getBaseBasketPriceForPaymentCostCalc($oBasket)\n    {\n        $dBasketPrice = 0;\n        $iRules = $this->oxpayments__oxaddsumrules->value;\n\n        // products brutto price\n        if (!$iRules || ($iRules & self::PAYMENT_ADDSUMRULE_ALLGOODS)) {\n            $dBasketPrice += $oBasket->getProductsPrice()->getSum($oBasket->isCalculationModeNetto());\n        }\n\n        // discounts\n        if (\n            (!$iRules || ($iRules & self::PAYMENT_ADDSUMRULE_DISCOUNTS)) &&\n            ($oCosts = $oBasket->getTotalDiscount())\n        ) {\n            $dBasketPrice -= $oCosts->getPrice();\n        }\n\n        // vouchers\n        if (!$iRules || ($iRules & self::PAYMENT_ADDSUMRULE_VOUCHERS)) {\n            $dBasketPrice -= $oBasket->getVoucherDiscValue();\n        }\n\n        // delivery\n        if (\n            (!$iRules || ($iRules & self::PAYMENT_ADDSUMRULE_SHIPCOSTS)) &&\n            ($oCosts = $oBasket->getCosts('oxdelivery'))\n        ) {\n            if ($oBasket->isCalculationModeNetto()) {\n                $dBasketPrice += $oCosts->getNettoPrice();\n            } else {\n                $dBasketPrice += $oCosts->getBruttoPrice();\n            }\n        }\n\n        // wrapping\n        if (\n            ($iRules & self::PAYMENT_ADDSUMRULE_GIFTS) &&\n            ($oCosts = $oBasket->getCosts('oxwrapping'))\n        ) {\n            if ($oBasket->isCalculationModeNetto()) {\n                $dBasketPrice += $oCosts->getNettoPrice();\n            } else {\n                $dBasketPrice += $oCosts->getBruttoPrice();\n            }\n        }\n\n        // gift card\n        if (\n            ($iRules & self::PAYMENT_ADDSUMRULE_GIFTS) &&\n            ($oCosts = $oBasket->getCosts('oxgiftcard'))\n        ) {\n            if ($oBasket->isCalculationModeNetto()) {\n                $dBasketPrice += $oCosts->getNettoPrice();\n            } else {\n                $dBasketPrice += $oCosts->getBruttoPrice();\n            }\n        }\n\n        return $dBasketPrice;\n    }\n\n    /**\n     * Returns price object for current payment applied on basket\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\UserBasket $oBasket session basket\n     */\n    public function calculate($oBasket)\n    {\n        //getting basket price with applied discounts and vouchers\n        $dPrice = $this->getPaymentValue($this->getBaseBasketPriceForPaymentCostCalc($oBasket));\n\n        if (!$dPrice) {\n            $dPrice = 0;\n        }\n        // calculating total price\n        $oPrice = oxNew(\\OxidEsales\\Eshop\\Core\\Price::class);\n        $oPrice->setNettoMode($this->_blPaymentVatOnTop);\n\n        $oPrice->setPrice($dPrice);\n        if ($dPrice > 0) {\n            $oPrice->setVat($oBasket->getAdditionalServicesVatPercent());\n        }\n\n        $this->_oPrice = $oPrice;\n    }\n\n    /**\n     * Returns calculated price.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getPrice()\n    {\n        return $this->_oPrice;\n    }\n\n    /**\n     * Returns formatted netto price.\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return string\n     */\n    public function getFNettoPrice()\n    {\n        if ($this->getPrice()) {\n            return \\OxidEsales\\Eshop\\Core\\Registry::getLang()->formatCurrency($this->getPrice()->getNettoPrice());\n        }\n    }\n\n    /**\n     * Returns formatted brutto price.\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return string\n     */\n    public function getFBruttoPrice()\n    {\n        if ($this->getPrice()) {\n            return \\OxidEsales\\Eshop\\Core\\Registry::getLang()->formatCurrency($this->getPrice()->getBruttoPrice());\n        }\n    }\n\n    /**\n     * Returns formatted vat value.\n     *\n     * @deprecated in v4.8/5.1 on 2013-10-14; for formatting use oxPrice template engine plugin\n     *\n     * @return string\n     */\n    public function getFPriceVat()\n    {\n        if ($this->getPrice()) {\n            return \\OxidEsales\\Eshop\\Core\\Registry::getLang()->formatCurrency($this->getPrice()->getVatValue());\n        }\n    }\n\n    /**\n     * Returns array of country Ids which are assigned to current payment\n     *\n     * @return array\n     */\n    public function getCountries()\n    {\n        if ($this->_aCountries === null) {\n            $oDb = DatabaseProvider::getDb();\n            $this->_aCountries = [];\n            $sSelect = 'select oxobjectid from oxobject2payment\n                where oxpaymentid = :oxpaymentid and oxtype = :oxtype ';\n            $rs = $oDb->getCol($sSelect, [\n                'oxpaymentid' => $this->getId(),\n                'oxtype' => 'oxcountry'\n            ]);\n            $this->_aCountries = $rs;\n        }\n\n        return $this->_aCountries;\n    }\n\n    /**\n     * Delete this object from the database, returns true on success.\n     *\n     * @param string $sOxId Object ID(default null)\n     *\n     * @return bool\n     */\n    public function delete($id = null)\n    {\n        if (parent::delete($id)) {\n            $id = $id ?: $this->getId();\n\n            $deletedRows = DatabaseProvider::getDb()->execute(\n                'delete from oxobject2payment where oxpaymentid = :oxpaymentid',\n                [\n                'oxpaymentid' => $id\n                ]\n            );\n\n            return $deletedRows > 0;\n        }\n\n        return false;\n    }\n\n    /**\n     * Function checks if loaded payment is valid to current basket\n     *\n     * @param array                                    $aDynValue    dynamical value (in this case oxiddebitnote is checked only)\n     * @param string                                   $sShopId      id of current shop\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser        the current user\n     * @param double                                   $dBasketPrice the current basket price (oBasket->dPrice)\n     * @param string                                   $sShipSetId   the current ship set\n     *\n     * @return bool true if payment is valid\n     */\n    public function isValidPayment($aDynValue, $sShopId, $oUser, $dBasketPrice, $sShipSetId)\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        if ($this->oxpayments__oxid->value == 'oxempty') {\n            // inactive or blOtherCountryOrder is off\n            if (!$this->oxpayments__oxactive->value || !$myConfig->getConfigParam(\"blOtherCountryOrder\")) {\n                $this->_iPaymentError = -2;\n\n                return false;\n            }\n            if (\n                count(\n                    \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\DeliverySetList::class)\n                    ->getDeliverySetList(\n                        $oUser,\n                        $oUser->getActiveCountry()\n                    )\n                )\n            ) {\n                $this->_iPaymentError = -3;\n\n                return false;\n            }\n\n            return true;\n        }\n\n        $mxValidationResult = \\OxidEsales\\Eshop\\Core\\Registry::getInputValidator()->validatePaymentInputData($this->oxpayments__oxid->value, $aDynValue);\n\n        if (is_integer($mxValidationResult)) {\n            $this->_iPaymentError = $mxValidationResult;\n\n            return false;\n        } elseif ($mxValidationResult === false) {\n            $this->_iPaymentError = 1;\n\n            return false;\n        }\n\n        $oCur = $myConfig->getActShopCurrencyObject();\n        $dBasketPrice = $dBasketPrice / $oCur->rate;\n\n        if ($sShipSetId) {\n            $aPaymentList = \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\PaymentList::class)->getPaymentList($sShipSetId, $dBasketPrice, $oUser);\n\n            if (!array_key_exists($this->getId(), $aPaymentList)) {\n                $this->_iPaymentError = -3;\n\n                return false;\n            }\n        } else {\n            $this->_iPaymentError = -2;\n\n            return false;\n        }\n\n        return true;\n    }\n\n    /**\n     * Payment error number getter\n     *\n     * @return int\n     */\n    public function getPaymentErrorNumber()\n    {\n        return $this->_iPaymentError;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/PaymentGateway.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\n/**\n * Payment gateway manager.\n * Checks and sets payment method data, executes payment.\n */\nclass PaymentGateway extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Payment status (active - true/not active - false) (default false).\n     *\n     * @var bool\n     */\n    protected $_blActive = false;\n\n    /**\n     * oUserpayment object (default null).\n     *\n     * @var object\n     */\n    protected $_oPaymentInfo = null;\n\n    /**\n     * Last error nr. For backward compatibility must be >3\n     *\n     * @abstract\n     * @var string\n     */\n    protected $_iLastErrorNo = 4;\n\n    /**\n     * Last error text.\n     *\n     * @abstract\n     * @var string\n     */\n    protected $_sLastError = null;\n\n    /**\n     * Sets payment parameters.\n     *\n     * @param object $oUserpayment User payment object\n     */\n    public function setPaymentParams($oUserpayment)\n    {\n        // store data\n        $this->_oPaymentInfo = & $oUserpayment;\n    }\n\n    /**\n     * Executes payment, returns true on success.\n     *\n     * @param double $dAmount Goods amount\n     * @param object $oOrder  User ordering object\n     *\n     * @return bool\n     */\n    public function executePayment($dAmount, &$oOrder)\n    {\n        $this->_iLastErrorNo = null;\n        $this->_sLastError = null;\n\n        if (!$this->isActive()) {\n            return true; // fake yes\n        }\n\n        // proceed with no payment\n        // used for other countries\n        if (@$this->_oPaymentInfo->oxuserpayments__oxpaymentsid->value == 'oxempty') {\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns last payment processing error nr.\n     *\n     * @return int\n     */\n    public function getLastErrorNo()\n    {\n        return $this->_iLastErrorNo;\n    }\n\n    /**\n     * Returns last payment processing error.\n     *\n     * @return int\n     */\n    public function getLastError()\n    {\n        return $this->_sLastError;\n    }\n\n    /**\n     * Returns true is payment active.\n     *\n     * @return bool\n     */\n    protected function isActive()\n    {\n        return $this->_blActive;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/PaymentList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Payment list manager.\n */\nclass PaymentList extends \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n{\n    /**\n     * Home country id\n     *\n     * @var string\n     */\n    protected $_sHomeCountry = null;\n\n    /**\n     * Class Constructor\n     */\n    public function __construct()\n    {\n        $this->setHomeCountry(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('aHomeCountry'));\n        parent::__construct('oxpayment');\n    }\n\n    /**\n     * Home country setter\n     *\n     * @param string $sHomeCountry country id\n     */\n    public function setHomeCountry($sHomeCountry)\n    {\n        if (is_array($sHomeCountry)) {\n            $this->_sHomeCountry = current($sHomeCountry);\n        } else {\n            $this->_sHomeCountry = $sHomeCountry;\n        }\n    }\n\n    /**\n     * Creates payment list filter SQL to load current state payment list\n     *\n     * @param string                                   $sShipSetId user chosen delivery set\n     * @param double                                   $dPrice     basket products price\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser      session user object\n     *\n     * @return string\n     */\n    protected function getFilterSelect($sShipSetId, $dPrice, $oUser)\n    {\n        $oDb = DatabaseProvider::getDb();\n        $sBoni = ($oUser && $oUser->getFieldData('oxboni')) ? $oUser->oxuser__oxboni->value : 0;\n\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sTable = $tableViewNameGenerator->getViewName('oxpayments');\n        $sQ = \"select {$sTable}.* from ( select distinct {$sTable}.* from {$sTable} \";\n        $sQ .= \"inner join oxobject2payment ON oxobject2payment.oxobjectid = \" . $oDb->quote($sShipSetId) . \" and oxobject2payment.oxpaymentid = {$sTable}.oxid \";\n        $sQ .= \"where {$sTable}.oxactive='1' \";\n        $sQ .= \" and {$sTable}.oxfromboni <= \" . $oDb->quote($sBoni) . \" and {$sTable}.oxfromamount <= \" . $oDb->quote($dPrice) . \" and {$sTable}.oxtoamount >= \" . $oDb->quote($dPrice);\n\n        // defining initial filter parameters\n        $sGroupIds = '';\n        $sCountryId = $this->getCountryId($oUser);\n\n        // checking for current session user which gives additional restrictions for user itself, users group and country\n        if ($oUser) {\n            // user groups ( maybe would be better to fetch by function \\OxidEsales\\Eshop\\Application\\Model\\User::getUserGroups() ? )\n            foreach ($oUser->getUserGroups() as $oGroup) {\n                if ($sGroupIds) {\n                    $sGroupIds .= ', ';\n                }\n                $sGroupIds .= \"'\" . $oGroup->getId() . \"'\";\n            }\n        }\n\n        $sGroupTable = $tableViewNameGenerator->getViewName('oxgroups');\n        $sCountryTable = $tableViewNameGenerator->getViewName('oxcountry');\n\n        $sCountrySql = $sCountryId ? \"exists( select 1 from oxobject2payment as s1 where s1.oxpaymentid={$sTable}.OXID and s1.oxtype='oxcountry' and s1.OXOBJECTID=\" . $oDb->quote($sCountryId) . \" limit 1 )\" : '0';\n        $sGroupSql = $sGroupIds ? \"exists( select 1 from oxobject2group as s3 where s3.OXOBJECTID={$sTable}.OXID and s3.OXGROUPSID in ( {$sGroupIds} ) limit 1 )\" : '0';\n\n        $sQ .= \"  order by {$sTable}.oxsort asc ) as $sTable where (\n                if( exists( select 1 from oxobject2payment as ss1, $sCountryTable where $sCountryTable.oxid=ss1.oxobjectid and ss1.oxpaymentid={$sTable}.OXID and ss1.oxtype='oxcountry' limit 1 ),\n                    {$sCountrySql},\n                    1) &&\n                if( exists( select 1 from oxobject2group as ss3, $sGroupTable where $sGroupTable.oxid=ss3.oxgroupsid and ss3.OXOBJECTID={$sTable}.OXID limit 1 ),\n                    {$sGroupSql},\n                    1)\n                )  order by {$sTable}.oxsort asc \";\n\n        return $sQ;\n    }\n\n    /**\n     * Returns user country id for for payment selection\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser oxuser object\n     *\n     * @return string\n     */\n    public function getCountryId($oUser)\n    {\n        $sCountryId = null;\n        if ($oUser) {\n            $sCountryId = $oUser->getActiveCountry();\n        }\n\n        if (!$sCountryId) {\n            $sCountryId = $this->_sHomeCountry;\n        }\n\n        return $sCountryId;\n    }\n\n    /**\n     * Loads and returns list of user payments.\n     *\n     * @param string                                   $sShipSetId user chosen delivery set\n     * @param double                                   $dPrice     basket product price excl. discount\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser      session user object\n     *\n     * @return array\n     */\n    public function getPaymentList($sShipSetId, $dPrice, $oUser = null)\n    {\n        $this->selectString($this->getFilterSelect($sShipSetId, $dPrice, $oUser));\n\n        return $this->_aArray;\n    }\n\n    /**\n     * Loads an object including all payments which are not mapped to a\n     * predefined GoodRelations payment method.\n     */\n    public function loadNonRDFaPaymentList()\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sTable = $tableViewNameGenerator->getViewName('oxpayments');\n        $sSubSql = \"SELECT * FROM oxobject2payment WHERE oxobject2payment.OXPAYMENTID = $sTable.OXID AND oxobject2payment.OXTYPE = 'rdfapayment'\";\n        $this->selectString(\"SELECT $sTable.* FROM $sTable WHERE NOT EXISTS($sSubSql) AND $sTable.OXACTIVE = 1\");\n    }\n\n    /**\n     * Loads payments mapped to a\n     * predefined GoodRelations payment method.\n     *\n     * @param double $dPrice product price\n     */\n    public function loadRDFaPaymentList($dPrice = null)\n    {\n        $oDb = DatabaseProvider::getDb();\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sTable = $tableViewNameGenerator->getViewName('oxpayments');\n        $sQ = \"select $sTable.*, oxobject2payment.oxobjectid from $sTable left join (select oxobject2payment.* from oxobject2payment where oxobject2payment.oxtype = 'rdfapayment') as oxobject2payment on oxobject2payment.oxpaymentid=$sTable.oxid \";\n        $sQ .= \"where $sTable.oxactive = 1 \";\n        if ($dPrice !== null) {\n            $sQ .= \"and $sTable.oxfromamount <= :amount and $sTable.oxtoamount >= :amount\";\n        }\n        $rs = $oDb->select($sQ, [\n            'amount' => $dPrice\n        ]);\n        if ($rs != false && $rs->count() > 0) {\n            $oSaved = clone $this->getBaseObject();\n            while (!$rs->EOF) {\n                $oListObject = clone $oSaved;\n                $this->assignElement($oListObject, $rs->fields);\n                $this->_aArray[] = $oListObject;\n                $rs->fetchRow();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/PriceAlarm.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxRegistry;\nuse oxField;\n\n/**\n * PriceAlarm manager.\n * Performs PriceAlarm data/objects loading, deleting.\n */\nclass PriceAlarm extends \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n{\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxpricealarm';\n\n    /**\n     * Article object\n     *\n     * @var object\n     */\n    protected $_oArticle = null;\n\n    /**\n     * Formatted original article price\n     *\n     * @var string\n     */\n    protected $_fPrice = null;\n\n    /**\n     * Original article price\n     *\n     * @var double\n     */\n    protected $_dPrice = null;\n\n    /**\n     * Full article title\n     *\n     * @var string\n     */\n    protected $_sTitle = null;\n\n    /**\n     * Currency object\n     *\n     * @var object\n     */\n    protected $_oCurrency = null;\n\n    /**\n     * Customer proposed price\n     *\n     * @var string\n     */\n    protected $_fProposedPrice = null;\n\n    /**\n     * PriceAlarm status\n     *\n     * @var int\n     */\n    protected $_iStatus = null;\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()), loads\n     * base shop objects.\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxpricealarm');\n    }\n\n    /**\n     * Inserts object data into DB, returns true on success.\n     *\n     * @return bool\n     */\n    protected function insert()\n    {\n        // set oxinsert value\n        $this->oxpricealarm__oxinsert = new \\OxidEsales\\Eshop\\Core\\Field(date('Y-m-d', \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime()));\n\n        return parent::insert();\n    }\n\n    /**\n     * Loads pricealarm article\n     *\n     * @return object\n     */\n    public function getArticle()\n    {\n        if ($this->_oArticle == null) {\n            $this->_oArticle = false;\n            $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            if ($oArticle->load($this->oxpricealarm__oxartid->value)) {\n                $this->_oArticle = $oArticle;\n            }\n        }\n\n        return $this->_oArticle;\n    }\n\n    /**\n     * Returns formatted pricealarm article original price\n     *\n     * @return string\n     */\n    public function getFPrice()\n    {\n        if ($this->_fPrice == null) {\n            $this->_fPrice = false;\n            if ($dArtPrice = $this->getPrice()) {\n                $myLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n                $oThisCurr = $this->getPriceAlarmCurrency();\n                $this->_fPrice = $myLang->formatCurrency($dArtPrice, $oThisCurr);\n            }\n        }\n\n        return $this->_fPrice;\n    }\n\n    /**\n     * Returns pricealarm article original price\n     *\n     * @return double\n     */\n    public function getPrice()\n    {\n        if ($this->_dPrice == null) {\n            $this->_dPrice = false;\n            if ($oArticle = $this->getArticle()) {\n                $myUtils = \\OxidEsales\\Eshop\\Core\\Registry::getUtils();\n                $oThisCurr = $this->getPriceAlarmCurrency();\n\n                // #889C - Netto prices in Admin\n                // (we have to call $oArticle->getPrice() to get price with VAT)\n                $dArtPrice = $oArticle->getPrice()->getBruttoPrice() * $oThisCurr->rate;\n                $dArtPrice = $myUtils->fRound($dArtPrice);\n\n                $this->_dPrice = $dArtPrice;\n            }\n        }\n\n        return $this->_dPrice;\n    }\n\n    /**\n     * Returns pricealarm article full title\n     *\n     * @return string\n     */\n    public function getTitle()\n    {\n        if ($this->_sTitle == null) {\n            $this->_sTitle = false;\n            if ($oArticle = $this->getArticle()) {\n                $this->_sTitle = $oArticle->oxarticles__oxtitle->value;\n                if ($oArticle->oxarticles__oxparentid->value && !$oArticle->oxarticles__oxtitle->value) {\n                    $oParent = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n                    $oParent->load($oArticle->oxarticles__oxparentid->value);\n                    $this->_sTitle = $oParent->oxarticles__oxtitle->value . \" \" . $oArticle->oxarticles__oxvarselect->value;\n                }\n            }\n        }\n\n        return $this->_sTitle;\n    }\n\n    /**\n     * Returns pricealarm currency object\n     *\n     * @return object\n     */\n    public function getPriceAlarmCurrency()\n    {\n        if ($this->_oCurrency == null) {\n            $this->_oCurrency = false;\n            $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n            $oThisCurr = $myConfig->getCurrencyObject($this->oxpricealarm__oxcurrency->value);\n\n            // #869A we should perform currency conversion\n            // (older versions doesn't have currency info - assume as it is default - first in currency array)\n            if (!$oThisCurr) {\n                $oDefCurr = $myConfig->getActShopCurrencyObject();\n                $oThisCurr = $myConfig->getCurrencyObject($oDefCurr->name);\n                $this->oxpricealarm__oxcurrency->setValue($oDefCurr->name);\n            }\n            $this->_oCurrency = $oThisCurr;\n        }\n\n        return $this->_oCurrency;\n    }\n\n    /**\n     * Returns formatted proposed price\n     *\n     * @return string\n     */\n    public function getFProposedPrice()\n    {\n        if ($this->_fProposedPrice == null) {\n            $this->_fProposedPrice = false;\n            if ($oThisCurr = $this->getPriceAlarmCurrency()) {\n                $myLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n                $this->_fProposedPrice = $myLang->formatCurrency($this->oxpricealarm__oxprice->value, $oThisCurr);\n            }\n        }\n\n        return $this->_fProposedPrice;\n    }\n\n    /**\n     * Returns pricealarm status\n     *\n     * @return integer\n     */\n    public function getPriceAlarmStatus()\n    {\n        if ($this->_iStatus == null) {\n            // neutral status\n            $this->_iStatus = 0;\n\n            // shop price is less or equal\n            $dArtPrice = $this->getPrice();\n            if ($this->oxpricealarm__oxprice->value >= $dArtPrice) {\n                $this->_iStatus = 1;\n            }\n\n            // suggestion to user is sent\n            if ($this->oxpricealarm__oxsended->value != \"0000-00-00 00:00:00\") {\n                $this->_iStatus = 2;\n            }\n        }\n\n        return $this->_iStatus;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Rating.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge\\ProductRatingBridgeInterface;\n\n/**\n * Article rate manager.\n * Performs loading, updating, inserting of article rates.\n */\nclass Rating extends \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n{\n    /**\n     * Shop control variable\n     *\n     * @var string\n     */\n    protected $_blDisableShopCheck = true;\n\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxrating';\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxratings');\n    }\n\n    /**\n     * Checks if user can rate product.\n     *\n     * @param string $sUserId   user id\n     * @param string $sType     object type\n     * @param string $sObjectId object id\n     *\n     * @return bool\n     */\n    public function allowRating($sUserId, $sType, $sObjectId)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        if ($iRatingLogsTimeout = $myConfig->getConfigParam('iRatingLogsTimeout')) {\n            $sExpDate = date('Y-m-d H:i:s', \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime() - $iRatingLogsTimeout * 24 * 60 * 60);\n            $oDb->execute(\"delete from oxratings where oxtimestamp < :expDate\", [\n                'expDate' => $sExpDate\n            ]);\n        }\n        $sSelect = \"select oxid from oxratings \n            where oxuserid = :oxuserid \n                and oxtype = :oxtype \n                and oxobjectid = :oxobjectid\";\n        $params = [\n            'oxuserid' => $sUserId,\n            'oxtype' => $sType,\n            'oxobjectid' => $sObjectId\n        ];\n\n        if ($oDb->getOne($sSelect, $params)) {\n            return false;\n        }\n\n        return true;\n    }\n\n\n    /**\n     * calculates and return objects rating\n     *\n     * @param string $sObjectId           object id\n     * @param string $sType               object type\n     * @param array  $aIncludedObjectsIds array of ids\n     *\n     * @return float\n     */\n    public function getRatingAverage($sObjectId, $sType, $aIncludedObjectsIds = null)\n    {\n        $sQuerySnipet = \" AND `oxobjectid` = :oxobjectid\";\n        if (is_array($aIncludedObjectsIds) && count($aIncludedObjectsIds) > 0) {\n            $sQuerySnipet = \" AND ( `oxobjectid` = :oxobjectid OR `oxobjectid` in ('\" . implode(\"', '\", $aIncludedObjectsIds) . \"') )\";\n        }\n\n        $sSelect = \"\n            SELECT\n                AVG(`oxrating`)\n            FROM `oxreviews`\n            WHERE `oxrating` > 0\n                 AND `oxtype` = :oxtype\"\n                   . $sQuerySnipet . \"\n            LIMIT 1\";\n\n        $params = [\n            'oxobjectid' => $sObjectId,\n            'oxtype' => $sType\n        ];\n\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster();\n        if ($fRating = $database->getOne($sSelect, $params)) {\n            $fRating = round($fRating, 1);\n        }\n\n        return $fRating;\n    }\n\n    /**\n     * calculates and return objects rating count\n     *\n     * @param string $sObjectId           object id\n     * @param string $sType               object type\n     * @param array  $aIncludedObjectsIds array of ids\n     *\n     * @return integer\n     */\n    public function getRatingCount($sObjectId, $sType, $aIncludedObjectsIds = null)\n    {\n        $sQuerySnipet = \" AND `oxobjectid` = :oxobjectid\";\n        if (is_array($aIncludedObjectsIds) && count($aIncludedObjectsIds) > 0) {\n            $sQuerySnipet = \" AND ( `oxobjectid` = :oxobjectid OR `oxobjectid` in ('\" . implode(\"', '\", $aIncludedObjectsIds) . \"') )\";\n        }\n\n        $sSelect = \"\n            SELECT\n                COUNT(*)\n            FROM `oxreviews`\n            WHERE `oxrating` > 0\n                AND `oxtype` = :oxtype\"\n                   . $sQuerySnipet . \"\n            LIMIT 1\";\n\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        $masterDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster();\n        $iCount = $masterDb->getOne($sSelect, [\n            'oxobjectid' => $sObjectId,\n            'oxtype' => $sType\n        ]);\n\n        return $iCount;\n    }\n\n    /**\n     * Retuns review object type\n     *\n     * @return string\n     */\n    public function getObjectType()\n    {\n        return $this->oxratings__oxtype->value;\n    }\n\n    /**\n     * Retuns review object id\n     *\n     * @return string\n     */\n    public function getObjectId()\n    {\n        return $this->oxratings__oxobjectid->value;\n    }\n\n    /**\n     * Delete this object from the database, returns true if entry was deleted.\n     *\n     * @param string $oxid Object ID(default null)\n     *\n     * @return bool\n     */\n    public function delete($oxid = null)\n    {\n        $isProductRating = $this->isProductObjectType();\n\n        $isDeleted = parent::delete($oxid);\n\n        if ($isProductRating) {\n            $this->updateProductRating();\n        }\n\n        return $isDeleted;\n    }\n\n\n    /**\n     * Returns true if Rating belongs to Product.\n     *\n     * @return bool\n     */\n    private function isProductObjectType()\n    {\n        return $this->getObjectType() === 'oxarticle';\n    }\n\n    /**\n     * Updates Product rating.\n     */\n    private function updateProductRating()\n    {\n        ContainerFacade::get(ProductRatingBridgeInterface::class)\n            ->updateProductRating(\n                $this->getObjectId()\n            );\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/RecommendationList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse Exception;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Recommendation list manager class.\n *\n * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n */\nclass RecommendationList extends \\OxidEsales\\Eshop\\Core\\Model\\BaseModel implements \\OxidEsales\\Eshop\\Core\\Contract\\IUrl\n{\n    /**\n     * Current object class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxRecommList';\n\n    /**\n     * Article list\n     *\n     * @var string\n     */\n    protected $_oArticles = null;\n\n    /**\n     * Article list loading filter (appended where statement)\n     *\n     * @var string\n     */\n    protected $_sArticlesFilter = '';\n\n    /**\n     * Seo article urls for languages\n     *\n     * @var array\n     */\n    protected $_aSeoUrls = [];\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxrecommlists');\n    }\n\n    /**\n     * Returns list of recommendation list items\n     *\n     * @param integer $iStart        start for sql limit\n     * @param integer $iNrofArticles nr of items per page\n     * @param bool    $blReload      if TRUE forces to reload list\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    public function getArticles($iStart = null, $iNrofArticles = null, $blReload = false)\n    {\n        // cached ?\n        if ($this->_oArticles !== null && !$blReload) {\n            return $this->_oArticles;\n        }\n\n        $this->_oArticles = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n\n        if ($iStart !== null && $iNrofArticles !== null) {\n            $this->_oArticles->setSqlLimit($iStart, $iNrofArticles);\n        }\n\n        // loading basket items\n        $this->_oArticles->loadRecommArticles($this->getId(), $this->_sArticlesFilter);\n\n        return $this->_oArticles;\n    }\n\n    /**\n     * Returns count of recommendation list items\n     *\n     * @return integer\n     */\n    public function getArtCount()\n    {\n        $iCnt = 0;\n        $sSelect = $this->getArticleSelect();\n        if ($sSelect) {\n            $iCnt = DatabaseProvider::getDb()->getOne($sSelect);\n        }\n\n        return $iCnt;\n    }\n\n    /**\n     * Returns the appropriate SQL select\n     *\n     * @return string\n     */\n    protected function getArticleSelect()\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sArtView = $tableViewNameGenerator->getViewName('oxarticles');\n        $sSelect = \"select count(distinct $sArtView.oxid) from oxobject2list \";\n        $sSelect .= \"left join $sArtView on oxobject2list.oxobjectid = $sArtView.oxid \";\n        $sSelect .= \"where (oxobject2list.oxlistid = '\" . $this->getId() . \"') \";\n\n        return $sSelect;\n    }\n\n    /**\n     * returns first article from this list's article list\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    public function getFirstArticle()\n    {\n        $oArtList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n        $oArtList->setSqlLimit(0, 1);\n        $oArtList->loadRecommArticles($this->getId(), $this->_sArticlesFilter);\n        $oArtList->rewind();\n\n        return $oArtList->current();\n    }\n\n    /**\n     * Removes articles from the recommlist and deletes list\n     *\n     * @param string $sOXID Object ID(default null)\n     *\n     * @return bool\n     */\n    public function delete($sOXID = null)\n    {\n        if (!$sOXID) {\n            $sOXID = $this->getId();\n        }\n        if (!$sOXID) {\n            return false;\n        }\n\n        if (($blDelete = parent::delete($sOXID))) {\n            $oDb = DatabaseProvider::getDb();\n            // cleaning up related data\n            $oDb->execute(\"delete from oxobject2list where oxlistid = :oxlistid\", [\n                'oxlistid' => $sOXID\n            ]);\n            $this->onDelete();\n        }\n\n        return $blDelete;\n    }\n\n    /**\n     * Returns article description for recommendation list\n     *\n     * @param string $sOXID Object ID\n     *\n     * @return string\n     */\n    public function getArtDescription($sOXID)\n    {\n        if (!$sOXID) {\n            return false;\n        }\n\n        $oDb = DatabaseProvider::getDb();\n        $sSelect = 'select oxdesc from oxobject2list \n            where oxlistid = :oxlistid and oxobjectid = :oxobjectid';\n\n        return $oDb->getOne($sSelect, [\n            'oxlistid' => $this->getId(),\n            'oxobjectid' => $sOXID\n        ]);\n    }\n\n    /**\n     * Remove article from recommendation list\n     *\n     * @param string $sOXID Object ID\n     *\n     * @return bool\n     */\n    public function removeArticle($sOXID)\n    {\n        if ($sOXID) {\n            $oDb = DatabaseProvider::getDb();\n            $sQ = \"delete from oxobject2list where oxobjectid = :oxobjectid and oxlistid = :oxlistid\";\n\n            return $oDb->execute($sQ, [\n                'oxobjectid' => $sOXID,\n                'oxlistid' => $this->getId()\n            ]);\n        }\n    }\n\n    /**\n     * Add article to recommendation list\n     *\n     * @param string $sOXID Object ID\n     * @param string $sDesc recommended article description\n     *\n     * @throws Exception\n     *\n     * @return bool\n     */\n    public function addArticle($sOXID, $sDesc)\n    {\n        $blAdd = false;\n        if ($sOXID) {\n            // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804 and ESDEV-3822).\n            $database = DatabaseProvider::getMaster();\n\n            $sql = \"select oxid from oxobject2list \n                where oxobjectid = :oxobjectid \n                    and oxlistid = :oxlistid\";\n            $params = [\n                'oxobjectid' => $sOXID,\n                'oxlistid' => $this->getId()\n            ];\n\n            if (!$database->getOne($sql, $params)) {\n                $sUid = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsObject()->generateUID();\n                $sQ = \"insert into oxobject2list (oxid, oxobjectid, oxlistid, oxdesc) values (:oxid, :oxobjectid, :oxlistid, :oxdesc)\";\n                $blAdd = $database->execute($sQ, [\n                    'oxid' => $sUid,\n                    'oxobjectid' => $sOXID,\n                    'oxlistid' => $this->getId(),\n                    'oxdesc' => $sDesc\n                ]);\n            }\n        }\n\n        return $blAdd;\n    }\n\n    /**\n     * get recommendation lists which include given article ids\n     * also sort these lists by these criteria:\n     *     1. show lists, that has more requested articles first\n     *     2. show lists, that have more any articles\n     *\n     * @param array $aArticleIds Object IDs\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    public function getRecommListsByIds($aArticleIds)\n    {\n        if (is_array($aArticleIds) && count($aArticleIds)) {\n            startProfile(__FUNCTION__);\n\n            $sIds = implode(\",\", DatabaseProvider::getDb()->quoteArray($aArticleIds));\n\n            $oRecommList = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n            $oRecommList->init('oxrecommlist');\n\n            $iCnt = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iNrofCrossellArticles');\n\n            $oRecommList->setSqlLimit(0, $iCnt);\n\n            $sSelect = \"SELECT distinct lists.* FROM oxobject2list AS o2l_lists\";\n            $sSelect .= \" LEFT JOIN oxobject2list AS o2l_count ON o2l_lists.oxlistid = o2l_count.oxlistid\";\n            $sSelect .= \" LEFT JOIN oxrecommlists as lists ON o2l_lists.oxlistid = lists.oxid\";\n            $sSelect .= \" WHERE o2l_lists.oxobjectid IN ( $sIds ) and lists.oxshopid = :oxshopid\";\n            $sSelect .= \" GROUP BY lists.oxid order by (\";\n            $sSelect .= \" SELECT count( order1.oxobjectid ) FROM oxobject2list AS order1\";\n            $sSelect .= \" WHERE order1.oxobjectid IN ( $sIds ) AND o2l_lists.oxlistid = order1.oxlistid\";\n            $sSelect .= \" ) DESC, count( lists.oxid ) DESC\";\n\n            $oRecommList->selectString($sSelect, [\n                'oxshopid' => \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId()\n            ]);\n\n            stopProfile(__FUNCTION__);\n\n            if ($oRecommList->count()) {\n                startProfile('_loadFirstArticles');\n\n                $this->loadFirstArticles($oRecommList, $aArticleIds);\n\n                stopProfile('_loadFirstArticles');\n\n                return $oRecommList;\n            }\n        }\n    }\n\n    /**\n     * loads first articles to recomm list also ordering them and clearing not usable list objects\n     * ordering priorities:\n     *     1. first show articles from our search\n     *     2. do not shown articles as 1st, which are shown in other recomm lists as 1st\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Model\\ListModel $oRecommList recommendation list\n     * @param array                                  $aIds        article ids\n     */\n    protected function loadFirstArticles(\\OxidEsales\\Eshop\\Core\\Model\\ListModel $oRecommList, $aIds)\n    {\n        $aIds = DatabaseProvider::getDb()->quoteArray($aIds);\n        $sIds = implode(\", \", $aIds);\n\n        $aPrevIds = [];\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sArtView = $tableViewNameGenerator->getViewName('oxarticles');\n        foreach ($oRecommList as $key => $oRecomm) {\n            if (count($aPrevIds)) {\n                $sNegateSql = \" AND $sArtView.oxid not in ( '\" . implode(\"','\", $aPrevIds) . \"' ) \";\n            } else {\n                $sNegateSql = '';\n            }\n            $sArticlesFilter = \"$sNegateSql ORDER BY $sArtView.oxid in ( $sIds ) desc\";\n            $oRecomm->setArticlesFilter($sArticlesFilter);\n            $oArtList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n            $oArtList->setSqlLimit(0, 1);\n            $oArtList->loadRecommArticles($oRecomm->getId(), $sArticlesFilter);\n\n            if (count($oArtList) == 1) {\n                $oArtList->rewind();\n                $oArticle = $oArtList->current();\n                $sId = $oArticle->getId();\n                $aPrevIds[$sId] = $sId;\n                unset($aIds[$sId]);\n                $sIds = implode(\", \", $aIds);\n            } else {\n                unset($oRecommList[$key]);\n            }\n        }\n    }\n\n    /**\n     * Returns user recommendation list objects\n     *\n     * @param string $sSearchStr Search string\n     *\n     * @return object oxlist with oxrecommlist objects\n     */\n    public function getSearchRecommLists($sSearchStr)\n    {\n        if ($sSearchStr) {\n            // sets active page\n            $iActPage = (int) Registry::getRequest()->getRequestEscapedParameter('pgNr');\n            $iActPage = ($iActPage < 0) ? 0 : $iActPage;\n\n            // load only lists which we show on screen\n            $iNrofCatArticles = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iNrofCatArticles');\n            $iNrofCatArticles = $iNrofCatArticles ? $iNrofCatArticles : 10;\n\n            $oRecommList = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n            $oRecommList->init('oxrecommlist');\n            $sSelect = $this->getSearchSelect($sSearchStr);\n            $oRecommList->setSqlLimit($iNrofCatArticles * $iActPage, $iNrofCatArticles);\n            $oRecommList->selectString($sSelect);\n\n            return $oRecommList;\n        }\n    }\n\n    /**\n     * Returns the amount of lists according to search parameters.\n     *\n     * @param string $sSearchStr Search string\n     *\n     * @return int\n     */\n    public function getSearchRecommListCount($sSearchStr)\n    {\n        $iCnt = 0;\n        $sSelect = $this->getSearchSelect($sSearchStr);\n        if ($sSelect) {\n            $sPartial = substr($sSelect, strpos($sSelect, ' from '));\n            $sSelect = \"select count( distinct rl.oxid ) $sPartial \";\n            $iCnt = DatabaseProvider::getDb()->getOne($sSelect);\n        }\n\n        return $iCnt;\n    }\n\n    /**\n     * Returns the appropriate SQL select according to search parameters\n     *\n     * @param string $sSearchStr Search string\n     *\n     * @return string\n     */\n    protected function getSearchSelect($sSearchStr)\n    {\n        $iShopId = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId();\n        $sSearchStrQuoted = DatabaseProvider::getDb()->quote(\"%$sSearchStr%\");\n\n        $sSelect = \"select distinct rl.* from oxrecommlists as rl\";\n        $sSelect .= \" inner join oxobject2list as o2l on o2l.oxlistid = rl.oxid\";\n        $sSelect .= \" where ( rl.oxtitle like $sSearchStrQuoted or rl.oxdesc like $sSearchStrQuoted\";\n        $sSelect .= \" or o2l.oxdesc like $sSearchStrQuoted ) and rl.oxshopid = '$iShopId'\";\n\n        return $sSelect;\n    }\n\n    /**\n     * Calculates and saves product rating average\n     *\n     * @param integer $iRating new rating value\n     */\n    public function addToRatingAverage($iRating)\n    {\n        $dOldRating = $this->oxrecommlists__oxrating->value;\n        $dOldCnt = $this->oxrecommlists__oxratingcnt->value;\n        $this->oxrecommlists__oxrating = new \\OxidEsales\\Eshop\\Core\\Field(($dOldRating * $dOldCnt + $iRating) / ($dOldCnt + 1), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        $this->oxrecommlists__oxratingcnt = new \\OxidEsales\\Eshop\\Core\\Field($dOldCnt + 1, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        $this->save();\n    }\n\n    /**\n     * Collects user written reviews about an article.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    public function getReviews()\n    {\n        $oReview = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Review::class);\n        $oRevs = $oReview->loadList('oxrecommlist', $this->getId());\n        //if no review found, return null\n        if ($oRevs->count() < 1) {\n            return null;\n        }\n\n        return $oRevs;\n    }\n\n    /**\n     * Returns raw recommlist seo url\n     *\n     * @param int $iLang language id\n     * @param int $iPage page number [optional]\n     *\n     * @return string\n     */\n    public function getBaseSeoLink($iLang, $iPage = 0)\n    {\n        $oEncoder = \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderRecomm::class);\n        if (!$iPage) {\n            return $oEncoder->getRecommUrl($this, $iLang);\n        }\n\n        return $oEncoder->getRecommPageUrl($this, $iPage, $iLang);\n    }\n\n    /**\n     * return url to this recomm list page\n     *\n     * @param int $iLang language id [optional]\n     *\n     * @return string\n     */\n    public function getLink($iLang = null)\n    {\n        if ($iLang === null) {\n            $iLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage();\n        }\n\n        if (!\\OxidEsales\\Eshop\\Core\\Registry::getUtils()->seoIsActive()) {\n            return $this->getStdLink($iLang);\n        }\n\n        if (!isset($this->_aSeoUrls[$iLang])) {\n            $this->_aSeoUrls[$iLang] = $this->getBaseSeoLink($iLang);\n        }\n\n        return $this->_aSeoUrls[$iLang];\n    }\n\n    /**\n     * Returns standard (dynamic) object URL\n     *\n     * @param int   $iLang   language id [optional]\n     * @param array $aParams additional params to use [optional]\n     *\n     * @return string\n     */\n    public function getStdLink($iLang = null, $aParams = [])\n    {\n        if ($iLang === null) {\n            $iLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage();\n        }\n\n        return \\OxidEsales\\Eshop\\Core\\Registry::getUtilsUrl()->processUrl($this->getBaseStdLink($iLang), true, $aParams, $iLang);\n    }\n\n    /**\n     * Returns base dynamic recommlist url: shopurl/index.php?cl=recommlist\n     *\n     * @param int  $iLang   language id\n     * @param bool $blAddId add current object id to url or not\n     * @param bool $blFull  return full including domain name [optional]\n     *\n     * @return string\n     */\n    public function getBaseStdLink($iLang, $blAddId = true, $blFull = true)\n    {\n        $sUrl = '';\n        if ($blFull) {\n            //always returns shop url, not admin\n            $sUrl = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopUrl($iLang, false);\n        }\n\n        return $sUrl . \"index.php?cl=recommlist\" . ($blAddId ? \"&amp;recommid=\" . $this->getId() : \"\");\n    }\n\n    /**\n     * set sql filter for article loading\n     *\n     * @param string $sArticlesFilter article filter\n     */\n    public function setArticlesFilter($sArticlesFilter)\n    {\n        $this->_sArticlesFilter = $sArticlesFilter;\n    }\n\n    /**\n     * Save this Object to database, insert or update as needed.\n     *\n     * @return mixed\n     */\n    public function save()\n    {\n        if (!$this->oxrecommlists__oxtitle->value) {\n            throw oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ObjectException::class, 'EXCEPTION_RECOMMLIST_NOTITLE');\n        }\n        $this->onSave();\n\n        return parent::save();\n    }\n\n    /**\n     * Method is used for overriding when deleting recommendation list.\n     */\n    protected function onDelete()\n    {\n    }\n\n    /**\n     * Method is used for overriding when saving.\n     */\n    protected function onSave()\n    {\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Remark.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxRegistry;\nuse oxField;\n\n/**\n * Remark manager.\n */\nclass Remark extends \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n{\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxremark';\n\n    /**\n     * Skip update fields\n     *\n     * @var array\n     */\n    protected $_aSkipSaveFields = ['oxtimestamp'];\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxremark');\n    }\n\n    /**\n     * Loads object information from DB. Returns true on success.\n     *\n     * @param string $oxID ID of object to load\n     *\n     * @return bool\n     */\n    public function load($oxID)\n    {\n        if ($blRet = parent::load($oxID)) {\n            // convert date's to international format\n            $this->assign([\n                'oxcreate'    => \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->formatDBDate($this->oxremark__oxcreate->value)\n            ]);\n        }\n\n        return $blRet;\n    }\n\n    /**\n     * Inserts object data fields in DB. Returns true on success.\n     *\n     * @return bool\n     */\n    protected function insert()\n    {\n        // set oxcreate\n        $sNow = date('Y-m-d H:i:s', \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime());\n        $this->oxremark__oxcreate = new \\OxidEsales\\Eshop\\Core\\Field($sNow, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        $this->oxremark__oxheader = new \\OxidEsales\\Eshop\\Core\\Field($sNow, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n\n        return parent::insert();\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/RequiredAddressFields.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxRegistry;\n\n/**\n * Defines and returns delivery and billing required fields.\n */\nclass RequiredAddressFields\n{\n    /**\n     * Default required fields for use when not set in config.\n     *\n     * @var array\n     */\n    private $_aDefaultRequiredFields = [\n        'oxuser__oxfname',\n        'oxuser__oxlname',\n        'oxuser__oxstreetnr',\n        'oxuser__oxstreet',\n        'oxuser__oxzip',\n        'oxuser__oxcity'\n    ];\n\n    /**\n     * Required fields.\n     *\n     * @var array\n     */\n    private $_aRequiredFields = [];\n\n    /**\n     * Sets default required fields either from config or from _aDefaultRequiredFields.\n     */\n    public function __construct()\n    {\n        $this->setRequiredFields($this->_aDefaultRequiredFields);\n\n        $aRequiredFields = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('aMustFillFields');\n        if (is_array($aRequiredFields)) {\n            $this->setRequiredFields($aRequiredFields);\n        }\n    }\n\n    /**\n     * Sets all required fields.\n     *\n     * @param array $aRequiredFields\n     */\n    public function setRequiredFields($aRequiredFields)\n    {\n        $this->_aRequiredFields = $aRequiredFields;\n    }\n\n    /**\n     * Returns all required fields.\n     *\n     * @return array\n     */\n    public function getRequiredFields()\n    {\n        return $this->_aRequiredFields;\n    }\n\n    /**\n     * Returns required fields for user address validation.\n     *\n     * @return mixed\n     */\n    public function getBillingFields()\n    {\n        $aRequiredFields = $this->getRequiredFields();\n\n        return $this->filterFields($aRequiredFields, 'oxuser__');\n    }\n\n    /**\n     * Returns required fields for delivery address validation.\n     *\n     * @return mixed\n     */\n    public function getDeliveryFields()\n    {\n        $aRequiredFields = $this->getRequiredFields();\n\n        return $this->filterFields($aRequiredFields, 'oxaddress__');\n    }\n\n    /**\n     * Removes delivery fields from fields list.\n     *\n     * @param array  $aFields\n     * @param string $sPrefix\n     *\n     * @return mixed\n     */\n    private function filterFields($aFields, $sPrefix)\n    {\n        $aAllowed = [];\n        foreach ($aFields as $sKey => $sValue) {\n            if (strpos($sValue, $sPrefix) === 0) {\n                $aAllowed[] = $aFields[$sKey];\n            }\n        }\n\n        return $aAllowed;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/RequiredFieldValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\n/**\n * Class for validating address\n */\nclass RequiredFieldValidator\n{\n    /**\n     * Validates field value.\n     *\n     * @param string $sFieldValue Field value\n     *\n     * @return bool\n     */\n    public function validateFieldValue($sFieldValue)\n    {\n        $blValid = true;\n        if (is_array($sFieldValue)) {\n            $blValid = $this->validateFieldValueArray($sFieldValue);\n        } else {\n            if (!trim($sFieldValue ?? '')) {\n                $blValid = false;\n            }\n        }\n\n        return $blValid;\n    }\n\n    /**\n     * Checks if all values are filled up\n     *\n     * @param array $aFieldValues field values\n     *\n     * @return bool\n     */\n    private function validateFieldValueArray($aFieldValues)\n    {\n        $blValid = true;\n        foreach ($aFieldValues as $sValue) {\n            if (!trim($sValue)) {\n                $blValid = false;\n                break;\n            }\n        }\n\n        return $blValid;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/RequiredFieldsValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\n/**\n * Class for validating address\n */\nclass RequiredFieldsValidator\n{\n    /**\n     * Required fields array.\n     *\n     * @var array\n     */\n    private $_aRequiredFields = [];\n\n    /**\n     * Invalid fields array.\n     *\n     * @var array\n     */\n    private $_aInvalidFields = [];\n\n    /**\n     * Required Field validator.\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\RequiredFieldValidator\n     */\n    private $_oFieldValidator = [];\n\n    /**\n     * Sets dependencies.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\RequiredFieldValidator $oFieldValidator\n     */\n    public function __construct($oFieldValidator = null)\n    {\n        if (is_null($oFieldValidator)) {\n            $oFieldValidator = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\RequiredFieldValidator::class);\n        }\n        $this->setFieldValidator($oFieldValidator);\n    }\n\n    /**\n     * Returns required fields for address.\n     *\n     * @return array\n     */\n    public function getRequiredFields()\n    {\n        return $this->_aRequiredFields;\n    }\n\n    /**\n     * Sets required fields array\n     *\n     * @param array $aFields Fields\n     */\n    public function setRequiredFields($aFields)\n    {\n        $this->_aRequiredFields = $aFields;\n    }\n\n    /**\n     * Returns required fields for address.\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\RequiredFieldValidator\n     */\n    public function getFieldValidator()\n    {\n        return $this->_oFieldValidator;\n    }\n\n    /**\n     * Sets required fields array\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\RequiredFieldValidator $oFieldValidator\n     */\n    public function setFieldValidator($oFieldValidator)\n    {\n        $this->_oFieldValidator = $oFieldValidator;\n    }\n\n    /**\n     * Gets invalid fields.\n     *\n     * @return array\n     */\n    public function getInvalidFields()\n    {\n        return $this->_aInvalidFields;\n    }\n\n    /**\n     * Checks if all required fields are filled.\n     * Returns array of invalid fields or empty array if all fields are fine.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Model\\BaseModel $oObject Address fields with values.\n     *\n     * @return bool If any invalid field exist.\n     */\n    public function validateFields($oObject)\n    {\n        $aRequiredFields = $this->getRequiredFields();\n        $oFieldValidator = $this->getFieldValidator();\n\n        $aInvalidFields = [];\n        foreach ($aRequiredFields as $sFieldName) {\n            if (!$oFieldValidator->validateFieldValue($oObject->getFieldData($sFieldName))) {\n                $aInvalidFields[] = $sFieldName;\n            }\n        }\n        $this->setInvalidFields($aInvalidFields);\n\n        return empty($aInvalidFields);\n    }\n\n    /**\n     * Add fields to invalid fields array.\n     *\n     * @param array $aFields Invalid field name.\n     */\n    private function setInvalidFields($aFields)\n    {\n        $this->_aInvalidFields = $aFields;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Review.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Model\\BaseModel;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge\\UserReviewAndRatingBridgeInterface;\n\nclass Review extends BaseModel\n{\n    /**\n     * @var string\n     */\n    protected $_blDisableShopCheck = true;\n\n    /**\n     * @var string\n     */\n    protected $_sClassName = 'oxreview';\n\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxreviews');\n    }\n\n    /**\n     * Calls parent::assign and assigns review writer data\n     *\n     * @param array $dbRecord database record\n     *\n     * @return bool\n     */\n    public function assign($dbRecord)\n    {\n        $blRet = parent::assign($dbRecord);\n\n        if (isset($this->oxreviews__oxuserid) && $this->oxreviews__oxuserid->value) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $params = [\n                'oxid' => $this->oxreviews__oxuserid->value\n            ];\n\n            $firstName = $oDb->getOne(\"SELECT oxfname FROM oxuser \n                WHERE oxid = :oxid\", $params);\n\n            $this->oxuser__oxfname = new \\OxidEsales\\Eshop\\Core\\Field($firstName);\n        }\n\n        return $blRet;\n    }\n\n    /**\n     * Loads object review information. Returns true on success.\n     *\n     * @param string $oxId ID of object to load\n     *\n     * @return bool\n     */\n    public function load($oxId)\n    {\n        if ($blRet = parent::load($oxId)) {\n            // convert date's to international format\n            $this->oxreviews__oxcreate->setValue(Registry::getUtilsDate()->formatDBDate($this->oxreviews__oxcreate->value));\n        }\n\n        return $blRet;\n    }\n\n    /**\n     * Inserts object data fiels in DB. Returns true on success.\n     *\n     * @return bool\n     */\n    protected function insert()\n    {\n        // set oxcreate\n        $this->oxreviews__oxcreate = new \\OxidEsales\\Eshop\\Core\\Field(date('Y-m-d H:i:s', Registry::getUtilsDate()->getTime()));\n\n        return parent::insert();\n    }\n\n    /**\n     * get oxList of reviews for given object ids and type\n     *\n     * @param string  $sType       type of given ids\n     * @param mixed   $aIds        given object ids to load, can be array or just one id, given as string\n     * @param boolean $blLoadEmpty true if want to load empty text reviews\n     * @param int     $iLoadInLang language to select for loading\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n     */\n    public function loadList($sType, $aIds, $blLoadEmpty = false, $iLoadInLang = null)\n    {\n        $reviews = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n        $reviews->init('oxreview');\n\n        $params = [\n            'oxtype' => $sType,\n            'oxlang' => is_null($iLoadInLang) ? (int) Registry::getLang()->getBaseLanguage() : (int) $iLoadInLang\n        ];\n\n        if (is_array($aIds) && count($aIds)) {\n            $sObjectIdWhere = \"oxreviews.oxobjectid in ( \" . implode(\", \", \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray($aIds)) . \" )\";\n        } elseif (is_string($aIds) && $aIds) {\n            $sObjectIdWhere = \"oxreviews.oxobjectid = :oxobjectid\";\n            $params['oxobjectid'] = $aIds;\n        } else {\n            return $reviews;\n        }\n\n        $sSelect = \"select oxreviews.* from oxreviews where oxreviews.oxtype = :oxtype and $sObjectIdWhere and oxreviews.oxlang = :oxlang\";\n\n        if (!$blLoadEmpty) {\n            $sSelect .= ' and oxreviews.oxtext != \"\" ';\n        }\n\n        if (Registry::getConfig()->getConfigParam('blGBModerate')) {\n            $sSelect .= ' and ( oxreviews.oxactive = \"1\" ';\n\n            if ($oUser = $this->getUser()) {\n                $sSelect .= 'or  oxreviews.oxuserid = :oxuserid ';\n                $params['oxuserid'] = $oUser->getId();\n            }\n\n            $sSelect .= ')';\n        }\n\n        $sSelect .= ' order by oxreviews.oxcreate desc ';\n\n        $reviews->selectString($sSelect, $params);\n\n        foreach ($reviews as $review) {\n            $reviewCreationDate = $review->oxreviews__oxcreate->getRawValue();\n            $review->oxreviews__oxcreate->setValue(\n                Registry::getUtilsDate()->formatDBDate($reviewCreationDate),\n                Field::T_RAW\n            );\n\n            $reviewText = (string)$review->oxreviews__oxtext->value;\n            $review->oxreviews__oxtext->setValue(\n                $reviewText,\n                Field::T_RAW\n            );\n        }\n\n        return $reviews;\n    }\n\n    /**\n     * Retuns review object type\n     *\n     * @return string\n     */\n    public function getObjectType()\n    {\n        return is_object($this->oxreviews__oxtype) ? $this->oxreviews__oxtype->value : $this->oxreviews__oxtype;\n    }\n\n    /**\n     * Retuns review object id\n     *\n     * @return string\n     */\n    public function getObjectId()\n    {\n        return is_object($this->oxreviews__oxobjectid) ? $this->oxreviews__oxobjectid->value : $this->oxreviews__oxobjectid;\n    }\n\n    /**\n     * Returns ReviewAndRating list by User id.\n     *\n     * @param string $userId\n     *\n     * @return array\n     */\n    public function getReviewAndRatingListByUserId($userId)\n    {\n        return ContainerFacade::get(UserReviewAndRatingBridgeInterface::class)\n            ->getReviewAndRatingList($userId);\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Search.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Implements search\n */\n#[\\AllowDynamicProperties]\nclass Search extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Active language id\n     *\n     * @var int\n     */\n    protected $_iLanguage = 0;\n\n    /**\n     * Class constructor. Executes search lenguage setter\n     */\n    public function __construct()\n    {\n        $this->setLanguage();\n    }\n\n    /**\n     * Search language setter. If no param is passed, will be taken default shop language\n     *\n     * @param string $iLanguage string (default null)\n     */\n    public function setLanguage($iLanguage = null)\n    {\n        if (!isset($iLanguage)) {\n            $this->_iLanguage = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage();\n        } else {\n            $this->_iLanguage = $iLanguage;\n        }\n    }\n\n    /**\n     * Returns a list of articles according to search parameters. Returns matched\n     *\n     * @param string|false $sSearchParamForQuery       query parameter\n     * @param string|false $sInitialSearchCat          initial category to seearch in\n     * @param string|false $sInitialSearchVendor       initial vendor to seearch for\n     * @param string|false $sInitialSearchManufacturer initial Manufacturer to seearch for\n     * @param string|false $sSortBy                    sort by\n     *\n     * @return ArticleList\n     */\n    public function getSearchArticles($sSearchParamForQuery = false, $sInitialSearchCat = false, $sInitialSearchVendor = false, $sInitialSearchManufacturer = false, $sSortBy = false)\n    {\n        // sets active page\n        $this->iActPage = (int) Registry::getRequest()->getRequestEscapedParameter('pgNr');\n        $this->iActPage = ($this->iActPage < 0) ? 0 : $this->iActPage;\n\n        // load only articles which we show on screen\n        //setting default values to avoid possible errors showing article list\n        $iNrofCatArticles = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iNrofCatArticles');\n        $iNrofCatArticles = $iNrofCatArticles ? $iNrofCatArticles : 10;\n\n        $oArtList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n        $oArtList->setSqlLimit($iNrofCatArticles * $this->iActPage, $iNrofCatArticles);\n\n        $sSelect = $this->getSearchSelect($sSearchParamForQuery, $sInitialSearchCat, $sInitialSearchVendor, $sInitialSearchManufacturer, $sSortBy);\n        if ($sSelect) {\n            $oArtList->selectString($sSelect);\n        }\n\n        return $oArtList;\n    }\n\n    /**\n     * Returns the amount of articles according to search parameters.\n     *\n     * @param string|bool $sSearchParamForQuery       query parameter\n     * @param string|bool $sInitialSearchCat          initial category to seearch in\n     * @param string|bool $sInitialSearchVendor       initial vendor to seearch for\n     * @param string|bool $sInitialSearchManufacturer initial Manufacturer to seearch for\n     *\n     * @return int\n     */\n    public function getSearchArticleCount($sSearchParamForQuery = false, $sInitialSearchCat = false, $sInitialSearchVendor = false, $sInitialSearchManufacturer = false)\n    {\n        $iCnt = 0;\n        $sSelect = $this->getSearchSelect($sSearchParamForQuery, $sInitialSearchCat, $sInitialSearchVendor, $sInitialSearchManufacturer, false);\n        if ($sSelect) {\n            $sPartial = substr($sSelect, strpos($sSelect, ' from '));\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $sSelect = \"select count( \" . $tableViewNameGenerator->getViewName('oxarticles', $this->_iLanguage) . \".oxid ) $sPartial \";\n\n            $iCnt = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->getOne($sSelect);\n        }\n\n        return $iCnt;\n    }\n\n    /**\n     * Returns the appropriate SQL select for a search according to search parameters\n     *\n     * @param string|false $sSearchParamForQuery       query parameter\n     * @param string|false $sInitialSearchCat          initial category to search in\n     * @param string|false $sInitialSearchVendor       initial vendor to search for\n     * @param string|false $sInitialSearchManufacturer initial Manufacturer to search for\n     * @param string|false $sSortBy                    sort by\n     *\n     * @return string\n     */\n    protected function getSearchSelect($sSearchParamForQuery = false, $sInitialSearchCat = false, $sInitialSearchVendor = false, $sInitialSearchManufacturer = false, $sSortBy = false)\n    {\n        if (!$sSearchParamForQuery && !$sInitialSearchCat && !$sInitialSearchVendor && !$sInitialSearchManufacturer) {\n            //no search string\n            return null;\n        }\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        // performance\n        if ($sInitialSearchCat) {\n            // lets search this category - is no such category - skip all other code\n            $oCategory = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n            $sCatTable = $oCategory->getViewName();\n\n            $sQ = \"select 1 from $sCatTable \n                where $sCatTable.oxid = :oxid \";\n            $sQ .= \"and \" . $oCategory->getSqlActiveSnippet();\n\n            $params = [\n                'oxid' => $sInitialSearchCat\n            ];\n\n            if (!$oDb->getOne($sQ, $params)) {\n                return;\n            }\n        }\n\n        // performance:\n        if ($sInitialSearchVendor) {\n            // lets search this vendor - if no such vendor - skip all other code\n            $oVendor = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Vendor::class);\n            $sVndTable = $oVendor->getViewName();\n\n            $sQ = \"select 1 from $sVndTable \n                where $sVndTable.oxid = :oxid \";\n            $sQ .= \"and \" . $oVendor->getSqlActiveSnippet();\n\n            $params = [\n                'oxid' => $sInitialSearchVendor\n            ];\n\n            if (!$oDb->getOne($sQ, $params)) {\n                return;\n            }\n        }\n\n        // performance:\n        if ($sInitialSearchManufacturer) {\n            // lets search this Manufacturer - if no such Manufacturer - skip all other code\n            $oManufacturer = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Manufacturer::class);\n            $sManTable = $oManufacturer->getViewName();\n\n            $sQ = \"select 1 from $sManTable \n                where $sManTable.oxid = :oxid \";\n            $sQ .= \"and \" . $oManufacturer->getSqlActiveSnippet();\n\n            $params = [\n                'oxid' => $sInitialSearchManufacturer\n            ];\n\n            if (!$oDb->getOne($sQ, $params)) {\n                return;\n            }\n        }\n\n        $sWhere = null;\n        if ($sSearchParamForQuery) {\n            $sWhere = $this->getWhere($sSearchParamForQuery);\n        }\n\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sArticleTable = $oArticle->getViewName();\n        $sO2CView = $tableViewNameGenerator->getViewName('oxobject2category');\n\n        $sSelectFields = $oArticle->getSelectFields();\n\n        // longdesc field now is kept on different table\n        $sDescJoin = $this->getDescriptionJoin($sArticleTable);\n\n        //select articles\n        $sSelect = \"select {$sSelectFields}, {$sArticleTable}.oxtimestamp from {$sArticleTable} {$sDescJoin} where \";\n\n        // must be additional conditions in select if searching in category\n        if ($sInitialSearchCat) {\n            $sCatView = $tableViewNameGenerator->getViewName('oxcategories', $this->_iLanguage);\n            $sInitialSearchCatQuoted = $oDb->quote($sInitialSearchCat);\n            $sSelectCat = \"select oxid from {$sCatView} where oxid = $sInitialSearchCatQuoted and (oxpricefrom != '0' or oxpriceto != 0)\";\n            if ($oDb->getOne($sSelectCat)) {\n                $sSelect = \"select {$sSelectFields}, {$sArticleTable}.oxtimestamp from {$sArticleTable} $sDescJoin \" .\n                           \"where {$sArticleTable}.oxid in ( select {$sArticleTable}.oxid as id from {$sArticleTable}, {$sO2CView} as oxobject2category, {$sCatView} as oxcategories \" .\n                           \"where (oxobject2category.oxcatnid=$sInitialSearchCatQuoted and oxobject2category.oxobjectid={$sArticleTable}.oxid) or (oxcategories.oxid=$sInitialSearchCatQuoted and {$sArticleTable}.oxprice >= oxcategories.oxpricefrom and\n                            {$sArticleTable}.oxprice <= oxcategories.oxpriceto )) and \";\n            } else {\n                $sSelect = \"select {$sSelectFields} from {$sO2CView} as\n                            oxobject2category, {$sArticleTable} {$sDescJoin} where oxobject2category.oxcatnid=$sInitialSearchCatQuoted and\n                            oxobject2category.oxobjectid={$sArticleTable}.oxid and \";\n            }\n        }\n\n        $sSelect .= $oArticle->getSqlActiveSnippet();\n        $sSelect .= \" and {$sArticleTable}.oxparentid = '' and {$sArticleTable}.oxissearch = 1 \";\n\n        if ($sInitialSearchVendor) {\n            $sSelect .= \" and {$sArticleTable}.oxvendorid = \" . $oDb->quote($sInitialSearchVendor) . \" \";\n        }\n\n        if ($sInitialSearchManufacturer) {\n            $sSelect .= \" and {$sArticleTable}.oxmanufacturerid = \" . $oDb->quote($sInitialSearchManufacturer) . \" \";\n        }\n\n        $sSelect .= $sWhere;\n\n        if ($sSortBy) {\n            $sSelect .= \" order by {$sSortBy} \";\n        }\n\n        return $sSelect;\n    }\n\n    /**\n     * Forms and returns SQL query string for search in DB.\n     *\n     * @param string $sSearchString searching string\n     *\n     * @return string\n     */\n    protected function getWhere($sSearchString)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $blSep = false;\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sArticleTable = $tableViewNameGenerator->getViewName('oxarticles', $this->_iLanguage);\n\n        $aSearchCols = $myConfig->getConfigParam('aSearchCols');\n        if (!(is_array($aSearchCols) && count($aSearchCols))) {\n            return '';\n        }\n\n        $sSearchSep = $myConfig->getConfigParam('blSearchUseAND') ? 'and ' : 'or ';\n        $aSearch = explode(' ', $sSearchString);\n        $sSearch = ' and ( ';\n        $myUtilsString = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsString();\n\n        foreach ($aSearch as $sSearchString) {\n            if (!strlen($sSearchString)) {\n                continue;\n            }\n\n            if ($blSep) {\n                $sSearch .= $sSearchSep;\n            }\n\n            $blSep2 = false;\n            $sSearch .= '( ';\n\n            foreach ($aSearchCols as $sField) {\n                if ($blSep2) {\n                    $sSearch .= ' or ';\n                }\n\n                // as long description now is on different table table must differ\n                $sSearchField = $this->getSearchField($sArticleTable, $sField);\n\n                $sSearch .= \" {$sSearchField} like \" . $oDb->quote(\"%$sSearchString%\");\n\n                // special chars ?\n                if (($sUml = $myUtilsString->prepareStrForSearch($sSearchString))) {\n                    $sSearch .= \" or {$sSearchField} like \" . $oDb->quote(\"%$sUml%\");\n                }\n\n                $blSep2 = true;\n            }\n            $sSearch .= ' ) ';\n\n            $blSep = true;\n        }\n\n        $sSearch .= ' ) ';\n\n        return $sSearch;\n    }\n\n    /**\n     * Get description join. Needed in case of searching for data in table oxartextends or its views.\n     *\n     * @param string $table\n     *\n     * @return string\n     */\n    protected function getDescriptionJoin($table)\n    {\n        $descriptionJoin = '';\n        $searchColumns = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('aSearchCols');\n\n        if (is_array($searchColumns) && in_array('oxlongdesc', $searchColumns)) {\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $viewName = $tableViewNameGenerator->getViewName('oxartextends', $this->_iLanguage);\n            $descriptionJoin = \" LEFT JOIN {$viewName } ON {$table}.oxid={$viewName }.oxid \";\n        }\n        return $descriptionJoin;\n    }\n\n    /**\n     * Get search field name.\n     * Needed in case of searching for data in table oxartextends or its views.\n     *\n     * @param string $table\n     * @param string $field Chose table depending on field.\n     *\n     * @return string\n     */\n    protected function getSearchField($table, $field)\n    {\n        if ($field == 'oxlongdesc') {\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $searchField = $tableViewNameGenerator->getViewName('oxartextends', $this->_iLanguage) . \".{$field}\";\n        } else {\n            $searchField = \"{$table}.{$field}\";\n        }\n        return $searchField;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/SelectList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxRegistry;\nuse oxDb;\nuse OxidEsales\\Eshop\\Core\\Str;\n\n/**\n * Select list manager\n */\nclass SelectList extends \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel implements \\OxidEsales\\Eshop\\Core\\Contract\\ISelectList\n{\n    /**\n     * Select list fields array\n     *\n     * @var array\n     */\n    protected $_aFieldList = null;\n\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxselectlist';\n\n    /**\n     * Selections array\n     *\n     * @var array\n     */\n    protected $_aList = null;\n\n    /**\n     * Product VAT\n     *\n     * @var float\n     */\n    protected $_dVat = null;\n\n    /**\n     * Active selection object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Selection\n     */\n    protected $_oActiveSelection = null;\n\n    /**\n     * Calls parent constructor and initializes selection list\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxselectlist');\n    }\n\n    /**\n     * Returns select list value list.\n     *\n     * @param double $dVat VAT value\n     *\n     * @return array\n     */\n    public function getFieldList($dVat = null)\n    {\n        if ($this->_aFieldList == null && $this->oxselectlist__oxvaldesc->value) {\n            $this->_aFieldList = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->assignValuesFromText($this->oxselectlist__oxvaldesc->value, $dVat);\n            foreach ($this->_aFieldList as $sKey => $oField) {\n                $this->_aFieldList[$sKey]->name = Str::getStr()->strip_tags($this->_aFieldList[$sKey]->name);\n            }\n        }\n\n        return $this->_aFieldList;\n    }\n\n    /**\n     * Removes selectlists from articles.\n     *\n     * @param string $sOXID object ID (default null)\n     *\n     * @return bool\n     */\n    public function delete($sOXID = null)\n    {\n        if (!$sOXID) {\n            $sOXID = $this->getId();\n        }\n        if (!$sOXID) {\n            return false;\n        }\n\n        // remove selectlists from articles also\n        if ($blRemove = parent::delete($sOXID)) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $oDb->execute(\"delete from oxobject2selectlist where oxselnid = :oxselnid\", [\n                'oxselnid' => $sOXID\n            ]);\n        }\n\n        return $blRemove;\n    }\n\n    /**\n     * VAT setter\n     *\n     * @param float $dVat product VAT\n     */\n    public function setVat($dVat)\n    {\n        $this->_dVat = $dVat;\n    }\n\n    /**\n     * Returns VAT set by oxSelectList::setVat()\n     *\n     * @return float\n     */\n    public function getVat()\n    {\n        return $this->_dVat;\n    }\n\n    /**\n     * Returns variant selection list label\n     *\n     * @return string\n     */\n    public function getLabel()\n    {\n        return $this->oxselectlist__oxtitle->value;\n    }\n\n    /**\n     * Returns array of oxSelection's\n     *\n     * @return array\n     */\n    public function getSelections()\n    {\n        if ($this->_aList === null && $this->oxselectlist__oxvaldesc->value) {\n            $this->_aList = false;\n            $aList = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->assignValuesFromText($this->oxselectlist__oxvaldesc->getRawValue(), $this->getVat());\n            foreach ($aList as $sKey => $oField) {\n                if ($oField->name) {\n                    $this->_aList[$sKey] = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Selection::class, Str::getStr()->strip_tags($oField->name), $sKey, false, $this->_aList === false ? true : false);\n                }\n            }\n        }\n\n        return $this->_aList;\n    }\n\n    /**\n     * Returns active selection object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Selection\n     */\n    public function getActiveSelection()\n    {\n        if ($this->_oActiveSelection === null) {\n            if (($aSelections = $this->getSelections())) {\n                // first is allways active\n                $this->_oActiveSelection = reset($aSelections);\n            }\n        }\n\n        return $this->_oActiveSelection;\n    }\n\n    /**\n     * Activates given by index selection\n     *\n     * @param int $iIdx selection index\n     */\n    public function setActiveSelectionByIndex($iIdx)\n    {\n        if (($aSelections = $this->getSelections())) {\n            $iSelIdx = 0;\n            foreach ($aSelections as $oSelection) {\n                $oSelection->setActiveState($iSelIdx == $iIdx);\n                if ($iSelIdx == $iIdx) {\n                    $this->_oActiveSelection = $oSelection;\n                }\n                $iSelIdx++;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Selection.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\Str;\n\n/**\n * Variant selection container class\n */\nclass Selection\n{\n    /**\n     * Selection name\n     *\n     * @var string\n     */\n    protected $_sName = null;\n\n    /**\n     * Selection value\n     *\n     * @var string\n     */\n    protected $_sValue = null;\n\n    /**\n     * Selection state: active\n     *\n     * @var bool\n     */\n    protected $_blActive = null;\n\n    /**\n     * Selection state: disabled\n     *\n     * @var bool\n     */\n    protected $_blDisabled = null;\n\n    /**\n     * Initializes oxSelection object\n     *\n     * @param string $sName      selection name\n     * @param string $sValue     selection value\n     * @param string $blDisabled selection state - disabled/enabled\n     * @param string $blActive   selection state - active/inactive\n     */\n    public function __construct($sName, $sValue, $blDisabled, $blActive)\n    {\n        $this->_sName = $sName;\n        $this->_sValue = $sValue;\n        $this->_blDisabled = $blDisabled;\n        $this->_blActive = $blActive;\n    }\n\n    /**\n     * Returns selection value\n     *\n     * @return string\n     */\n    public function getValue()\n    {\n        return $this->_sValue;\n    }\n\n    /**\n     * Returns selection name\n     *\n     * @return string\n     */\n    public function getName()\n    {\n        return Str::getStr()->htmlspecialchars($this->_sName);\n    }\n\n    /**\n     * Returns TRUE if current selection is active (chosen)\n     *\n     * @return bool\n     */\n    public function isActive()\n    {\n        return $this->_blActive;\n    }\n\n    /**\n     * Returns TRUE if current selection is disabled\n     *\n     * @return bool\n     */\n    public function isDisabled()\n    {\n        return $this->_blDisabled;\n    }\n\n    /**\n     * Sets selection active/inactive\n     *\n     * @param bool $blActive selection state TRUE/FALSE\n     */\n    public function setActiveState($blActive)\n    {\n        $this->_blActive = $blActive;\n    }\n\n    /**\n     * Sets selection disabled/enables\n     *\n     * @param bool $blDisabled selection state TRUE/FALSE\n     */\n    public function setDisabled($blDisabled)\n    {\n        $this->_blDisabled = $blDisabled;\n    }\n\n    /**\n     * Returns selection link (currently returns \"#\")\n     *\n     * @return string\n     */\n    public function getLink()\n    {\n        return \"#\";\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/SeoEncoderArticle.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse oxView;\nuse oxRegistry;\nuse oxUBase;\nuse oxDb;\nuse oxCategory;\n\n/**\n * Seo encoder for articles\n */\nclass SeoEncoderArticle extends \\OxidEsales\\Eshop\\Core\\SeoEncoder\n{\n    /**\n     * Product parent title cache\n     *\n     * @var array\n     */\n    protected static $_aTitleCache = [];\n\n    /**\n     * Returns target \"extension\" (.html)\n     *\n     * @return string\n     */\n    protected function getUrlExtension()\n    {\n        return '.html';\n    }\n\n    /**\n     * Checks if current article is in same language as preferred (language id passed by param).\n     * In case languages are not the same - reloads article object in different language\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle article to check language\n     * @param int                                         $iLang    user defined language id\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected function getProductForLang($oArticle, $iLang)\n    {\n        if (isset($iLang) && $iLang != $oArticle->getLanguage()) {\n            $sId = $oArticle->getId();\n            $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            $oArticle->setSkipAssign(true);\n            $oArticle->loadInLang($iLang, $sId);\n        }\n\n        return $oArticle;\n    }\n\n    /**\n     * Returns SEO uri for passed article and active tag\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle article object\n     * @param int                                         $iLang    language id\n     *\n     * @return string\n     */\n    public function getArticleRecommUri($oArticle, $iLang)\n    {\n        $sSeoUri = null;\n        if ($oRecomm = $this->_getRecomm($oArticle, $iLang)) {\n            //load details link from DB\n            if (!($sSeoUri = $this->loadFromDb('oxarticle', $oArticle->getId(), $iLang, null, $oRecomm->getId(), true))) {\n                $oArticle = $this->getProductForLang($oArticle, $iLang);\n\n                // create title part for uri\n                $sTitle = $this->prepareArticleTitle($oArticle);\n\n                // create uri for all categories\n                $sSeoUri = \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderRecomm::class)->getRecommUri($oRecomm, $iLang);\n                $sSeoUri = $this->processSeoUrl($sSeoUri . $sTitle, $oArticle->getId(), $iLang);\n\n                $aStdParams = ['recommid' => $oRecomm->getId(), 'listtype' => $this->getListType()];\n                $this->saveToDb(\n                    'oxarticle',\n                    $oArticle->getId(),\n                    \\OxidEsales\\Eshop\\Core\\Registry::getUtilsUrl()->appendUrl(\n                        $oArticle->getBaseStdLink($iLang),\n                        $aStdParams\n                    ),\n                    $sSeoUri,\n                    $iLang,\n                    null,\n                    0,\n                    $oRecomm->getId()\n                );\n            }\n        }\n\n        return $sSeoUri;\n    }\n\n    /**\n     * Returns active recommendation list object if available\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle product\n     * @param int                                         $iLang    language id\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\RecommendationList|null\n     */\n    protected function _getRecomm($oArticle, $iLang) // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore\n    {\n        $oList = null;\n        $oView = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActiveView();\n        if ($oView instanceof \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController) {\n            $oList = $oView->getActiveRecommList();\n        }\n\n        return $oList;\n    }\n\n    /**\n     * Returns active list type\n     *\n     * @return string\n     */\n    protected function getListType()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActiveView()->getListType();\n    }\n\n    /**\n     * create article uri for given category and save it\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article  $oArticle  article object\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Category $oCategory category object\n     * @param int                                          $iLang     language to generate uri for\n     *\n     * @return string\n     */\n    protected function createArticleCategoryUri($oArticle, $oCategory, $iLang)\n    {\n        startProfile(__FUNCTION__);\n        $oArticle = $this->getProductForLang($oArticle, $iLang);\n\n        // create title part for uri\n        $sTitle = $this->prepareArticleTitle($oArticle);\n\n        // writing category path\n        $sSeoUri = $this->processSeoUrl(\n            \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderCategory::class)->getCategoryUri($oCategory, $iLang) . $sTitle,\n            $oArticle->getId(),\n            $iLang\n        );\n        $sCatId = $oCategory->getId();\n        $this->saveToDb(\n            'oxarticle',\n            $oArticle->getId(),\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtilsUrl()->appendUrl(\n                $oArticle->getBaseStdLink($iLang),\n                ['cnid' => $sCatId]\n            ),\n            $sSeoUri,\n            $iLang,\n            null,\n            0,\n            $sCatId\n        );\n\n        stopProfile(__FUNCTION__);\n\n        return $sSeoUri;\n    }\n\n    /**\n     * Returns SEO uri for passed article\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle     article object\n     * @param int                                         $iLang        language id\n     * @param bool                                        $blRegenerate if TRUE forces seo url regeneration\n     *\n     * @return string\n     */\n    public function getArticleUri($oArticle, $iLang, $blRegenerate = false)\n    {\n        startProfile(__FUNCTION__);\n\n        $sActCatId = '';\n\n        $oActCat = $this->getCategory($oArticle, $iLang);\n\n        if ($oActCat instanceof \\OxidEsales\\Eshop\\Application\\Model\\Category) {\n            $sActCatId = $oActCat->getId();\n        } elseif ($oActCat = $this->getMainCategory($oArticle)) {\n            $sActCatId = $oActCat->getId();\n        }\n\n        //load details link from DB\n        if ($blRegenerate || !($sSeoUri = $this->loadFromDb('oxarticle', $oArticle->getId(), $iLang, null, $sActCatId, true))) {\n            if ($oActCat) {\n                $blInCat = $oActCat->isPriceCategory()\n                    ? $oArticle->inPriceCategory($sActCatId)\n                    : $oArticle->inCategory($sActCatId);\n\n                if ($blInCat) {\n                    $sSeoUri = $this->createArticleCategoryUri($oArticle, $oActCat, $iLang);\n                }\n            }\n        }\n\n        stopProfile(__FUNCTION__);\n\n        return $sSeoUri;\n    }\n\n    /**\n     * Returns active category if available\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle product\n     * @param int                                         $iLang    language id\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Category|null\n     */\n    protected function getCategory($oArticle, $iLang)\n    {\n        $oCat = null;\n        $oView = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActiveView();\n        if ($oView instanceof \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController) {\n            $oCat = $oView->getActiveCategory();\n        } elseif ($oView instanceof \\OxidEsales\\Eshop\\Core\\Controller\\BaseController) {\n            $oCat = $oView->getActCategory();\n        }\n\n        return $oCat;\n    }\n\n    /**\n     * Returns products main category id\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle product\n     *\n     * @return string\n     */\n    protected function getMainCategory($oArticle)\n    {\n        $oMainCat = null;\n\n        // if variant parent id must be used\n        $sArtId = $oArticle->getId();\n        if (isset($oArticle->oxarticles__oxparentid->value) && $oArticle->oxarticles__oxparentid->value) {\n            $sArtId = $oArticle->oxarticles__oxparentid->value;\n        }\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $categoryViewName = $tableViewNameGenerator->getViewName(\"oxobject2category\");\n\n        // add main category caching;\n        $sQ = \"select oxcatnid from \" . $categoryViewName . \" where oxobjectid = :oxobjectid order by oxtime\";\n        $sIdent = md5($categoryViewName . $sArtId);\n\n        if (($sMainCatId = $this->loadFromCache($sIdent, \"oxarticle\")) === false) {\n            $sMainCatId = $oDb->getOne($sQ, [\n                'oxobjectid' => $sArtId\n            ]);\n            // storing in cache\n            $this->saveInCache($sIdent, $sMainCatId, \"oxarticle\");\n        }\n\n        if ($sMainCatId) {\n            $oMainCat = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n            if (!$oMainCat->load($sMainCatId)) {\n                $oMainCat = null;\n            }\n        }\n\n        return $oMainCat;\n    }\n\n    /**\n     * Returns SEO uri for passed article\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle article object\n     * @param int                                         $iLang    language id\n     *\n     * @return string\n     */\n    public function getArticleMainUri($oArticle, $iLang)\n    {\n        startProfile(__FUNCTION__);\n\n        $oMainCat = $this->getMainCategory($oArticle);\n        $sMainCatId = $oMainCat ? $oMainCat->getId() : '';\n\n        //load default article url from DB\n        if (!($sSeoUri = $this->loadFromDb('oxarticle', $oArticle->getId(), $iLang, null, $sMainCatId, true))) {\n            // save for main category\n            if ($oMainCat) {\n                $sSeoUri = $this->createArticleCategoryUri($oArticle, $oMainCat, $iLang);\n            } else {\n                // get default article url\n                $oArticle = $this->getProductForLang($oArticle, $iLang);\n                $sSeoUri = $this->processSeoUrl($this->prepareArticleTitle($oArticle), $oArticle->getId(), $iLang);\n\n                // save default article url\n                $this->saveToDb(\n                    'oxarticle',\n                    $oArticle->getId(),\n                    $oArticle->getBaseStdLink($iLang),\n                    $sSeoUri,\n                    $iLang,\n                    null,\n                    0,\n                    ''\n                );\n            }\n        }\n\n        stopProfile(__FUNCTION__);\n\n        return $sSeoUri;\n    }\n\n    /**\n     * Returns seo title for current article (if oxTitle field is empty, oxArtnum is used).\n     * Additionally - if oxVarSelect is set - title is appended with its value\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle article object\n     *\n     * @return string\n     */\n    protected function prepareArticleTitle($oArticle)\n    {\n        // create title part for uri\n        if (!($sTitle = $oArticle->oxarticles__oxtitle->value)) {\n            // taking parent article title\n            if (($sParentId = $oArticle->oxarticles__oxparentid->value)) {\n                // looking in cache ..\n                if (!isset(self::$_aTitleCache[$sParentId])) {\n                    $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n                    $sQ = \"select oxtitle from \" . $oArticle->getViewName() . \" where oxid = :oxid\";\n                    self::$_aTitleCache[$sParentId] = $oDb->getOne($sQ, [\n                        'oxid' => $sParentId\n                    ]);\n                }\n                $sTitle = self::$_aTitleCache[$sParentId];\n            }\n        }\n\n        // variant has varselect value\n        if ($oArticle->oxarticles__oxvarselect->value) {\n            $sTitle .= ($sTitle ? ' ' : '') . $oArticle->oxarticles__oxvarselect->value . ' ';\n        } elseif (!$sTitle || ($oArticle->oxarticles__oxparentid->value)) {\n            // in case nothing was found - looking for number\n            $sTitle .= ($sTitle ? ' ' : '') . $oArticle->oxarticles__oxartnum->value;\n        }\n\n        return $this->prepareTitle($sTitle, false, $oArticle->getLanguage()) . $this->getUrlExtension();\n    }\n\n    /**\n     * Returns vendor seo uri for current article\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle     article object\n     * @param int                                         $iLang        language id\n     * @param bool                                        $blRegenerate if TRUE forces seo url regeneration\n     *\n     * @return string\n     */\n    public function getArticleVendorUri($oArticle, $iLang, $blRegenerate = false)\n    {\n        startProfile(__FUNCTION__);\n\n        $sSeoUri = null;\n        if ($oVendor = $this->getVendor($oArticle, $iLang)) {\n            //load details link from DB\n            if ($blRegenerate || !($sSeoUri = $this->loadFromDb('oxarticle', $oArticle->getId(), $iLang, null, $oVendor->getId(), true))) {\n                $oArticle = $this->getProductForLang($oArticle, $iLang);\n\n                // create title part for uri\n                $sTitle = $this->prepareArticleTitle($oArticle);\n\n                // create uri for all categories\n                $sSeoUri = \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderVendor::class)->getVendorUri($oVendor, $iLang);\n                $sSeoUri = $this->processSeoUrl($sSeoUri . $sTitle, $oArticle->getId(), $iLang);\n\n                $aStdParams = ['cnid' => \"v_\" . $oVendor->getId(), 'listtype' => $this->getListType()];\n                $this->saveToDb(\n                    'oxarticle',\n                    $oArticle->getId(),\n                    \\OxidEsales\\Eshop\\Core\\Registry::getUtilsUrl()->appendUrl(\n                        $oArticle->getBaseStdLink($iLang),\n                        $aStdParams\n                    ),\n                    $sSeoUri,\n                    $iLang,\n                    null,\n                    0,\n                    $oVendor->getId()\n                );\n            }\n\n            stopProfile(__FUNCTION__);\n        }\n\n        return $sSeoUri;\n    }\n\n    /**\n     * Returns active vendor if available\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle product\n     * @param int                                         $iLang    language id\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Vendor|null\n     */\n    protected function getVendor($oArticle, $iLang)\n    {\n        $oView = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActiveView();\n\n        $oVendor = null;\n        if ($sActVendorId = $oArticle->oxarticles__oxvendorid->value) {\n            if ($oView instanceof \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController && ($oActVendor = $oView->getActVendor())) {\n                $oVendor = $oActVendor;\n            } else {\n                $oVendor = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Vendor::class);\n            }\n            if ($oVendor->getId() !== $sActVendorId) {\n                $oVendor = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Vendor::class);\n                if (!$oVendor->loadInLang($iLang, $sActVendorId)) {\n                    $oVendor = null;\n                }\n            }\n        }\n\n        return $oVendor;\n    }\n\n    /**\n     * Returns manufacturer seo uri for current article\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle     article object\n     * @param int                                         $iLang        language id\n     * @param bool                                        $blRegenerate if TRUE forces seo url regeneration\n     *\n     * @return string\n     */\n    public function getArticleManufacturerUri($oArticle, $iLang, $blRegenerate = false)\n    {\n        $sSeoUri = null;\n        startProfile(__FUNCTION__);\n        if ($oManufacturer = $this->getManufacturer($oArticle, $iLang)) {\n            //load details link from DB\n            if ($blRegenerate || !($sSeoUri = $this->loadFromDb('oxarticle', $oArticle->getId(), $iLang, null, $oManufacturer->getId(), true))) {\n                $oArticle = $this->getProductForLang($oArticle, $iLang);\n\n                // create title part for uri\n                $sTitle = $this->prepareArticleTitle($oArticle);\n\n                // create uri for all categories\n                $sSeoUri = \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderManufacturer::class)->getManufacturerUri($oManufacturer, $iLang);\n                $sSeoUri = $this->processSeoUrl($sSeoUri . $sTitle, $oArticle->getId(), $iLang);\n\n                $aStdParams = ['mnid' => $oManufacturer->getId(), 'listtype' => $this->getListType()];\n                $this->saveToDb(\n                    'oxarticle',\n                    $oArticle->getId(),\n                    \\OxidEsales\\Eshop\\Core\\Registry::getUtilsUrl()->appendUrl(\n                        $oArticle->getBaseStdLink($iLang),\n                        $aStdParams\n                    ),\n                    $sSeoUri,\n                    $iLang,\n                    null,\n                    0,\n                    $oManufacturer->getId()\n                );\n            }\n\n            stopProfile(__FUNCTION__);\n        }\n\n        return $sSeoUri;\n    }\n\n    /**\n     * Returns active manufacturer if available\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle product\n     * @param int                                         $iLang    language id\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Manufacturer|null\n     */\n    protected function getManufacturer($oArticle, $iLang)\n    {\n        $oManufacturer = null;\n        if ($sActManufacturerId = $oArticle->oxarticles__oxmanufacturerid->value) {\n            $oView = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActiveView();\n\n            if ($oView instanceof \\OxidEsales\\Eshop\\Application\\Controller\\FrontendController && ($oActManufacturer = $oView->getActManufacturer())) {\n                $oManufacturer = $oActManufacturer;\n            } else {\n                $oManufacturer = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Manufacturer::class);\n            }\n\n            if ($oManufacturer->getId() !== $sActManufacturerId || $oManufacturer->getLanguage() != $iLang) {\n                $oManufacturer = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Manufacturer::class);\n                if (!$oManufacturer->loadInLang($iLang, $sActManufacturerId)) {\n                    $oManufacturer = null;\n                }\n            }\n        }\n\n        return $oManufacturer;\n    }\n\n    /**\n     * return article main url, with path of its default category\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle product\n     * @param int                                         $iLang    language id\n     *\n     * @return string\n     */\n    public function getArticleMainUrl($oArticle, $iLang = null)\n    {\n        if (!isset($iLang)) {\n            $iLang = $oArticle->getLanguage();\n        }\n\n        return $this->getFullUrl($this->getArticleMainUri($oArticle, $iLang), $iLang);\n    }\n\n    /**\n     * Encodes article URLs into SEO format\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle Article object\n     * @param int                                         $iLang    language\n     * @param int                                         $iType    type\n     *\n     * @return string\n     */\n    public function getArticleUrl($oArticle, $iLang = null, $iType = 0)\n    {\n        if (!isset($iLang)) {\n            $iLang = $oArticle->getLanguage();\n        }\n\n        $sUri = null;\n        switch ($iType) {\n            case OXARTICLE_LINKTYPE_VENDOR:\n                $sUri = $this->getArticleVendorUri($oArticle, $iLang);\n                break;\n            case OXARTICLE_LINKTYPE_MANUFACTURER:\n                $sUri = $this->getArticleManufacturerUri($oArticle, $iLang);\n                break;\n            // @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n            case OXARTICLE_LINKTYPE_RECOMM:\n                $sUri = $this->getArticleRecommUri($oArticle, $iLang);\n                break;\n            // END deprecated\n            case OXARTICLE_LINKTYPE_PRICECATEGORY: // goes price category urls to default (category urls)\n            default:\n                $sUri = $this->getArticleUri($oArticle, $iLang);\n                break;\n        }\n\n        // if was unable to fetch type uri - returning main\n        if (!$sUri) {\n            $sUri = $this->getArticleMainUri($oArticle, $iLang);\n        }\n\n        return $this->getFullUrl($sUri, $iLang);\n    }\n\n    /**\n     * deletes article seo entries\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle article to remove\n     */\n    public function onDeleteArticle($oArticle)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $oDb->execute(\"delete from oxseo where oxobjectid = :oxobjectid and oxtype = 'oxarticle'\", [\n            'oxobjectid' => $oArticle->getId()\n        ]);\n        $oDb->execute(\"delete from oxobject2seodata where oxobjectid = :oxobjectid\", [\n            'oxobjectid' => $oArticle->getId()\n        ]);\n        $oDb->execute(\"delete from oxseohistory where oxobjectid = :oxobjectid\", [\n            'oxobjectid' => $oArticle->getId()\n        ]);\n    }\n\n    /**\n     * Returns alternative uri used while updating seo\n     *\n     * @param string $sObjectId object id\n     * @param int    $iLang     language id\n     *\n     * @return string\n     */\n    protected function getAltUri($sObjectId, $iLang)\n    {\n        $sSeoUrl = null;\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $oArticle->setSkipAssign(true);\n        if ($oArticle->loadInLang($iLang, $sObjectId)) {\n            // choosing URI type to generate\n            switch ($this->getListType()) {\n                case 'vendor':\n                    $sSeoUrl = $this->getArticleVendorUri($oArticle, $iLang, true);\n                    break;\n                case 'manufacturer':\n                    $sSeoUrl = $this->getArticleManufacturerUri($oArticle, $iLang, true);\n                    break;\n                default:\n                    $sSeoUrl = $this->getArticleUri($oArticle, $iLang, true);\n                    break;\n            }\n        }\n\n        return $sSeoUrl;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/SeoEncoderCategory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Category;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\n\n/**\n * Seo encoder category\n */\nclass SeoEncoderCategory extends \\OxidEsales\\Eshop\\Core\\SeoEncoder\n{\n    /** @var array _aCatCache cache for categories. */\n    protected $_aCatCache = [];\n\n    /**\n     * Returns target \"extension\" (/)\n     *\n     * @return string\n     */\n    protected function getUrlExtension()\n    {\n        return '/';\n    }\n\n    /**\n     * _categoryUrlLoader loads category from db\n     * returns false if cat needs to be encoded (load failed)\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Category $oCat  category object\n     * @param int                                          $iLang active language id\n     *\n     * @access protected\n     *\n     * @return boolean\n     */\n    protected function categoryUrlLoader($oCat, $iLang)\n    {\n        $sCacheId = $this->getCategoryCacheId($oCat, $iLang);\n        if (isset($this->_aCatCache[$sCacheId])) {\n            $sSeoUrl = $this->_aCatCache[$sCacheId];\n        } elseif (($sSeoUrl = $this->loadFromDb('oxcategory', $oCat->getId(), $iLang))) {\n            // caching\n            $this->_aCatCache[$sCacheId] = $sSeoUrl;\n        }\n\n        return $sSeoUrl;\n    }\n\n    /**\n     * _getCatecgoryCacheId return string for isntance cache id\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Category $oCat  category object\n     * @param int                                          $iLang active language\n     *\n     * @access private\n     *\n     * @return string\n     */\n    private function getCategoryCacheId($oCat, $iLang)\n    {\n        return $oCat->getId() . '_' . ((int) $iLang);\n    }\n\n    /**\n     * Returns SEO uri for passed category\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Category $oCat         category object\n     * @param int                                          $iLang        language\n     * @param bool                                         $blRegenerate if TRUE forces seo url regeneration\n     *\n     * @return string\n     */\n    public function getCategoryUri($oCat, $iLang = null, $blRegenerate = false)\n    {\n        startProfile(__FUNCTION__);\n        $sCatId = $oCat->getId();\n\n        // skipping external category URLs\n        if ($oCat->oxcategories__oxextlink->value) {\n            $sSeoUrl = null;\n        } else {\n            // not found in cache, process it from the top\n            if (!isset($iLang)) {\n                $iLang = $oCat->getLanguage();\n            }\n\n            $aCacheMap = [];\n            $aStdLinks = [];\n\n            while ($oCat && !($sSeoUrl = $this->categoryUrlLoader($oCat, $iLang))) {\n                if ($iLang != $oCat->getLanguage()) {\n                    $sId = $oCat->getId();\n                    $oCat = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n                    $oCat->loadInLang($iLang, $sId);\n                }\n\n                // prepare oCat title part\n                $sTitle = $this->prepareTitle($oCat->oxcategories__oxtitle->value, false, $oCat->getLanguage());\n\n                foreach (array_keys($aCacheMap) as $id) {\n                    $aCacheMap[$id] = $sTitle . '/' . $aCacheMap[$id];\n                }\n\n                $aCacheMap[$oCat->getId()] = $sTitle;\n                $aStdLinks[$oCat->getId()] = $oCat->getBaseStdLink($iLang);\n\n                // load parent\n                $oCat = $oCat->getParentCategory();\n            }\n\n            foreach ($aCacheMap as $sId => $sUri) {\n                $this->_aCatCache[$sId . '_' . $iLang] = $this->processSeoUrl($sSeoUrl . $sUri . '/', $sId, $iLang);\n                $this->saveToDb('oxcategory', $sId, $aStdLinks[$sId], $this->_aCatCache[$sId . '_' . $iLang], $iLang);\n            }\n\n            $sSeoUrl = $this->_aCatCache[$sCatId . '_' . $iLang];\n        }\n\n        stopProfile(__FUNCTION__);\n\n        return $sSeoUrl;\n    }\n\n    /**\n     * Returns category SEO url for specified page\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Category $category   Category object.\n     * @param int                                          $pageNumber Number of the page which should be prepared.\n     * @param int                                          $languageId Language id.\n     * @param bool                                         $isFixed    Fixed url marker (default is null).\n     *\n     * @return string\n     */\n    public function getCategoryPageUrl($category, $pageNumber, $languageId = null, $isFixed = null)\n    {\n        if (!isset($languageId)) {\n            $languageId = $category->getLanguage();\n        }\n        $stdUrl = $category->getBaseStdLink($languageId);\n        $parameters = null;\n\n        $stdUrl = $this->trimUrl($stdUrl, $languageId);\n        $seoUrl = $this->getCategoryUri($category, $languageId);\n\n        if ($isFixed === null) {\n            $isFixed = $this->isFixed('oxcategory', $category->getId(), $languageId);\n        }\n\n        return $this->assembleFullPageUrl($category, 'oxcategory', $stdUrl, $seoUrl, $pageNumber, $parameters, $languageId, $isFixed);\n    }\n\n    /**\n     * Category URL encoder. If category has external URLs, skip encoding\n     * for this category. If SEO id is not set, generates and saves SEO id\n     * for category (\\OxidEsales\\Eshop\\Core\\SeoEncoder::_getSeoId()).\n     * If category has subcategories, it iterates through them.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Category $oCategory Category object\n     * @param int                                          $iLang     Language\n     *\n     * @return string\n     */\n    public function getCategoryUrl($oCategory, $iLang = null)\n    {\n        $sUrl = '';\n        if (!isset($iLang)) {\n            $iLang = $oCategory->getLanguage();\n        }\n        // category may have specified url\n        if (($sSeoUrl = $this->getCategoryUri($oCategory, $iLang))) {\n            $sUrl = $this->getFullUrl($sSeoUrl, $iLang);\n        }\n\n        return $sUrl;\n    }\n\n    /**\n     * Marks related to category objects as expired\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Category $oCategory Category object\n     */\n    public function markRelatedAsExpired($oCategory)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        // select it from table instead of using object carrying value\n        // this is because this method is usually called inside update,\n        // where object may already be carrying changed id\n        $aCatInfo = $oDb->getRow(\"select oxrootid, oxleft, oxright from oxcategories where oxid = :oxid limit 1\", [\n            'oxid' => $oCategory->getId()\n        ]);\n\n        // update sub cats\n        $sQ = \"update oxseo as seo1, (select oxid from oxcategories \n            where oxrootid = :oxrootid \n            and oxleft > :oxleft \n            and oxright < :oxright ) as seo2 \n                set seo1.oxexpired = '1' where seo1.oxtype = 'oxcategory' and seo1.oxobjectid = seo2.oxid\";\n        $oDb->execute($sQ, [\n            'oxrootid' => $aCatInfo['oxrootid'],\n            'oxleft' => (int) $aCatInfo['oxleft'],\n            'oxright' => (int) $aCatInfo['oxright']\n        ]);\n\n        // update subarticles\n        $sQ = \"update oxseo as seo1, (select distinct o2c.oxobjectid as id from oxcategories as cat left join oxobject2category \"\n              . \"as o2c on o2c.oxcatnid=cat.oxid where cat.oxrootid = :oxrootid and cat.oxleft >= :oxleft \"\n              . \"and cat.oxright <= :oxright) as seo2 \"\n              . \"set seo1.oxexpired = '1' where seo1.oxtype = 'oxarticle' and seo1.oxobjectid = seo2.id \"\n              . \"and seo1.oxfixed = 0\";\n        $oDb->execute($sQ, [\n            'oxrootid' => $aCatInfo['oxrootid'],\n            'oxleft' => (int) $aCatInfo['oxleft'],\n            'oxright' => (int) $aCatInfo['oxright']\n        ]);\n    }\n\n    /**\n     * @param Category $category\n     */\n    public function onDeleteCategory($category)\n    {\n        $this->setRelatedToCategorySeoUrlsAsExpired($category);\n\n        $database = DatabaseProvider::getDb();\n\n        $database->execute(\"delete from oxseo where oxseo.oxtype = 'oxarticle' and oxseo.oxparams = :oxparams\", [\n            'oxparams' => $category->getId()\n        ]);\n        $database->execute(\"delete from oxseo where oxobjectid = :oxobjectid and oxtype = 'oxcategory'\", [\n            'oxobjectid' => $category->getId()\n        ]);\n        $database->execute(\"delete from oxobject2seodata where oxobjectid = :oxobjectid\", [\n            'oxobjectid' => $category->getId()\n        ]);\n        $database->execute(\"delete from oxseohistory where oxobjectid = :oxobjectid\", [\n            'oxobjectid' => $category->getId()\n        ]);\n    }\n\n    /**\n     * Returns alternative uri used while updating seo\n     *\n     * @param string $sObjectId object id\n     * @param int    $iLang     language id\n     *\n     * @return string\n     */\n    protected function getAltUri($sObjectId, $iLang)\n    {\n        $sSeoUrl = null;\n        $oCat = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n        if ($oCat->loadInLang($iLang, $sObjectId)) {\n            $sSeoUrl = $this->getCategoryUri($oCat, $iLang);\n        }\n\n        return $sSeoUrl;\n    }\n\n    private function setRelatedToCategorySeoUrlsAsExpired(Category $category): void\n    {\n        foreach ($this->getSeoUrlsForCategory($category) as $seoUrl) {\n            $this->setSeoUrlsAsExpired(\n                $this->getRelatedProductsAndSubCategories($seoUrl)\n            );\n        }\n    }\n\n    private function getSeoUrlsForCategory(Category $category): array\n    {\n        return DatabaseProvider::getDb()\n            ->getCol(\n                \"select oxseourl from oxseo where oxobjectid = :oxobjectid and oxtype = 'oxcategory'\",\n                ['oxobjectid' => $category->getId()]\n            );\n    }\n\n    private function getRelatedProductsAndSubCategories(string $rootCategoryUrl): array\n    {\n        return DatabaseProvider::getDb()\n            ->getCol(\n                \"\n            select oxident\n            from oxseo\n            where oxseo.oxseourl like CONCAT(:url, '%') \n              and oxtype in ('oxarticle', 'oxcategory')\",\n                ['url' => $rootCategoryUrl]\n            );\n    }\n\n    private function setSeoUrlsAsExpired(array $idents): void\n    {\n        DatabaseProvider::getDb()\n            ->execute(\n                sprintf(\n                    \"update oxseo set oxseo.oxexpired=1 where oxseo.oxident in ('%s')\",\n                    implode(\"','\", $idents)\n                )\n            );\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/SeoEncoderContent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxRegistry;\nuse oxDb;\n\n/**\n * Seo encoder base\n */\nclass SeoEncoderContent extends \\OxidEsales\\Eshop\\Core\\SeoEncoder\n{\n    /**\n     * Returns target \"extension\" (/)\n     *\n     * @return string\n     */\n    protected function getUrlExtension()\n    {\n        return '/';\n    }\n\n    /**\n     * Returns SEO uri for content object. Includes parent category path info if\n     * content is assigned to it\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Content $oCont        content category object\n     * @param int                                         $iLang        language\n     * @param bool                                        $blRegenerate if TRUE forces seo url regeneration\n     *\n     * @return string\n     */\n    public function getContentUri($oCont, $iLang = null, $blRegenerate = false)\n    {\n        if (!isset($iLang)) {\n            $iLang = $oCont->getLanguage();\n        }\n        //load details link from DB\n        if ($blRegenerate || !($sSeoUrl = $this->loadFromDb('oxContent', $oCont->getId(), $iLang))) {\n            if ($iLang != $oCont->getLanguage()) {\n                $sId = $oCont->getId();\n                $oCont = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Content::class);\n                $oCont->loadInLang($iLang, $sId);\n            }\n\n            $sSeoUrl = '';\n            if ($oCont->getCategoryId() && $oCont->getType() === 2) {\n                $oCat = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n                if ($oCat->loadInLang($iLang, $oCont->oxcontents__oxcatid->value)) {\n                    $sParentId = $oCat->oxcategories__oxparentid->value;\n                    if ($sParentId && $sParentId != 'oxrootid') {\n                        $oParentCat = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n                        if ($oParentCat->loadInLang($iLang, $oCat->oxcategories__oxparentid->value)) {\n                            $sSeoUrl .= \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderCategory::class)->getCategoryUri($oParentCat);\n                        }\n                    }\n                }\n            }\n\n            $sSeoUrl .= $this->prepareTitle($oCont->oxcontents__oxtitle->value, false, $oCont->getLanguage()) . '/';\n            $sSeoUrl = $this->processSeoUrl($sSeoUrl, $oCont->getId(), $iLang);\n\n            $this->saveToDb('oxcontent', $oCont->getId(), $oCont->getBaseStdLink($iLang), $sSeoUrl, $iLang);\n        }\n\n        return $sSeoUrl;\n    }\n\n    /**\n     * encodeContentUrl encodes content link\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Content $oCont category object\n     * @param int                                         $iLang language\n     *\n     * @return string|bool\n     */\n    public function getContentUrl($oCont, $iLang = null)\n    {\n        if (!isset($iLang)) {\n            $iLang = $oCont->getLanguage();\n        }\n\n        return $this->getFullUrl($this->getContentUri($oCont, $iLang), $iLang);\n    }\n\n    /**\n     * deletes content seo entries\n     *\n     * @param string $sId content ids\n     */\n    public function onDeleteContent($sId)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $oDb->execute(\"delete from oxseo where oxobjectid = :oxobjectid and oxtype = 'oxcontent'\", [\n            'oxobjectid' => $sId\n        ]);\n        $oDb->execute(\"delete from oxobject2seodata where oxobjectid = :oxobjectid\", [\n            'oxobjectid' => $sId\n        ]);\n        $oDb->execute(\"delete from oxseohistory where oxobjectid = :oxobjectid\", [\n            'oxobjectid' => $sId\n        ]);\n    }\n\n    /**\n     * Returns alternative uri used while updating seo\n     *\n     * @param string $sObjectId object id\n     * @param int    $iLang     language id\n     *\n     * @return string\n     */\n    protected function getAltUri($sObjectId, $iLang)\n    {\n        $sSeoUrl = null;\n        $oCont = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Content::class);\n        if ($oCont->loadInLang($iLang, $sObjectId)) {\n            $sSeoUrl = $this->getContentUri($oCont, $iLang, true);\n        }\n\n        return $sSeoUrl;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/SeoEncoderManufacturer.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\n\n/**\n * Seo encoder base\n */\nclass SeoEncoderManufacturer extends \\OxidEsales\\Eshop\\Core\\SeoEncoder\n{\n    /**\n     * Root manufacturer uri cache\n     *\n     * @var array\n     */\n    protected $_aRootManufacturerUri = null;\n\n    /**\n     * Returns target \"extension\" (/)\n     *\n     * @return string\n     */\n    protected function getUrlExtension()\n    {\n        return '/';\n    }\n\n    /**\n     * Returns part of SEO url excluding path\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Manufacturer $oManufacturer manufacturer object\n     * @param int                                              $iLang         language\n     * @param bool                                             $blRegenerate  if TRUE forces seo url regeneration\n     *\n     * @return string\n     */\n    public function getManufacturerUri($oManufacturer, $iLang = null, $blRegenerate = false)\n    {\n        if (!isset($iLang)) {\n            $iLang = $oManufacturer->getLanguage();\n        }\n        // load from db\n        if ($blRegenerate || !($sSeoUrl = $this->loadFromDb('oxmanufacturer', $oManufacturer->getId(), $iLang))) {\n            if ($iLang != $oManufacturer->getLanguage()) {\n                $sId = $oManufacturer->getId();\n                $oManufacturer = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Manufacturer::class);\n                $oManufacturer->loadInLang($iLang, $sId);\n            }\n\n            $sSeoUrl = '';\n            if ($oManufacturer->getId() != 'root') {\n                if (!isset($this->_aRootManufacturerUri[$iLang])) {\n                    $oRootManufacturer = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Manufacturer::class);\n                    $oRootManufacturer->loadInLang($iLang, 'root');\n                    $this->_aRootManufacturerUri[$iLang] = $this->getManufacturerUri($oRootManufacturer, $iLang);\n                }\n                $sSeoUrl .= $this->_aRootManufacturerUri[$iLang];\n            }\n\n            $sSeoUrl .= $this->prepareTitle($oManufacturer->oxmanufacturers__oxtitle->value, false, $oManufacturer->getLanguage()) . '/';\n            $sSeoUrl = $this->processSeoUrl($sSeoUrl, $oManufacturer->getId(), $iLang);\n\n            // save to db\n            $this->saveToDb('oxmanufacturer', $oManufacturer->getId(), $oManufacturer->getBaseStdLink($iLang), $sSeoUrl, $iLang);\n        }\n\n        return $sSeoUrl;\n    }\n\n    /**\n     * Returns Manufacturer SEO url for specified page\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Manufacturer $manufacturer Manufacturer object\n     * @param int                                              $pageNumber   Number of the page which should be prepared.\n     * @param int                                              $languageId   Language id.\n     * @param bool                                             $isFixed      Fixed url marker (default is null).\n     *\n     * @return string\n     */\n    public function getManufacturerPageUrl($manufacturer, $pageNumber, $languageId = null, $isFixed = null)\n    {\n        if (!isset($languageId)) {\n            $languageId = $manufacturer->getLanguage();\n        }\n        $stdUrl = $manufacturer->getBaseStdLink($languageId);\n        $parameters = null;\n\n        $stdUrl = $this->trimUrl($stdUrl, $languageId);\n        $seoUrl = $this->getManufacturerUri($manufacturer, $languageId);\n\n        if ($isFixed === null) {\n            $isFixed = $this->isFixed('oxmanufacturer', $manufacturer->getId(), $languageId);\n        }\n\n        return $this->assembleFullPageUrl($manufacturer, 'oxmanufacturer', $stdUrl, $seoUrl, $pageNumber, $parameters, $languageId, $isFixed);\n    }\n\n    /**\n     * Encodes manufacturer category URLs into SEO format\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Manufacturer $oManufacturer Manufacturer object\n     * @param int                                              $iLang         language\n     *\n     * @return string\n     */\n    public function getManufacturerUrl($oManufacturer, $iLang = null)\n    {\n        if (!isset($iLang)) {\n            $iLang = $oManufacturer->getLanguage();\n        }\n\n        return $this->getFullUrl($this->getManufacturerUri($oManufacturer, $iLang), $iLang);\n    }\n\n    /**\n     * Deletes manufacturer seo entry\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Manufacturer $oManufacturer Manufacturer object\n     */\n    public function onDeleteManufacturer($oManufacturer)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $oDb->execute(\"delete from oxseo where oxobjectid = :oxobjectid and oxtype = 'oxmanufacturer'\", [\n            'oxobjectid' => $oManufacturer->getId()\n        ]);\n        $oDb->execute(\"delete from oxobject2seodata where oxobjectid = :oxobjectid\", [\n            'oxobjectid' => $oManufacturer->getId()\n        ]);\n        $oDb->execute(\"delete from oxseohistory where oxobjectid = :oxobjectid\", [\n            'oxobjectid' => $oManufacturer->getId()\n        ]);\n    }\n\n    /**\n     * Returns alternative uri used while updating seo\n     *\n     * @param string $sObjectId object id\n     * @param int    $iLang     language id\n     *\n     * @return string\n     */\n    protected function getAltUri($sObjectId, $iLang)\n    {\n        $sSeoUrl = null;\n        $oManufacturer = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Manufacturer::class);\n        if ($oManufacturer->loadInLang($iLang, $sObjectId)) {\n            $sSeoUrl = $this->getManufacturerUri($oManufacturer, $iLang, true);\n        }\n\n        return $sSeoUrl;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/SeoEncoderRecomm.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxRegistry;\n\n/**\n * Seo encoder base\n *\n * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n */\nclass SeoEncoderRecomm extends \\OxidEsales\\Eshop\\Core\\SeoEncoder\n{\n    /**\n     * Returns SEO uri for tag.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\RecommendationList $oRecomm recommendation list object\n     * @param int                                                    $iLang   language\n     *\n     * @return string\n     */\n    public function getRecommUri($oRecomm, $iLang = null)\n    {\n        if (!($sSeoUrl = $this->loadFromDb('dynamic', $oRecomm->getId(), $iLang))) {\n            $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n            // fetching part of base url\n            $sSeoUrl = $this->getStaticUri(\n                $oRecomm->getBaseStdLink($iLang, false),\n                $myConfig->getShopId(),\n                $iLang\n            )\n            . $this->prepareTitle($oRecomm->oxrecommlists__oxtitle->value, false, $iLang);\n\n            // creating unique\n            $sSeoUrl = $this->processSeoUrl($sSeoUrl, $oRecomm->getId(), $iLang);\n\n            // inserting\n            $this->saveToDb('dynamic', $oRecomm->getId(), $oRecomm->getBaseStdLink($iLang), $sSeoUrl, $iLang, $myConfig->getShopId());\n        }\n\n        return $sSeoUrl;\n    }\n\n    /**\n     * Returns full url for passed tag\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\RecommendationList $oRecomm recommendation list object\n     * @param int                                                    $iLang   language\n     *\n     * @return string\n     */\n    public function getRecommUrl($oRecomm, $iLang = null)\n    {\n        if (!isset($iLang)) {\n            $iLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage();\n        }\n\n        return $this->getFullUrl($this->getRecommUri($oRecomm, $iLang), $iLang);\n    }\n\n    /**\n     * Returns tag SEO url for specified page\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\RecommendationList $recomm     Recommendation list object.\n     * @param int                                                    $pageNumber Number of the page which should be prepared.\n     * @param int                                                    $languageId Language id.\n     * @param bool                                                   $isFixed    Fixed url marker (default is null).\n     *\n     * @return string\n     */\n    public function getRecommPageUrl($recomm, $pageNumber, $languageId = null, $isFixed = false)\n    {\n        if (!isset($languageId)) {\n            $languageId = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage();\n        }\n        $stdUrl = $recomm->getBaseStdLink($languageId);\n        $parameters = null;\n\n        $stdUrl = $this->trimUrl($stdUrl, $languageId);\n        $seoUrl = $this->getRecommUri($recomm, $languageId);\n\n        return $this->assembleFullPageUrl($recomm, 'dynamic', $stdUrl, $seoUrl, $pageNumber, $parameters, $languageId, $isFixed);\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/SeoEncoderVendor.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\n\n/**\n * Seo encoder base\n */\nclass SeoEncoderVendor extends \\OxidEsales\\Eshop\\Core\\SeoEncoder\n{\n    /**\n     * Root vendor uri cache\n     *\n     * @var string\n     */\n    protected $_aRootVendorUri = null;\n\n    /**\n     * Returns target \"extension\" (/)\n     *\n     * @return string\n     */\n    protected function getUrlExtension()\n    {\n        return '/';\n    }\n\n    /**\n     * Returns part of SEO url excluding path\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Vendor $vendor           Vendor object\n     * @param int                                        $languageId       Language id\n     * @param bool                                       $shouldRegenerate If TRUE - forces seo url regeneration\n     *\n     * @return string\n     */\n    public function getVendorUri($vendor, $languageId = null, $shouldRegenerate = false)\n    {\n        if (!isset($languageId)) {\n            $languageId = $vendor->getLanguage();\n        }\n        // load from db\n        if ($shouldRegenerate || !($seoUrl = $this->loadFromDb('oxvendor', $vendor->getId(), $languageId))) {\n            if ($languageId != $vendor->getLanguage()) {\n                $vendorId = $vendor->getId();\n                $vendor = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Vendor::class);\n                $vendor->loadInLang($languageId, $vendorId);\n            }\n\n            $seoUrl = '';\n            if ($vendor->getId() != 'root') {\n                if (!isset($this->_aRootVendorUri[$languageId])) {\n                    $rootVendor = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Vendor::class);\n                    $rootVendor->loadInLang($languageId, 'root');\n                    $this->_aRootVendorUri[$languageId] = $this->getVendorUri($rootVendor, $languageId);\n                }\n                $seoUrl .= $this->_aRootVendorUri[$languageId];\n            }\n\n            $seoUrl .= $this->prepareTitle($vendor->oxvendor__oxtitle->value, false, $vendor->getLanguage()) . '/';\n            $seoUrl = $this->processSeoUrl($seoUrl, $vendor->getId(), $languageId);\n\n            // save to db\n            $this->saveToDb('oxvendor', $vendor->getId(), $vendor->getBaseStdLink($languageId), $seoUrl, $languageId);\n        }\n\n        return $seoUrl;\n    }\n\n    /**\n     * Returns vendor SEO url for specified page\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Vendor $vendor     Vendor object.\n     * @param int                                        $pageNumber Number of the page which should be prepared.\n     * @param int                                        $languageId Language id.\n     * @param bool                                       $isFixed    Fixed url marker (default is null).\n     *\n     * @return string\n     */\n    public function getVendorPageUrl($vendor, $pageNumber, $languageId = null, $isFixed = null)\n    {\n        if (!isset($languageId)) {\n            $languageId = $vendor->getLanguage();\n        }\n        $stdUrl = $vendor->getBaseStdLink($languageId);\n        $parameters = null;\n\n        $stdUrl = $this->trimUrl($stdUrl, $languageId);\n        $seoUrl = $this->getVendorUri($vendor, $languageId);\n\n        if ($isFixed === null) {\n            $isFixed = $this->isFixed('oxvendor', $vendor->getId(), $languageId);\n        }\n\n        return $this->assembleFullPageUrl($vendor, 'oxvendor', $stdUrl, $seoUrl, $pageNumber, $parameters, $languageId, $isFixed);\n    }\n\n    /**\n     * Encodes vendor category URLs into SEO format.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Vendor $vendor     Vendor object\n     * @param int                                        $languageId Language id\n     *\n     * @return null\n     */\n    public function getVendorUrl($vendor, $languageId = null)\n    {\n        if (!isset($languageId)) {\n            $languageId = $vendor->getLanguage();\n        }\n\n        return $this->getFullUrl($this->getVendorUri($vendor, $languageId), $languageId);\n    }\n\n    /**\n     * Deletes Vendor seo entry\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Vendor $vendor Vendor object\n     */\n    public function onDeleteVendor($vendor)\n    {\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $vendorId = $vendor->getId();\n        $database->execute(\"delete from oxseo where oxobjectid = :oxobjectid and oxtype = 'oxvendor'\", [\n            'oxobjectid' => $vendorId\n        ]);\n        $database->execute(\"delete from oxobject2seodata where oxobjectid = :oxobjectid\", [\n            'oxobjectid' => $vendorId\n        ]);\n        $database->execute(\"delete from oxseohistory where oxobjectid = :oxobjectid\", [\n            'oxobjectid' => $vendorId\n        ]);\n    }\n\n    /**\n     * Returns alternative uri used while updating seo.\n     *\n     * @param string $vendorId   Vendor id\n     * @param int    $languageId Language id\n     *\n     * @return string\n     */\n    protected function getAltUri($vendorId, $languageId)\n    {\n        $seoUrl = null;\n        $vendor = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Vendor::class);\n        if ($vendor->loadInLang($languageId, $vendorId)) {\n            $seoUrl = $this->getVendorUri($vendor, $languageId, true);\n        }\n\n        return $seoUrl;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Shop.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Shop manager.\n * Performs configuration and object loading or deletion.\n */\nclass Shop extends \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel\n{\n    /** @var string Name of current class. */\n    protected $_sClassName = 'oxshop';\n\n    /** @var array Multi shop tables, set in config. */\n    protected array $_aMultiShopTables = [];\n\n    /** @var array Query variables. */\n    protected $_aQueries = [];\n\n    /** @var array Database tables. */\n    protected $_aTables = null;\n\n    /** @var bool Defines if multishop inherits categories. */\n    protected $_blMultiShopInheritCategories = false;\n\n    private static bool $disabledViewUsage = false;\n\n    public static function disableViews(): void\n    {\n        self::$disabledViewUsage = true;\n    }\n\n    /**\n     * Database tables setter.\n     *\n     * @param array $aTables\n     */\n    public function setTables($aTables)\n    {\n        $this->_aTables = $aTables;\n    }\n\n    /**\n     * Database tables getter.\n     *\n     * @return array\n     */\n    public function getTables()\n    {\n        if (is_null($this->_aTables)) {\n            $aTables = $this->formDatabaseTablesArray();\n            $this->setTables($aTables);\n        }\n\n        return $this->_aTables;\n    }\n\n    /**\n     * Database queries setter.\n     *\n     * @param array $aQueries\n     */\n    public function setQueries($aQueries)\n    {\n        $this->_aQueries = $aQueries;\n    }\n\n    /**\n     * Database queries getter.\n     *\n     * @return array\n     */\n    public function getQueries()\n    {\n        return $this->_aQueries;\n    }\n\n    /**\n     * Add a query to query array.\n     *\n     * @param string $sQuery\n     */\n    public function addQuery($sQuery)\n    {\n        $this->_aQueries[] = $sQuery;\n    }\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n\n        if (!$this->isShopValid()) {\n            Registry::getLogger()->error('Shop is not valid');\n\n            return;\n        }\n\n        $this->init('oxshops');\n\n        if ($iMax = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iMaxShopId')) {\n            $this->setMaxShopId($iMax);\n        }\n    }\n\n    /**\n     * Sets multi shop tables\n     *\n     * @param string $aMultiShopTables multi shop tables\n     */\n    public function setMultiShopTables($aMultiShopTables)\n    {\n        $this->_aMultiShopTables = $aMultiShopTables;\n    }\n\n    public function getMultiShopTables(): array\n    {\n        return $this->_aMultiShopTables;\n    }\n\n    /**\n     * (Re)generates shop views\n     *\n     * @param bool  $multishopInheritCategories Config option blMultishopInherit_oxcategories\n     * @param array $mallInherit                Array of config options blMallInherit\n     *\n     * @return bool is all views generated successfully\n     */\n    public function generateViews($multishopInheritCategories = false, $mallInherit = null)\n    {\n        $this->prepareViewsQueries();\n        $blSuccess = $this->runQueries();\n\n        $this->cleanInvalidViews();\n\n        return $blSuccess;\n    }\n\n    /**\n     * Returns default category of the shop.\n     *\n     * @return string\n     */\n    public function getDefaultCategory()\n    {\n        return $this->oxshops__oxdefcat->value;\n    }\n\n    /**\n     * Returns true if shop in productive mode\n     *\n     * @return bool\n     */\n    public function isProductiveMode()\n    {\n        return (bool) $this->oxshops__oxproductive->value;\n    }\n\n    /**\n     * Creates view query and adds it to query array.\n     *\n     * @param string $sTable     Table name\n     * @param array  $aLanguages Language array( id => abbreviation )\n     */\n    public function createViewQuery($sTable, $aLanguages = null)\n    {\n        $sStart = 'CREATE OR REPLACE SQL SECURITY INVOKER VIEW';\n\n        if (!is_array($aLanguages)) {\n            $aLanguages = [0 => null];\n        }\n\n        foreach ($aLanguages as $iLang => $sLang) {\n            $this->addViewLanguageQuery($sStart, $sTable, $iLang, $sLang);\n        }\n    }\n\n    public function getViewName($forceCoreTableUsage = null)\n    {\n        if (self::$disabledViewUsage) {\n            return $this->getCoreTableName();\n        }\n\n        return parent::getViewName($forceCoreTableUsage);\n    }\n\n    /**\n     * Returns table field name mapping sql section for single language views\n     *\n     * @param string $sTable Table name\n     * @param int    $iLang  Language id\n     *\n     * @return string\n     */\n    protected function getViewSelect($sTable, $iLang)\n    {\n        $oMetaData = oxNew(\\OxidEsales\\Eshop\\Core\\DbMetaDataHandler::class);\n        $aFields = $oMetaData->getSinglelangFields($sTable, $iLang);\n        foreach ($aFields as $sCoreField => $sField) {\n            if ($sCoreField !== $sField) {\n                $aFields[$sCoreField] = $sField . ' AS ' . $sCoreField;\n            }\n        }\n\n        return implode(',', $aFields);\n    }\n\n    /**\n     * Returns table fields sql section for multiple language views\n     *\n     * @param string $sTable table name\n     *\n     * @return string\n     */\n    protected function getViewSelectMultilang($sTable)\n    {\n        $aFields = [];\n\n        $oMetaData = oxNew(\\OxidEsales\\Eshop\\Core\\DbMetaDataHandler::class);\n        $aTables = array_merge([$sTable], $oMetaData->getAllMultiTables($sTable));\n        foreach ($aTables as $sTableKey => $sTableName) {\n            $aTableFields = $oMetaData->getFields($sTableName);\n            foreach ($aTableFields as $sCoreField => $sField) {\n                if (!isset($aFields[$sCoreField])) {\n                    $aFields[$sCoreField] = $sField;\n                }\n            }\n        }\n\n        return implode(',', $aFields);\n    }\n\n    /**\n     * Returns all language table view JOIN section\n     *\n     * @param string $sTable table name\n     *\n     * @return string $sSQL\n     */\n    protected function getViewJoinAll($sTable)\n    {\n        $sJoin = ' ';\n        $oMetaData = oxNew(\\OxidEsales\\Eshop\\Core\\DbMetaDataHandler::class);\n        $aTables = $oMetaData->getAllMultiTables($sTable);\n        if (count($aTables)) {\n            foreach ($aTables as $sTableKey => $sTableName) {\n                $sJoin .= \"LEFT JOIN {$sTableName} USING (OXID) \";\n            }\n        }\n\n        return $sJoin;\n    }\n\n    /**\n     * Returns language table view JOIN section\n     *\n     * @param string $sTable table name\n     * @param int    $iLang  language id\n     *\n     * @return string $sSQL\n     */\n    protected function getViewJoinLang($sTable, $iLang)\n    {\n        $sJoin = ' ';\n        $sLangTable = getLangTableName($sTable, $iLang);\n        if ($sLangTable && $sLangTable !== $sTable) {\n            $sJoin .= \"LEFT JOIN {$sLangTable} USING (OXID) \";\n        }\n\n        return $sJoin;\n    }\n\n    /**\n     * Gets all invalid views and drops them from database\n     */\n    protected function cleanInvalidViews()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $oLang = Registry::getLang();\n        $aLanguages = $oLang->getLanguageIds($this->getId());\n\n        $aMultilangTables = Registry::getLang()->getMultiLangTables();\n        $aMultishopTables = $this->getMultiShopTables();\n\n        $oLang = Registry::getLang();\n        $aAllShopLanguages = $oLang->getAllShopLanguageIds();\n\n        $oViewsValidator = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ShopViewValidator::class);\n\n        $oViewsValidator->setShopId($this->getId());\n        $oViewsValidator->setLanguages($aLanguages);\n        $oViewsValidator->setAllShopLanguages($aAllShopLanguages);\n        $oViewsValidator->setMultiLangTables($aMultilangTables);\n        $oViewsValidator->setMultiShopTables($aMultishopTables);\n\n        $aViews = $oViewsValidator->getInvalidViews();\n\n        foreach ($aViews as $sView) {\n            $oDb->execute('DROP VIEW IF EXISTS `' . $sView . '`');\n        }\n    }\n\n    /**\n     * Creates all view queries and adds them in query array\n     */\n    protected function prepareViewsQueries()\n    {\n        $oLang = Registry::getLang();\n        $aLanguages = $oLang->getLanguageIds($this->getId());\n\n        $aMultilangTables = Registry::getLang()->getMultiLangTables();\n        $aTables = $this->getTables();\n        foreach ($aTables as $sTable) {\n            $this->createViewQuery($sTable);\n            if (in_array($sTable, $aMultilangTables)) {\n                $this->createViewQuery($sTable, $aLanguages);\n            }\n        }\n    }\n\n    /**\n     * Adds view language query to query array.\n     *\n     * @param string $queryStart\n     * @param string $table\n     * @param int    $languageId\n     * @param string $languageAbbr\n     */\n    protected function addViewLanguageQuery($queryStart, $table, $languageId, $languageAbbr)\n    {\n        $sLangAddition = $languageAbbr === null ? '' : \"_{$languageAbbr}\";\n\n        $sViewTable = \"oxv_{$table}{$sLangAddition}\";\n\n        if ($languageAbbr === null) {\n            $sFields = $this->getViewSelectMultilang($table);\n            $sJoin = $this->getViewJoinAll($table);\n        } else {\n            $sFields = $this->getViewSelect($table, $languageId);\n            $sJoin = $this->getViewJoinLang($table, $languageId);\n        }\n\n        if (\"\" === $sFields) {\n            Registry::getLogger()->error(\"View for $table can not be generated, Please check if table exists\");\n            return;\n        }\n\n        $sQuery = \"{$queryStart} `{$sViewTable}` AS SELECT {$sFields} FROM {$table}{$sJoin}\";\n        $this->addQuery($sQuery);\n    }\n\n    /**\n     * Runs stored queries\n     * Returns false when any of the queries fail, otherwise return true\n     *\n     * @return bool\n     */\n    protected function runQueries()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $aQueries = $this->getQueries();\n        $bSuccess = true;\n        foreach ($aQueries as $sQuery) {\n            try {\n                $oDb->execute($sQuery);\n            } catch (\\OxidEsales\\Eshop\\Core\\Exception\\StandardException $exception) {\n                \\OxidEsales\\Eshop\\Core\\Registry::getLogger()->error($exception->getMessage(), [$exception]);\n                $bSuccess = false;\n            }\n        }\n\n        return $bSuccess;\n    }\n\n    /**\n     * Forms array of tables which are available.\n     *\n     * @return array\n     */\n    protected function formDatabaseTablesArray()\n    {\n        $multilanguageTables = Registry::getLang()->getMultiLangTables();\n\n        return array_unique($multilanguageTables);\n    }\n\n    /**\n     * Checks whether current shop is valid.\n     *\n     * @return bool\n     */\n    protected function isShopValid()\n    {\n        return true;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/ShopList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\n/**\n * Shop list manager.\n * Organizes list of shop objects.\n */\nclass ShopList extends \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n{\n    /**\n     * Calls parent constructor\n     */\n    public function __construct()\n    {\n        parent::__construct('oxshop');\n    }\n\n    /**\n     * Loads all shops to list\n     */\n    public function getAll()\n    {\n        $this->selectString('SELECT `oxshops`.* FROM `oxshops`');\n    }\n\n    /**\n     * Gets shop list into object\n     */\n    public function getIdTitleList()\n    {\n        $this->setBaseObject(oxNew('oxListObject', 'oxshops'));\n        $this->selectString('SELECT `OXID`, `OXNAME` FROM `oxshops`');\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/ShopViewValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\n\n/**\n * Shop view validator.\n * checks which views are valid / invalid\n */\nclass ShopViewValidator\n{\n    protected $_aMultiLangTables = [];\n\n    protected $_aMultiShopTables = [];\n\n    protected $_aLanguages = [];\n\n    protected $_aAllShopLanguages = [];\n\n    protected $_iShopId = null;\n\n    protected $_aAllViews = [];\n\n    protected $_aShopViews = [];\n\n    protected $_aValidShopViews = [];\n\n    /**\n     * Sets multi language tables.\n     *\n     * @param null $aMultiLangTables\n     */\n    public function setMultiLangTables($aMultiLangTables)\n    {\n        $this->_aMultiLangTables = $aMultiLangTables;\n    }\n\n    /**\n     * Returns multi lang tables\n     *\n     * @return array\n     */\n    public function getMultiLangTables()\n    {\n        return $this->_aMultiLangTables;\n    }\n\n\n    /**\n     * Sets multi shop tables.\n     *\n     * @param array $aMultiShopTables\n     */\n    public function setMultiShopTables($aMultiShopTables)\n    {\n        $this->_aMultiShopTables = $aMultiShopTables;\n    }\n\n    /**\n     * Returns multi shop tables\n     *\n     * @return array\n     */\n    public function getMultiShopTables()\n    {\n        return $this->_aMultiShopTables;\n    }\n\n    /**\n     * Returns list of active languages in shop\n     *\n     * @param array $aLanguages\n     */\n    public function setLanguages($aLanguages)\n    {\n        $this->_aLanguages = $aLanguages;\n    }\n\n    /**\n     * Gets languages.\n     *\n     * @return array\n     */\n    public function getLanguages()\n    {\n        return $this->_aLanguages;\n    }\n\n    /**\n     * Returns list of active languages in shop\n     *\n     * @param array $aAllShopLanguages\n     */\n    public function setAllShopLanguages($aAllShopLanguages)\n    {\n        $this->_aAllShopLanguages = $aAllShopLanguages;\n    }\n\n    /**\n     * Gets all shop languages.\n     *\n     * @return array\n     */\n    public function getAllShopLanguages()\n    {\n        return $this->_aAllShopLanguages;\n    }\n\n\n    /**\n     * Sets shop id.\n     *\n     * @param integer $iShopId\n     */\n    public function setShopId($iShopId)\n    {\n        $this->_iShopId = $iShopId;\n    }\n\n    /**\n     * Returns list of available shops\n     *\n     * @return integer\n     */\n    public function getShopId()\n    {\n        return $this->_iShopId;\n    }\n\n    /**\n     * Returns list of all shop views\n     *\n     * @return array\n     */\n    protected function getAllViews()\n    {\n        if (empty($this->_aAllViews)) {\n            $this->_aAllViews = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->getCol(\"SHOW TABLES LIKE  'oxv\\_%'\");\n        }\n\n        return $this->_aAllViews;\n    }\n\n    /**\n     * Checks if given view name belongs to current subshop or is general view\n     *\n     * @param string $sViewName View name\n     *\n     * @return bool\n     */\n    protected function isCurrentShopView($sViewName)\n    {\n        $blResult = false;\n\n        $blEndsWithShopId = preg_match(\"/[_]([0-9]+)$/\", $sViewName, $aMatchEndsWithShopId);\n        $blContainsShopId = preg_match(\"/[_]([0-9]+)[_]/\", $sViewName, $aMatchContainsShopId);\n\n        if (\n            (!$blEndsWithShopId && !$blContainsShopId) ||\n            ($blEndsWithShopId && $aMatchEndsWithShopId[1] == $this->getShopId()) ||\n            ($blContainsShopId && $aMatchContainsShopId[1] == $this->getShopId())\n        ) {\n            $blResult = true;\n        }\n\n        return $blResult;\n    }\n\n\n    /**\n     * Returns list of shop specific views currently in database\n     *\n     * @return array\n     */\n    protected function getShopViews()\n    {\n        if (empty($this->_aShopViews)) {\n            $this->_aShopViews = [];\n            $aAllViews = $this->getAllViews();\n\n            foreach ($aAllViews as $sView) {\n                if ($this->isCurrentShopView($sView)) {\n                    $this->_aShopViews[] = $sView;\n                }\n            }\n        }\n\n        return $this->_aShopViews;\n    }\n\n    /**\n     * Returns list of valid shop views\n     *\n     * @return array\n     */\n    protected function getValidShopViews()\n    {\n        if (empty($this->_aValidShopViews)) {\n            $aTables = $this->getShopTables();\n            $this->_aValidShopViews = [];\n\n            foreach ($aTables as $sTable) {\n                $this->prepareShopTableViewNames($sTable);\n            }\n        }\n\n        return $this->_aValidShopViews;\n    }\n\n    /**\n     * Get list of shop tables\n     *\n     * @return array\n     */\n    protected function getShopTables()\n    {\n        $shopTables = $this->getMultilangTables();\n\n        return $shopTables;\n    }\n\n    /**\n     * Appends possible table views to $this->_aValidShopViews variable.\n     *\n     * @param string $tableName\n     */\n    protected function prepareShopTableViewNames($tableName)\n    {\n        $this->_aValidShopViews[] = 'oxv_' . $tableName;\n\n        if (in_array($tableName, $this->getMultiLangTables())) {\n            foreach ($this->getAllShopLanguages() as $sLang) {\n                $this->_aValidShopViews[] = 'oxv_' . $tableName . '_' . $sLang;\n            }\n        }\n    }\n\n    /**\n     * Checks if view name is valid according to current config\n     *\n     * @param string $sViewName View name\n     *\n     * @return bool\n     */\n    protected function isViewValid($sViewName)\n    {\n        return in_array($sViewName, $this->getValidShopViews());\n    }\n\n    /**\n     * Returns list of invalid views\n     *\n     * @return array\n     */\n    public function getInvalidViews()\n    {\n        $aInvalidViews = [];\n        $aShopViews = $this->getShopViews();\n\n        foreach ($aShopViews as $sView) {\n            if (!$this->isViewValid($sView)) {\n                $aInvalidViews[] = $sView;\n            }\n        }\n\n        return $aInvalidViews;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/SimpleVariant.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse oxRegistry;\nuse oxPrice;\n\n/**\n * Lightweight variant handler. Implemnets only absolutely needed oxArticle methods.\n */\nclass SimpleVariant extends \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel implements \\OxidEsales\\Eshop\\Core\\Contract\\IUrl\n{\n    /**\n     * Use lazy loading for this item\n     *\n     * @var bool\n     */\n    protected $_blUseLazyLoading = true;\n\n    /**\n     * Variant price\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Price\n     */\n    protected $_oPrice = null;\n\n    /**\n     * Parent article\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected $_oParent = null;\n\n    /**\n     * Stardard/dynamic article urls for languages\n     *\n     * @var array\n     */\n    protected $_aStdUrls = [];\n\n    /**\n     * Stardard/dynamic article urls for languages\n     *\n     * @var array\n     */\n    protected $_aBaseStdUrls = [];\n\n    /**\n     * Seo article urls for languages\n     *\n     * @var array\n     */\n    protected $_aSeoUrls = [];\n\n    /**\n     * user object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\User\n     */\n    protected $_oUser = null;\n\n    /**\n     * Initializes instance\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->_sCacheKey = \"simplevariants\";\n        $this->init('oxarticles');\n    }\n\n    /**\n     * Implementing (fakeing) performance friendly method from oxArticle\n     * oxbase\n     *\n     * @return null\n     */\n    public function getSelectLists()\n    {\n        return null;\n    }\n\n    /**\n     * Returns article user\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\User\n     */\n    public function getArticleUser()\n    {\n        if ($this->_oUser === null) {\n            $this->_oUser = $this->getUser();\n        }\n\n        return $this->_oUser;\n    }\n\n    /**\n     * get user Group A, B or C price, returns db price if user is not in groups\n     *\n     * @return double\n     */\n    protected function getGroupPrice()\n    {\n        $dPrice = $this->oxarticles__oxprice->value;\n        if ($oUser = $this->getArticleUser()) {\n            if ($oUser->inGroup('oxidpricea')) {\n                $dPrice = $this->oxarticles__oxpricea->value;\n            } elseif ($oUser->inGroup('oxidpriceb')) {\n                $dPrice = $this->oxarticles__oxpriceb->value;\n            } elseif ($oUser->inGroup('oxidpricec')) {\n                $dPrice = $this->oxarticles__oxpricec->value;\n            }\n        }\n\n        // #1437/1436C - added config option, and check for zero A,B,C price values\n        if (Registry::getConfig()->getConfigParam('blOverrideZeroABCPrices') && (float) $dPrice == 0) {\n            $dPrice = $this->oxarticles__oxprice->value;\n        }\n\n        return $dPrice;\n    }\n\n    /**\n     * Implementing (faking) performance friendly method from oxArticle\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getPrice()\n    {\n        $myConfig = Registry::getConfig();\n        // 0002030 No need to return price if it disabled for better performance.\n        if (!$myConfig->getConfigParam('bl_perfLoadPrice')) {\n            return;\n        }\n\n        if ($this->_oPrice === null) {\n            $this->_oPrice = oxNew(\\OxidEsales\\Eshop\\Core\\Price::class);\n            if (($dPrice = $this->getGroupPrice())) {\n                $dPrice = $this->modifyGroupPrice($dPrice);\n                $this->_oPrice->setPrice($dPrice, $this->_dVat);\n\n                $this->applyParentVat($this->_oPrice);\n                $this->applyCurrency($this->_oPrice);\n                // apply discounts\n                $this->applyParentDiscounts($this->_oPrice);\n            } elseif (($oParent = $this->getParent())) {\n                $this->_oPrice = $oParent->getPrice();\n            }\n        }\n\n        return $this->_oPrice;\n    }\n\n    /**\n     * Make changes to price on getting price.\n     *\n     * @param float $price\n     * @return float\n     */\n    public function modifyGroupPrice($price)\n    {\n        return $price;\n    }\n\n    /**\n     * Applies currency factor\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Price $oPrice Price object\n     * @param object                       $oCur   Currency object\n     */\n    protected function applyCurrency(\\OxidEsales\\Eshop\\Core\\Price $oPrice, $oCur = null)\n    {\n        if (!$oCur) {\n            $oCur = Registry::getConfig()->getActShopCurrencyObject();\n        }\n\n        $oPrice->multiply($oCur->rate);\n    }\n\n    /**\n     * Applies discounts which should be applied in general case (for 0 amount)\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Price $oPrice Price object\n     */\n    protected function applyParentDiscounts($oPrice)\n    {\n        if (($oParent = $this->getParent())) {\n            $oParent->applyDiscountsForVariant($oPrice);\n        }\n    }\n\n    /**\n     * apply parent article VAT to given price\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Price $oPrice price object\n     */\n    protected function applyParentVat($oPrice)\n    {\n        if (($oParent = $this->getParent()) && !Registry::getConfig()->getConfigParam('bl_perfCalcVatOnlyForBasketOrder')) {\n            $oParent->applyVats($oPrice);\n        }\n    }\n\n    /**\n     * Price setter\n     *\n     * @param object $oPrice price object\n     */\n    public function setPrice($oPrice)\n    {\n        $this->_oPrice = $oPrice;\n    }\n\n    /**\n     * Returns formated product price.\n     *\n     * @return double\n     */\n    public function getFPrice()\n    {\n        $sPrice = null;\n        if (($oPrice = $this->getPrice())) {\n            $sPrice = Registry::getLang()->formatCurrency($oPrice->getBruttoPrice());\n        }\n\n        return $sPrice;\n    }\n\n    /**\n     * Sets parent article\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oParent Parent article\n     */\n    public function setParent($oParent)\n    {\n        $this->_oParent = $oParent;\n    }\n\n    /**\n     * Parent article getter.\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    public function getParent()\n    {\n        return $this->_oParent;\n    }\n\n    /**\n     * Get link type\n     *\n     * @return int\n     */\n    public function getLinkType()\n    {\n        $iLinkType = 0;\n        if (($oParent = $this->getParent())) {\n            $iLinkType = $oParent->getLinkType();\n        }\n\n        return $iLinkType;\n    }\n\n    /**\n     * Checks if article is assigned to category\n     *\n     * @param string $sCatNid category ID\n     *\n     * @return bool\n     */\n    public function inCategory($sCatNid)\n    {\n        $blIn = false;\n        if (($oParent = $this->getParent())) {\n            $blIn = $oParent->inCategory($sCatNid);\n        }\n\n        return $blIn;\n    }\n\n    /**\n     * Checks if article is assigned to price category $sCatNID\n     *\n     * @param string $sCatNid Price category ID\n     *\n     * @return bool\n     */\n    public function inPriceCategory($sCatNid)\n    {\n        $blIn = false;\n        if (($oParent = $this->getParent())) {\n            $blIn = $oParent->inPriceCategory($sCatNid);\n        }\n\n        return $blIn;\n    }\n\n    /**\n     * Returns base dynamic url: shopurl/index.php?cl=details\n     *\n     * @param int  $iLang   language id\n     * @param bool $blAddId add current object id to url or not\n     * @param bool $blFull  return full including domain name [optional]\n     *\n     * @return string\n     */\n    public function getBaseStdLink($iLang, $blAddId = true, $blFull = true)\n    {\n        if (!isset($this->_aBaseStdUrls[$iLang][$iLinkType])) {\n            $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            $oArticle->setId($this->getId());\n            $oArticle->setLinkType($iLinkType);\n            $this->_aBaseStdUrls[$iLang][$iLinkType] = $oArticle->getBaseStdLink($iLang, $blAddId, $blFull);\n        }\n\n        return $this->_aBaseStdUrls[$iLang][$iLinkType];\n    }\n\n    /**\n     * Gets article link\n     *\n     * @param int   $iLang   required language [optional]\n     * @param array $aParams additional params to use [optional]\n     *\n     * @return string\n     */\n    public function getStdLink($iLang = null, $aParams = [])\n    {\n        if ($iLang === null) {\n            $iLang = (int) $this->getLanguage();\n        }\n\n        $iLinkType = $this->getLinkType();\n        if (!isset($this->_aStdUrls[$iLang][$iLinkType])) {\n            $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            $oArticle->setId($this->getId());\n            $oArticle->setLinkType($iLinkType);\n            $this->_aStdUrls[$iLang][$iLinkType] = $oArticle->getStdLink($iLang, $aParams);\n        }\n\n        return $this->_aStdUrls[$iLang][$iLinkType];\n    }\n\n    /**\n     * Returns raw recommlist seo url\n     *\n     * @param int $iLang language id\n     *\n     * @return string\n     */\n    public function getBaseSeoLink($iLang)\n    {\n        return Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderArticle::class)->getArticleUrl($this, $iLang, $iLinkType);\n    }\n\n    /**\n     * Gets article link\n     *\n     * @param int $iLang required language id [optional]\n     *\n     * @return string\n     */\n    public function getLink($iLang = null)\n    {\n        if ($iLang === null) {\n            $iLang = (int) $this->getLanguage();\n        }\n\n        if (!Registry::getUtils()->seoIsActive()) {\n            return $this->getStdLink($iLang);\n        }\n\n        $iLinkType = $this->getLinkType();\n        if (!isset($this->_aSeoUrls[$iLang][$iLinkType])) {\n            $this->_aSeoUrls[$iLang][$iLinkType] = $this->getBaseSeoLink($iLang);\n        }\n\n        return $this->_aSeoUrls[$iLang][$iLinkType];\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/SimpleVariantList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\n/**\n * Simple variant list.\n */\nclass SimpleVariantList extends \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n{\n    /**\n     * Parent article for list variants\n     */\n    protected $_oParent = null;\n\n    /**\n     * List Object class name\n     *\n     * @var string\n     */\n    protected $_sObjectsInListName = 'oxsimplevariant';\n\n    /**\n     * Sets parent variant\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oParent Parent article\n     */\n    public function setParent($oParent)\n    {\n        $this->_oParent = $oParent;\n    }\n\n    /**\n     * Sets parent for variant. This method is invoked for each element in oxList::assign() loop.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\SimpleVariant $oListObject Simple variant\n     * @param array          $aDbFields   Array of available\n     */\n    protected function assignElement($oListObject, $aDbFields)\n    {\n        $oListObject->setParent($this->_oParent);\n        parent::assignElement($oListObject, $aDbFields);\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/State.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * State handler\n */\nclass State extends \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel\n{\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxstate';\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxI18n()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init(\"oxstates\");\n    }\n\n    /**\n     * Returns country id by code\n     *\n     * @param string $sCode      country code\n     * @param string $sCountryId country id\n     *\n     * @return string\n     */\n    public function getIdByCode($sCode, $sCountryId)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $params = [\n            'oxisoalpha2' => $sCode,\n            'oxcountryid' => $sCountryId\n        ];\n\n        return $oDb->getOne(\"SELECT oxid FROM oxstates \n            WHERE oxisoalpha2 = :oxisoalpha2 \n              AND oxcountryid = :oxcountryid\", $params);\n    }\n\n    /**\n     * Get state title by id\n     *\n     * @param integer|string $iStateId\n     *\n     * @return string\n     */\n    public function getTitleById($iStateId)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sQ = \"SELECT oxtitle FROM \" . $tableViewNameGenerator->getViewName(\"oxstates\") . \" \n            WHERE oxid = :oxid\";\n\n        $sStateTitle = $oDb->getOne($sQ, [\n            'oxid' => $iStateId\n        ]);\n\n        return (string) $sStateTitle;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/User/UserShippingAddressUpdatableFields.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model\\User;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Address;\nuse OxidEsales\\Eshop\\Core\\Contract\\AbstractUpdatableFields;\n\n/**\n * @inheritdoc\n */\nclass UserShippingAddressUpdatableFields extends AbstractUpdatableFields\n{\n    /**\n     * UserShippingAddressUpdatableFields constructor.\n     */\n    public function __construct()\n    {\n        $address = oxNew(Address::class);\n        $this->tableName = $address->getCoreTableName();\n    }\n\n    /**\n     * Return list of fields which could be updated by shop customer.\n     *\n     * @return array\n     */\n    public function getUpdatableFields()\n    {\n        return [\n            'OXCOMPANY',\n            'OXFNAME',\n            'OXLNAME',\n            'OXSTREET',\n            'OXSTREETNR',\n            'OXADDINFO',\n            'OXCITY',\n            'OXCOUNTRY',\n            'OXCOUNTRYID',\n            'OXSTATEID',\n            'OXZIP',\n            'OXFON',\n            'OXFAX',\n            'OXSAL',\n            'OXTIMESTAMP'\n        ];\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/User/UserUpdatableFields.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model\\User;\n\nuse OxidEsales\\Eshop\\Application\\Model\\User;\nuse OxidEsales\\Eshop\\Core\\Contract\\AbstractUpdatableFields;\n\n/**\n * @inheritdoc\n */\nclass UserUpdatableFields extends AbstractUpdatableFields\n{\n    /**\n     * UserUpdatableFields constructor.\n     */\n    public function __construct()\n    {\n        $user = oxNew(User::class);\n        $this->tableName = $user->getCoreTableName();\n    }\n\n    /**\n     * Return list of fields which could be updated by shop customer.\n     *\n     * @return array\n     */\n    public function getUpdatableFields()\n    {\n        return [\n            'OXACTIVE',\n            'OXRIGHTS',\n            'OXSHOPID',\n            'OXUSERNAME',\n            'OXPASSWORD',\n            'OXPASSSALT',\n            'OXCUSTNR',\n            'OXUSTID',\n            'OXCOMPANY',\n            'OXFNAME',\n            'OXLNAME',\n            'OXSTREET',\n            'OXSTREETNR',\n            'OXADDINFO',\n            'OXCITY',\n            'OXCOUNTRYID',\n            'OXSTATEID',\n            'OXZIP',\n            'OXFON',\n            'OXFAX',\n            'OXSAL',\n            'OXCREATE',\n            'OXREGISTER',\n            'OXPRIVFON',\n            'OXMOBFON',\n            'OXBIRTHDATE',\n            'OXURL',\n            'OXUPDATEKEY',\n            'OXUPDATEEXP',\n            'OXTIMESTAMP'\n        ];\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/User.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\Database\\Adapter\\DatabaseInterface;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Email;\nuse OxidEsales\\Eshop\\Core\\Exception\\CookieException;\nuse OxidEsales\\Eshop\\Core\\Exception\\StandardException;\nuse OxidEsales\\Eshop\\Core\\Exception\\UserException;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Model\\ListModel;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse OxidEsales\\EshopCommunity\\Application\\Enum\\SubscriptionOptedInStatus;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\PasswordServiceBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\RandomTokenGeneratorBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface;\n\n/**\n * User manager.\n * Performs user managing function, as assigning to groups, updating\n * information, deletion and other.\n */\nclass User extends \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n{\n    const USER_COOKIE_SALT = 'user_cookie_salt';\n\n    /**\n     * Shop control variable\n     *\n     * @var string\n     */\n    protected $_blDisableShopCheck = true;\n\n    /**\n     * Current Subscription Object if there is any\n     *\n     * @var object\n     */\n    protected $_oNewsSubscription = null;\n\n    /**\n     * Current object class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxuser';\n\n    /**\n     * User wish / notice list\n     *\n     * @var array\n     */\n    protected $_aBaskets = [];\n\n    /**\n     * User groups list\n     *\n     * @var ListModel\n     */\n    protected $_oGroups;\n\n    /**\n     * User address list array\n     *\n     * @var array\n     */\n    protected $_aAddresses = [];\n\n    /**\n     * User payment list\n     *\n     * @var ListModel\n     */\n    protected $_oPayments;\n\n    /**\n     * User recommendation list\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @var ListModel\n     */\n    protected $_oRecommList;\n\n    /**\n     * Mall user status\n     *\n     * @var bool\n     */\n    protected $_blMallUsers = false;\n\n    /**\n     * user cookies\n     *\n     * @var array\n     */\n    protected static $_aUserCookie = [];\n\n    /**\n     * Notice list item's count\n     *\n     * @var integer\n     */\n    protected $_iCntNoticeListArticles = null;\n\n    /**\n     * Wishlist item's count\n     *\n     * @var integer\n     */\n    protected $_iCntWishListArticles = null;\n\n    /**\n     * User recommlist count\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @var integer\n     */\n    protected $_iCntRecommLists = null;\n\n    /**\n     * Password update key\n     *\n     * @var string\n     */\n    protected $_sUpdateKey = null;\n\n    /**\n     * User loaded from cookie\n     *\n     * @var bool\n     */\n    protected $_blLoadedFromCookie = null;\n\n    /**\n     * User selected shipping address id\n     *\n     * @var string\n     */\n    protected $_sSelAddressId = null;\n\n    /**\n     * User selected shipping address\n     *\n     * @var object\n     */\n    protected $_oSelAddress = null;\n\n    /**\n     * Id of wishlist user\n     *\n     * @var string\n     */\n    protected $_sWishId = null;\n\n    /**\n     * Country title field\n     *\n     * @var object\n     */\n    protected $_oUserCountryTitle = null;\n\n    /**\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\State\n     */\n    protected $_oStateObject = null;\n\n    /**\n     * @var bool\n     *\n     * @deprecated since v6.4.0 (2019-03-15); `\\OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\PasswordServiceBridgeInterface`\n     *                                        was added as the new default for hashing passwords.\n     */\n    private $isOutdatedPasswordHashAlgorithmUsed = false;\n\n    /**\n     * Gets state object.\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\State\n     */\n    protected function getStateObject()\n    {\n        if (is_null($this->_oStateObject)) {\n            $this->_oStateObject = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\State::class);\n        }\n\n        return $this->_oStateObject;\n    }\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()).\n     */\n    public function __construct()\n    {\n        $this->setMallUsersStatus(Registry::getConfig()->getConfigParam('blMallUsers'));\n\n        parent::__construct();\n        $this->init('oxuser');\n    }\n\n    /**\n     * Sets mall user status\n     *\n     * @param bool $blOn mall users is on or off\n     */\n    public function setMallUsersStatus($blOn = false)\n    {\n        $this->_blMallUsers = $blOn;\n    }\n\n    /**\n     * Getter for special not frequently used fields\n     *\n     * @param string $sParamName name of parameter to get value\n     *\n     * @return mixed\n     */\n    public function __get($sParamName)\n    {\n        // it saves memory using - loads data only if it is used\n        switch ($sParamName) {\n            case 'oGroups':\n                return $this->_oGroups = $this->getUserGroups();\n                break;\n            case 'iCntNoticeListArticles':\n                return $this->_iCntNoticeListArticles = $this->getNoticeListArtCnt();\n                break;\n            case 'iCntWishListArticles':\n                return $this->_iCntWishListArticles = $this->getWishListArtCnt();\n                break;\n            // @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n            case 'iCntRecommLists':\n                return $this->_iCntRecommLists = $this->getRecommListsCount();\n                break;\n            // END deprecated\n            case 'oAddresses':\n                return $this->getUserAddresses();\n                break;\n            case 'oPayments':\n                return $this->_oPayments = $this->getUserPayments();\n                break;\n            case 'oxuser__oxcountry':\n                return $this->oxuser__oxcountry = $this->getUserCountry();\n                break;\n            case 'sDBOptin':\n                return $this->sDBOptin = $this->getNewsSubscription()->getOptInStatus();\n                break;\n            case 'sEmailFailed':\n                return $this->sEmailFailed = $this->getNewsSubscription()->getOptInEmailStatus();\n                break;\n        }\n    }\n\n    /**\n     * Returns user newsletter subscription controller object\n     *\n     * @return object oxnewssubscribed\n     */\n    public function getNewsSubscription()\n    {\n        if ($this->_oNewsSubscription !== null) {\n            return $this->_oNewsSubscription;\n        }\n\n        $this->_oNewsSubscription = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\NewsSubscribed::class);\n\n        // if subscription object is not set yet - we should create one\n        if (!$this->_oNewsSubscription->loadFromUserId($this->getId())) {\n            if (!$this->_oNewsSubscription->loadFromEmail($this->getFieldData('oxusername'))) {\n                // no subscription defined yet - creating one\n\n                $this->_oNewsSubscription->assign([\n                    'oxuserid' => $this->getId(),\n                    'oxemail' => $this->getFieldData('oxusername'),\n                    'oxsal' => $this->getFieldData('oxsal'),\n                    'oxfname' => $this->getFieldData('oxfname'),\n                    'oxlname' => $this->getFieldData('oxlname')\n                ]);\n            }\n        }\n\n        return $this->_oNewsSubscription;\n    }\n\n    /**\n     * Returns user country (object) according to passed parameters or they\n     * are taken from user object ( oxid, country id) and session (language)\n     *\n     * @param string $sCountryId country id (optional)\n     * @param int    $iLang      active language (optional)\n     *\n     * @return string\n     */\n    public function getUserCountry($sCountryId = null, $iLang = null)\n    {\n        if ($this->_oUserCountryTitle == null || $sCountryId) {\n            $sId = $sCountryId ? $sCountryId : $this->oxuser__oxcountryid->value;\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $sViewName = $tableViewNameGenerator->getViewName('oxcountry', $iLang);\n\n            $countryTitle = $oDb->getOne(\"select oxtitle from {$sViewName} where oxid = :oxid\", [\n                'oxid' => $sId\n            ]);\n\n            $oCountry = new \\OxidEsales\\Eshop\\Core\\Field($countryTitle, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n            if (!$sCountryId) {\n                $this->_oUserCountryTitle = $oCountry;\n            }\n        } else {\n            return $this->_oUserCountryTitle;\n        }\n\n        return $oCountry;\n    }\n\n    /**\n     * Returns user countryid according to passed name\n     *\n     * @param string $sCountry country\n     *\n     * @return string\n     */\n    public function getUserCountryId($sCountry = null)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sQ = \"select oxid from \" . $tableViewNameGenerator->getViewName(\"oxcountry\") . \"\n            where oxactive = '1' and oxisoalpha2 = :oxisoalpha2\";\n        $sCountryId = $oDb->getOne($sQ, [\n            'oxisoalpha2' => $sCountry\n        ]);\n\n        return $sCountryId;\n    }\n\n    /**\n     * Returns assigned user groups list object\n     *\n     * @param string $sOXID object ID (default is null)\n     *\n     * @return object\n     */\n    public function getUserGroups($sOXID = null)\n    {\n        if (isset($this->_oGroups)) {\n            return $this->_oGroups;\n        }\n\n        if (!$sOXID) {\n            $sOXID = $this->getId();\n        }\n\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sViewName = $tableViewNameGenerator->getViewName(\"oxgroups\");\n        $this->_oGroups = oxNew(ListModel::class, 'oxgroups');\n        $sSelect = \"select {$sViewName}.* from {$sViewName} left join oxobject2group on oxobject2group.oxgroupsid = {$sViewName}.oxid\n                     where oxobject2group.oxobjectid = :oxobjectid\";\n        $this->_oGroups->selectString($sSelect, [\n            'oxobjectid' => $sOXID\n        ]);\n\n        return $this->_oGroups;\n    }\n\n    /**\n     * Returns user defined Address list object\n     *\n     * @param string $sUserId object ID (default is null)\n     *\n     * @return array\n     */\n    public function getUserAddresses($sUserId = null)\n    {\n        $sUserId = isset($sUserId) ? $sUserId : $this->getId();\n        if (!isset($this->_aAddresses[$sUserId])) {\n            $oUserAddressList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\UserAddressList::class);\n            $oUserAddressList->load($sUserId);\n            $this->_aAddresses[$sUserId] = $oUserAddressList;\n\n            // marking selected\n            if ($sAddressId = $this->getSelectedAddressId()) {\n                foreach ($this->_aAddresses[$sUserId] as $oAddress) {\n                    if ($oAddress->getId() === $sAddressId) {\n                        $oAddress->setSelected();\n                        break;\n                    }\n                }\n            }\n        }\n\n        return $this->_aAddresses[$sUserId];\n    }\n\n    /**\n     * Selected user address setter\n     *\n     * @param string $sAddressId selected address id\n     */\n    public function setSelectedAddressId($sAddressId)\n    {\n        $this->_sSelAddressId = $sAddressId;\n    }\n\n    /**\n     * Returns user chosen address id (\"oxaddressid\" or \"deladrid\")\n     *\n     * @return string\n     */\n    public function getSelectedAddressId()\n    {\n        if ($this->_sSelAddressId !== null) {\n            return $this->_sSelAddressId;\n        }\n\n        $sAddressId = Registry::getRequest()->getRequestEscapedParameter(\"oxaddressid\");\n        if (!$sAddressId && !Registry::getRequest()->getRequestEscapedParameter('reloadaddress')) {\n            $sAddressId = Registry::getSession()->getVariable(\"deladrid\");\n        }\n\n        return $sAddressId;\n    }\n\n    /**\n     * Checks if product from wishlist is added\n     *\n     * @return $sWishId\n     */\n    protected function getWishListId()\n    {\n        $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n        $this->_sWishId = null;\n        // check if we have to set it here\n        $oBasket = $session->getBasket();\n        foreach ($oBasket->getContents() as $oBasketItem) {\n            if ($this->_sWishId = $oBasketItem->getWishId()) {\n                // stop on first found\n                break;\n            }\n        }\n\n        return $this->_sWishId;\n    }\n\n    /**\n     * Sets in the array \\OxidEsales\\Eshop\\Application\\Model\\User::_aAddresses selected address.\n     * Returns user selected address object.\n     *\n     * @return object $oSelectedAddress\n     */\n    public function getSelectedAddress()\n    {\n        if ($this->_oSelAddress !== null) {\n            return $this->_oSelAddress;\n        }\n\n        $oSelectedAddress = null;\n        $oAddresses = $this->getUserAddresses();\n        if ($oAddresses->count()) {\n            if ($sAddressId = $this->getSelectedAddressId()) {\n                foreach ($oAddresses as $oAddress) {\n                    if ($oAddress->getId() == $sAddressId) {\n                        $oAddress->selected = 1;\n                        $oAddress->setSelected();\n                        $oSelectedAddress = $oAddress;\n                        break;\n                    }\n                }\n            }\n\n            // in case none is set - setting first one\n            if (!$oSelectedAddress) {\n                if (!$sAddressId || $sAddressId >= 0) {\n                    $oAddresses->rewind();\n                    $oAddress = $oAddresses->current();\n                } else {\n                    $aAddresses = $oAddresses->getArray();\n                    $oAddress = array_pop($aAddresses);\n                }\n                $oAddress->selected = 1;\n                $oAddress->setSelected();\n                $oSelectedAddress = $oAddress;\n            }\n        }\n        $this->_oSelAddress = $oSelectedAddress;\n\n        return $oSelectedAddress;\n    }\n\n    /**\n     * Returns user payment history list object\n     *\n     * @param string $sOXID object ID (default is null)\n     *\n     * @return ListModel with oxuserpayments objects\n     */\n    public function getUserPayments($sOXID = null)\n    {\n        if ($this->_oPayments === null) {\n            if (!$sOXID) {\n                $sOXID = $this->getId();\n            }\n\n            $sSelect = 'select * from oxuserpayments\n                where oxuserid = :oxuserid ';\n\n            $this->_oPayments = oxNew(ListModel::class);\n            $this->_oPayments->init('oxUserPayment');\n            $this->_oPayments->selectString($sSelect, [\n                'oxuserid' => $sOXID\n            ]);\n        }\n\n        return $this->_oPayments;\n    }\n\n    /**\n     * Saves (updates) user object data information in DB. Return true on success.\n     *\n     * @return bool\n     */\n    public function save()\n    {\n        $blAddRemark = false;\n        if (\n            $this->getFieldData('oxpassword')\n            && (!$this->oxuser__oxregister instanceof \\OxidEsales\\Eshop\\Core\\Field || $this->oxuser__oxregister->value < 1)\n        ) {\n            $blAddRemark = true;\n            //save oxregister value\n            $this->oxuser__oxregister = new \\OxidEsales\\Eshop\\Core\\Field(date('Y-m-d H:i:s'), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        }\n\n        // setting user rights\n        $this->oxuser__oxrights = new \\OxidEsales\\Eshop\\Core\\Field(\n            $this->getUserRights(),\n            \\OxidEsales\\Eshop\\Core\\Field::T_RAW\n        );\n\n        // processing birth date which came from output as array\n        if ($this->oxuser__oxbirthdate && is_array($this->oxuser__oxbirthdate->value)) {\n            $this->oxuser__oxbirthdate = new \\OxidEsales\\Eshop\\Core\\Field(\n                $this->convertBirthday($this->oxuser__oxbirthdate->value),\n                \\OxidEsales\\Eshop\\Core\\Field::T_RAW\n            );\n        }\n\n        $blRet = parent::save();\n\n        //add registered remark\n        if ($blAddRemark && $blRet) {\n            $oRemark = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Remark::class);\n            $oRemark->oxremark__oxtext = new \\OxidEsales\\Eshop\\Core\\Field(\n                Registry::getLang()->translateString('usrRegistered', null, true),\n                \\OxidEsales\\Eshop\\Core\\Field::T_RAW\n            );\n            $oRemark->oxremark__oxtype = new \\OxidEsales\\Eshop\\Core\\Field('r', \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n            $oRemark->oxremark__oxparentid = new \\OxidEsales\\Eshop\\Core\\Field($this->getId(), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n            $oRemark->save();\n        }\n\n        return $blRet;\n    }\n\n    /**\n     * Overrides parent isDerived check and returns true\n     *\n     * @return bool\n     */\n    public function allowDerivedUpdate()\n    {\n        return true;\n    }\n\n    /**\n     * Checks if this object is in group, returns true on success.\n     *\n     * @param string $sGroupID user group ID\n     *\n     * @return bool\n     */\n    public function inGroup($sGroupID)\n    {\n        $blIn = false;\n        if (($oGroups = $this->getUserGroups())) {\n            $blIn = isset($oGroups[$sGroupID]);\n        }\n\n        return $blIn;\n    }\n\n    /**\n     * Removes user data stored in some DB tables (such as oxuserpayments, oxaddress\n     * oxobject2group, oxremark, etc). Return true on success.\n     *\n     * @param string $oxid object ID (default null)\n     *\n     * @return bool\n     */\n    public function delete($oxid = null)\n    {\n        if (!$oxid) {\n            $oxid = $this->getId();\n        }\n        if (!$oxid) {\n            return false;\n        }\n\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $database->startTransaction();\n        try {\n            if (parent::delete($oxid)) {\n                $quotedUserId = $database->quote($oxid);\n\n                $this->deleteAddresses($database);\n                $this->deleteUserFromGroups($database);\n                $this->deleteBaskets($database);\n                $this->deleteNewsletterSubscriptions($database);\n                $this->deleteDeliveries($database);\n                $this->deleteDiscounts($database);\n                $this->deleteRecommendationLists($database);\n                $this->deleteReviews($database);\n                $this->deleteRatings($database);\n                $this->deletePriceAlarms($database);\n                $this->deleteAcceptedTerms($database);\n                $this->deleteNotOrderRelatedRemarks($database);\n\n                $this->deleteAdditionally($quotedUserId);\n            }\n\n            $database->commitTransaction();\n            $deleted = true;\n        } catch (\\Exception $exeption) {\n            $database->rollbackTransaction();\n\n            throw $exeption;\n        }\n\n        return $deleted;\n    }\n\n    /**\n     * Loads object (user) details from DB. Returns true on success.\n     *\n     * @param string $oxID User ID\n     *\n     * @return bool\n     */\n    public function load($oxID)\n    {\n        $blRet = parent::load($oxID);\n\n        // convert date's to international format\n        if (isset($this->oxuser__oxcreate->value)) {\n            $this->oxuser__oxcreate->setValue(Registry::getUtilsDate()->formatDBDate($this->oxuser__oxcreate->value));\n        }\n\n        // change newsSubcription user id\n        if (isset($this->_oNewsSubscription)) {\n            $this->_oNewsSubscription->oxnewssubscribed__oxuserid = new \\OxidEsales\\Eshop\\Core\\Field($oxID, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        }\n\n        return $blRet;\n    }\n\n    /**\n     * Checks if user exists in database.\n     *\n     * @param string $sOXID object ID (default null)\n     *\n     * @return bool\n     */\n    public function exists($sOXID = null)\n    {\n        if (!$sOXID) {\n            $sOXID = $this->getId();\n        }\n        //#5901 if physical record exists return true unconditionally\n        if (parent::exists($sOXID)) {\n            $this->setId($sOXID);\n            return true;\n        }\n\n        //additional username check\n        //This part is used by not yet saved user object, to detect the case when such username exists in db.\n        //Basically it is called when anonymous visitor enters existing username for newsletter subscription\n        //see Newsletter::send()\n        //TODO: transfer this validation to newsletter part\n\n        $params = [];\n\n        $sShopSelect = '';\n        if (!$this->_blMallUsers && $this->getFieldData('oxrights') != 'malladmin') {\n            $sShopSelect = ' AND oxshopid = :oxshopid ';\n            $params['oxshopid'] = Registry::getConfig()->getShopId();\n        }\n\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        $masterDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster();\n        $sSelect = 'SELECT oxid FROM ' . $this->getViewName() . '\n                    WHERE oxusername = :oxusername ';\n        $sSelect .= $sShopSelect;\n        $params['oxusername'] = (string) $this->getFieldData('oxusername');\n\n        if (($sOxid = $masterDb->getOne($sSelect, $params))) {\n            // update - set oxid\n            $this->setId($sOxid);\n\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns object with ordering information (order articles list).\n     *\n     * @param int $iLimit how many entries to load\n     * @param int $iPage  which page to start\n     *\n     * @return ListModel\n     */\n    public function getOrders($iLimit = false, $iPage = 0)\n    {\n        $oOrders = oxNew(ListModel::class);\n        $oOrders->init('oxorder');\n\n        if ($iLimit !== false) {\n            $oOrders->setSqlLimit($iLimit * $iPage, $iLimit);\n        }\n\n        //P\n        // Lists does not support loading from two tables, so orders\n        // articles now are loaded in account_order.php view and no need to use blLoadProdInfo\n        // forcing to load product info which is used in templates\n        // $oOrders->aSetBeforeAssign['blLoadProdInfo'] = true;\n\n        //loading order for registered user\n        if ($this->oxuser__oxregister->value > 1) {\n            $sQ = 'select * from oxorder\n                where oxuserid = :oxuserid\n                and oxorderdate >= :oxorderdate ';\n            $sQ = $this->updateGetOrdersQuery($sQ);\n\n            $sQ .= ' order by oxorderdate desc ';\n            $oOrders->selectString($sQ, [\n                'oxuserid' => $this->getId(),\n                'oxorderdate' => $this->oxuser__oxregister->value\n            ]);\n        }\n\n        return $oOrders;\n    }\n\n    /**\n     * Caclulates amount of orders made by user\n     *\n     * @return int\n     */\n    public function getOrderCount()\n    {\n        $iCnt = 0;\n        if ($this->getId() && $this->oxuser__oxregister->value > 1) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $sQ = 'select count(*) from oxorder\n                where oxuserid = :oxuserid\n                    AND oxorderdate >= :oxorderdate\n                    and oxshopid = :oxshopid ';\n            $iCnt = (int) $oDb->getOne($sQ, [\n                'oxuserid' => $this->getId(),\n                'oxorderdate' => $this->oxuser__oxregister->value,\n                'oxshopid' => Registry::getConfig()->getShopId()\n            ]);\n        }\n\n        return $iCnt;\n    }\n\n    /**\n     * Returns amount of articles in noticelist\n     *\n     * @return int\n     */\n    public function getNoticeListArtCnt()\n    {\n        if ($this->_iCntNoticeListArticles === null) {\n            $this->_iCntNoticeListArticles = 0;\n            if ($this->getId()) {\n                $this->_iCntNoticeListArticles = $this->getBasket('noticelist')->getItemCount();\n            }\n        }\n\n        return $this->_iCntNoticeListArticles;\n    }\n\n    /**\n     * Calculating user wishlist item count\n     *\n     * @return int\n     */\n    public function getWishListArtCnt()\n    {\n        if ($this->_iCntWishListArticles === null) {\n            $this->_iCntWishListArticles = false;\n            if ($this->getId()) {\n                $this->_iCntWishListArticles = $this->getBasket('wishlist')->getItemCount();\n            }\n        }\n\n        return $this->_iCntWishListArticles;\n    }\n\n    /**\n     * Returns encoded delivery address.\n     *\n     * @return string\n     */\n    public function getEncodedDeliveryAddress()\n    {\n        return md5($this->getMergedAddressFields());\n    }\n\n    /**\n     * Returns user country ID, but If delivery address is given - returns\n     * delivery country.\n     *\n     * @return string\n     */\n    public function getActiveCountry()\n    {\n        $deliveryCountryId = '';\n        $deliveryAddressId = Registry::getSession()->getVariable('deladrid');\n        if ($deliveryAddressId) {\n            $deliveryAddress = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Address::class);\n            $deliveryAddress->load($deliveryAddressId);\n            $deliveryCountryId = $deliveryAddress->getFieldData('oxcountryid');\n        } elseif ($this->getId()) {\n            $deliveryCountryId = $this->getFieldData('oxcountryid');\n        } else {\n            $user = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n            if ($user->loadActiveUser()) {\n                $deliveryCountryId = $user->getFieldData('oxcountryid');\n            }\n        }\n\n        return $deliveryCountryId;\n    }\n\n    /**\n     * Inserts new or updates existing user\n     *\n     * @throws UserException exception\n     *\n     * @return bool\n     */\n    public function createUser()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sShopID = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId();\n\n        // check if user exists AND there is no password - in this case we update otherwise we try to insert\n        $sSelect = \"select oxid from oxuser\n            where oxusername = :oxusername\n            and oxpassword = :oxpassword \";\n        $params = [\n            'oxusername' => (string) $this->oxuser__oxusername->value,\n            'oxpassword' => ''\n        ];\n        if (!$this->_blMallUsers) {\n            $sSelect .= \" and oxshopid = :oxshopid \";\n            $params['oxshopid'] = $sShopID;\n        }\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        $masterDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster();\n        $oldUserId = $masterDb->getOne($sSelect, $params);\n\n        if ($oldUserId) {\n            $this->delete($oldUserId);\n        } elseif ($this->_blMallUsers) {\n            // must be sure if there is no duplicate user\n            $sQ = \"select oxid from oxuser\n                where oxusername = :oxusername\n                and oxusername != '' \";\n            $params = [\n                'oxusername' => (string) $this->oxuser__oxusername->value\n            ];\n\n            // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n            if ($masterDb->getOne($sQ, $params)) {\n                /** @var UserException $oEx */\n                $oEx = oxNew(UserException::class);\n                $oLang = Registry::getLang();\n                $oEx->setMessage(sprintf($oLang->translateString('ERROR_MESSAGE_USER_USEREXISTS', $oLang->getTplLanguage()), $this->oxuser__oxusername->value));\n                throw $oEx;\n            }\n        }\n\n        $this->oxuser__oxshopid = new Field($sShopID, Field::T_RAW);\n\n        $newUserId = $this->save();\n        if ($newUserId === false) {\n            throw oxNew(UserException::class, 'ERROR_MESSAGE_USER_USERCREATIONFAILED');\n        } else {\n            // @TODO the following statements make no sense and should be removed: oxuser__oxid is freshly created and the conditions will never match\n            // dropping/cleaning old delivery address/payment info\n            $oDb->execute(\"delete from oxaddress where oxaddress.oxuserid = :oxuserid\", [\n                'oxuserid' => $this->oxuser__oxid->value\n            ]);\n\n            $query = \"update oxuserpayments\n                      set oxuserpayments.oxuserid = :newUserId\n                      where oxuserpayments.oxuserid = :oldUserId\";\n            $oDb->execute($query, [\n                'newUserId' => $this->oxuser__oxusername->value,\n                'oldUserId' => $this->oxuser__oxid->value,\n            ]);\n        }\n\n        return $newUserId;\n    }\n\n    public function sendRegistrationEmail(bool $sendConfirmEmail = false): void\n    {\n        $email = oxNew(Email::class);\n        if ($sendConfirmEmail) {\n            $email->sendRegisterConfirmEmail($this);\n        } else {\n            $email->sendRegisterEmail($this);\n        }\n    }\n\n    /**\n     * Adds user to the group\n     *\n     * @param string $sGroupID group id\n     *\n     * @return bool\n     */\n    public function addToGroup($sGroupID)\n    {\n        if (!$this->inGroup($sGroupID)) {\n            // create oxgroup object\n            $oGroup = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Groups::class);\n            if ($oGroup->load($sGroupID)) {\n                $oNewGroup = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Object2Group::class);\n                $oNewGroup->oxobject2group__oxobjectid = new \\OxidEsales\\Eshop\\Core\\Field($this->getId(), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n                $oNewGroup->oxobject2group__oxgroupsid = new \\OxidEsales\\Eshop\\Core\\Field($sGroupID, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n                if ($oNewGroup->save()) {\n                    $this->_oGroups[$sGroupID] = $oGroup;\n\n                    return true;\n                }\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Removes user from passed user group.\n     *\n     * @param string $sGroupID group id\n     */\n    public function removeFromGroup($sGroupID = null)\n    {\n        if ($sGroupID != null && $this->inGroup($sGroupID)) {\n            $oGroups = oxNew(ListModel::class);\n            $oGroups->init('oxobject2group');\n            $sSelect = 'select * from oxobject2group\n                where oxobject2group.oxobjectid = :oxobjectid\n                and oxobject2group.oxgroupsid = :oxgroupsid ';\n            $oGroups->selectString($sSelect, [\n                'oxobjectid' => $this->getId(),\n                'oxgroupsid' => $sGroupID\n            ]);\n            foreach ($oGroups as $oRemgroup) {\n                if ($oRemgroup->delete()) {\n                    unset($this->_oGroups[$oRemgroup->oxobject2group__oxgroupsid->value]);\n                }\n            }\n        }\n    }\n\n    /**\n     * Called after saving an order.\n     *\n     * @param object $oBasket  Shopping basket object\n     * @param int    $iSuccess order success status\n     */\n    public function onOrderExecute($oBasket, $iSuccess)\n    {\n        if (is_numeric($iSuccess) && $iSuccess != 2 && $iSuccess <= 3) {\n            //adding user to particular customer groups\n            $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n            $dMidlleCustPrice = (float) $myConfig->getConfigParam('sMidlleCustPrice');\n            $dLargeCustPrice = (float) $myConfig->getConfigParam('sLargeCustPrice');\n\n            $this->addToGroup('oxidcustomer');\n            $dBasketPrice = $oBasket->getPrice()->getBruttoPrice();\n            if ($dBasketPrice < $dMidlleCustPrice) {\n                $this->addToGroup('oxidsmallcust');\n            }\n            if ($dBasketPrice >= $dMidlleCustPrice && $dBasketPrice < $dLargeCustPrice) {\n                $this->addToGroup('oxidmiddlecust');\n            }\n            if ($dBasketPrice >= $dLargeCustPrice) {\n                $this->addToGroup('oxidgoodcust');\n            }\n\n            if ($this->inGroup('oxidnotyetordered')) {\n                $this->removeFromGroup('oxidnotyetordered');\n            }\n        }\n    }\n\n    /**\n     * Returns notice, wishlist or saved basket object\n     *\n     * @param string $sName name/type of basket\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\UserBasket\n     */\n    public function getBasket($sName)\n    {\n        if (!isset($this->_aBaskets[$sName])) {\n            /** @var \\OxidEsales\\Eshop\\Application\\Model\\UserBasket $oBasket */\n            $oBasket = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\UserBasket::class);\n            $aWhere = ['oxuserbaskets.oxuserid' => $this->getId(), 'oxuserbaskets.oxtitle' => $sName];\n\n            $query = $oBasket->buildSelectString($aWhere);\n            $record = DatabaseProvider::getDb()->select($query);\n            if ($record && $record->count() > 0) {\n                $oBasket->assign($record->fields);\n            } else {\n                //creating if it does not exist\n                $oBasket->oxuserbaskets__oxtitle = new \\OxidEsales\\Eshop\\Core\\Field($sName);\n                $oBasket->oxuserbaskets__oxuserid = new \\OxidEsales\\Eshop\\Core\\Field($this->getId());\n\n                // marking basket as new (it will not be saved in DB yet)\n                $oBasket->setIsNewBasket();\n            }\n\n            $this->_aBaskets[$sName] = $oBasket;\n        }\n\n        return $this->_aBaskets[$sName];\n    }\n\n    /**\n     * User birthday converter. Usually this data comes in array form, so before\n     * writing into DB it must be converted into string\n     *\n     * @param array $aData birthday data\n     *\n     * @return string\n     */\n    public function convertBirthday($aData)\n    {\n\n        // preparing data to process\n        $iYear = isset($aData['year']) ? ((int) $aData['year']) : false;\n        $iMonth = isset($aData['month']) ? ((int) $aData['month']) : false;\n        $iDay = isset($aData['day']) ? ((int) $aData['day']) : false;\n\n        // leaving empty if not set\n        if (!$iYear && !$iMonth && !$iDay) {\n            return \"\";\n        }\n\n        // year\n        if (!$iYear || $iYear < 1000 || $iYear > 9999) {\n            $iYear = date('Y');\n        }\n\n        // month\n        if (!$iMonth || $iMonth < 1 || $iMonth > 12) {\n            $iMonth = 1;\n        }\n\n        // maximum number of days in month\n        $iMaxDays = 31;\n        switch ($iMonth) {\n            case 2:\n                if ($iMaxDays > 28) {\n                    $iMaxDays = ($iYear % 4 == 0 && ($iYear % 100 != 0 || $iYear % 400 == 0)) ? 29 : 28;\n                }\n                break;\n            case 4:\n            case 6:\n            case 9:\n            case 11:\n                $iMaxDays = min(30, $iMaxDays);\n                break;\n        }\n\n        // day\n        if (!$iDay || $iDay < 1 || $iDay > $iMaxDays) {\n            $iDay = 1;\n        }\n\n        // whole date\n        return sprintf(\"%04d-%02d-%02d\", $iYear, $iMonth, $iDay);\n    }\n\n    /**\n     * Return standard credit rating, can be set in config option iCreditRating;\n     *\n     * @return integer\n     */\n    public function getBoni()\n    {\n        return ContainerFacade::getParameter('oxid_esales.shop_credit_rating');\n    }\n\n    /**\n     * Performs bunch of checks if user profile data is correct; on any\n     * error exception is thrown\n     *\n     * @param string $sLogin      user login name\n     * @param string $sPassword   user password\n     * @param string $sPassword2  user password to compare\n     * @param array  $aInvAddress array of user profile data\n     * @param array  $aDelAddress array of user profile data\n     *\n     * @throws StandardException\n     */\n    public function checkValues($sLogin, $sPassword, $sPassword2, $aInvAddress, $aDelAddress)\n    {\n        /** @var \\OxidEsales\\Eshop\\Core\\InputValidator $oInputValidator */\n        $oInputValidator = Registry::getInputValidator();\n\n        // 1. checking user name\n        $sLogin = $oInputValidator->checkLogin($this, $sLogin, $aInvAddress);\n\n        // 2. checking email\n        $oInputValidator->checkEmail($this, $sLogin);\n\n        // 3. password\n        $oInputValidator->checkPassword($this, $sPassword, $sPassword2, ((int) Registry::getRequest()->getRequestEscapedParameter('option') == 3));\n\n        // 4. required fields\n        $oInputValidator->checkRequiredFields($this, $aInvAddress, $aDelAddress);\n\n        // 5. country check\n        $oInputValidator->checkCountries($this, $aInvAddress, $aDelAddress);\n\n        // 6. vat id check.\n        try {\n            $oInputValidator->checkVatId($this, $aInvAddress);\n        } catch (\\OxidEsales\\Eshop\\Core\\Exception\\ConnectionException $e) {\n            // R080730 just oxInputException is passed here\n            // if it oxConnectionException, it means it could not check vat id\n            // and will set 'not checked' status to it later\n        }\n\n        // throwing first validation error\n        if ($oError = Registry::getInputValidator()->getFirstValidationError()) {\n            throw $oError;\n        }\n    }\n\n    /**\n     * Sets newsletter subscription status to user\n     *\n     * @param bool $blSubscribe       subscribes/unsubscribes user from newsletter\n     * @param bool $blSendOptIn       if to send confirmation email\n     * @param bool $blForceCheckOptIn forces to check subscription even when it is set to 1\n     *\n     * @return bool\n     */\n    public function setNewsSubscription($blSubscribe, $blSendOptIn, $blForceCheckOptIn = false)\n    {\n        $newsSubscription = $this->getNewsSubscription();\n        if (!$newsSubscription) {\n            return false;\n        }\n\n        if (!$blSubscribe) {\n            return $this->handleUnsubscription($newsSubscription);\n        }\n\n        return $this->handleSubscription($newsSubscription, $blSendOptIn, $blForceCheckOptIn);\n    }\n\n    private function handleUnsubscription($newsSubscription): bool\n    {\n        $this->removeFromGroup('oxidnewsletter');\n        $newsSubscription->setOptInStatus(SubscriptionOptedInStatus::Disabled->value);\n\n        return true;\n    }\n\n    private function handleSubscription($newsSubscription, $blSendOptIn, $blForceCheckOptIn): bool\n    {\n        $optInStatus = $newsSubscription->getOptInStatus();\n\n        if (!$blForceCheckOptIn && $optInStatus == SubscriptionOptedInStatus::Active->value) {\n            return true;\n        }\n\n        if (!$blSendOptIn) {\n            return $this->activateDirectly($newsSubscription);\n        }\n\n        return $this->processOptInEmail($newsSubscription);\n    }\n\n    private function activateDirectly($newsSubscription): bool\n    {\n        $this->addToGroup('oxidnewsletter');\n        $newsSubscription->setOptInStatus(SubscriptionOptedInStatus::Active->value);\n\n        return true;\n    }\n\n    private function processOptInEmail($newsSubscription): bool\n    {\n        $email = oxNew(Email::class);\n        if (!$email->sendNewsletterDbOptInMail($this)) {\n            return false;\n        }\n\n        $newsSubscription->setOptInStatus(SubscriptionOptedInStatus::Pending->value);\n\n        return true;\n    }\n\n    /**\n     * When changing/updating user information in frontend this method validates user\n     * input. If data is fine - automatically assigns this values. Additionally calls\n     * methods (\\OxidEsales\\Eshop\\Application\\Model\\User::_setAutoGroups, \\OxidEsales\\Eshop\\Application\\Model\\User::setNewsSubscription) to perform automatic\n     * groups assignment and returns newsletter subscription status. If some action\n     * fails - exception is thrown.\n     *\n     * @param string $sUser       user login name\n     * @param string $sPassword   user password\n     * @param string $sPassword2  user confirmation password\n     * @param array  $aInvAddress user billing address\n     * @param array  $aDelAddress delivery address\n     *\n     * @throws StandardException\n     */\n    public function changeUserData($sUser, $sPassword, $sPassword2, $aInvAddress, $aDelAddress)\n    {\n        // validating values before saving. If validation fails - exception is thrown\n        $this->checkValues($sUser, $sPassword, $sPassword2, $aInvAddress, $aDelAddress);\n        // input data is fine - lets save updated user info\n\n        $this->assign($aInvAddress);\n\n        $this->onChangeUserData($aInvAddress);\n\n        // update old or add new delivery address\n        $this->assignAddress($aDelAddress);\n\n        // saving new values\n        if ($this->save()) {\n            // assigning automatically to specific groups\n            $sCountryId = isset($aInvAddress['oxuser__oxcountryid']) ? $aInvAddress['oxuser__oxcountryid'] : '';\n            $this->setAutoGroups($sCountryId);\n        }\n    }\n\n    /**\n     * Returns merged delivery address fields.\n     *\n     * @return string\n     */\n    protected function getMergedAddressFields()\n    {\n        $sDelAddress = '';\n        $sDelAddress .= $this->oxuser__oxcompany;\n        $sDelAddress .= $this->oxuser__oxusername;\n        $sDelAddress .= $this->oxuser__oxfname;\n        $sDelAddress .= $this->oxuser__oxlname;\n        $sDelAddress .= $this->oxuser__oxstreet;\n        $sDelAddress .= $this->oxuser__oxstreetnr;\n        $sDelAddress .= $this->oxuser__oxaddinfo;\n        $sDelAddress .= $this->oxuser__oxustid;\n        $sDelAddress .= $this->oxuser__oxcity;\n        $sDelAddress .= $this->oxuser__oxcountryid;\n        $sDelAddress .= $this->oxuser__oxstateid;\n        $sDelAddress .= $this->oxuser__oxzip;\n        $sDelAddress .= $this->oxuser__oxfon;\n        $sDelAddress .= $this->oxuser__oxfax;\n        $sDelAddress .= $this->oxuser__oxsal;\n\n        return $sDelAddress;\n    }\n\n    /**\n     * creates new address entry or updates existing\n     *\n     * @param array $aDelAddress address data array\n     */\n    protected function assignAddress($aDelAddress)\n    {\n        if (is_array($aDelAddress) && count($aDelAddress)) {\n            $sAddressId = Registry::getRequest()->getRequestEscapedParameter('oxaddressid');\n            $sAddressId = ($sAddressId === null || $sAddressId == -1 || $sAddressId == -2) ? null : $sAddressId;\n\n            $oAddress = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Address::class);\n            $oAddress->setId($sAddressId);\n            $oAddress->load($sAddressId);\n            $oAddress->assign($aDelAddress);\n            $oAddress->oxaddress__oxuserid = new \\OxidEsales\\Eshop\\Core\\Field($this->getId(), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n            $oAddress->oxaddress__oxcountry = $this->getUserCountry($oAddress->oxaddress__oxcountryid->value);\n            $oAddress->save();\n\n            // resetting addresses\n            $this->_aAddresses = null;\n\n            // saving delivery Address for later use\n            Registry::getSession()->setVariable('deladrid', $oAddress->getId());\n        } else {\n            // resetting\n            Registry::getSession()->setVariable('deladrid', null);\n        }\n    }\n\n    /**\n     * Builds and returns user login query.\n     *\n     * MD5 encoding is used in legacy eShop versions.\n     * We still allow to perform the login for users registered in the previous eshop versions.\n     *\n     * @param string $userName login name\n     * @param string $password login password\n     * @param int    $shopId   shopid\n     * @param bool   $isAdmin  admin/non admin mode\n     *\n     * @deprecated since v6.4.0 (2019-03-15); `\\OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\PasswordServiceBridgeInterface`\n     *                                        was added as the new default for hashing passwords. Hashing passwords with\n     *                                        MD5 and SHA512 is still supported in order support login with older\n     *                                        password hashes. Therefor this method might not be\n     *                                        compatible with the current passhword hash any more.\n     *\n     * @return string\n     */\n    protected function _getLoginQueryHashedWithMD5($userName, $password, $shopId, $isAdmin) // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore\n    {\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $userNameCondition = $this->formQueryPartForUserName($userName, $database);\n        $passwordCondition = $this->formQueryPartForMD5Password($password, $database);\n        $shopOrRightsCondition = $this->formQueryPartForAdminView($shopId, $isAdmin);\n        $userActiveCondition = $this->formQueryPartForActiveUser();\n\n        $query = \"SELECT `oxid`\n                    FROM oxuser\n                    WHERE 1\n                    AND $userActiveCondition\n                    AND $passwordCondition\n                    AND $userNameCondition\n                    $shopOrRightsCondition\n                    \";\n\n        return $query;\n    }\n\n    /**\n     * Builds and returns user login query\n     *\n     * @param string $userName\n     * @param string $password\n     * @param int    $shopId\n     * @param bool   $isAdmin\n     *\n     * @deprecated since v6.4.0 (2019-03-15); `\\OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\PasswordServiceBridgeInterface`\n     *                                        was added as the new default for hashing passwords. Hashing passwords with\n     *                                        MD5 and SHA512 is still supported in order support login with older\n     *                                        password hashes. Therefor this method might not be\n     *                                        compatible with the current passhword hash any more.\n     *\n     * @return string\n     */\n    protected function _getLoginQuery($userName, $password, $shopId, $isAdmin) // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore\n    {\n        $database = DatabaseProvider::getDb();\n        $userNameCondition = $this->formQueryPartForUserName($userName, $database);\n        $shopOrRightsCondition = $this->formQueryPartForAdminView($shopId, $isAdmin);\n        $passwordCondition = $this->formQueryPartForSha512Password($password, $database, $userNameCondition, $shopOrRightsCondition);\n        $userActiveCondition = $this->formQueryPartForActiveUser();\n\n\n        $query = \"SELECT `oxid`\n                    FROM oxuser\n                    WHERE 1\n                    AND $userActiveCondition\n                    AND $passwordCondition\n                    AND $userNameCondition\n                    $shopOrRightsCondition\n                    \";\n\n        return $query;\n    }\n\n    /**\n     * Performs user login by username and password. Fetches user data from DB.\n     * Registers in session. Returns true on success, FALSE otherwise.\n     *\n     * NOTE: It the user has already been loaded prior calling \\OxidEsales\\Eshop\\Application\\Model\\User::login,\n     * NO valid password is necessary for login.\n     *\n     * @param string $userName         User username\n     * @param string $password         User password\n     * @param bool   $setSessionCookie (default false)\n     *\n     * @throws CookieException\n     * @throws UserException\n     *\n     * @return bool\n     */\n    public function login($userName, $password, $setSessionCookie = false)\n    {\n        $isAdmin = $this->isAdmin();\n\n        $cookie = Registry::getUtilsServer()->getOxCookie();\n        if ($cookie === null && $isAdmin) {\n            throw oxNew(CookieException::class, 'ERROR_MESSAGE_COOKIE_NOCOOKIE');\n        }\n\n        $config = Registry::getConfig();\n        $shopId = $config->getShopId();\n\n        /** New authentication mechanism */\n        $passwordHash = $this->getPasswordHashFromDatabase($userName, $shopId, $isAdmin);\n        $passwordServiceBridge = ContainerFacade::get(PasswordServiceBridgeInterface::class);\n        if ($password && !$this->isLoaded() && $this->verifyHash($password, $passwordHash)) {\n            $this->loadAuthenticatedUser($userName, $shopId);\n        }\n\n        /** Old authentication + authorization */\n        if ($password && !$this->isLoaded()) {\n            $this->_dbLogin($userName, $password, $shopId);\n        }\n\n        /** If needed, store a rehashed password with the authenticated user */\n        if ($password && $this->isLoaded()) {\n            $passwordNeedsRehash = $this->isOutdatedPasswordHashAlgorithmUsed ||\n                                   $passwordServiceBridge->passwordNeedsRehash($passwordHash);\n            if ($passwordNeedsRehash) {\n                $passwordHash = $this->getHash($password);\n                $this->oxuser__oxpassword = new Field($passwordHash, Field::T_RAW);\n                /** The use of a salt is deprecated and an empty salt will be stored */\n                $this->oxuser__oxpasssalt = new Field('');\n                $this->save();\n            }\n        }\n\n        /** Event for alternative authentication and authorization mechanisms, or whatsoever */\n        $this->onLogin($userName, $password);\n\n        /**\n         * If the user has not been loaded until this point, authentication & authorization is considered as failed.\n         */\n        if (!$this->isLoaded()) {\n            throw oxNew(UserException::class, 'ERROR_MESSAGE_USER_NOVALIDLOGIN');\n        }\n\n        //resetting active user\n        $this->setUser(null);\n\n        $userIdParameter = $isAdmin ? 'auth' : 'usr';\n        Registry::getSession()->setVariable($userIdParameter, $this->getFieldData('oxid'));\n        Registry::getSession()->setVariable('login-token', $this->getHash($passwordHash));\n\n        // cookie must be set ?\n        if ($setSessionCookie && $config->getConfigParam('blShowRememberMe')) {\n            Registry::getUtilsServer()->setUserCookie(\n                $this->oxuser__oxusername->value,\n                $this->oxuser__oxpassword->value,\n                $config->getShopId(),\n                31536000,\n                static::USER_COOKIE_SALT\n            );\n        }\n\n        return true;\n    }\n\n    /**\n     * @param string $userName\n     * @param int    $shopId\n     *\n     * @throws UserException\n     */\n    private function loadAuthenticatedUser(string $userName, int $shopId)\n    {\n        $isLoginToAdminBackend = $this->isAdmin();\n        $userId = $this->getAuthenticatedUserId($userName, $shopId, $isLoginToAdminBackend);\n        if (!$this->load($userId)) {\n            throw oxNew(UserException::class, 'ERROR_MESSAGE_USER_NOVALIDLOGIN');\n        }\n    }\n\n    /**\n     * @param string $userName\n     * @param int    $shopId\n     * @param bool   $isLoginToAdminBackend\n     *\n     * @return false|string\n     */\n    private function getAuthenticatedUserId(string $userName, int $shopId, bool $isLoginToAdminBackend)\n    {\n        $database = DatabaseProvider::getDb();\n        $userNameCondition = $this->formQueryPartForUserName($userName, $database);\n        $shopOrRightsCondition = $this->formQueryPartForAdminView($shopId, $isLoginToAdminBackend);\n        $userActiveCondition = $this->formQueryPartForActiveUser();\n\n        $query = \"SELECT `OXID`\n                    FROM oxuser\n                    WHERE 1\n                    AND $userActiveCondition\n                    AND $userNameCondition\n                    $shopOrRightsCondition\n                    \";\n\n        return $database->getOne($query);\n    }\n\n    /**\n     * Logs out session user. Returns true on success\n     *\n     * @return bool\n     */\n    public function logout()\n    {\n        Registry::getSession()->deleteVariable('usr'); // for front end\n        Registry::getSession()->deleteVariable('auth'); // for back end\n        Registry::getSession()->deleteVariable('dynvalue');\n        Registry::getSession()->deleteVariable('paymentid');\n        Registry::getSession()->deleteVariable('login-token');\n\n        Registry::getUtilsServer()->deleteUserCookie(Registry::getConfig()->getShopID());\n\n        $this->setUser(null);\n\n        return true;\n    }\n\n    /**\n     * Loads active admin user object (if possible). If\n     * user is not available - returns false.\n     *\n     * @return bool\n     */\n    public function loadAdminUser()\n    {\n        return $this->loadActiveUser(true);\n    }\n\n    /**\n     * @param bool $blForceAdmin\n     *\n     * @return bool\n     */\n    public function loadActiveUser($blForceAdmin = false)\n    {\n        $isAdmin = $this->isAdmin() || $blForceAdmin;\n        $userIdParameter = $isAdmin ? 'auth' : 'usr';\n        $userId = Registry::getSession()->getVariable($userIdParameter);\n\n        // trying automatic login (by 'remember me' cookie)\n        $isUserIdInCookie = false;\n        if (!$userId && !$isAdmin && Registry::getConfig()->getConfigParam('blShowRememberMe')) {\n            $userId = $this->getCookieUserId();\n            $isUserIdInCookie = (bool)$userId;\n        }\n        if (!$userId) {\n            Registry::getSession()->deleteVariable($userIdParameter);\n            return false;\n        }\n        if ($this->load($userId)) {\n            $loginToken = (string)Registry::getSession()->getVariable('login-token');\n            $passwordHash = (string)$this->getFieldData('oxpassword');\n            if ($loginToken && !$this->verifyHash($passwordHash, $loginToken)) {\n                $this->logout();\n                return false;\n            }\n            Registry::getSession()->setVariable($userIdParameter, $userId);\n            $this->_blLoadedFromCookie = $isUserIdInCookie;\n            return true;\n        }\n    }\n\n    /**\n     * Checks if user is connected via cookies and if so, returns user id.\n     *\n     * @return string\n     */\n    protected function getCookieUserId()\n    {\n        $sUserID = null;\n        $oConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $sShopID = $oConfig->getShopId();\n        if (($sSet = Registry::getUtilsServer()->getUserCookie($sShopID))) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $aData = explode('@@@', $sSet);\n            $sUser = $aData[0];\n            $sPWD = @$aData[1];\n\n            $sSelect = $this->formUserCookieQuery($sUser, $sShopID);\n            $rs = $oDb->select($sSelect);\n            if ($rs != false && $rs->count() > 0) {\n                while (!$rs->EOF) {\n                    if ($this->verifyHash($rs->fields['oxpassword'] . static::USER_COOKIE_SALT, $sPWD)) {\n                        // found\n                        $sUserID = $rs->fields['oxid'];\n                        break;\n                    }\n                    $rs->fetchRow();\n                }\n            }\n            // if cookie info is not valid, remove it.\n            if (!$sUserID) {\n                Registry::getUtilsServer()->deleteUserCookie($sShopID);\n            }\n        }\n\n        return $sUserID;\n    }\n\n    /**\n     * Returns user rights index. Index cannot be higher than current session\n     * user rights index.\n     *\n     * @return string\n     */\n    protected function getUserRights()\n    {\n        // previously user had no rights defined\n        if (!$this->oxuser__oxrights instanceof \\OxidEsales\\Eshop\\Core\\Field || !$this->oxuser__oxrights->value) {\n            return 'user';\n        }\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $sAuthRights = null;\n\n        // choosing possible user rights index\n        $sAuthUserID = $this->isAdmin() ? Registry::getSession()->getVariable('auth') : null;\n        $sAuthUserID = $sAuthUserID ? $sAuthUserID : Registry::getSession()->getVariable('usr');\n        if ($sAuthUserID) {\n            $authRightsSql = 'select oxrights from ' . $this->getViewName() . ' where oxid = :oxid';\n            $sAuthRights = $oDb->getOne($authRightsSql, [\n                'oxid' => $sAuthUserID\n            ]);\n        }\n\n        //preventing user rights edit for non admin\n        $aRights = [];\n\n        // selecting current users rights ...\n        $currentRightsSql = 'select oxrights from ' . $this->getViewName() . ' where oxid = :oxid';\n        $params = [\n            'oxid' => $this->getId()\n        ];\n        if ($sCurrRights = $oDb->getOne($currentRightsSql, $params)) {\n            $aRights[] = $sCurrRights;\n        }\n        $aRights[] = 'user';\n\n        if (!$sAuthRights || !($sAuthRights == 'malladmin' || $sAuthRights == $myConfig->getShopId())) {\n            return current($aRights);\n        } elseif ($sAuthRights == $myConfig->getShopId()) {\n            $aRights[] = $sAuthRights;\n            if (!in_array($this->oxuser__oxrights->value, $aRights)) {\n                return current($aRights);\n            }\n        }\n\n        // leaving as it was set ...\n        return $this->oxuser__oxrights->value;\n    }\n\n    /**\n     * Inserts user object data to DB. Returns true on success.\n     *\n     * @return bool\n     */\n    protected function insert()\n    {\n\n        // set oxcreate date\n        $this->oxuser__oxcreate = new \\OxidEsales\\Eshop\\Core\\Field(date('Y-m-d H:i:s'), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n\n        if (!isset($this->oxuser__oxboni->value)) {\n            $this->oxuser__oxboni = new \\OxidEsales\\Eshop\\Core\\Field($this->getBoni(), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        }\n\n        return parent::insert();\n    }\n\n    /**\n     * Updates changed user object data to DB. Returns true on success.\n     *\n     * @return bool\n     */\n    protected function update()\n    {\n        //V #M418: for not registered users, don't change boni during update\n        if (!$this->oxuser__oxpassword->value && $this->oxuser__oxregister->value < 1) {\n            $this->_aSkipSaveFields[] = 'oxboni';\n        }\n\n        // don't change this field\n        $this->_aSkipSaveFields[] = 'oxcreate';\n        if (!$this->isAdmin()) {\n            $this->_aSkipSaveFields[] = 'oxcustnr';\n            $this->_aSkipSaveFields[] = 'oxrights';\n        }\n\n        // updating subscription information\n        if (($blUpdate = parent::update())) {\n            $this->getNewsSubscription()->updateSubscription($this);\n        }\n\n        return $blUpdate;\n    }\n\n    /**\n     * Checks for already used email\n     *\n     * @param string $email user email/login\n     *\n     * @return bool\n     */\n    public function checkIfEmailExists($email)\n    {\n        if (!$email) {\n            return false;\n        }\n\n        $config = Registry::getConfig();\n        $masterDb = DatabaseProvider::getMaster();\n        $shopId = $config->getShopId();\n\n        $query = 'SELECT oxshopid, oxrights, oxpassword FROM oxuser WHERE oxusername = :oxusername';\n        $params = ['oxusername' => (string) $email];\n\n        if ($id = $this->getId()) {\n            $query .= \" AND oxid <> :notoxid\";\n            $params['notoxid'] = $id;\n        }\n\n        $result = $masterDb->select($query, $params);\n\n        if (!$result || $result->count() === 0) {\n            return false;\n        }\n\n        if ($this->_blMallUsers) {\n            if ($result->fields['oxrights'] === 'user' && !$result->fields['oxpassword']) {\n                return false; // No password set - allow override\n            }\n            return true;\n        }\n\n        while (!$result->EOF) {\n            if ($result->fields['oxrights'] !== 'user') {\n                return true; // Admin user exists\n            }\n\n            if ($result->fields['oxshopid'] == $shopId && ($result->fields['oxpassword'])) {\n                return true; // User exists in same shop with password\n            }\n\n            $result->fetchRow();\n        }\n\n        return false;\n    }\n\n    public function isEmailInUse(string $email): bool\n    {\n        $queryBuilder = ContainerFacade::get(QueryBuilderFactoryInterface::class)->create();\n        $queryBuilder\n            ->select('1')\n            ->from('oxuser')\n            ->where('oxusername = :email')\n            ->andWhere('oxshopid = :shopId')\n            ->setParameters([\n                'email' => $email,\n                'shopId' => Registry::getConfig()->getShopId()\n            ]);\n\n        if ($this->getId()) {\n            $queryBuilder\n                ->andWhere('oxid != :currentUserId')\n                ->setParameter('currentUserId', $this->getId());\n        }\n\n        return (bool) $queryBuilder->fetchOne();\n    }\n\n    /**\n     * Returns user recommendation list object\n     *\n     * @param string $sOXID object ID (default is null)\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @return ListModel with oxrecommlist objects\n     */\n    public function getUserRecommLists($sOXID = null)\n    {\n        if (!$sOXID) {\n            $sOXID = $this->getId();\n        }\n\n        // sets active page\n        $iActPage = (int) Registry::getRequest()->getRequestEscapedParameter('pgNr');\n        $iActPage = ($iActPage < 0) ? 0 : $iActPage;\n\n        // load only lists which we show on screen\n        $iNrofCatArticles = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iNrofCatArticles');\n        $iNrofCatArticles = $iNrofCatArticles ? $iNrofCatArticles : 10;\n\n\n        $oRecommList = oxNew(ListModel::class);\n        $oRecommList->init('oxrecommlist');\n        $oRecommList->setSqlLimit($iNrofCatArticles * $iActPage, $iNrofCatArticles);\n        $iShopId = Registry::getConfig()->getShopId();\n        $sSelect = 'select * from oxrecommlists\n            where oxuserid = :oxuserid\n                and oxshopid = :oxshopid';\n        $oRecommList->selectString($sSelect, [\n            'oxuserid' => $sOXID,\n            'oxshopid' => $iShopId\n        ]);\n\n        return $oRecommList;\n    }\n\n    /**\n     * Returns recommlist count\n     *\n     * @param string $sOx object ID (default is null)\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @return int\n     */\n    public function getRecommListsCount($sOx = null)\n    {\n        if (!$sOx) {\n            $sOx = $this->getId();\n        }\n\n        if ($this->_iCntRecommLists === null || $sOx) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $this->_iCntRecommLists = 0;\n            $iShopId = Registry::getConfig()->getShopId();\n            $sSelect = 'select count(oxid) from oxrecommlists\n                where oxuserid = :oxuserid and oxshopid = :oxshopid';\n            $this->_iCntRecommLists = $oDb->getOne($sSelect, [\n                'oxuserid' => $sOx,\n                'oxshopid' => $iShopId\n            ]);\n        }\n\n        return $this->_iCntRecommLists;\n    }\n\n    /**\n     * Automatically assigns user to specific groups\n     * according to users country information\n     *\n     * @param string $sCountryId users country id\n     */\n    protected function setAutoGroups($sCountryId)\n    {\n        // assigning automatically to specific groups\n        $blForeigner = true;\n        $blForeignGroupExists = false;\n        $blInlandGroupExists = false;\n\n        $aHomeCountry = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('aHomeCountry');\n        // foreigner ?\n        if (is_array($aHomeCountry)) {\n            if (in_array($sCountryId, $aHomeCountry)) {\n                $blForeigner = false;\n            }\n        } elseif ($sCountryId == $aHomeCountry) {\n            $blForeigner = false;\n        }\n\n        if ($this->inGroup('oxidforeigncustomer')) {\n            $blForeignGroupExists = true;\n            if (!$blForeigner) {\n                $this->removeFromGroup('oxidforeigncustomer');\n            }\n        }\n\n        if ($this->inGroup('oxidnewcustomer')) {\n            $blInlandGroupExists = true;\n            if ($blForeigner) {\n                $this->removeFromGroup('oxidnewcustomer');\n            }\n        }\n\n        if (!$blForeignGroupExists && $blForeigner) {\n            $this->addToGroup('oxidforeigncustomer');\n        }\n        if (!$blInlandGroupExists && !$blForeigner) {\n            $this->addToGroup('oxidnewcustomer');\n        }\n    }\n\n    /**\n     * Tries to load user object by passed update id. Update id is\n     * generated when user forgot passwords and wants to update it\n     *\n     * @param string $sUid update id\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\User\n     */\n    public function loadUserByUpdateId($sUid)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sQ = \"select oxid from \" . $this->getViewName() . \"\n            where oxupdateexp >= :time\n                and MD5( CONCAT( oxid, oxshopid, oxupdatekey ) ) = :hash\";\n        if ($sUserId = $oDb->getOne($sQ, ['time' => time(), 'hash' => $sUid])) {\n            return $this->load($sUserId);\n        }\n    }\n\n    /**\n     * Generates or resets and saves users update key\n     *\n     * @param bool $reset marker to reset update info\n     */\n    public function setUpdateKey($reset = false)\n    {\n        $token = $reset ? '' : $this->getRandomToken();\n        $tokenExpirationTime = $reset ? 0 : Registry::getUtilsDate()->getTime() + $this->getUpdateLinkTerm();\n\n        $this->oxuser__oxupdatekey = new Field($token, Field::T_RAW);\n        $this->oxuser__oxupdateexp = new Field($tokenExpirationTime, Field::T_RAW);\n        $this->save();\n    }\n\n    /**\n     * Return password update link validity term (seconds). Default 3600 * 6\n     *\n     * @return int\n     */\n    public function getUpdateLinkTerm()\n    {\n        return 3600 * 6;\n    }\n\n    /**\n     * Checks if password update key is not expired yet\n     *\n     * @param string $sKey key\n     *\n     * @return bool\n     */\n    public function isExpiredUpdateId($sKey)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sQ = \"select 1 from \" . $this->getViewName() . \"\n            where oxupdateexp >= :time\n            and MD5( CONCAT( oxid, oxshopid, oxupdatekey ) ) = :hash\";\n\n        return !((bool) $oDb->getOne($sQ, ['time' => time(), 'hash' => $sKey]));\n    }\n\n    /**\n     * Returns user passwords update id\n     *\n     * @return string\n     */\n    public function getUpdateId()\n    {\n        if ($this->_sUpdateKey === null) {\n            $this->setUpdateKey();\n            $this->_sUpdateKey = md5($this->getId() . $this->oxuser__oxshopid->value . $this->oxuser__oxupdatekey->value);\n        }\n\n        return $this->_sUpdateKey;\n    }\n\n    /**\n     * Encodes and returns given password\n     *\n     * @param string $sPassword password to encode\n     * @param string $sSalt     any unique string value\n     * @deprecated since v6.4.0 (2019-03-15); `\\OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\PasswordServiceBridgeInterface`\n     *                                        was added as the new default for hashing passwords. Hashing passwords with\n     *                                        MD5 and SHA512 is still supported in order support login with older\n     *                                        password hashes. Therefor this method might not be\n     *                                        compatible with the current passhword hash any more.\n     *\n     * @return string\n     */\n    public function encodePassword($sPassword, $sSalt)\n    {\n        $oSha512Hasher = oxNew(\\OxidEsales\\Eshop\\Core\\Sha512Hasher::class);\n        $oHasher = oxNew(\\OxidEsales\\Eshop\\Core\\PasswordHasher::class, $oSha512Hasher);\n\n        return $oHasher->hash($sPassword, $sSalt);\n    }\n\n    /**\n     * Sets new password for user (save is not called)\n     *\n     * @param string $password\n     */\n    public function setPassword($password = null)\n    {\n        $this->oxuser__oxpassword = new Field(\n            empty($password) ? '' : $this->getHash($password),\n            Field::T_RAW\n        );\n        $this->oxuser__oxpasssalt = new Field('');\n    }\n\n\n    /**\n     * Checks if user entered password is the same as old\n     *\n     * @param string $password new password\n     *\n     * @return bool\n     */\n    public function isSamePassword($password)\n    {\n        return  password_verify($password, $this->oxuser__oxpassword->value);\n    }\n\n    /**\n     * Returns if user was loaded from cookie\n     *\n     * @return bool\n     */\n    public function isLoadedFromCookie()\n    {\n        return $this->_blLoadedFromCookie;\n    }\n\n    /**\n     * Generates user password and username hash for review\n     *\n     * @param string $sUserId userid\n     *\n     * @return string\n     */\n    public function getReviewUserHash($sUserId)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $hashSql = 'select md5(concat(\"oxid\", oxpassword, oxusername )) from oxuser\n            where oxid = :oxid';\n        $sReviewUserHash = $oDb->getOne($hashSql, [\n            'oxid' => $sUserId\n        ]);\n\n        return $sReviewUserHash;\n    }\n\n    /**\n     * Gets from review user hash user id\n     *\n     * @param string $sReviewUserHash review user hash\n     *\n     * @return string\n     */\n    public function getReviewUserId($sReviewUserHash)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $userIdSql = 'select oxid from oxuser where md5(concat(\"oxid\", oxpassword, oxusername )) = :hash';\n        $sUserId = $oDb->getOne($userIdSql, [\n            'hash' => $sReviewUserHash\n        ]);\n\n        return $sUserId;\n    }\n\n    /**\n     * Get state id for current user\n     *\n     * @return mixed\n     */\n    public function getStateId()\n    {\n        return $this->oxuser__oxstateid->value;\n    }\n\n    /**\n     * Get state title by id\n     *\n     * @param string $sId state ID\n     *\n     * @return string\n     */\n    public function getStateTitle($sId = null)\n    {\n        $oState = $this->getStateObject();\n\n        if (is_null($sId)) {\n            $sId = $this->getStateId();\n        }\n\n        return $oState->getTitleById($sId);\n    }\n\n    /**\n     * Checks if user accepted latest shopping terms and conditions version\n     *\n     * @return bool\n     */\n    public function isTermsAccepted()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $termsSql = \"select 1 from oxacceptedterms where oxuserid = :oxuserid and oxshopid = :oxshopid\";\n        return (bool) $oDb->getOne($termsSql, [\n            'oxuserid' => $this->getId(),\n            'oxshopid' => Registry::getConfig()->getShopId()\n        ]);\n    }\n\n    /**\n     * Writes terms acceptance info to db\n     */\n    public function acceptTerms()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sUserId = $oDb->quote($this->getId());\n        $sShopId = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId();\n        $sVersion = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Content::class)->getTermsVersion();\n\n        $oDb->execute(\"replace oxacceptedterms set oxuserid={$sUserId}, oxshopid='{$sShopId}', oxtermversion='{$sVersion}'\");\n    }\n\n    /**\n     * Assigns registration points for invited user and\n     * its inviter (calls \\OxidEsales\\Eshop\\Application\\Model\\User::setInvitationCreditPoints())\n     *\n     * @param string $sUserId   inviter user id\n     * @param string $sRecEmail recipient (registrant) email\n     *\n     * @return bool\n     */\n    public function setCreditPointsForRegistrant($sUserId, $sRecEmail)\n    {\n        $blSet = false;\n        $iPoints = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('dPointsForRegistration');\n        // check if this invitation is still not accepted\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        $masterDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster();\n        $pendingSql = \"select count(oxuserid) from oxinvitations\n            where oxuserid = :oxuserid\n                and md5(oxemail) = :oxemailhash\n                and oxpending = :oxpending\n                and oxaccepted = :oxaccepted\";\n        $iPending = $masterDb->getOne($pendingSql, [\n            'oxuserid' => $sUserId,\n            'oxemailhash' => $sRecEmail,\n            'oxpending' => 1,\n            'oxaccepted' => 0\n        ]);\n        if ($iPoints && $iPending) {\n            $this->oxuser__oxpoints = new Field($iPoints, Field::T_RAW);\n            if ($blSet = $this->save()) {\n                // updating users statistics\n                $query = \"UPDATE oxinvitations\n                          SET oxpending = '0',\n                              oxaccepted = '1'\n                          WHERE oxuserid = :oxuserid AND\n                                md5(oxemail) = :oxemail\";\n                $masterDb->execute($query, [\n                    'oxuserid' => $sUserId,\n                    'oxemail' => $sRecEmail\n                ]);\n                $oInvUser = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n                if ($oInvUser->load($sUserId)) {\n                    $blSet = $oInvUser->setCreditPointsForInviter();\n                }\n            }\n        }\n        Registry::getSession()->deleteVariable('su');\n        Registry::getSession()->deleteVariable('re');\n\n        return $blSet;\n    }\n\n    /**\n     * Assigns credit points to inviter\n     *\n     * @return bool\n     */\n    public function setCreditPointsForInviter()\n    {\n        $blSet = false;\n        $iPoints = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('dPointsForInvitation');\n        if ($iPoints) {\n            $iNewPoints = $this->oxuser__oxpoints->value + $iPoints;\n            $this->oxuser__oxpoints = new Field($iNewPoints, Field::T_RAW);\n            $blSet = $this->save();\n        }\n\n        return $blSet;\n    }\n\n    /**\n     * Updating invitations statistics\n     *\n     * @param array $aRecEmail array of recipients emails\n     */\n    public function updateInvitationStatistics($aRecEmail)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sUserId = $this->getId();\n\n        if ($sUserId && is_array($aRecEmail) && count($aRecEmail) > 0) {\n            //iserting statistics about invitation\n            $sDate = Registry::getUtilsDate()->formatDBDate(date(\"Y-m-d\"), true);\n            foreach ($aRecEmail as $sRecEmail) {\n                $sSql = \"INSERT INTO oxinvitations SET oxuserid = :oxuserid, oxemail = :oxemail, oxdate = :oxdate, oxpending = '1', oxaccepted = '0', oxtype = '1'\";\n                $oDb->execute($sSql, [\n                    'oxuserid' => $sUserId,\n                    'oxemail' => $sRecEmail,\n                    'oxdate' => $sDate\n                ]);\n            }\n        }\n    }\n\n    /**\n     * return user id by user name\n     *\n     * @param string $userName\n     *\n     * @return false|string\n     */\n    public function getIdByUserName($userName)\n    {\n        $userId = DatabaseProvider::getDb()\n            ->getOne(\n                'SELECT `OXID` FROM `oxuser` WHERE `OXUSERNAME` = :oxusername AND `OXSHOPID` = :oxshopid',\n                [\n                    'oxusername' => (string) $userName,\n                    'oxshopid' => Registry::getConfig()->getShopId()\n                ]\n            );\n\n        return $userId;\n    }\n\n    /**\n     * returns true if user registered and have account\n     *\n     * @return bool\n     */\n    public function hasAccount()\n    {\n        return (bool) $this->oxuser__oxpassword->value;\n    }\n\n    /**\n     * Return user price view mode, true - if netto mode\n     *\n     * @return bool\n     */\n    public function isPriceViewModeNetto()\n    {\n        return (bool) \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blShowNetPrice');\n    }\n\n    /**\n     * Returns true if User is mall admin.\n     *\n     * @return bool\n     */\n    public function isMallAdmin()\n    {\n        return 'malladmin' === $this->oxuser__oxrights->value;\n    }\n\n    /**\n     * Initiates user login against data in DB.\n     *\n     * @param string $userName User\n     * @param string $password Password\n     * @param int    $shopId   Shop id\n     *\n     * @throws UserException\n     *\n     * @deprecated since v6.4.0 (2019-03-15); `\\OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\PasswordServiceBridgeInterface`\n     *                                        was added as the new default for hashing passwords. Hashing passwords with\n     *                                        MD5 and SHA512 is still supported in order support login with older\n     *                                        password hashes. Therefor this method might not be\n     *                                        compatible with the current passhword hash any more.\n     *\n     * @return void\n     */\n    protected function _dbLogin(string $userName, $password, $shopId) // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore\n    {\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $userId = $database->getOne($this->_getLoginQuery($userName, $password, $shopId, $this->isAdmin()));\n        if (!$userId) {\n            $userId = $database->getOne($this->_getLoginQueryHashedWithMD5($userName, $password, $shopId, $this->isAdmin()));\n        }\n\n        /** Return here to give other log-in mechanisms the possibility to be triggered */\n        if (!$userId) {\n            return;\n        }\n\n        $this->loadAuthenticatedUser($userName, $shopId);\n        $this->isOutdatedPasswordHashAlgorithmUsed = true;\n    }\n\n    /**\n     * @param string $userName\n     * @param int    $shopId\n     * @param bool   $isLoginToAdminBackend\n     *\n     * @return false|string\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\DatabaseConnectionException\n     */\n    protected function getPasswordHashFromDatabase(string $userName, int $shopId, bool $isLoginToAdminBackend)\n    {\n        $database = DatabaseProvider::getDb();\n        $userNameCondition = $this->formQueryPartForUserName($userName, $database);\n        $shopOrRightsCondition = $this->formQueryPartForAdminView($shopId, $isLoginToAdminBackend);\n        $userActiveCondition = $this->formQueryPartForActiveUser();\n\n        $query = \"SELECT `oxpassword`\n                    FROM oxuser\n                    WHERE 1\n                    AND $userActiveCondition\n                    AND $userNameCondition\n                    $shopOrRightsCondition\n                    \";\n\n        return $database->getOne($query);\n    }\n\n    /**\n     * Return true - if shop is in demo mode\n     *\n     * @return bool\n     */\n    protected function isDemoShop()\n    {\n        $blDemoMode = false;\n\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->isDemoShop()) {\n            $blDemoMode = true;\n        }\n\n        return $blDemoMode;\n    }\n\n    /**\n     * Return sql to get id of mall admin in demo shop\n     *\n     * @param string $sUser     User name\n     * @param string $sPassword User password\n     *\n     * @throws object $oEx\n     *\n     * @return string\n     */\n    protected function getDemoShopLoginQuery($sUser, $sPassword)\n    {\n        if ($sPassword == \"admin\" && $sUser == \"admin\") {\n            $sSelect = \"SELECT `oxid` FROM `oxuser` WHERE `oxrights` = 'malladmin' \";\n        } else {\n            /** @var UserException $oEx */\n            $oEx = oxNew(UserException::class);\n            $oEx->setMessage('ERROR_MESSAGE_USER_NOVALIDLOGIN');\n            throw $oEx;\n        }\n\n        return $sSelect;\n    }\n\n    /**\n     * Method used for override.\n     *\n     * @param array $aInvAddress\n     */\n    protected function onChangeUserData($aInvAddress)\n    {\n    }\n\n    /**\n     * Method is used to make additional delete actions.\n     *\n     * @param string $sOXIDQuoted\n     */\n    protected function deleteAdditionally($sOXIDQuoted)\n    {\n    }\n\n    /**\n     * Updates query for selecting orders.\n     *\n     * @param string $query\n     *\n     * @return string\n     */\n    protected function updateGetOrdersQuery($query)\n    {\n        return $query;\n    }\n\n    /**\n     * Method is used for overriding and add additional actions when logging in.\n     *\n     * @param string $userName\n     * @param string $password\n     */\n    protected function onLogin($userName, $password)\n    {\n        /** Demo shop log in */\n        if (!$this->isLoaded() && $this->isDemoShop() && $this->isAdmin()) {\n            $database = DatabaseProvider::getDb();\n            $userId = $database->getOne($this->getDemoShopLoginQuery($userName, $password));\n            if ($userId) {\n                $this->load($userId);\n            }\n        }\n    }\n\n    /**\n     * Deletes User from groups.\n     *\n     * @param DatabaseInterface $database\n     */\n    private function deleteUserFromGroups(DatabaseInterface $database)\n    {\n        $database->execute('delete from oxobject2group where oxobject2group.oxobjectid = :oxobjectid', [\n            'oxobjectid' => $this->getId()\n        ]);\n    }\n\n    /**\n     * Deletes deliveries.\n     *\n     * @param DatabaseInterface $database\n     */\n    private function deleteDeliveries(DatabaseInterface $database)\n    {\n        $database->execute('delete from oxobject2delivery where oxobjectid = :oxobjectid', [\n            'oxobjectid' => $this->getId()\n        ]);\n    }\n\n    /**\n     * Deletes discounts.\n     *\n     * @param DatabaseInterface $database\n     */\n    private function deleteDiscounts(DatabaseInterface $database)\n    {\n        $database->execute('delete from oxobject2discount where oxobjectid = :oxobjectid', [\n            'oxobjectid' => $this->getId()\n        ]);\n    }\n\n    /**\n     * Deletes user accepted terms.\n     *\n     * @param DatabaseInterface $database\n     */\n    private function deleteAcceptedTerms(DatabaseInterface $database)\n    {\n        $database->execute('delete from oxacceptedterms where oxuserid = :oxuserid', [\n            'oxuserid' => $this->getId()\n        ]);\n    }\n\n    /**\n     * Deletes User addresses.\n     *\n     * @param DatabaseInterface $database\n     */\n    private function deleteAddresses(DatabaseInterface $database)\n    {\n        $ids = $database->getCol('SELECT oxid FROM oxaddress WHERE oxuserid = :oxuserid', [\n            'oxuserid' => $this->getId()\n        ]);\n        array_walk($ids, [$this, 'deleteItemById'], \\OxidEsales\\Eshop\\Application\\Model\\Address::class);\n    }\n\n    /**\n     * Deletes noticelists, wishlists or saved baskets\n     *\n     * @param DatabaseInterface $database\n     */\n    private function deleteBaskets(DatabaseInterface $database)\n    {\n        $ids = $database->getCol('SELECT oxid FROM oxuserbaskets WHERE oxuserid = :oxuserid', [\n            'oxuserid' => $this->getId()\n        ]);\n        array_walk($ids, [$this, 'deleteItemById'], \\OxidEsales\\Eshop\\Application\\Model\\UserBasket::class);\n    }\n\n    /**\n     * Deletes not Order related remarks.\n     *\n     * @param DatabaseInterface $database\n     */\n    private function deleteNotOrderRelatedRemarks(DatabaseInterface $database)\n    {\n        $sql = 'SELECT oxid FROM oxremark WHERE oxparentid = :oxparentid and oxtype != :notoxtype';\n        $ids = $database->getCol($sql, [\n            'oxparentid' => $this->getId(),\n            'notoxtype' => 'o'\n        ]);\n        array_walk($ids, [$this, 'deleteItemById'], \\OxidEsales\\Eshop\\Application\\Model\\Remark::class);\n    }\n\n    /**\n     * Deletes recommendation lists.\n     *\n     * @param DatabaseInterface $database\n     */\n    private function deleteRecommendationLists(DatabaseInterface $database)\n    {\n        $ids = $database->getCol('SELECT oxid FROM oxrecommlists WHERE oxuserid = :oxuserid ', [\n            'oxuserid' => $this->getId()\n        ]);\n        array_walk($ids, [$this, 'deleteItemById'], \\OxidEsales\\Eshop\\Application\\Model\\RecommendationList::class);\n    }\n\n    /**\n     * Deletes newsletter subscriptions.\n     *\n     * @param DatabaseInterface $database\n     */\n    private function deleteNewsletterSubscriptions(DatabaseInterface $database)\n    {\n        $ids = $database->getCol('SELECT oxid FROM oxnewssubscribed WHERE oxuserid = :oxuserid ', [\n            'oxuserid' => $this->getId()\n        ]);\n        array_walk($ids, [$this, 'deleteItemById'], \\OxidEsales\\Eshop\\Application\\Model\\NewsSubscribed::class);\n    }\n\n\n    /**\n     * Deletes User reviews.\n     *\n     * @param DatabaseInterface $database\n     */\n    private function deleteReviews(DatabaseInterface $database)\n    {\n        $ids = $database->getCol('select oxid from oxreviews where oxuserid = :oxuserid', [\n            'oxuserid' => $this->getId()\n        ]);\n        array_walk($ids, [$this, 'deleteItemById'], \\OxidEsales\\Eshop\\Application\\Model\\Review::class);\n    }\n\n    /**\n     * Deletes User ratings.\n     *\n     * @param DatabaseInterface $database\n     */\n    private function deleteRatings(DatabaseInterface $database)\n    {\n        $ids = $database->getCol('SELECT oxid FROM oxratings WHERE oxuserid = :oxuserid', [\n            'oxuserid' => $this->getId()\n        ]);\n        array_walk($ids, [$this, 'deleteItemById'], \\OxidEsales\\Eshop\\Application\\Model\\Rating::class);\n    }\n\n    /**\n     * Deletes price alarms.\n     *\n     * @param DatabaseInterface $database\n     */\n    private function deletePriceAlarms(DatabaseInterface $database)\n    {\n        $ids = $database->getCol('SELECT oxid FROM oxpricealarm WHERE oxuserid = :oxuserid', [\n            'oxuserid' => $this->getId()\n        ]);\n        array_walk($ids, [$this, 'deleteItemById'], \\OxidEsales\\Eshop\\Application\\Model\\PriceAlarm::class);\n    }\n\n    /**\n     * Callback function for array_walk to delete items using the delete method of the given model class\n     *\n     * @param string  $id        Id of the item to be deleted\n     * @param integer $key       Key of the array\n     * @param string  $className Model class to be used\n     */\n    private function deleteItemById($id, $key, $className)\n    {\n        /** @var \\OxidEsales\\Eshop\\Core\\Model\\BaseModel $modelObject */\n        $modelObject = oxNew($className);\n\n        if ($modelObject->load($id)) {\n            if ($this->_blMallUsers) {\n                $modelObject->setIsDerived(false);\n            }\n            $modelObject->delete();\n        }\n    }\n\n    /**\n     * @param string            $password\n     * @param DatabaseInterface $database\n     * @param string            $userCondition\n     * @param string            $shopCondition\n     *\n     * @deprecated since v6.4.0 (2019-03-15); `\\OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\PasswordServiceBridgeInterface`\n     *                                        was added as the new default for hashing passwords. Hashing passwords with\n     *                                        MD5 and SHA512 is still supported in order support login with older\n     *                                        password hashes. Therefor this method might not be\n     *                                        compatible with the current passhword hash any more.\n     *\n     * @return string\n     */\n    protected function formQueryPartForSha512Password(string $password, DatabaseInterface $database, string $userCondition, string $shopCondition): string\n    {\n        $salt = $database->getOne(\"SELECT `oxpasssalt` FROM `oxuser` WHERE  1 AND $userCondition $shopCondition\");\n        if (false !== $salt) {\n            $passwordSelect = ' oxuser.oxpassword = ' . $database->quote($this->encodePassword($password, $salt));\n        } else {\n            $passwordSelect = ' 1 ';\n        }\n\n        return $passwordSelect;\n    }\n\n    /**\n     * @param string            $password\n     * @param DatabaseInterface $databaseb\n     *\n     * @deprecated since v6.4.0 (2019-03-15); `\\OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\PasswordServiceBridgeInterface`\n     *                                        was added as the new default for hashing passwords. Hashing passwords with\n     *                                        MD5 and SHA512 is still supported in order support login with older\n     *                                        password hashes. Therefor this method might not be\n     *                                        compatible with the current passhword hash any more.\n     *\n     * @return string\n     */\n    protected function formQueryPartForMD5Password($password, DatabaseInterface $databaseb): string\n    {\n        $sPassSelect = ' oxuser.oxpassword = BINARY MD5( CONCAT( ' . $databaseb->quote($password) . ', UNHEX( oxuser.oxpasssalt ) ) ) ';\n\n        return $sPassSelect;\n    }\n\n    /**\n     * @param string            $user\n     * @param DatabaseInterface $database\n     *\n     * @return string\n     */\n    private function formQueryPartForUserName($user, DatabaseInterface $database): string\n    {\n        $condition = 'oxuser.oxusername = ' . $database->quote($user);\n\n        return $condition;\n    }\n\n    /**\n     * Forms shop select query.\n     *\n     * @param string $sShopID Shop id is used when method is overridden.\n     * @param bool   $blAdmin\n     *\n     * @return string\n     */\n    protected function formQueryPartForAdminView($sShopID, $blAdmin)\n    {\n        $sShopSelect = '';\n\n        // Admin view: can only login with higher than 'user' rights\n        if ($blAdmin) {\n            $sShopSelect = \" and ( oxrights != 'user' ) \";\n        }\n\n        return $sShopSelect;\n    }\n\n    /**\n     * @return string\n     */\n    private function formQueryPartForActiveUser(): string\n    {\n        $userActiveCondition = 'oxuser.oxactive = 1';\n\n        return $userActiveCondition;\n    }\n\n    /**\n     * Updates given query. Method is for overriding.\n     *\n     * @param string $user\n     * @param int    $shopId\n     *\n     * @return string\n     */\n    protected function formUserCookieQuery($user, $shopId)\n    {\n        $query = 'select oxid, oxpassword, oxpasssalt from oxuser '\n                 . 'where oxuser.oxpassword != \"\" and  oxuser.oxactive = 1 and oxuser.oxusername = '\n                 . \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quote($user);\n\n        return $query;\n    }\n\n    private function getRandomToken(): string\n    {\n        return ContainerFacade::get(RandomTokenGeneratorBridgeInterface::class)\n            ->getAlphanumericToken(32);\n    }\n\n    private function getHash(string $password): string\n    {\n        return ContainerFacade::get(PasswordServiceBridgeInterface::class)\n            ->hash($password);\n    }\n\n    private function verifyHash(string $password, string $hash): string\n    {\n        return ContainerFacade::get(PasswordServiceBridgeInterface::class)\n            ->verifyPassword($password, $hash);\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/UserAddressList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Class oxUserAddressList\n */\nclass UserAddressList extends \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n{\n    /**\n     * Call parent class constructor\n     */\n    public function __construct()\n    {\n        parent::__construct('oxaddress');\n    }\n\n    /**\n     * Selects and loads all address for particular user.\n     *\n     * @param string $sUserId user id\n     */\n    public function load($sUserId)\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sViewName = $tableViewNameGenerator->getViewName('oxcountry');\n        $oBaseObject = $this->getBaseObject();\n        $sSelectFields = $oBaseObject->getSelectFields();\n\n        $sSelect = \"\n                SELECT {$sSelectFields}, `oxcountry`.`oxtitle` AS oxcountry\n                FROM oxaddress\n                LEFT JOIN {$sViewName} AS oxcountry ON oxaddress.oxcountryid = oxcountry.oxid\n                WHERE oxaddress.oxuserid = :oxuserid\";\n        $this->selectString($sSelect, [\n            'oxuserid' => $sUserId\n        ]);\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/UserBasket.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxRegistry;\nuse oxField;\nuse oxDb;\n\n/**\n * Virtual basket manager class. Virtual baskets are user article lists which are stored in database (noticelists, wishlists).\n * The name of the class is left like this because of historic reasons.\n * It is more relevant to wishlist and noticelist than to shoping basket.\n * Collects shopping basket information, updates it (DB level), removes or adds\n * articles to it.\n */\nclass UserBasket extends \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n{\n    /**\n     * Array of fields which must be skipped when updating object data\n     *\n     * @var array\n     */\n    protected $_aSkipSaveFields = ['oxcreate', 'oxtimestamp'];\n\n    /**\n     * Current object class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxUserbasket';\n\n    /**\n     * Array of basket items\n     *\n     * @var array\n     */\n    protected $_aBasketItems = null;\n\n    /**\n     * Marker if basket is newly created. This avoids empty basket storing to DB\n     *\n     * @var bool\n     */\n    protected $_blNewBasket = false;\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxuserbaskets');\n    }\n\n    /**\n     * Inserts object data to DB, returns true on success.\n     *\n     * @return mixed\n     */\n    protected function insert()\n    {\n        // marking basket as not new any more\n        $this->_blNewBasket = false;\n\n        if (!isset($this->oxuserbaskets__oxpublic->value)) {\n            $public = in_array($this->oxuserbaskets__oxtitle->value, ['noticelist', 'wishlist']);\n\n            $this->oxuserbaskets__oxpublic = new \\OxidEsales\\Eshop\\Core\\Field($public, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        }\n\n        $iTime = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime();\n        $this->oxuserbaskets__oxupdate = new \\OxidEsales\\Eshop\\Core\\Field($iTime);\n\n        return parent::insert();\n    }\n\n    /**\n     * Sets basket as newly created. This usually means that it is not\n     * yet stored in DB and will only be stored if some item is added\n     */\n    public function setIsNewBasket()\n    {\n        $this->_blNewBasket = true;\n        $iTime = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime();\n        $this->oxuserbaskets__oxupdate = new \\OxidEsales\\Eshop\\Core\\Field($iTime);\n    }\n\n    /**\n     * Checks if user basket is newly created\n     *\n     * @return bool\n     */\n    public function isNewBasket()\n    {\n        return $this->_blNewBasket;\n    }\n\n    /**\n     * Checks if user basket is empty\n     *\n     * @return bool\n     */\n    public function isEmpty()\n    {\n        if ($this->isNewBasket() || $this->getItemCount() < 1) {\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns an array of articles belonging to the Items in the basket\n     *\n     * @return array of oxArticle\n     */\n    public function getArticles()\n    {\n        $aRes = [];\n        $aItems = $this->getItems();\n        if (is_array($aItems)) {\n            foreach ($aItems as $sId => $oItem) {\n                $oArticle = $oItem->getArticle($sId);\n                $aRes[$this->getItemKey($oArticle->getId(), $oItem->getSelList(), $oItem->getPersParams())] = $oArticle;\n            }\n        }\n\n        return $aRes;\n    }\n\n    /**\n     * Returns list of basket items\n     *\n     * @param bool $blReload      if TRUE forces to reload list\n     * @param bool $blActiveCheck should articles be checked for active state?\n     *\n     * @return array of oxUserBasketItems\n     */\n    public function getItems($blReload = false, $blActiveCheck = true)\n    {\n        // cached ?\n        if ($this->_aBasketItems !== null && !$blReload) {\n            return $this->_aBasketItems;\n        }\n\n        // initializing\n        $this->_aBasketItems = [];\n\n        // loading basket items\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $sViewName = $oArticle->getViewName();\n\n        $sSelect = \"select oxuserbasketitems.* from oxuserbasketitems \n            left join $sViewName on oxuserbasketitems.oxartid = $sViewName.oxid \";\n        if ($blActiveCheck) {\n            $sSelect .= 'and ' . $oArticle->getSqlActiveSnippet() . ' ';\n        }\n        $sSelect .= \"where oxuserbasketitems.oxbasketid = :oxbasketid and $sViewName.oxid is not null \";\n\n        $sSelect .= \" order by oxartnum, oxsellist, oxpersparam \";\n\n        $oItems = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n        $oItems->init('oxuserbasketitem');\n        $oItems->selectstring($sSelect, [\n            'oxbasketid' => $this->getId()\n        ]);\n\n        foreach ($oItems as $oItem) {\n            $sKey = $this->getItemKey($oItem->oxuserbasketitems__oxartid->value, $oItem->getSelList(), $oItem->getPersParams());\n            $this->_aBasketItems[$sKey] = $oItem;\n        }\n\n        return $this->_aBasketItems;\n    }\n\n    /**\n     * Creates and returns  oxuserbasketitem object\n     *\n     * @param string $sProductId  Product Id\n     * @param array  $aSelList    product select lists\n     * @param string $aPersParams persistent parameters\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\UserBasketItem\n     */\n    protected function createItem($sProductId, $aSelList = null, $aPersParams = null)\n    {\n        $oNewItem = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\UserBasketItem::class);\n        $oNewItem->oxuserbasketitems__oxartid = new \\OxidEsales\\Eshop\\Core\\Field($sProductId, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        $oNewItem->oxuserbasketitems__oxbasketid = new \\OxidEsales\\Eshop\\Core\\Field($this->getId(), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        if ($aPersParams && count($aPersParams)) {\n            $oNewItem->setPersParams($aPersParams);\n        }\n\n        if (!$aSelList) {\n            $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n            $oArticle->load($sProductId);\n            $aSelectLists = $oArticle->getSelectLists();\n            if (($iSelCnt = count($aSelectLists))) {\n                $aSelList = array_fill(0, $iSelCnt, '0');\n            }\n        }\n\n        $oNewItem->setSelList($aSelList);\n\n        return $oNewItem;\n    }\n\n\n    /**\n     * Searches for item in basket items array and returns it. If not item was\n     * found - new item is created.\n     *\n     * @param string $sProductId  product id, basket item id or basket item index\n     * @param array  $aSelList    select lists\n     * @param string $aPersParams persistent parameters\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\UserBasketItem\n     */\n    public function getItem($sProductId, $aSelList, $aPersParams = null)\n    {\n        // loading basket item list\n        $aItems = $this->getItems();\n        $sItemKey = $this->getItemKey($sProductId, $aSelList, $aPersParams);\n        $oItem = null;\n        // returning existing item\n        if (isset($aItems[$sProductId])) {\n            $oItem = $aItems[$sProductId];\n        } elseif (isset($aItems[$sItemKey])) {\n            $oItem = $aItems[$sItemKey];\n        } else {\n            $oItem = $this->createItem($sProductId, $aSelList, $aPersParams);\n        }\n\n        return $oItem;\n    }\n\n    /**\n     * Returns unique item key according to its ID and user chosen select\n     *\n     * @param string $sProductId Product Id\n     * @param array  $aSel       product select lists\n     * @param array  $aPersParam basket item persistent parameters\n     *\n     * @return string\n     */\n    protected function getItemKey($sProductId, $aSel = null, $aPersParam = null)\n    {\n        $aSel = ($aSel != null) ? $aSel : [0 => '0'];\n\n        return md5($sProductId . '|' . serialize($aSel) . '|' . serialize($aPersParam));\n    }\n\n    /**\n     * Returns current basket item count\n     *\n     * @param bool $blReload if TRUE forces to reload list\n     *\n     * @return int\n     */\n    public function getItemCount($blReload = false)\n    {\n        return count($this->getItems($blReload));\n    }\n\n    /**\n     * Method adds/removes user chosen article to/from his noticelist or wishlist. Returns total amount\n     * of articles in list.\n     *\n     * @param string $sProductId Article ID\n     * @param double $dAmount    Product amount\n     * @param array  $aSel       product select lists\n     * @param bool   $blOverride if true overrides $dAmount, else sums previous with current it\n     * @param array  $aPersParam product persistent parameters (default null)\n     *\n     * @return integer\n     */\n    public function addItemToBasket($sProductId = null, $dAmount = null, $aSel = null, $blOverride = false, $aPersParam = null)\n    {\n        // basket info is only written in DB when something is in it\n        if ($this->_blNewBasket) {\n            $this->save();\n        }\n\n        if (($oUserBasketItem = $this->getItem($sProductId, $aSel, $aPersParam))) {\n            // updating object info and adding (if not yet added) item into basket items array\n            if (!$blOverride && !empty($oUserBasketItem->oxuserbasketitems__oxamount->value)) {\n                $dAmount += $oUserBasketItem->oxuserbasketitems__oxamount->value;\n            }\n\n            if (!$dAmount) {\n                // amount = 0 removes the item\n                $oUserBasketItem->delete();\n                if (isset($this->_aBasketItems[$this->getItemKey($sProductId, $aSel, $aPersParam)])) {\n                    unset($this->_aBasketItems[$this->getItemKey($sProductId, $aSel, $aPersParam)]);\n                }\n            } else {\n                $oUserBasketItem->oxuserbasketitems__oxamount = new \\OxidEsales\\Eshop\\Core\\Field($dAmount, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n                $oUserBasketItem->save();\n\n                $this->_aBasketItems[$this->getItemKey($sProductId, $aSel, $aPersParam)] = $oUserBasketItem;\n            }\n\n            //update timestamp\n            $this->oxuserbaskets__oxupdate = new \\OxidEsales\\Eshop\\Core\\Field(\\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime());\n            $this->save();\n\n            return $dAmount;\n        }\n    }\n\n    /**\n     * Deletes current basket history\n     *\n     * @param string $sOXID Object ID(default null)\n     *\n     * @return bool\n     */\n    public function delete($sOXID = null)\n    {\n        if (!$sOXID) {\n            $sOXID = $this->getId();\n        }\n\n        $blDelete = false;\n        if ($sOXID && ($blDelete = parent::delete($sOXID))) {\n            // cleaning up related data\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $sQ = \"delete from oxuserbasketitems where oxbasketid = :oxbasketid\";\n            $oDb->execute($sQ, [\n                'oxbasketid' => $sOXID\n            ]);\n            $this->_aBasketItems = null;\n        }\n\n        return $blDelete;\n    }\n\n    /**\n     * Checks if user basket is visible for current user (public or own basket)\n     *\n     * @return bool\n     */\n    public function isVisible()\n    {\n        $oActivUser = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getUser();\n        $sActivUserId = null;\n        if ($oActivUser) {\n            $sActivUserId = $oActivUser->getId();\n        }\n\n        $blIsVisible = (bool) ($this->oxuserbaskets__oxpublic->value) ||\n                       ($sActivUserId && ($this->oxuserbaskets__oxuserid->value == $sActivUserId));\n\n        return $blIsVisible;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/UserBasketItem.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxField;\n\n/**\n * Shopping basket item manager.\n * Manager class for shopping basket item (class may be overriden).\n */\nclass UserBasketItem extends \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n{\n    /**\n     * Current class name\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxuserbasketitem';\n\n    /**\n     * Article object assigned to userbasketitem\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    protected $_oArticle = null;\n\n    /**\n     * Variant parent \"buyable\" status\n     *\n     * @var bool\n     */\n    protected $_blParentBuyable = false;\n\n    /**\n     * Basket item selection list\n     *\n     * @var array\n     */\n    protected $_aSelList = null;\n\n    /**\n     * Basket item persistent parameters\n     *\n     * @var array\n     */\n    protected $_aPersParam = null;\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()).\n     */\n    public function __construct()\n    {\n        $this->setVariantParentBuyable(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blVariantParentBuyable'));\n        parent::__construct();\n        $this->init('oxuserbasketitems');\n    }\n\n    /**\n     * Variant parent \"buyable\" status setter\n     *\n     * @param bool $blBuyable parent \"buyable\" status\n     */\n    public function setVariantParentBuyable($blBuyable = false)\n    {\n        $this->_blParentBuyable = $blBuyable;\n    }\n\n    /**\n     * Loads and returns the article for that basket item\n     *\n     * @param string $sItemKey the key that will be given to oxarticle setItemKey\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\ArticleException\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    public function getArticle($sItemKey)\n    {\n        if (!$this->oxuserbasketitems__oxartid->value) {\n            //this exception may not be caught, anyhow this is a critical exception\n            $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ArticleException::class);\n            $oEx->setMessage('EXCEPTION_ARTICLE_NOPRODUCTID');\n            throw $oEx;\n        }\n\n        if ($this->_oArticle === null) {\n            $this->_oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n\n            // performance\n            /* removed due to #4178\n             if ( $this->_blParentBuyable ) {\n                $this->_oArticle->setNoVariantLoading( true );\n            }\n            */\n\n            if (!$this->_oArticle->load($this->oxuserbasketitems__oxartid->value)) {\n                return false;\n            }\n\n            $aSelList = $this->getSelList();\n            if (($aSelectlist = $this->_oArticle->getSelectLists()) && is_array($aSelList)) {\n                foreach ($aSelList as $iKey => $iSel) {\n                    if (isset($aSelectlist[$iKey][$iSel])) {\n                        // cloning select list information\n                        $aSelectlist[$iKey][$iSel] = clone $aSelectlist[$iKey][$iSel];\n                        $aSelectlist[$iKey][$iSel]->selected = 1;\n                    }\n                }\n                $this->_oArticle->setSelectlist($aSelectlist);\n            }\n\n            // generating item key\n            $this->_oArticle->setItemKey($sItemKey);\n        }\n\n        return $this->_oArticle;\n    }\n\n    /**\n     * Does not return _oArticle var on serialisation\n     *\n     * @return array\n     */\n    public function __sleep()\n    {\n        $aRet = [];\n        foreach (get_object_vars($this) as $sKey => $sVar) {\n            if ($sKey != '_oArticle') {\n                $aRet[] = $sKey;\n            }\n        }\n\n        return $aRet;\n    }\n\n    /**\n     * Basket item selection list getter\n     *\n     * @return array\n     */\n    public function getSelList()\n    {\n        if ($this->_aSelList == null && $this->oxuserbasketitems__oxsellist->value) {\n            $this->_aSelList = unserialize($this->oxuserbasketitems__oxsellist->value);\n        }\n\n        return $this->_aSelList;\n    }\n\n    /**\n     * Basket item selection list setter\n     *\n     * @param array $aSelList selection list\n     */\n    public function setSelList($aSelList)\n    {\n        $this->oxuserbasketitems__oxsellist = new \\OxidEsales\\Eshop\\Core\\Field(serialize($aSelList), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n    }\n\n    /**\n     * Basket item persistent parameters getter\n     *\n     * @return array\n     */\n    public function getPersParams()\n    {\n        if ($this->_aPersParam == null && $this->oxuserbasketitems__oxpersparam->value) {\n            $this->_aPersParam = unserialize($this->oxuserbasketitems__oxpersparam->value);\n        }\n\n        return $this->_aPersParam;\n    }\n\n    /**\n     * Basket item persistent parameters setter\n     *\n     * @param string $sPersParams persistent parameters\n     */\n    public function setPersParams($sPersParams)\n    {\n        $this->oxuserbasketitems__oxpersparam = new \\OxidEsales\\Eshop\\Core\\Field(serialize($sPersParams), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n    }\n\n    /**\n     * Sets data field value\n     *\n     * @param string $sFieldName index OR name (eg. 'oxarticles__oxtitle') of a data field to set\n     * @param string $sValue     value of data field\n     * @param int    $iDataType  field type\n     *\n     * @return null\n     */\n    protected function setFieldData($sFieldName, $sValue, $iDataType = \\OxidEsales\\Eshop\\Core\\Field::T_TEXT)\n    {\n        if (\n            'oxsellist' === strtolower($sFieldName) || 'oxuserbasketitems__oxsellist' === strtolower($sFieldName)\n            || 'oxpersparam' === strtolower($sFieldName) || 'oxuserbasketitems__oxpersparam' === strtolower($sFieldName)\n        ) {\n            $iDataType = \\OxidEsales\\Eshop\\Core\\Field::T_RAW;\n        }\n\n        return parent::setFieldData($sFieldName, $sValue, $iDataType);\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/UserList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\n\n/**\n * User list manager.\n */\nclass UserList extends \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n{\n    /**\n     * Class constructor\n     */\n    public function __construct()\n    {\n        parent::__construct('oxuser');\n    }\n\n\n    /**\n     * Load searched user list with wishlist\n     *\n     * @param string $sSearchStr Search string\n     *\n     * @return null\n     */\n    public function loadWishlistUsers($sSearchStr)\n    {\n        $sSearchStr = trim($sSearchStr);\n\n        if (!$sSearchStr) {\n            return;\n        }\n\n        $sSelect = \"select oxuser.oxid, oxuser.oxfname, oxuser.oxlname from oxuser \";\n        $sSelect .= \"left join oxuserbaskets on oxuserbaskets.oxuserid = oxuser.oxid \";\n        $sSelect .= \"where oxuserbaskets.oxid is not null and oxuserbaskets.oxtitle = 'wishlist' \";\n        $sSelect .= \"and oxuserbaskets.oxpublic = 1 \";\n        $sSelect .= \"and ( oxuser.oxusername = :search or oxuser.oxlname = :search)\";\n        $sSelect .= \"and ( select 1 from oxuserbasketitems where oxuserbasketitems.oxbasketid = oxuserbaskets.oxid limit 1)\";\n\n        $this->selectString($sSelect, [\n            'search' => \"$sSearchStr\"\n        ]);\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/UserPayment.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse oxRegistry;\nuse oxDb;\n\n/**\n * User payment manager.\n * Performs assigning, loading, inserting and updating functions for\n * user payment.\n */\nclass UserPayment extends \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n{\n    /**\n     * Name of current class\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxuserpayment';\n\n    /**\n     * Payment info object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Payment\n     */\n    protected $_oPayment = null;\n\n    /**\n     * current dyn values\n     *\n     * @var array\n     */\n    protected $_aDynValues = null;\n\n    /**\n     * Special getter for oxpayments__oxdesc field\n     *\n     * @param string $sName name of field\n     *\n     * @return string\n     */\n    public function __get($sName)\n    {\n        //due to compatibility with templates\n        if ($sName == 'oxpayments__oxdesc') {\n            if ($this->_oPayment === null) {\n                $this->_oPayment = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Payment::class);\n                $this->_oPayment->load($this->oxuserpayments__oxpaymentsid->value);\n            }\n\n            return $this->_oPayment->oxpayments__oxdesc;\n        }\n\n        if ($sName == 'aDynValues') {\n            if ($this->_aDynValues === null) {\n                $this->_aDynValues = $this->getDynValues();\n            }\n\n            return $this->_aDynValues;\n        }\n\n        return parent::__get($sName);\n    }\n\n    /**\n     * Class constructor. Sets payment key for encoding sensitive data and\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxuserpayments');\n    }\n\n    /**\n     * Get user payment by payment id\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser        user object\n     * @param string                                   $sPaymentType payment type\n     *\n     * @return bool\n     */\n    public function getPaymentByPaymentType($oUser = null, $sPaymentType = null)\n    {\n        $blGet = false;\n        if ($oUser && $sPaymentType != null) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $sQ = 'select oxpaymentid from oxorder where oxpaymenttype = :oxpaymenttype and\n                    oxuserid = :oxuserid order by oxorderdate desc';\n            $params = [\n                'oxpaymenttype' => $sPaymentType,\n                'oxuserid' => $oUser->getId()\n            ];\n\n            if (($sOxId = $oDb->getOne($sQ, $params))) {\n                $blGet = $this->load($sOxId);\n            }\n        }\n\n        return $blGet;\n    }\n\n    /**\n     * Returns an array of dyn payment values\n     *\n     * @return array\n     */\n    public function getDynValues()\n    {\n        if (!$this->_aDynValues) {\n            $sRawDynValue = null;\n            if (is_object($this->oxuserpayments__oxvalue)) {\n                $sRawDynValue = $this->oxuserpayments__oxvalue->getRawValue();\n            }\n\n            $this->_aDynValues = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->assignValuesFromText($sRawDynValue);\n        }\n\n        return $this->_aDynValues;\n    }\n\n    /**\n     * sets the dyn values\n     *\n     * @param array $aDynValues the array of dy values\n     */\n    public function setDynValues($aDynValues)\n    {\n        $this->_aDynValues = $aDynValues;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/VariantHandler.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductVariantMediaServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse oxRegistry;\nuse oxDb;\n\n/**\n * VariantHandler encapsulates methods dealing with multidimensional variant and variant names.\n */\nclass VariantHandler extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Variant names\n     *\n     * @var array\n     */\n    protected $_oArticles = null;\n\n    /**\n     * Multidimensional variant separator\n     *\n     * @var string\n     */\n    protected $_sMdSeparator = \" | \";\n\n    /**\n     * Multidimensional variant tree structure\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\MdVariant\n     */\n    protected $_oMdVariants = null;\n\n    /**\n     * Sets internal variant name array from article list.\n     *\n     * @param array $oArticles Variant list\n     */\n    public function init($oArticles)\n    {\n        $this->_oArticles = $oArticles;\n    }\n\n    /**\n     * Returns multidimensional variant structure\n     *\n     * @param object $oVariants all article variants\n     * @param string $sParentId parent article id\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\MdVariant\n     */\n    public function buildMdVariants($oVariants, $sParentId)\n    {\n        $oMdVariants = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\MdVariant::class);\n        $oMdVariants->setParentId($sParentId);\n        $oMdVariants->setName(\"_parent_product_\");\n        foreach ($oVariants as $sKey => $oVariant) {\n            $aNames = explode(trim($this->_sMdSeparator), $oVariant->oxarticles__oxvarselect->value);\n            foreach ($aNames as $sNameKey => $sName) {\n                $aNames[$sNameKey] = trim($sName);\n            }\n            $oMdVariants->addNames(\n                $sKey,\n                $aNames,\n                (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('bl_perfLoadPrice')) ? $oVariant->getPrice()->getPrice() : null,\n                $oVariant->getLink()\n            );\n        }\n\n        return $oMdVariants;\n    }\n\n    /**\n     * Generate variants from selection lists\n     *\n     * @param array  $aSels    ids of selection list\n     * @param object $oArticle parent article\n     */\n    public function genVariantFromSell($aSels, $oArticle)\n    {\n        $oVariants = $oArticle->getAdminVariants();\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $myUtils = \\OxidEsales\\Eshop\\Core\\Registry::getUtils();\n        $myLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n        $aConfLanguages = $myLang->getLanguageIds();\n\n        foreach ($aSels as $sSelId) {\n            $oSel = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel::class);\n            $oSel->setEnableMultilang(false);\n            $oSel->init('oxselectlist');\n            $oSel->load($sSelId);\n            $sVarNameUpdate = \"\";\n            foreach ($aConfLanguages as $sKey => $sLang) {\n                $sPrefix = $myLang->getLanguageTag($sKey);\n                $aSelValues = $myUtils->assignValuesFromText($oSel->{\"oxselectlist__oxvaldesc\" . $sPrefix}->value);\n                foreach ($aSelValues as $sI => $oValue) {\n                    $aValues[$sI][$sKey] = $oValue;\n                }\n                $aSelTitle[$sKey] = $oSel->{\"oxselectlist__oxtitle\" . $sPrefix}->value;\n                $sMdSeparator = ($oArticle->oxarticles__oxvarname->value) ? $this->_sMdSeparator : '';\n                if ($sVarNameUpdate) {\n                    $sVarNameUpdate .= \", \";\n                }\n                $sVarName = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quote($sMdSeparator . $aSelTitle[$sKey]);\n                $sVarNameUpdate .= \"oxvarname\" . $sPrefix . \" = CONCAT(oxvarname\" . $sPrefix . \", \" . $sVarName . \")\";\n            }\n            $oMDVariants = $this->assignValues($aValues, $oVariants, $oArticle, $aConfLanguages);\n            if ($myConfig->getConfigParam('blUseMultidimensionVariants')) {\n                $oAttribute = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Attribute::class);\n                $oAttribute->assignVarToAttribute($oMDVariants, $aSelTitle);\n            }\n            $this->updateArticleVarName($sVarNameUpdate, $oArticle->oxarticles__oxid->value);\n        }\n    }\n\n    /**\n     * Assigns values of selection list to variants\n     *\n     * @param array  $aValues        multilang values of selection list\n     * @param object $oVariants      variant list\n     * @param object $oArticle       parent article\n     * @param array  $aConfLanguages array of all active languages\n     *\n     * @return mixed\n     */\n    protected function assignValues($aValues, $oVariants, $oArticle, $aConfLanguages)\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $myLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n        $iCounter = 0;\n        $aVarselect = []; //multilanguage names of existing variants\n        //iterating through all select list values (eg. $oValue->name = S, M, X, XL)\n        for ($i = 0; $i < count($aValues); $i++) {\n            $oValue = $aValues[$i][0];\n            $dPriceMod = $this->getValuePrice($oValue, $oArticle->oxarticles__oxprice->value);\n            if ($oVariants->count() > 0) {\n                //if we have any existing variants then copying each variant with $oValue->name\n                foreach ($oVariants as $oSimpleVariant) {\n                    if (!$iCounter) {\n                        //we just update the first variant\n                        $oVariant = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n                        $oVariant->setEnableMultilang(false);\n                        $oVariant->load($oSimpleVariant->oxarticles__oxid->value);\n                        $oVariant->oxarticles__oxprice->setValue($oVariant->oxarticles__oxprice->value + $dPriceMod);\n                        //assign for all languages\n                        foreach ($aConfLanguages as $sKey => $sLang) {\n                            $oValue = $aValues[$i][$sKey];\n                            $sPrefix = $myLang->getLanguageTag($sKey);\n                            $aVarselect[$oSimpleVariant->oxarticles__oxid->value][$sKey] = $oVariant->{\"oxarticles__oxvarselect\" . $sPrefix}->value;\n                            $oVariant->{'oxarticles__oxvarselect' . $sPrefix}->setValue($oVariant->{\"oxarticles__oxvarselect\" . $sPrefix}->value . $this->_sMdSeparator . $oValue->name);\n                        }\n                        $oVariant->oxarticles__oxsort->setValue($oVariant->oxarticles__oxsort->value * 10);\n                        $oVariant->save();\n                        $sVarId = $oSimpleVariant->oxarticles__oxid->value;\n                    } else {\n                        //we create new variants\n                        foreach ($aVarselect[$oSimpleVariant->oxarticles__oxid->value] as $sKey => $sVarselect) {\n                            $oValue = $aValues[$i][$sKey];\n                            $sPrefix = $myLang->getLanguageTag($sKey);\n                            $aParams['oxarticles__oxvarselect' . $sPrefix] = $sVarselect . $this->_sMdSeparator . $oValue->name;\n                        }\n                        $aParams['oxarticles__oxartnum'] = $oSimpleVariant->oxarticles__oxartnum->value . \"-\" . $iCounter;\n                        $aParams['oxarticles__oxprice'] = $oSimpleVariant->oxarticles__oxprice->value + $dPriceMod;\n                        $aParams['oxarticles__oxsort'] = $oSimpleVariant->oxarticles__oxsort->value * 10 + 10 * $iCounter;\n                        $aParams['oxarticles__oxstock'] = 0;\n                        $aParams['oxarticles__oxstockflag'] = $oSimpleVariant->oxarticles__oxstockflag->value;\n                        $aParams['oxarticles__oxisconfigurable'] = $oSimpleVariant->oxarticles__oxisconfigurable->value;\n                        $sVarId = $this->createNewVariant($aParams, $oArticle->oxarticles__oxid->value);\n                        if ($myConfig->getConfigParam('blUseMultidimensionVariants')) {\n                            $oAttrList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Attribute::class);\n                            $aIds = $oAttrList->getAttributeAssigns($oSimpleVariant->oxarticles__oxid->value);\n                            $aMDVariants[\"mdvar_\" . $sVarId] = $aIds;\n                        }\n                    }\n                    if ($myConfig->getConfigParam('blUseMultidimensionVariants')) {\n                        $aMDVariants[$sVarId] = $aValues[$i];\n                    }\n                }\n                $iCounter++;\n            } else {\n                //in case we don't have any variants then we just create variant(s) with $oValue->name\n                $iCounter++;\n                foreach ($aConfLanguages as $sKey => $sLang) {\n                    $oValue = $aValues[$i][$sKey];\n                    $sPrefix = $myLang->getLanguageTag($sKey);\n                    $aParams['oxarticles__oxvarselect' . $sPrefix] = $oValue->name;\n                }\n                $aParams['oxarticles__oxartnum'] = $oArticle->oxarticles__oxartnum->value . \"-\" . $iCounter;\n                $aParams['oxarticles__oxprice'] = $oArticle->oxarticles__oxprice->value + $dPriceMod;\n                $aParams['oxarticles__oxsort'] = $iCounter * 100; // reduction\n                $aParams['oxarticles__oxstock'] = 0;\n                $aParams['oxarticles__oxstockflag'] = $oArticle->oxarticles__oxstockflag->value;\n                $aParams['oxarticles__oxisconfigurable'] = $oArticle->oxarticles__oxisconfigurable->value;\n                $sVarId = $this->createNewVariant($aParams, $oArticle->oxarticles__oxid->value);\n                if ($myConfig->getConfigParam('blUseMultidimensionVariants')) {\n                    $aMDVariants[$sVarId] = $aValues[$i];\n                }\n            }\n        }\n\n        return $aMDVariants;\n    }\n\n    /**\n     * Returns article price\n     *\n     * @param object $oValue       selection list value\n     * @param double $dParentPrice parent article price\n     *\n     * @return double\n     */\n    protected function getValuePrice($oValue, $dParentPrice)\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $dPriceMod = 0;\n        if ($myConfig->getConfigParam('bl_perfLoadSelectLists') && $myConfig->getConfigParam('bl_perfUseSelectlistPrice')) {\n            if ($oValue->priceUnit == 'abs') {\n                $dPriceMod = $oValue->price;\n            } elseif ($oValue->priceUnit == '%') {\n                $dPriceModPerc = abs($oValue->price) * $dParentPrice / 100.0;\n                if (($oValue->price) >= 0.0) {\n                    $dPriceMod = $dPriceModPerc;\n                } else {\n                    $dPriceMod = -$dPriceModPerc;\n                }\n            }\n        }\n\n        return $dPriceMod;\n    }\n\n    /**\n     * Creates new article variant.\n     *\n     * @param array  $aParams   assigned parameters\n     * @param string $sParentId parent article id\n     *\n     * @return null\n     */\n    protected function createNewVariant($aParams = null, $sParentId = null)\n    {\n        // checkbox handling\n        $aParams['oxarticles__oxactive'] = 0;\n\n        // shopid\n        $sShopID = \\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable(\"actshop\");\n        $aParams['oxarticles__oxshopid'] = $sShopID;\n\n        // varianthandling\n        $aParams['oxarticles__oxparentid'] = $sParentId;\n\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $oArticle->setEnableMultilang(false);\n        $oArticle->assign($aParams);\n        $oArticle->save();\n\n        $variantId = $oArticle->getId();\n\n        $this->copyMediaFromParent($sParentId, $variantId);\n\n        return $variantId;\n    }\n\n    private function copyMediaFromParent(string $parentId, string $variantId): void\n    {\n        ContainerFacade::get(ProductVariantMediaServiceInterface::class)->assignFromParentToVariant(\n            Id::fromString($parentId),\n            Id::fromString($variantId)\n        );\n    }\n\n    /**\n     * Inserts article variant name for all languages\n     *\n     * @param string $sUpdate query for update variant name\n     * @param string $sArtId  parent article id\n     */\n    protected function updateArticleVarName($sUpdate, $sArtId)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sUpdate = \"update oxarticles set \" . $sUpdate . \" where oxid = :oxid\";\n        $oDb->execute($sUpdate, ['oxid' => $sArtId]);\n    }\n\n    /**\n     * Check if variant is multidimensional\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle Article object\n     *\n     * @return bool\n     */\n    public function isMdVariant($oArticle)\n    {\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blUseMultidimensionVariants')) {\n            if (is_object($oArticle) && isset($oArticle->oxarticles__oxvarselect)) {\n                if (strpos($oArticle->oxarticles__oxvarselect->value, trim($this->_sMdSeparator)) !== false) {\n                    return true;\n                }\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Creates array/matrix with variant selections\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\ArticleList $oVariantList  variant list\n     * @param int                                             $iVarSelCnt    possible variant selection count\n     * @param array                                           $aFilter       active filter array\n     * @param string                                          $sActVariantId active variant id\n     *\n     * @return array\n     */\n    protected function fillVariantSelections($oVariantList, $iVarSelCnt, &$aFilter, $sActVariantId)\n    {\n        $aSelections = [];\n\n        // filling selections\n        foreach ($oVariantList as $oVariant) {\n            $aNames = $this->getSelections($oVariant->oxarticles__oxvarselect->getRawValue());\n            $blActive = ($sActVariantId === $oVariant->getId()) ? true : false;\n            for ($i = 0; $i < $iVarSelCnt; $i++) {\n                $sName = isset($aNames[$i]) ? trim($aNames[$i]) : false;\n                if ($sName !== '' && $sName !== false) {\n                    $sHash = md5($sName);\n\n                    // filling up filter\n                    if ($blActive) {\n                        $aFilter[$i] = $sHash;\n                    }\n\n                    $aSelections[$oVariant->getId()][$i] = ['name' => $sName, 'disabled' => null, 'active' => false, 'hash' => $sHash];\n                }\n            }\n        }\n\n        return $aSelections;\n    }\n\n    /**\n     * Cleans up user given filter. If filter was empty - returns false\n     *\n     * @param array $aFilter user given filter\n     *\n     * @return array|bool\n     */\n    protected function cleanFilter($aFilter)\n    {\n        $aCleanFilter = false;\n        if (is_array($aFilter) && count($aFilter)) {\n            foreach ($aFilter as $iKey => $sFilter) {\n                if ($sFilter) {\n                    $aCleanFilter[$iKey] = $sFilter;\n                }\n            }\n        }\n\n        return $aCleanFilter;\n    }\n\n    /**\n     * Applies filter on variant selection array\n     *\n     * @param array $aSelections selections\n     * @param array $aFilter     filter\n     *\n     * @return array\n     */\n    protected function applyVariantSelectionsFilter($aSelections, $aFilter)\n    {\n        $iMaxActiveCount = 0;\n        $sMostSuitableVariantId = null;\n        $blPerfectFit = false;\n        // applying filters, disabling/activating items\n        if (($aFilter = $this->cleanFilter($aFilter))) {\n            $aFilterKeys = array_keys($aFilter);\n            $iFilterKeysCount = count($aFilter);\n            foreach ($aSelections as $sVariantId => &$aLineSelections) {\n                $iActive = 0;\n                foreach ($aFilter as $iKey => $sVal) {\n                    if (strcmp($aLineSelections[$iKey]['hash'], $sVal) === 0) {\n                        $aLineSelections[$iKey]['active'] = true;\n                        $iActive++;\n                    } else {\n                        foreach ($aLineSelections as $iOtherKey => &$aLineOtherVariant) {\n                            if ($iKey != $iOtherKey) {\n                                $aLineOtherVariant['disabled'] = true;\n                            }\n                        }\n                    }\n                }\n                foreach ($aLineSelections as $iOtherKey => &$aLineOtherVariant) {\n                    if (!in_array($iOtherKey, $aFilterKeys)) {\n                        $aLineOtherVariant['disabled'] = !($iFilterKeysCount == $iActive);\n                    }\n                }\n\n                $blFitsAll = $iActive && (count($aLineSelections) == $iActive) && ($iFilterKeysCount == $iActive);\n                if (($iActive > $iMaxActiveCount) || (!$blPerfectFit && $blFitsAll)) {\n                    $blPerfectFit = $blFitsAll;\n                    $sMostSuitableVariantId = $sVariantId;\n                    $iMaxActiveCount = $iActive;\n                }\n\n                unset($aLineSelections);\n            }\n        }\n\n        return [$aSelections, $sMostSuitableVariantId, $blPerfectFit];\n    }\n\n    /**\n     * Builds variant selections list - array containing oxVariantSelectList\n     *\n     * @param array $aVarSelects variant selection titles\n     * @param array $aSelections variant selections\n     *\n     * @return array\n     */\n    protected function buildVariantSelectionsList($aVarSelects, $aSelections)\n    {\n        // creating selection lists\n        foreach ($aVarSelects as $iKey => $sLabel) {\n            $aVariantSelections[$iKey] = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\VariantSelectList::class, $sLabel, $iKey);\n        }\n\n        // building variant selections\n        foreach ($aSelections as $aLineSelections) {\n            foreach ($aLineSelections as $oPos => $aLine) {\n                $aVariantSelections[$oPos]->addVariant($aLine['name'], $aLine['hash'], $aLine['disabled'], $aLine['active']);\n            }\n        }\n\n        return $aVariantSelections;\n    }\n\n    /**\n     * In case multidimentional variants ON explodes title by _sMdSeparator\n     * and returns array, else - returns array containing title\n     *\n     * @param string $sTitle title to process\n     *\n     * @return array\n     */\n    protected function getSelections($sTitle)\n    {\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blUseMultidimensionVariants')) {\n            $aSelections = explode($this->_sMdSeparator, $sTitle);\n        } else {\n            $aSelections = [$sTitle];\n        }\n\n        return $aSelections;\n    }\n\n    /**\n     * Builds variant selection list\n     *\n     * @param string                                          $sVarName      product (parent product) oxvarname value\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\ArticleList $oVariantList  variant list\n     * @param array                                           $aFilter       variant filter\n     * @param string                                          $sActVariantId active variant id\n     * @param int                                             $iLimit        limit variant lists count (if non zero, return limited number of multidimensional variant selections)\n     *\n     * @return array|false\n     */\n    public function buildVariantSelections($sVarName, $oVariantList, $aFilter, $sActVariantId, $iLimit = 0)\n    {\n        // assigning variants\n        $aVarSelects = $this->getSelections($sVarName);\n\n        if ($iLimit) {\n            $aVarSelects = array_slice($aVarSelects, 0, $iLimit);\n        }\n        if (($iVarSelCnt = count($aVarSelects))) {\n            // filling selections\n            $aRawVariantSelections = $this->fillVariantSelections($oVariantList, $iVarSelCnt, $aFilter, $sActVariantId);\n\n            // applying filters, disabling/activating items\n            list($aRawVariantSelections, $sActVariantId, $blPerfectFit) = $this->applyVariantSelectionsFilter($aRawVariantSelections, $aFilter);\n            // creating selection lists\n            $aVariantSelections = $this->buildVariantSelectionsList($aVarSelects, $aRawVariantSelections);\n\n            $oCurrentVariant = null;\n            if ($sActVariantId) {\n                $oCurrentVariant = $oVariantList[$sActVariantId];\n            }\n\n            return [\n                'selections'     => $aVariantSelections,\n                'rawselections'  => $aRawVariantSelections,\n                'oActiveVariant' => $oCurrentVariant,\n                'blPerfectFit'   => $blPerfectFit\n            ];\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/VariantSelectList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\Str;\n\n/**\n * Variant selection lists manager class\n */\nclass VariantSelectList implements \\OxidEsales\\Eshop\\Core\\Contract\\ISelectList\n{\n    /**\n     * Variant selection list label\n     *\n     * @var string\n     */\n    protected $_sLabel = null;\n\n    /**\n     * Selection list index\n     *\n     * @var int\n     */\n    protected $_iIndex = 0;\n\n    /**\n     * List with selections\n     *\n     * @var array\n     */\n    protected $_aList = [];\n\n    /**\n     * Active variant selection object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Selection\n     */\n    protected $_oActiveSelection = null;\n\n    /**\n     * Builds current selection list\n     *\n     * @param string $sLabel list label\n     * @param int    $iIndex list index\n     */\n    public function __construct($sLabel, $iIndex)\n    {\n        $this->_sLabel = trim($sLabel);\n        $this->_iIndex = $iIndex;\n    }\n\n    /**\n     * Returns variant selection list label\n     *\n     * @return string\n     */\n    public function getLabel()\n    {\n        return Str::getStr()->htmlspecialchars($this->_sLabel);\n    }\n\n    /**\n     * Adds given variant info to current variant selection list\n     *\n     * @param string $sName      selection name\n     * @param string $sValue     selection value\n     * @param string $blDisabled selection state - disabled/enabled\n     * @param string $blActive   selection state - active/inactive\n     */\n    public function addVariant($sName, $sValue, $blDisabled, $blActive)\n    {\n        $sName = trim($sName);\n        //#6053 Allow \"0\" as a valid value.\n        if (!empty($sName) || $sName === '0') {\n            $sKey = $sValue;\n\n            // creating new\n            if (!isset($this->_aList[$sKey])) {\n                $this->_aList[$sKey] = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Selection::class, $sName, $sValue, $blDisabled, $blActive);\n            } else {\n                // overriding states\n                if ($this->_aList[$sKey]->isDisabled() && !$blDisabled) {\n                    $this->_aList[$sKey]->setDisabled($blDisabled);\n                }\n\n                if (!$this->_aList[$sKey]->isActive() && $blActive) {\n                    $this->_aList[$sKey]->setActiveState($blActive);\n                }\n            }\n\n            // storing active selection\n            if ($this->_aList[$sKey]->isActive()) {\n                $this->_oActiveSelection = $this->_aList[$sKey];\n            }\n        }\n    }\n\n    /**\n     * Returns active selection object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Selection\n     */\n    public function getActiveSelection()\n    {\n        return $this->_oActiveSelection;\n    }\n\n    /**\n     * Returns array of oxSelection's\n     *\n     * @return array\n     */\n    public function getSelections()\n    {\n        return $this->_aList;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/VatSelector.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxDb;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse oxObjectException;\n\nuse function array_key_exists;\n\n/**\n * Class, responsible for retrieving correct vat for users and articles\n */\nclass VatSelector extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * State is VAT calculation for category is set\n     *\n     * @var bool\n     */\n    protected $_blCatVatSet = null;\n\n    /**\n     * keeps loaded user Vats for later reusage\n     *\n     * @var array\n     */\n    protected static $_aUserVatCache = [];\n\n    /**\n     * get VAT for user, can NOT be null\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser        given  user object\n     * @param bool                                     $blCacheReset reset cache\n     *\n     * @throws oxObjectException if wrong country\n     * @return double|false\n     */\n    public function getUserVat(\\OxidEsales\\Eshop\\Application\\Model\\User $oUser, $blCacheReset = false)\n    {\n        $cacheId = sprintf(\n            '%s_%s',\n            $oUser->getId(),\n            $oUser->getFieldData('oxcountryid') ?? ''\n        );\n        if (\n            !$blCacheReset &&\n            array_key_exists($cacheId, self::$_aUserVatCache) &&\n            self::$_aUserVatCache[$cacheId] !== null\n        ) {\n            return self::$_aUserVatCache[$cacheId];\n        }\n\n        $ret = false;\n\n        $sCountryId = $this->getVatCountry($oUser);\n\n        if ($sCountryId) {\n            $oCountry = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Country::class);\n            if (!$oCountry->load($sCountryId)) {\n                throw oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ObjectException::class);\n            }\n            if ($oCountry->isForeignCountry()) {\n                $ret = $this->getForeignCountryUserVat($oUser, $oCountry);\n            }\n        }\n\n        self::$_aUserVatCache[$cacheId] = $ret;\n\n        return $ret;\n    }\n\n    /**\n     * get vat for user of a foreign country\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User    $oUser    given user object\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Country $oCountry given country object\n     *\n     * @return mixed\n     */\n    protected function getForeignCountryUserVat(\\OxidEsales\\Eshop\\Application\\Model\\User $oUser, \\OxidEsales\\Eshop\\Application\\Model\\Country $oCountry)\n    {\n        if ($oCountry->isInEU()) {\n            if ($oUser->oxuser__oxustid->value) {\n                return 0;\n            }\n\n            return false;\n        }\n\n        return 0;\n    }\n\n    /**\n     * return Vat value for category type assignment only\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle given article\n     *\n     * @return float|false\n     */\n    protected function getVatForArticleCategory(\\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sCatT = $tableViewNameGenerator->getViewName('oxcategories');\n\n        if ($this->_blCatVatSet === null) {\n            $sSelect = \"SELECT oxid FROM $sCatT WHERE oxvat IS NOT NULL LIMIT 1\";\n\n            //no category specific vats in shop?\n            //then for performance reasons we just return false\n            $this->_blCatVatSet = (bool) $oDb->getOne($sSelect);\n        }\n\n        if (!$this->_blCatVatSet) {\n            return false;\n        }\n\n        $sO2C = $tableViewNameGenerator->getViewName('oxobject2category');\n        $sSql = \"SELECT c.oxvat\n                 FROM $sCatT AS c, $sO2C AS o2c\n                 WHERE c.oxid=o2c.oxcatnid AND\n                       o2c.oxobjectid = :oxobjectid AND\n                       c.oxvat IS NOT NULL\n                 ORDER BY o2c.oxtime \";\n\n        $fVat = $oDb->getOne($sSql, [\n            'oxobjectid' => $oArticle->getId()\n        ]);\n        if ($fVat !== false && $fVat !== null) {\n            return $fVat;\n        }\n\n        return false;\n    }\n\n    /**\n     * get VAT for given article, can NOT be null\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle given article\n     *\n     * @return double\n     */\n    public function getArticleVat(\\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle)\n    {\n        startProfile(\"_assignPriceInternal\");\n        // article has its own VAT ?\n\n        if (($dArticleVat = $oArticle->getCustomVAT()) !== null) {\n            stopProfile(\"_assignPriceInternal\");\n\n            return $dArticleVat;\n        }\n        if (($dArticleVat = $this->getVatForArticleCategory($oArticle)) !== false) {\n            stopProfile(\"_assignPriceInternal\");\n\n            return $dArticleVat;\n        }\n\n        stopProfile(\"_assignPriceInternal\");\n\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('dDefaultVAT');\n    }\n\n    /**\n     * Currently returns vats percent that can be applied for basket\n     * item ( executes \\OxidEsales\\Eshop\\Application\\Model\\VatSelector::getArticleVat()). Can be used to override\n     * basket price calculation behaviour (\\OxidEsales\\Eshop\\Application\\Model\\Article::getBasketPrice())\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle article object\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket  $oBasket  oxbasket object\n     *\n     * @return double\n     */\n    public function getBasketItemVat(\\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle, $oBasket)\n    {\n        return $this->getArticleVat($oArticle);\n    }\n\n    /**\n     * get article user vat\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle article object\n     *\n     * @return double|false\n     */\n    public function getArticleUserVat(\\OxidEsales\\Eshop\\Application\\Model\\Article $oArticle)\n    {\n        if (($oUser = $oArticle->getArticleUser())) {\n            return $this->getUserVat($oUser);\n        }\n\n        return false;\n    }\n\n\n    /**\n     * Returns country id which VAT should be applied to.\n     * Depending on configuration option either user billing country or shipping country (if available) is returned.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $oUser user object\n     *\n     * @return string\n     */\n    protected function getVatCountry(\\OxidEsales\\Eshop\\Application\\Model\\User $oUser)\n    {\n        $blUseShippingCountry = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam(\"blShippingCountryVat\");\n\n        if ($blUseShippingCountry) {\n            $aAddresses = $oUser->getUserAddresses($oUser->getId());\n            $sSelectedAddress = $oUser->getSelectedAddressId();\n\n            if (isset($aAddresses[$sSelectedAddress])) {\n                return $aAddresses[$sSelectedAddress]->oxaddress__oxcountryid->value;\n            }\n        }\n\n        return $oUser->getFieldData('oxcountryid');\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Vendor.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxRegistry;\nuse oxField;\n\n/**\n * Vendor manager\n */\nclass Vendor extends \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel implements \\OxidEsales\\Eshop\\Core\\Contract\\IUrl\n{\n    protected static $_aRootVendor = [];\n\n    /**\n     * @var string Name of current class\n     */\n    protected $_sClassName = 'oxvendor';\n\n    /**\n     * Marker to load vendor article count info\n     *\n     * @var bool\n     */\n    protected $_blShowArticleCnt = false;\n\n    /**\n     * Vendor article count (default is -1, which means not calculated)\n     *\n     * @var int\n     */\n    protected $_iNrOfArticles = -1;\n\n    /**\n     * Marks that current object is managed by SEO\n     *\n     * @var bool\n     */\n    protected $_blIsSeoObject = true;\n\n    /**\n     * Visibility of a vendor\n     *\n     * @var int\n     */\n    protected $_blIsVisible;\n\n    /**\n     * has visible endors state of a category\n     *\n     * @var int\n     */\n    protected $_blHasVisibleSubCats;\n\n    /**\n     * Seo article urls for languages\n     *\n     * @var array\n     */\n    protected $_aSeoUrls = [];\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxI18n()).\n     */\n    public function __construct()\n    {\n        $this->setShowArticleCnt(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('bl_perfShowActionCatArticleCnt'));\n        parent::__construct();\n        $this->init('oxvendor');\n    }\n\n    /**\n     * Marker to load vendor article count info setter\n     *\n     * @param bool $blShowArticleCount Marker to load vendor article count\n     */\n    public function setShowArticleCnt($blShowArticleCount = false)\n    {\n        $this->_blShowArticleCnt = $blShowArticleCount;\n    }\n\n    /**\n     * Assigns to $this object some base parameters/values.\n     *\n     * @param array $dbRecord parameters/values\n     */\n    public function assign($dbRecord)\n    {\n        parent::assign($dbRecord);\n\n        // vendor article count is stored in cache\n        if ($this->_blShowArticleCnt && !$this->isAdmin()) {\n            $this->_iNrOfArticles = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsCount()->getVendorArticleCount($this->getId());\n        }\n\n        $this->oxvendor__oxnrofarticles = new \\OxidEsales\\Eshop\\Core\\Field($this->_iNrOfArticles, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n    }\n\n    /**\n     * Loads object data from DB (object data ID is passed to method). Returns\n     * true on success.\n     *\n     * @param string $sOxid object id\n     *\n     * @return bool\n     */\n    public function load($sOxid)\n    {\n        if ($sOxid == 'root') {\n            return $this->setRootObjectData();\n        }\n\n        return parent::load($sOxid);\n    }\n\n    /**\n     * Sets root vendor data. Returns true\n     *\n     * @return bool\n     */\n    protected function setRootObjectData()\n    {\n        $this->setId('root');\n        $this->oxvendor__oxicon = new \\OxidEsales\\Eshop\\Core\\Field('', \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        $this->oxvendor__oxtitle = new \\OxidEsales\\Eshop\\Core\\Field(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('BY_VENDOR', $this->getLanguage(), false), \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n        $this->oxvendor__oxshortdesc = new \\OxidEsales\\Eshop\\Core\\Field('', \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n\n        return true;\n    }\n\n    /**\n     * Returns raw content seo url\n     *\n     * @param int $iLang language id\n     * @param int $iPage page number [optional]\n     *\n     * @return string\n     */\n    public function getBaseSeoLink($iLang, $iPage = 0)\n    {\n        $oEncoder = \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderVendor::class);\n        if (!$iPage) {\n            return $oEncoder->getVendorUrl($this, $iLang);\n        }\n\n        return $oEncoder->getVendorPageUrl($this, $iPage, $iLang);\n    }\n\n    /**\n     * Returns vendor link Url\n     *\n     * @param int $iLang language id [optional]\n     *\n     * @return string\n     */\n    public function getLink($iLang = null)\n    {\n        if (!\\OxidEsales\\Eshop\\Core\\Registry::getUtils()->seoIsActive()) {\n            return $this->getStdLink($iLang);\n        }\n\n        if ($iLang === null) {\n            $iLang = $this->getLanguage();\n        }\n\n        if (!isset($this->_aSeoUrls[$iLang])) {\n            $this->_aSeoUrls[$iLang] = $this->getBaseSeoLink($iLang);\n        }\n\n        return $this->_aSeoUrls[$iLang];\n    }\n\n    /**\n     * Returns base dynamic url: shopurl/index.php?cl=details\n     *\n     * @param int  $iLang   language id\n     * @param bool $blAddId add current object id to url or not\n     * @param bool $blFull  return full including domain name [optional]\n     *\n     * @return string\n     */\n    public function getBaseStdLink($iLang, $blAddId = true, $blFull = true)\n    {\n        $sUrl = '';\n        if ($blFull) {\n            //always returns shop url, not admin\n            $sUrl = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopUrl($iLang, false);\n        }\n\n        return $sUrl . \"index.php?cl=vendorlist\" . ($blAddId ? \"&amp;cnid=v_\" . $this->getId() : \"\");\n    }\n\n    /**\n     * Returns standard URL to vendor\n     *\n     * @param int   $iLang   language\n     * @param array $aParams additional params to use [optional]\n     *\n     * @return string\n     */\n    public function getStdLink($iLang = null, $aParams = [])\n    {\n        if ($iLang === null) {\n            $iLang = $this->getLanguage();\n        }\n\n        return \\OxidEsales\\Eshop\\Core\\Registry::getUtilsUrl()->processUrl($this->getBaseStdLink($iLang), true, $aParams, $iLang);\n    }\n\n    /**\n     * returns number or articles of this vendor\n     *\n     * @return integer\n     */\n    public function getNrOfArticles()\n    {\n        if (!$this->_blShowArticleCnt || $this->isAdmin()) {\n            return -1;\n        }\n\n        return $this->_iNrOfArticles;\n    }\n\n    /**\n     * returns the sub category array\n     */\n    public function getSubCats()\n    {\n    }\n\n    /**\n     * returns the visibility of a vendor\n     *\n     * @return bool\n     */\n    public function getIsVisible()\n    {\n        return $this->_blIsVisible;\n    }\n\n    /**\n     * sets the visibilty of a category\n     *\n     * @param bool $blVisible vendors visibility status setter\n     */\n    public function setIsVisible($blVisible)\n    {\n        $this->_blIsVisible = $blVisible;\n    }\n\n    /**\n     * returns if a vendor has visible sub categories\n     *\n     * @return bool\n     */\n    public function getHasVisibleSubCats()\n    {\n        if (!isset($this->_blHasVisibleSubCats)) {\n            $this->_blHasVisibleSubCats = false;\n        }\n\n        return $this->_blHasVisibleSubCats;\n    }\n\n    /**\n     * sets the state of has visible sub vendors\n     *\n     * @param bool $blHasVisibleSubcats marker if vendor has visible subcategories\n     */\n    public function setHasVisibleSubCats($blHasVisibleSubcats)\n    {\n        $this->_blHasVisibleSubCats = $blHasVisibleSubcats;\n    }\n\n    /**\n     * Empty method, called in templates when vendor is used in same code like category\n     */\n    public function getContentCats()\n    {\n    }\n\n    /**\n     * Delete this object from the database, returns true on success.\n     *\n     * @param string $oxid Object ID(default null)\n     *\n     * @return bool\n     */\n    public function delete($oxid = null)\n    {\n        if ($oxid) {\n            $this->load($oxid);\n        } else {\n            $oxid = $this->getId();\n        }\n\n        if (parent::delete($oxid)) {\n            \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderVendor::class)->onDeleteVendor($this);\n\n            return true;\n        }\n\n        return false;\n    }\n\n\n    /**\n     * Returns article picture\n     *\n     * @return string\n     */\n    public function getIconUrl()\n    {\n        if (($sIcon = $this->oxvendor__oxicon->value)) {\n            $oConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n            $sSize = $oConfig->getConfigParam('sManufacturerIconsize');\n            if (!isset($sSize)) {\n                $sSize = $oConfig->getConfigParam('sIconsize');\n            }\n\n            return \\OxidEsales\\Eshop\\Core\\Registry::getPictureHandler()->getPicUrl(\"vendor/icon/\", $sIcon, $sSize);\n        }\n    }\n\n    /**\n     * Returns category thumbnail picture url if exist, false - if not\n     *\n     * @return mixed\n     */\n    public function getThumbUrl()\n    {\n        return false;\n    }\n\n    /**\n     * Returns vendor title\n     *\n     * @return string\n     */\n    public function getTitle()\n    {\n        return $this->oxvendor__oxtitle->value;\n    }\n\n    /**\n     * Returns short description\n     *\n     * @return string\n     */\n    public function getShortDescription()\n    {\n        return $this->oxvendor__oxshortdesc->value;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/VendorList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse oxRegistry;\nuse oxField;\n\n/**\n * Vendor list manager.\n * Collects list of vendors according to collection rules (activ, etc.).\n */\nclass VendorList extends \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n{\n    /**\n     * Vendor root.\n     *\n     * @var \\stdClass\n     */\n    protected $_oRoot = null;\n\n    /**\n     * Vendor tree path.\n     *\n     * @var array\n     */\n    protected $_aPath = [];\n\n    /**\n     * To show vendor article count or not\n     *\n     * @var bool\n     */\n    protected $_blShowVendorArticleCnt = false;\n\n    /**\n     * Active vendor object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Vendor\n     */\n    protected $_oClickedVendor = null;\n\n    /**\n     * Calls parent constructor and defines if Article vendor count is shown\n     */\n    public function __construct()\n    {\n        $this->setShowVendorArticleCnt(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('bl_perfShowActionCatArticleCnt'));\n        parent::__construct('oxvendor');\n    }\n\n    /**\n     * Enables/disables vendor article count calculation\n     *\n     * @param bool $blShowVendorArticleCnt to show article count or not\n     */\n    public function setShowVendorArticleCnt($blShowVendorArticleCnt = false)\n    {\n        $this->_blShowVendorArticleCnt = $blShowVendorArticleCnt;\n    }\n\n    /**\n     * Loads simple vendor list\n     */\n    public function loadVendorList()\n    {\n        $oBaseObject = $this->getBaseObject();\n        $sFieldList = $oBaseObject->getSelectFields();\n        $sViewName = $oBaseObject->getViewName();\n        $this->getBaseObject()->setShowArticleCnt($this->_blShowVendorArticleCnt);\n\n        $sWhere = '';\n        if (!$this->isAdmin()) {\n            $sWhere = $oBaseObject->getSqlActiveSnippet();\n            $sWhere = $sWhere ? \" where $sWhere and \" : ' where ';\n            $sWhere .= \"{$sViewName}.oxtitle != '' \";\n        }\n\n        $sSelect = \"select {$sFieldList} from {$sViewName} {$sWhere} order by {$sViewName}.oxtitle\";\n        $this->selectString($sSelect);\n    }\n\n    /**\n     * Creates fake root for vendor tree, and ads category list fileds for each vendor item\n     *\n     * @param string $sLinkTarget  Name of class, responsible for category rendering\n     * @param string $sActCat      Active category\n     * @param string $sShopHomeUrl base shop url ($myConfig->getShopHomeUrl())\n     */\n    public function buildVendorTree($sLinkTarget, $sActCat, $sShopHomeUrl)\n    {\n        $sActCat = str_replace('v_', '', $sActCat);\n\n        //Load vendor list\n        $this->loadVendorList();\n\n\n        //Create fake vendor root category\n        $this->_oRoot = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Vendor::class);\n        $this->_oRoot->load('root');\n\n        //category fields\n        $this->addCategoryFields($this->_oRoot);\n        $this->_aPath[] = $this->_oRoot;\n\n        foreach ($this as $sVndId => $oVendor) {\n            // storing active vendor object\n            if ($sVndId == $sActCat) {\n                $this->setClickVendor($oVendor);\n            }\n\n            $this->addCategoryFields($oVendor);\n            if ($sActCat == $oVendor->oxvendor__oxid->value) {\n                $this->_aPath[] = $oVendor;\n            }\n        }\n\n        $this->seoSetVendorData();\n    }\n\n    /**\n     * Root vendor list node (which usually is a manually prefilled object) getter\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Vendor\n     */\n    public function getRootCat()\n    {\n        return $this->_oRoot;\n    }\n\n    /**\n     * Returns vendor path array\n     *\n     * @return array\n     */\n    public function getPath()\n    {\n        return $this->_aPath;\n    }\n\n    /**\n     * Adds category specific fields to vendor object\n     *\n     * @param object $oVendor vendor object\n     */\n    protected function addCategoryFields($oVendor)\n    {\n        $oVendor->oxcategories__oxid = new \\OxidEsales\\Eshop\\Core\\Field(\"v_\" . $oVendor->oxvendor__oxid->value);\n        $oVendor->oxcategories__oxicon = $oVendor->oxvendor__oxicon;\n        $oVendor->oxcategories__oxtitle = $oVendor->oxvendor__oxtitle;\n        $oVendor->oxcategories__oxdesc = $oVendor->oxvendor__oxshortdesc;\n\n        $oVendor->setIsVisible(true);\n        $oVendor->setHasVisibleSubCats(false);\n    }\n\n    /**\n     * Sets active (open) vendor object\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Vendor $oVendor active vendor\n     */\n    public function setClickVendor($oVendor)\n    {\n        $this->_oClickedVendor = $oVendor;\n    }\n\n    /**\n     * returns active (open) vendor object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Vendor\n     */\n    public function getClickVendor()\n    {\n        return $this->_oClickedVendor;\n    }\n\n    /**\n     * Processes vendor category URLs\n     */\n    protected function seoSetVendorData()\n    {\n        // only when SEO id on and in front end\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getUtils()->seoIsActive() && !$this->isAdmin()) {\n            $oEncoder = \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Application\\Model\\SeoEncoderVendor::class);\n\n            // preparing root vendor category\n            if ($this->_oRoot) {\n                $oEncoder->getVendorUrl($this->_oRoot);\n            }\n\n            // encoding vendor category\n            foreach ($this as $sVndId => $value) {\n                $oEncoder->getVendorUrl($this->_aArray[$sVndId]);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Voucher.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse stdClass;\n\n/**\n * Voucher manager.\n * Performs deletion, generating, assigning to group and other voucher\n * managing functions.\n */\nclass Voucher extends \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n{\n    protected $_oSerie = null;\n\n    /**\n     * Vouchers does not need shop id check as this causes problems with\n     * inherited vouchers. Voucher validity check is made by oxVoucher::getVoucherByNr()\n     *\n     * @var bool\n     */\n    protected $_blDisableShopCheck = true;\n\n    /**\n     * @var string Name of current class\n     */\n    protected $_sClassName = 'oxvoucher';\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxvouchers');\n    }\n\n    /**\n     * Gets voucher from db by given number.\n     *\n     * @param string $sVoucherNr         Voucher number\n     * @param array  $aVouchers          Array of available vouchers (default array())\n     * @param bool   $blCheckavalability check if voucher is still reserver od not\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\VoucherException\n     *\n     * @return mixed\n     */\n    public function getVoucherByNr($sVoucherNr, $aVouchers = [], $blCheckavalability = false)\n    {\n        $oRet = null;\n        if (!empty($sVoucherNr)) {\n            $sViewName = $this->getViewName();\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $sSeriesViewName = $tableViewNameGenerator->getViewName('oxvoucherseries');\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster();\n\n            $sQ = \"select {$sViewName}.* from {$sViewName}, {$sSeriesViewName} where\n                        {$sSeriesViewName}.oxid = {$sViewName}.oxvoucherserieid and\n                        {$sViewName}.oxvouchernr = \" . $oDb->quote($sVoucherNr) . \" and \";\n\n            if (is_array($aVouchers)) {\n                foreach ($aVouchers as $sVoucherId => $sSkipVoucherNr) {\n                    $sQ .= \"{$sViewName}.oxid != \" . $oDb->quote($sVoucherId) . \" and \";\n                }\n            }\n            $sQ .= \"( {$sViewName}.oxorderid is NULL || {$sViewName}.oxorderid = '' ) \";\n            $sQ .= \" and ( {$sViewName}.oxdateused is NULL || {$sViewName}.oxdateused = 0 ) \";\n\n            //voucher timeout for 3 hours\n            if ($blCheckavalability) {\n                $iTime = time() - $this->getVoucherTimeout();\n                $sQ .= \" and {$sViewName}.oxreserved < '{$iTime}' order by {$sViewName}.oxreserved asc \";\n            }\n\n            $sQ .= \" limit 1 FOR UPDATE\";\n\n            if (!($oRet = $this->assignRecord($sQ))) {\n                $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\VoucherException::class);\n                $oEx->setMessage('ERROR_MESSAGE_VOUCHER_NOVOUCHER');\n                $oEx->setVoucherNr($sVoucherNr);\n                throw $oEx;\n            }\n        }\n\n        return $oRet;\n    }\n\n    /**\n     * marks voucher as used\n     *\n     * @param string $sOrderId  order id\n     * @param string $sUserId   user id\n     * @param double $dDiscount used discount\n     */\n    public function markAsUsed($sOrderId, $sUserId, $dDiscount)\n    {\n        //saving oxreserved field\n        if ($this->oxvouchers__oxid->value) {\n            $this->oxvouchers__oxorderid->setValue($sOrderId);\n            $this->oxvouchers__oxuserid->setValue($sUserId);\n            $this->oxvouchers__oxdiscount->setValue($dDiscount);\n            $this->oxvouchers__oxdateused->setValue(date(\"Y-m-d\", \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime()));\n            $this->save();\n        }\n    }\n\n    /**\n     * mark voucher as reserved\n     */\n    public function markAsReserved()\n    {\n        //saving oxreserved field\n        $sVoucherID = $this->oxvouchers__oxid->value;\n\n        if ($sVoucherID) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster();\n            $sQ = \"update oxvouchers set oxreserved = :oxreserved where oxid = :oxid\";\n            $oDb->execute($sQ, [\n                'oxreserved' => time(),\n                'oxid' => $sVoucherID\n            ]);\n        }\n    }\n\n    /**\n     * un mark as reserved\n     */\n    public function unMarkAsReserved()\n    {\n        //saving oxreserved field\n        $sVoucherID = $this->oxvouchers__oxid->value;\n\n        if ($sVoucherID) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $sQ = \"update oxvouchers set oxreserved = 0 where oxid = :oxid\";\n            $oDb->execute($sQ, ['oxid' => $sVoucherID]);\n        }\n    }\n\n    /**\n     * Returns the discount value used.\n     *\n     * @param double $dPrice price to calculate discount on it\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\VoucherException\n     *\n     * @return float\n     */\n    public function getDiscountValue($dPrice)\n    {\n        if ($this->isProductVoucher()) {\n            return $this->getProductDiscountValue((float) $dPrice);\n        } elseif ($this->isCategoryVoucher()) {\n            return $this->getCategoryDiscountValue((float) $dPrice);\n        } else {\n            return $this->getGenericDiscountValue((float) $dPrice);\n        }\n    }\n\n    // Checking General Availability\n    /**\n     * Checks availability without user logged in. Returns array with errors.\n     *\n     * @param array  $aVouchers array of vouchers\n     * @param double $dPrice    current sum (price)\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\VoucherException\n     *\n     * @return array\n     */\n    public function checkVoucherAvailability($aVouchers, $dPrice)\n    {\n        $this->isAvailableWithSameSeries($aVouchers);\n        $this->isAvailableWithOtherSeries($aVouchers);\n        $this->isValidDate();\n        $this->isAvailablePrice($dPrice);\n        $this->isNotReserved();\n        $this->isAvailable();\n\n        // returning true - no exception was thrown\n        return true;\n    }\n\n    /**\n     * Performs basket level voucher availability check (no need to check if voucher\n     * is reserved or so).\n     *\n     * @param array  $aVouchers array of vouchers\n     * @param double $dPrice    current sum (price)\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\VoucherException\n     *\n     * @return array\n     */\n    public function checkBasketVoucherAvailability($aVouchers, $dPrice)\n    {\n        $this->isAvailableWithSameSeries($aVouchers);\n        $this->isAvailableWithOtherSeries($aVouchers);\n        $this->isValidDate();\n        $this->isAvailablePrice($dPrice);\n        $this->isAvailable();\n\n        // returning true - no exception was thrown\n        return true;\n    }\n\n    protected function isAvailable()\n    {\n        if (empty($this->oxvouchers__oxorderid->value)) {\n            return true;\n        }\n\n        $exception = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\VoucherException::class);\n        $exception->setMessage('ERROR_MESSAGE_VOUCHER_NOVOUCHER');\n        $exception->setVoucherNr($this->oxvouchers__oxvouchernr->value);\n        throw $exception;\n    }\n\n    /**\n     * Checks availability about price. Returns error array.\n     *\n     * @param double $dPrice base article price\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\VoucherException\n     *\n     * @return array\n     */\n    protected function isAvailablePrice($dPrice)\n    {\n        $oSeries = $this->getSerie();\n        $oCur = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActShopCurrencyObject();\n        if ($oSeries->oxvoucherseries__oxminimumvalue->value && $dPrice < ($oSeries->oxvoucherseries__oxminimumvalue->value * $oCur->rate)) {\n            $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\VoucherException::class);\n            $oEx->setMessage('ERROR_MESSAGE_VOUCHER_INCORRECTPRICE');\n            $oEx->setVoucherNr($this->oxvouchers__oxvouchernr->value);\n            throw $oEx;\n        }\n\n        return true;\n    }\n\n    /**\n     * Checks if calculation with vouchers of the same series possible. Returns\n     * true on success.\n     *\n     * @param array $aVouchers array of vouchers\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\VoucherException\n     *\n     * @return bool\n     */\n    protected function isAvailableWithSameSeries($aVouchers)\n    {\n        if (is_array($aVouchers)) {\n            $sId = $this->getId();\n            if (isset($aVouchers[$sId])) {\n                unset($aVouchers[$sId]);\n            }\n            $oSeries = $this->getSerie();\n            if (!$oSeries->oxvoucherseries__oxallowsameseries->value) {\n                foreach ($aVouchers as $voucherId => $voucherNr) {\n                    $oVoucher = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Voucher::class);\n                    $oVoucher->load($voucherId);\n                    if ($this->oxvouchers__oxvoucherserieid->value == $oVoucher->oxvouchers__oxvoucherserieid->value) {\n                        $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\VoucherException::class);\n                        $oEx->setMessage('ERROR_MESSAGE_VOUCHER_NOTALLOWEDSAMESERIES');\n                        $oEx->setVoucherNr($this->oxvouchers__oxvouchernr->value);\n                        throw $oEx;\n                    }\n                }\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * Checks if calculation with vouchers from the other series possible.\n     * Returns true on success.\n     *\n     * @param array $aVouchers array of vouchers\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\VoucherException\n     *\n     * @return bool\n     */\n    protected function isAvailableWithOtherSeries($aVouchers)\n    {\n        if (is_array($aVouchers) && count($aVouchers)) {\n            $oSeries = $this->getSerie();\n            $sIds = implode(',', \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quoteArray(array_keys($aVouchers)));\n            $blAvailable = true;\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            if (!$oSeries->oxvoucherseries__oxallowotherseries->value) {\n                // just search for vouchers with different series\n                $sSql = \"select 1 from oxvouchers where oxvouchers.oxid in ($sIds) and \";\n                $sSql .= \"oxvouchers.oxvoucherserieid != :notoxvoucherserieid\";\n                $blAvailable &= !$oDb->getOne($sSql, [\n                    'notoxvoucherserieid' => $this->oxvouchers__oxvoucherserieid->value\n                ]);\n            } else {\n                // search for vouchers with different series and those vouchers do not allow other series\n                $sSql = \"select 1 from oxvouchers left join oxvoucherseries on oxvouchers.oxvoucherserieid=oxvoucherseries.oxid \";\n                $sSql .= \"where oxvouchers.oxid in ($sIds) and oxvouchers.oxvoucherserieid != :notoxvoucherserieid \";\n                $sSql .= \"and not oxvoucherseries.oxallowotherseries\";\n                $blAvailable &= !$oDb->getOne($sSql, [\n                    'notoxvoucherserieid' => $this->oxvouchers__oxvoucherserieid->value\n                ]);\n            }\n            if (!$blAvailable) {\n                $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\VoucherException::class);\n                $oEx->setMessage('ERROR_MESSAGE_VOUCHER_NOTALLOWEDOTHERSERIES');\n                $oEx->setVoucherNr($this->oxvouchers__oxvouchernr->value);\n                throw $oEx;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * Checks if voucher is in valid time period. Returns true on success.\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\VoucherException\n     *\n     * @return bool\n     */\n    protected function isValidDate()\n    {\n        $oSeries = $this->getSerie();\n        $iTime = time();\n\n        // If date is not set will add day before and day after to check if voucher valid today.\n        $iTomorrow = mktime(0, 0, 0, date(\"m\"), date(\"d\") + 1, date(\"Y\"));\n        $iYesterday = mktime(0, 0, 0, date(\"m\"), date(\"d\") - 1, date(\"Y\"));\n\n        // Checks if beginning date is set, if not set $iFrom to yesterday so it will be valid.\n        $iFrom = ((int) $oSeries->oxvoucherseries__oxbegindate->value) ?\n            strtotime($oSeries->oxvoucherseries__oxbegindate->value) : $iYesterday;\n\n        // Checks if end date is set, if no set $iTo to tomorrow so it will be valid.\n        $iTo = ((int) $oSeries->oxvoucherseries__oxenddate->value) ?\n            strtotime($oSeries->oxvoucherseries__oxenddate->value) : $iTomorrow;\n\n        if ($iFrom < $iTime && $iTo > $iTime) {\n            return true;\n        }\n\n        $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\VoucherException::class);\n        $oEx->setMessage('MESSAGE_COUPON_EXPIRED');\n        if ($iFrom > $iTime && $iTo > $iTime) {\n            $oEx->setMessage('ERROR_MESSAGE_VOUCHER_NOVOUCHER');\n        }\n        $oEx->setVoucherNr($this->oxvouchers__oxvouchernr->value);\n        throw $oEx;\n    }\n\n    /**\n     * Checks if voucher is not yet reserved before.\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\VoucherException\n     *\n     * @return bool\n     */\n    protected function isNotReserved()\n    {\n        if ($this->oxvouchers__oxreserved->value < time() - $this->getVoucherTimeout()) {\n            return true;\n        }\n\n        $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\VoucherException::class);\n        $oEx->setMessage('EXCEPTION_VOUCHER_ISRESERVED');\n        $oEx->setVoucherNr($this->oxvouchers__oxvouchernr->value);\n        throw $oEx;\n    }\n\n    // Checking User Availability\n    /**\n     * Checks availability for the given user. Returns array with errors.\n     *\n     * @param object $oUser user object\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\VoucherException\n     *\n     * @return array\n     */\n    public function checkUserAvailability($oUser)\n    {\n        $this->isAvailableInOtherOrder($oUser);\n        $this->isValidUserGroup($oUser);\n\n        // returning true if no exception was thrown\n        return true;\n    }\n\n    /**\n     * Checks if user already used vouchers from this series and can he use it again.\n     *\n     * @param object $oUser user object\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\VoucherException\n     *\n     * @return boolean\n     */\n    protected function isAvailableInOtherOrder($oUser)\n    {\n        $oSeries = $this->getSerie();\n        if (!$oSeries->oxvoucherseries__oxallowuseanother->value) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $sSelect = 'select count(*) from ' . $this->getViewName() . ' \n                where oxuserid = :oxuserid and ';\n            $sSelect .= 'oxvoucherserieid = :oxvoucherserieid and ';\n            $sSelect .= '((oxorderid is not NULL and oxorderid != \"\") or (oxdateused is not NULL and oxdateused != 0)) ';\n\n            $params = [\n                'oxuserid' => $oUser->oxuser__oxid->value,\n                'oxvoucherserieid' => $this->oxvouchers__oxvoucherserieid->value\n            ];\n\n            if ($oDb->getOne($sSelect, $params)) {\n                $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\VoucherException::class);\n                $oEx->setMessage('ERROR_MESSAGE_VOUCHER_NOTALLOWEDSAMESERIES');\n                $oEx->setVoucherNr($this->oxvouchers__oxvouchernr->value);\n                throw $oEx;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * Checks if user belongs to the same group as the voucher. Returns true on sucess.\n     *\n     * @param object $oUser user object\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\VoucherException\n     *\n     * @return bool\n     */\n    protected function isValidUserGroup($oUser)\n    {\n        $oVoucherSeries = $this->getSerie();\n        $oUserGroups = $oVoucherSeries->setUserGroups();\n\n        if (!$oUserGroups->count()) {\n            return true;\n        }\n\n        if ($oUser) {\n            foreach ($oUserGroups as $oGroup) {\n                if ($oUser->inGroup($oGroup->getId())) {\n                    return true;\n                }\n            }\n        }\n\n        $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\VoucherException::class);\n        $oEx->setMessage('ERROR_MESSAGE_VOUCHER_NOTVALIDUSERGROUP');\n        $oEx->setVoucherNr($this->oxvouchers__oxvouchernr->value);\n        throw $oEx;\n    }\n\n    /**\n     * Returns compact voucher object which is used in oxBasket\n     *\n     * @return stdClass\n     */\n    public function getSimpleVoucher()\n    {\n        $oVoucher = new stdClass();\n        $oVoucher->sVoucherId = $this->getId();\n        $oVoucher->sVoucherNr = null;\n        if ($this->oxvouchers__oxvouchernr) {\n            $oVoucher->sVoucherNr = $this->oxvouchers__oxvouchernr->value;\n        }\n\n        // R. set in oxBasket : $oVoucher->fVoucherdiscount = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->formatCurrency( $this->oxvouchers__oxdiscount->value );\n\n        return $oVoucher;\n    }\n\n    /**\n     * create oxVoucherSerie object of this voucher\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\ObjectException\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\VoucherSerie\n     */\n    public function getSerie()\n    {\n        if ($this->_oSerie !== null) {\n            return $this->_oSerie;\n        }\n        $oSerie = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\VoucherSerie::class);\n        if (!$oSerie->load($this->oxvouchers__oxvoucherserieid->value)) {\n            throw oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ObjectException::class);\n        }\n        $this->_oSerie = $oSerie;\n\n        return $oSerie;\n    }\n\n    /**\n     * Returns true if voucher is product specific, otherwise false\n     *\n     * @return boolean\n     */\n    protected function isProductVoucher()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $oSeries = $this->getSerie();\n        $sSelect = \"select 1 from oxobject2discount \n            where oxdiscountid = :oxdiscountid and oxtype = :oxtype\";\n        $blOk = (bool) $oDb->getOne($sSelect, [\n            'oxdiscountid' => $oSeries->getId(),\n            'oxtype' => 'oxarticles'\n        ]);\n\n        return $blOk;\n    }\n\n    /**\n     * Returns true if voucher is category specific, otherwise false\n     *\n     * @return boolean\n     */\n    protected function isCategoryVoucher()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $oSeries = $this->getSerie();\n        $sSelect = \"select 1 from oxobject2discount \n            where oxdiscountid = :oxdiscountid and oxtype = :oxtype\";\n        $blOk = (bool) $oDb->getOne($sSelect, [\n            'oxdiscountid' => $oSeries->getId(),\n            'oxtype' => 'oxcategories'\n        ]);\n\n        return $blOk;\n    }\n\n    /**\n     * Returns the discount object created from voucher serie data\n     *\n     * @return object\n     */\n    protected function getSerieDiscount()\n    {\n        $oSeries = $this->getSerie();\n        $oDiscount = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Discount::class);\n\n        $oDiscount->setId($oSeries->getId());\n        $oDiscount->oxdiscount__oxshopid = new \\OxidEsales\\Eshop\\Core\\Field($oSeries->oxvoucherseries__oxshopid->value);\n        $oDiscount->oxdiscount__oxactive = new \\OxidEsales\\Eshop\\Core\\Field(true);\n        $oDiscount->oxdiscount__oxactivefrom = new \\OxidEsales\\Eshop\\Core\\Field($oSeries->oxvoucherseries__oxbegindate->value);\n        $oDiscount->oxdiscount__oxactiveto = new \\OxidEsales\\Eshop\\Core\\Field($oSeries->oxvoucherseries__oxenddate->value);\n        $oDiscount->oxdiscount__oxtitle = new \\OxidEsales\\Eshop\\Core\\Field($oSeries->oxvoucherseries__oxserienr->value);\n        $oDiscount->oxdiscount__oxamount = new \\OxidEsales\\Eshop\\Core\\Field(1);\n        $oDiscount->oxdiscount__oxamountto = new \\OxidEsales\\Eshop\\Core\\Field(MAX_64BIT_INTEGER);\n        $oDiscount->oxdiscount__oxprice = new \\OxidEsales\\Eshop\\Core\\Field(0);\n        $oDiscount->oxdiscount__oxpriceto = new \\OxidEsales\\Eshop\\Core\\Field(MAX_64BIT_INTEGER);\n        $oDiscount->oxdiscount__oxaddsumtype = new \\OxidEsales\\Eshop\\Core\\Field($oSeries->oxvoucherseries__oxdiscounttype->value == 'percent' ? '%' : 'abs');\n        $oDiscount->oxdiscount__oxaddsum = new \\OxidEsales\\Eshop\\Core\\Field($oSeries->oxvoucherseries__oxdiscount->value);\n        $oDiscount->oxdiscount__oxitmartid = new \\OxidEsales\\Eshop\\Core\\Field();\n        $oDiscount->oxdiscount__oxitmamount = new \\OxidEsales\\Eshop\\Core\\Field();\n        $oDiscount->oxdiscount__oxitmmultiple = new \\OxidEsales\\Eshop\\Core\\Field();\n\n        return $oDiscount;\n    }\n\n    /**\n     * Returns basket item information array from session or order.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Discount $oDiscount discount object\n     *\n     * @return array\n     */\n    protected function getBasketItems($oDiscount = null)\n    {\n        $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n        if ($this->oxvouchers__oxorderid->value) {\n            return $this->getOrderBasketItems($oDiscount);\n        } elseif ($session->getBasket()) {\n            return $this->getSessionBasketItems($oDiscount);\n        } else {\n            return [];\n        }\n    }\n\n    /**\n     * Returns basket item information (id,amount,price) array takig item list from order.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Discount $oDiscount discount object\n     *\n     * @return array\n     */\n    protected function getOrderBasketItems($oDiscount = null)\n    {\n        if (is_null($oDiscount)) {\n            $oDiscount = $this->getSerieDiscount();\n        }\n\n        $oOrder = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Order::class);\n        $oOrder->load($this->oxvouchers__oxorderid->value);\n\n        $aItems = [];\n        $iCount = 0;\n\n        foreach ($oOrder->getOrderArticles(true) as $oOrderArticle) {\n            if (!$oOrderArticle->skipDiscounts() && $oDiscount->isForBasketItem($oOrderArticle)) {\n                $aItems[$iCount] = [\n                    'oxid'     => $oOrderArticle->getProductId(),\n                    'price'    => $oOrderArticle->oxorderarticles__oxbprice->value,\n                    'discount' => $oDiscount->getAbsValue($oOrderArticle->oxorderarticles__oxbprice->value),\n                    'amount'   => $oOrderArticle->oxorderarticles__oxamount->value,\n                ];\n                $iCount++;\n            }\n        }\n\n        return $aItems;\n    }\n\n    /**\n     * Returns basket item information (id,amount,price) array taking item list from session.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Discount $oDiscount discount object\n     *\n     * @return array\n     */\n    protected function getSessionBasketItems($oDiscount = null)\n    {\n        if (is_null($oDiscount)) {\n            $oDiscount = $this->getSerieDiscount();\n        }\n\n        $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n        $oBasket = $session->getBasket();\n        $aItems = [];\n        $iCount = 0;\n\n        foreach ($oBasket->getContents() as $oBasketItem) {\n            if (!$oBasketItem->isDiscountArticle() && ($oArticle = $oBasketItem->getArticle()) && !$oArticle->skipDiscounts() && $oDiscount->isForBasketItem($oArticle)) {\n                $aItems[$iCount] = [\n                    'oxid'     => $oArticle->getId(),\n                    'price'    => $oArticle->getBasketPrice($oBasketItem->getAmount(), $oBasketItem->getSelList(), $oBasket)->getPrice(),\n                    'discount' => $oDiscount->getAbsValue($oArticle->getBasketPrice($oBasketItem->getAmount(), $oBasketItem->getSelList(), $oBasket)->getPrice()),\n                    'amount'   => $oBasketItem->getAmount(),\n                ];\n\n                $iCount++;\n            }\n        }\n\n        return $aItems;\n    }\n\n    /**\n     * Returns the discount value used.\n     *\n     * @param double $dPrice price to calculate discount on it\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\VoucherException\n     *\n     * @return double\n     */\n    protected function getGenericDiscountValue($dPrice)\n    {\n        $oSeries = $this->getSerie();\n        if ($oSeries->oxvoucherseries__oxdiscounttype->value == 'absolute') {\n            $oCur = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActShopCurrencyObject();\n            $dDiscount = $oSeries->oxvoucherseries__oxdiscount->value * $oCur->rate;\n        } else {\n            $dDiscount = $oSeries->oxvoucherseries__oxdiscount->value / 100 * $dPrice;\n        }\n\n        if ($dDiscount > $dPrice) {\n            $dDiscount = $dPrice;\n        }\n\n        return $dDiscount;\n    }\n\n\n    /**\n     * Return discount value\n     *\n     * @return double\n     */\n    public function getDiscount()\n    {\n        $oSeries = $this->getSerie();\n\n        return $oSeries->oxvoucherseries__oxdiscount->value;\n    }\n\n    /**\n     * Return discount type\n     *\n     * @return string\n     */\n    public function getDiscountType()\n    {\n        $oSeries = $this->getSerie();\n\n        return $oSeries->oxvoucherseries__oxdiscounttype->value;\n    }\n\n    /**\n     * Returns the discount value used, if voucher is aplied only for specific products.\n     *\n     * @param double $dPrice price to calculate discount on it\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\VoucherException\n     *\n     * @return double\n     */\n    protected function getProductDiscountValue($dPrice)\n    {\n        $oDiscount = $this->getSerieDiscount();\n        $aBasketItems = $this->getBasketItems($oDiscount);\n\n        // Basket Item Count and isAdmin check (unble to access property $oOrder->getOrderBasket()->_blSkipVouchersAvailabilityChecking)\n        if (!count($aBasketItems) && !$this->isAdmin()) {\n            $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\VoucherException::class);\n            $oEx->setMessage('ERROR_MESSAGE_VOUCHER_NOVOUCHER');\n            $oEx->setVoucherNr($this->oxvouchers__oxvouchernr->value);\n            throw $oEx;\n        }\n\n        $oSeries = $this->getSerie();\n\n        $oVoucherPrice = oxNew(\\OxidEsales\\Eshop\\Core\\Price::class);\n        $oDiscountPrice = oxNew(\\OxidEsales\\Eshop\\Core\\Price::class);\n        $oProductPrice = oxNew(\\OxidEsales\\Eshop\\Core\\Price::class);\n        $oProductTotal = oxNew(\\OxidEsales\\Eshop\\Core\\Price::class);\n\n        // Is the voucher discount applied to at least one basket item\n        $blDiscountApplied = false;\n\n        foreach ($aBasketItems as $aBasketItem) {\n            // If discount was already applied for the voucher to at least one basket items, then break\n            if ($blDiscountApplied and !empty($oSeries->oxvoucherseries__oxcalculateonce->value)) {\n                break;\n            }\n\n            $oDiscountPrice->setPrice($aBasketItem['discount']);\n            $oProductPrice->setPrice($aBasketItem['price']);\n\n            // Individual voucher is not multiplied by article amount\n            if (!$oSeries->oxvoucherseries__oxcalculateonce->value) {\n                $oDiscountPrice->multiply($aBasketItem['amount']);\n                $oProductPrice->multiply($aBasketItem['amount']);\n            }\n\n            $oVoucherPrice->add($oDiscountPrice->getBruttoPrice());\n            $oProductTotal->add($oProductPrice->getBruttoPrice());\n\n            if (!empty($aBasketItem['discount'])) {\n                $blDiscountApplied = true;\n            }\n        }\n\n        $dVoucher = $oVoucherPrice->getBruttoPrice();\n        $dProduct = $oProductTotal->getBruttoPrice();\n\n        if ($dVoucher > $dProduct) {\n            return $dProduct;\n        }\n\n        return $dVoucher;\n    }\n\n    /**\n     * Returns the discount value used, if voucher is applied only for specific categories.\n     *\n     * @param double $dPrice price to calculate discount on it\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\VoucherException\n     *\n     * @return double\n     */\n    protected function getCategoryDiscountValue($dPrice)\n    {\n        $oDiscount = $this->getSerieDiscount();\n        $aBasketItems = $this->getBasketItems($oDiscount);\n\n        // Basket Item Count and isAdmin check (unable to access property $oOrder->getOrderBasket()->_blSkipVouchersAvailabilityChecking)\n        if (!count($aBasketItems) && !$this->isAdmin()) {\n            $oEx = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\VoucherException::class);\n            $oEx->setMessage('ERROR_MESSAGE_VOUCHER_NOVOUCHER');\n            $oEx->setVoucherNr($this->oxvouchers__oxvouchernr->value);\n            throw $oEx;\n        }\n\n        $oProductPrice = oxNew(\\OxidEsales\\Eshop\\Core\\Price::class);\n        $oProductTotal = oxNew(\\OxidEsales\\Eshop\\Core\\Price::class);\n\n        foreach ($aBasketItems as $aBasketItem) {\n            $oProductPrice->setPrice($aBasketItem['price']);\n            $oProductPrice->multiply($aBasketItem['amount']);\n            $oProductTotal->add($oProductPrice->getBruttoPrice());\n        }\n\n        $dProduct = $oProductTotal->getBruttoPrice();\n        $dVoucher = $oDiscount->getAbsValue($dProduct);\n\n        return ($dVoucher > $dProduct) ? $dProduct : $dVoucher;\n    }\n\n    /**\n     * Extra getter to guarantee compatibility with templates\n     *\n     * @param string $sName name of variable to get\n     *\n     * @return string\n     */\n    public function __get($sName)\n    {\n        switch ($sName) {\n            // simple voucher mapping\n            case 'sVoucherId':\n                return $this->getId();\n                break;\n            case 'sVoucherNr':\n                return $this->oxvouchers__oxvouchernr;\n                break;\n            case 'fVoucherdiscount':\n                return $this->oxvouchers__oxdiscount;\n                break;\n        }\n        return parent::__get($sName);\n    }\n\n    /**\n     * Returns a configured value for voucher timeouts or a default\n     * of 3 hours if not configured\n     *\n     * @return integer Seconds a voucher can stay in status reserved\n     */\n    protected function getVoucherTimeout()\n    {\n        $iVoucherTimeout = (int) \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iVoucherTimeout') ?:\n            3 * 3600;\n\n        return $iVoucherTimeout;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/VoucherList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\n/**\n * Voucher list manager.\n */\nclass VoucherList extends \\OxidEsales\\Eshop\\Core\\Model\\ListModel\n{\n    /**\n     * Calls parent constructor\n     */\n    public function __construct()\n    {\n        parent::__construct('oxvoucher');\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/VoucherSerie.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse oxRegistry;\nuse oxDb;\n\n/**\n * Voucher serie manager.\n * Manages list of available Vouchers (fetches, deletes, etc.).\n */\nclass VoucherSerie extends \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n{\n    /**\n     * User groups array (default null).\n     *\n     * @var object\n     */\n    protected $_oGroups = null;\n\n    /**\n     * @var string name of current class\n     */\n    protected $_sClassName = 'oxvoucherserie';\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n        $this->init('oxvoucherseries');\n    }\n\n    /**\n     * Override delete function so we can delete user group and article or category relations first.\n     *\n     * @param string $sOxId object ID (default null)\n     *\n     * @return null\n     */\n    public function delete($sOxId = null)\n    {\n        if (!$sOxId) {\n            $sOxId = $this->getId();\n        }\n\n        $this->unsetDiscountRelations();\n        $this->unsetUserGroups();\n        $this->deleteVoucherList();\n\n        return parent::delete($sOxId);\n    }\n\n    /**\n     * Collects and returns user group list.\n     *\n     * @return object\n     */\n    public function setUserGroups()\n    {\n        if ($this->_oGroups === null) {\n            $this->_oGroups = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n            $this->_oGroups->init('oxgroups');\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $sViewName = $tableViewNameGenerator->getViewName(\"oxgroups\");\n            $sSelect = \"select gr.* from {$sViewName} as gr, oxobject2group as o2g where\n                         o2g.oxobjectid = :oxobjectid and gr.oxid = o2g.oxgroupsid \";\n            $this->_oGroups->selectString($sSelect, [\n                'oxobjectid' => $this->getId()\n            ]);\n        }\n\n        return $this->_oGroups;\n    }\n\n    /**\n     * Removes user groups relations.\n     */\n    public function unsetUserGroups()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sDelete = 'delete from oxobject2group where oxobjectid = :oxobjectid';\n        $oDb->execute($sDelete, [\n            'oxobjectid' => $this->getId()\n        ]);\n    }\n\n    /**\n     * Removes product or dategory relations.\n     */\n    public function unsetDiscountRelations()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sDelete = 'delete from oxobject2discount where oxobject2discount.oxdiscountid = :oxdiscountid';\n        $oDb->execute($sDelete, [\n            'oxdiscountid' => $this->getId()\n        ]);\n    }\n\n    /**\n     * Returns array of a vouchers assigned to this serie.\n     *\n     * @return array\n     */\n    public function getVoucherList()\n    {\n        $oVoucherList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\VoucherList::class);\n        $sSelect = 'select * from oxvouchers \n            where oxvoucherserieid = :oxvoucherserieid';\n        $oVoucherList->selectString($sSelect, [\n            'oxvoucherserieid' => $this->getId()\n        ]);\n\n        return $oVoucherList;\n    }\n\n    /**\n     * Deletes assigned voucher list.\n     */\n    public function deleteVoucherList()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sDelete = 'delete from oxvouchers where oxvoucherserieid = :oxvoucherserieid';\n        $oDb->execute($sDelete, [\n            'oxvoucherserieid' => $this->getId()\n        ]);\n    }\n\n    /**\n     * Returns array of vouchers counts.\n     *\n     * @return array\n     */\n    public function countVouchers()\n    {\n        $aStatus = [];\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sQuery = 'select count(*) as total from oxvouchers \n            where oxvoucherserieid = :oxvoucherserieid';\n        $aStatus['total'] = $oDb->getOne($sQuery, [\n            'oxvoucherserieid' => $this->getId()\n        ]);\n\n        $sQuery = 'select count(*) as used from oxvouchers \n            where oxvoucherserieid = :oxvoucherserieid \n                and ((oxorderid is not NULL and oxorderid != \"\") or (oxdateused is not NULL and oxdateused != 0))';\n        $aStatus['used'] = $oDb->getOne($sQuery, [\n            'oxvoucherserieid' => $this->getId()\n        ]);\n\n        $aStatus['available'] = $aStatus['total'] - $aStatus['used'];\n\n        return $aStatus;\n    }\n\n    /**\n     * Get voucher status base on given date (if nothing was passed, current datetime will be used as a measure).\n     *\n     * @param string|null $sNow Date\n     *\n     * @return int\n     */\n    public function getVoucherStatusByDatetime($sNow = null)\n    {\n        //return content\n        $iActive = 1;\n        $iInactive = 0;\n\n        $oUtilsDate = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate();\n        //current object datetime\n        $sBeginDate = $this->oxvoucherseries__oxbegindate->value;\n        $sEndDate = $this->oxvoucherseries__oxenddate->value;\n\n        //If nothing pass, use current server time\n        if ($sNow == null) {\n            $sNow = date('Y-m-d H:i:s', $oUtilsDate->getTime());\n        }\n\n        //Check for active status.\n        if (\n            ($sBeginDate == '0000-00-00 00:00:00' && $sEndDate == '0000-00-00 00:00:00') || //If both dates are empty => treat it as always active\n            ($sBeginDate == '0000-00-00 00:00:00' && $sNow <= $sEndDate) || //check for end date without start date\n            ($sBeginDate <= $sNow && $sEndDate == '0000-00-00 00:00:00') || //check for start date without end date\n            ($sBeginDate <= $sNow && $sNow <= $sEndDate)\n        ) { //check for both start date and end date.\n            return $iActive;\n        }\n\n        //If active status code was reached, return as inactive\n        return $iInactive;\n    }\n}\n"
  },
  {
    "path": "source/Application/Model/Wrapping.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse oxRegistry;\nuse oxDb;\n\n/**\n * Wrapping manager.\n * Performs Wrapping data/objects loading, deleting.\n */\nclass Wrapping extends \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel\n{\n    /**\n     * Class name\n     *\n     * @var string name of current class\n     */\n    protected $_sClassName = 'oxwrapping';\n\n    /**\n     * Wrapping oxprice object.\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Price\n     */\n    protected $_oPrice = null;\n\n    /**\n     * Wrapping Vat\n     *\n     * @var double\n     */\n    protected $_dVat = 0;\n\n    /**\n     * Wrapping VAT config\n     *\n     * @var bool\n     */\n    protected $_blWrappingVatOnTop = false;\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()), loads\n     * base shop objects.\n     */\n    public function __construct()\n    {\n        $oConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $this->setWrappingVat($oConfig->getConfigParam('dDefaultVAT'));\n        $this->setWrappingVatOnTop($oConfig->getConfigParam('blWrappingVatOnTop'));\n        parent::__construct();\n        $this->init('oxwrapping');\n    }\n\n    /**\n     * Wrapping Vat setter\n     *\n     * @param double $dVat vat\n     */\n    public function setWrappingVat($dVat)\n    {\n        $this->_dVat = $dVat;\n    }\n\n    /**\n     * Wrapping VAT config setter\n     *\n     * @param bool $blOnTop wrapping vat config\n     */\n    public function setWrappingVatOnTop($blOnTop)\n    {\n        $this->_blWrappingVatOnTop = $blOnTop;\n    }\n\n    /**\n     * Returns oxprice object for wrapping\n     *\n     * @param int $dAmount article amount\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function getWrappingPrice($dAmount = 1)\n    {\n        if ($this->_oPrice === null) {\n            $this->_oPrice = oxNew(\\OxidEsales\\Eshop\\Core\\Price::class);\n\n            if (!$this->_blWrappingVatOnTop) {\n                $this->_oPrice->setBruttoPriceMode();\n            } else {\n                $this->_oPrice->setNettoPriceMode();\n            }\n\n            $oCur = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActShopCurrencyObject();\n            $this->_oPrice->setPrice($this->oxwrapping__oxprice->value * $oCur->rate, $this->_dVat);\n            $this->_oPrice->multiply($dAmount);\n        }\n\n        return $this->_oPrice;\n    }\n\n    /**\n     * Loads wrapping list for specific wrap type\n     *\n     * @param string $sWrapType wrap type\n     *\n     * @return array $oEntries wrapping list\n     */\n    public function getWrappingList($sWrapType)\n    {\n        // load wrapping\n        $oEntries = oxNew(\\OxidEsales\\Eshop\\Core\\Model\\ListModel::class);\n        $oEntries->init('oxwrapping');\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sWrappingViewName = $tableViewNameGenerator->getViewName('oxwrapping');\n        $sSelect = \"select * from $sWrappingViewName \n            where $sWrappingViewName.oxactive = :oxactive\n              and $sWrappingViewName.oxtype = :oxtype\";\n        $oEntries->selectString($sSelect, [\n            'oxactive' => '1',\n            'oxtype' => $sWrapType\n        ]);\n\n        return $oEntries;\n    }\n\n    /**\n     * Counts amount of wrapping/card options\n     *\n     * @param string $sWrapType type - wrapping paper (WRAP) or card (CARD)\n     *\n     * @return int\n     */\n    public function getWrappingCount($sWrapType)\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sWrappingViewName = $tableViewNameGenerator->getViewName('oxwrapping');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sQ = \"select count(*) from $sWrappingViewName \n            where $sWrappingViewName.oxactive = :oxactive \n              and $sWrappingViewName.oxtype = :oxtype\";\n\n        return (int) $oDb->getOne($sQ, [\n            'oxactive' => '1',\n            'oxtype' => $sWrapType\n        ]);\n    }\n\n    /**\n     * Checks and return true if price view mode is netto\n     *\n     * @return bool\n     */\n    protected function isPriceViewModeNetto()\n    {\n        $blResult = (bool) \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blShowNetPrice');\n        $oUser = $this->getUser();\n        if ($oUser) {\n            $blResult = $oUser->isPriceViewModeNetto();\n        }\n\n        return $blResult;\n    }\n\n    /**\n     * Returns formatted wrapping price\n     *\n     * @deprecated since v5.1 (2013-10-13); use oxPrice template engine plugin for formatting in templates\n     *\n     * @return string\n     */\n    public function getFPrice()\n    {\n        $dPrice = $this->getPrice();\n\n        return \\OxidEsales\\Eshop\\Core\\Registry::getLang()->formatCurrency($dPrice, \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActShopCurrencyObject());\n    }\n\n    /**\n     * Gets price.\n     *\n     * @return double\n     */\n    public function getPrice()\n    {\n        if ($this->isPriceViewModeNetto()) {\n            $dPrice = $this->getWrappingPrice()->getNettoPrice();\n        } else {\n            $dPrice = $this->getWrappingPrice()->getBruttoPrice();\n        }\n\n        return $dPrice;\n    }\n\n    /**\n     * Returns returns dyn image dir (not ssl)\n     *\n     * @return string\n     */\n    public function getNoSslDynImageDir()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getPictureUrl(null, false, false, null, $this->oxwrapping__oxshopid->value);\n    }\n\n    /**\n     * Returns returns dyn image dir\n     *\n     * @return string\n     */\n    public function getPictureUrl()\n    {\n        if ($this->oxwrapping__oxpic->value) {\n            return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getPictureUrl(\"master/wrapping/\" . $this->oxwrapping__oxpic->value, false, \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->isSsl(), null, $this->oxwrapping__oxshopid->value);\n        }\n    }\n}\n"
  },
  {
    "path": "source/Application/translations/de/cust_lang.php.dist",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\n$sLangName  = \"Deutsch\";\n// -------------------------------\n// RESOURCE IDENTIFIER = STRING\n// -------------------------------\n$aLang = [\n\n'charset'                                   => 'UTF-8',\n\n];\n\n/*\n[{ oxmultilang ident=\"GENERAL_YOUWANTTODELETE\" }]\n*/\n"
  },
  {
    "path": "source/Application/translations/de/lang.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\n$sLangName  = \"Deutsch\";\n\n// -------------------------------\n// RESOURCE IDENTIFIER = STRING\n// -------------------------------\n$aLang = [\n'charset'                                                     => 'UTF-8',\n'fullDateFormat'                                              => 'd.m.Y H:i:s',\n'simpleDateFormat'                                            => 'd.m.Y',\n'grid'                                                        => 'Galerie',\n'infogrid'                                                    => 'Galerie zweispaltig',\n'line'                                                        => 'Liste',\n'LIST_DISPLAY_TYPE'                                           => 'Ansicht',\n\n'COLON'                                                       => ':',\n'ELLIPSIS'                                                    => '...',\n'ACCESSORIES'                                                 => 'Zubehör',\n'ACCOUNT'                                                     => 'Konto',\n'ACCOUNT_INFORMATION'                                         => 'Kontoinformationen',\n'ADD'                                                         => 'hinzufügen',\n'ADDITIONAL_INFO'                                             => 'Zus. Info',\n'ADDRESS'                                                     => 'Adresse',\n'ADDRESSES'                                                   => 'Adressen',\n'ADD_THIS_PAGE_TO'                                            => 'Eintragen bei',\n'ADD_TO_CART'                                                 => 'In den Warenkorb',\n'ADD_TO_GIFT_REGISTRY'                                        => 'Auf den Wunschzettel',\n'ADD_TO_LISTMANIA_LIST'                                       => 'In die Lieblingsliste',\n'ADD_TO_WISH_LIST'                                            => 'Auf den Merkzettel',\n'ADD_WRAPPING'                                                => 'Als Geschenk verpacken',\n'ADD_YOUR_COMMENTS'                                           => 'Ihre Nachricht',\n'ALL'                                                         => 'Alle',\n'ALL_LISTMANIA'                                               => 'Alle Lieblingslisten',\n'ALREADY_CUSTOMER'                                            => 'Ich bin bereits Kunde',\n'APPLY'                                                       => 'Übernehmen',\n'ARTNUM'                                                      => 'Artikelnummer',\n'ATENTION_GREETING_CARD'                                      => 'ACHTUNG Grußkarte',\n'AUTHOR'                                                      => 'Autor',\n'AVAILABLE_ON'                                                => 'Lieferbar ab',\n'BACK_TO_OVERVIEW'                                            => 'Zurück zur Übersicht',\n'BACK_TO_SHOP'                                                => 'Zurück zum Shop',\n'BACK_TO_START_PAGE'                                          => 'weiter zur Startseite',\n'BANK_DETAILS'                                                => 'Bankdetails',\n'BANK'                                                        => 'Bankname',\n'BANK_ACCOUNT_HOLDER'                                         => 'Kontoinhaber',\n'BANK_ACCOUNT_NUMBER'                                         => 'IBAN',\n'BANK_CODE'                                                   => 'BIC',\n'BARGAIN'                                                     => 'Schnäppchen',\n'BARGAIN_PRODUCTS'                                            => 'Die besten Schnäppchen des Shops',\n'BASKET_EMPTY'                                                => 'Der Warenkorb ist leer.',\n'BASKET_ITEMS_CHANGED_ERROR'                                  => 'Die Warenkorbartikel wurden geändert.',\n'BIC'                                                         => 'BIC',\n'BILLING_ADDRESS'                                             => 'Rechnungsadresse',\n'BILLING_SHIPPING_SETTINGS'                                   => 'Rechnungs- und Lieferadressen',\n'BIRTHDATE'                                                   => 'Geburtsdatum',\n'BLOCK_PRICE'                                                 => 'Mengenstaffelpreise',\n'CANCEL'                                                      => 'Beenden',\n'CART'                                                        => 'Warenkorb',\n'CATEGORIES'                                                  => 'Kategorien',\n'CATEGORY'                                                    => 'Kategorie',\n// @deprecated oxmore feature will be removed in v8.0\n'CATEGORY_OVERVIEW'                                           => 'Kategorieübersicht',\n// END deprecated\n'CATEGORY_PRODUCTS_S'                                         => 'Kategorie/%s',\n'CATEGORY_S'                                                  => '| Kategorie: %s',\n'CELLUAR_PHONE'                                               => 'Mobiltelefon',\n'CHANGE'                                                      => 'Ändern',\n'CHANGE_ACCOUNT_PASSWORD'                                     => 'Kontopasswort ändern',\n'CHANGE_LANGUAGE_AND_CURRENCY'                                => 'Sprache und Währung ändern',\n'CHANGE_PASSWORD'                                             => 'Passwort ändern',\n'CHARGES'                                                     => 'Kosten',\n'CHECKOUT'                                                    => 'Zur Kasse',\n'CHECK_YOUR_ORDER_HISTORY'                                    => 'Ihre Bestellhistorie aufrufen',\n'CHOOSE'                                                      => 'Wählen',\n'CHOOSE_VARIANT'                                              => 'Variante wählen',\n'CLICK_HERE'                                                  => 'hier klicken.',\n'CLOSE'                                                       => 'Schließen',\n'COMPANY'                                                     => 'Firma',\n'COMPARE'                                                     => 'Vergleichen',\n'COMPLETE_MARKED_FIELDS'                                      => 'Bitte alle fett beschrifteten Pflichtfelder ausfüllen.',\n'COMPLETE_ORDER'                                              => 'Bestellung abschließen',\n'CONFIRM_PASSWORD'                                            => 'Passwort bestätigen',\n'CONTACT'                                                     => 'Kontakt',\n'CONTINUE_SHOPPING'                                           => 'Weiter shoppen',\n'CONTINUE_TO_NEXT_STEP'                                       => 'Weiter zum nächsten Schritt',\n'COUNTRY'                                                     => 'Land',\n'COUPON'                                                      => 'Gutschein',\n'COUPON_NOT_ACCEPTED'                                         => 'Der Gutschein \"%s\" kann nicht akzeptiert werden.',\n'CREATE_PASSWORD'                                             => 'Passwort erstellen',\n'CURRENT_PRODUCT'                                             => 'Aktueller Artikel',\n'CUSTOMERS_ALSO_BOUGHT'                                       => 'Kunden, die diesen Artikel gekauft haben, kauften auch',\n'DATE'                                                        => 'Datum',\n'DELIVERYTIME_DAY'                                            => '%s Tag',\n'DELIVERYTIME_DAYS'                                           => '%s Tage',\n'DAY'                                                         => 'Tag',\n'DAYS'                                                        => 'Tage',\n'YEAR'                                                        => 'Jahr',\n'DEDUCTION'                                                   => 'Abschlag',\n'DELIVERABLE'                                                 => 'Lieferbar',\n'DELIVERYTIME_DELIVERYTIME'                                   => 'Lieferzeit',\n'DELIVERY_STATUS'                                             => 'Lieferstatus',\n'DELIVERY_STATUS_ANG'                                         => 'Angenommen, wird bearbeitet',\n'DELIVERY_STATUS_AUS'                                         => 'Versendet',\n'DELIVERY_STATUS_BES'                                         => 'Bestellt beim Lieferanten',\n'DELIVERY_STATUS_EIN'                                         => 'In der Kommissionierung',\n'DELIVERY_STATUS_HAL'                                         => 'Wartet auf Zahlungseingang',\n'DELIVERY_STATUS_NLB'                                         => 'Nicht lieferbar',\n'DELIVERY_STATUS_STO'                                         => 'Storniert',\n'DESCRIPTION'                                                 => 'Beschreibung',\n'DETAILS'                                                     => 'Details',\n'DISCOUNT'                                                    => 'Rabatt',\n'DISPLAY_BASKET'                                              => 'Warenkorb zeigen',\n'DO_NOT_WANT_CREATE_ACCOUNT'                                  => '(Ich möchte kein Kundenkonto eröffnen)',\n'EDIT'                                                        => 'Ändern',\n'EMAIL'                                                       => 'E-Mail',\n'EMAIL_ADDRESS'                                               => 'E-Mail-Adresse',\n'ENABLE'                                                      => 'Anzeigen',\n'ENTER_COUPON_NUMBER'                                         => 'Gutscheincode eingeben',\n'ENTER_EMAIL_OR_NAME'                                         => 'E-Mail-Adresse oder Nachname eingeben',\n'ENTER_NEW_PASSWORD'                                          => 'Bitte geben Sie ein neues Passwort ein.',\n'ERROR'                                                       => 'Fehler',\n'ERROR_404'                                                   => 'Die angeforderte Seite %s konnte nicht gefunden werden.',\n'ERROR_MESSAGE_ACCESSRIGHT_ACCESSDENIED'                      => 'Zugriff verweigert, keine ausreichenden Rechte!',\n'ERROR_MESSAGE_ACCESS_DENIED'                                 => 'Zugriff verweigert.',\n'ERROR_MESSAGE_ARTICLE_ARTICLE_DOES_NOT_EXIST'                => 'Der Artikel \"%s\" ist leider nicht mehr verfügbar!',\n'ERROR_MESSAGE_ARTICLE_ARTICLE_NOT_BUYABLE'                   => 'Artikel ist nicht kaufbar',\n'ERROR_MESSAGE_ARTICLE_NOPRODUCTID'                           => 'Keine Artikel ID angegeben!',\n'ERROR_MESSAGE_CHECK_EMAIL'                                   => 'Fehler beim Versenden - bitte E-Mail-Adressen überprüfen.',\n'EXCEPTION_CONNECTION_NODB'                                   => 'Keine Verbindung zur Datenbank möglich!',\n'ERROR_MESSAGE_COOKIE_NOCOOKIE'                               => 'Für diese Aktion werden Cookies benötigt. Bitte aktivieren Sie Cookies oder nutzen Sie einen anderen Browser.',\n'ERROR_MESSAGE_FILE_ERRORINFILE'                              => 'Fehler in Datei!',\n'ERROR_MESSAGE_INPUT_EMPTYPASS'                               => 'Bitte geben Sie ein Passwort ein.',\n'ERROR_MESSAGE_INPUT_INVALIDAMOUNT'                           => 'Bitte geben Sie eine gültige Menge für den Artikel ein!',\n'ERROR_MESSAGE_INPUT_NOTALLFIELDS'                            => 'Bitte Wert angeben!',\n'ERROR_MESSAGE_INPUT_NOVALIDEMAIL'                            => 'Bitte geben Sie eine gültige E-Mail-Adresse ein',\n'ERROR_MESSAGE_INVITE_INCORRECTEMAILADDRESS'                  => 'Ungültige E-Mail-Adresse. Bitte überprüfen Sie die E-Mail-Adressen.',\n'ERROR_MESSAGE_MANDATES_EXCEEDED'                             => 'Die Anzahl der lizenzierten Mandanten ist überschritten. Tragen Sie bitte im Shop Admin einen gültigen Lizenzschlüssel ein oder kontaktieren Sie',\n'FOR_MORE_INFORMATION'                                        => 'für mehr Informationen.',\n'ERROR_MESSAGE_NOFILE'                                        => 'Keine Datei hochgeladen',\n'EXCEPTION_NOTALLOWEDTYPE'                                    => 'Verbotener Dateityp. Bitte config.inc.php anpassen, um diesen Dateityp zu erlauben.',\n'ERROR_MESSAGE_OUTOFSTOCK_OUTOFSTOCK'                         => 'Der Lagerbestand dieses Artikels ist nicht ausreichend! Verfügbar',\n'ERROR_MESSAGE_OXID_ESALES'                                   => 'OXID eSales',\n'ERROR_MESSAGE_OXID_SHOP_ERROR'                               => 'OXID eShop Fehler',\n'ERROR_MESSAGE_PASSWORD_DO_NOT_MATCH'                         => 'Fehler: Die Passwörter stimmen nicht überein.',\n// @deprecated will be removed in next major\n'ERROR_MESSAGE_PASSWORD_EMAIL_INVALID'                        => 'Bitte geben Sie eine gültige E-Mail-Adresse ein!',\n// END deprecated\n'ERROR_MESSAGE_PASSWORD_LINK_EXPIRED'                         => 'Diese Seite ist nicht mehr gültig. Bitte benutzen Sie die Funktion \"Passwort vergessen?\" erneut.',\n'ERROR_MESSAGE_PASSWORD_TOO_SHORT'                            => 'Fehler: Ihr Passwort ist zu kurz.',\n'ERROR_REVIEW_AND_RATING_NOT_DELETED'                         => 'Bewertung und Sterne-Rating konnten nicht gelöscht werden',\n'ERROR_MESSAGE_CURRENT_PASSWORD_INVALID'                      => 'Fehler: Ihr aktuelles Passwort ist falsch.',\n'ERROR_MESSAGE_RECOMMLIST_NOTITLE'                            => 'Kein Titel angegeben',\n'ERROR_MESSAGE_SYSTEMCOMPONENT_CLASSNOTFOUND'                 => 'Class \"%s\" nicht gefunden',\n'EXCEPTION_SYSTEMCOMPONENT_CLASSNOTFOUND'                     => 'Class \"%s\" nicht gefunden!',\n'ERROR_MESSAGE_SYSTEMCOMPONENT_FUNCTIONNOTFOUND'              => 'Function \"%s\" nicht gefunden',\n'EXCEPTION_SYSTEMCOMPONENT_TEMPLATENOTFOUND'                  => 'Template \"%s\" nicht gefunden',\n'ERROR_MESSAGE_UNKNOWN_ERROR'                                 => 'Unbekannter Fehler',\n'ERROR_MESSAGE_UNLICENSED1'                                   => 'Ihr Shop ist nicht lizenziert. Tragen Sie bitte im Shop Admin einen gültigen Lizenzschlüssel ein oder kontaktieren Sie',\n'ERROR_MESSAGE_USER_NOVALIDLOGIN'                             => 'Falsche E-Mail-Adresse oder falsches Passwort!',\n'ERROR_MESSAGE_USER_NOVALUES'                                 => 'E-Mail und Passwort müssen ausgefüllt sein!',\n'ERROR_MESSAGE_USER_USERCREATIONFAILED'                       => 'Fehler beim Anlegen des Benutzers!',\n'ERROR_MESSAGE_USER_UPDATE_FAILED'                            => 'Fehler beim Aktualisieren der Benutzerdaten!',\n'ERROR_MESSAGE_USER_USEREXISTS'                               => 'Bei dieser E-Mail-Adresse ist ein Fehler aufgetreten. Bitte wenden Sie sich an unseren Support.',\n'ERROR_MESSAGE_VERSION_EXPIRED1'                              => 'Ihre Version ist leider abgelaufen. Bitte kontaktieren Sie',\n'ERROR_MESSAGE_VOUCHER_INCORRECTPRICE'                        => 'Einkaufswert ist zu niedrig für diesen Gutschein!',\n'ERROR_MESSAGE_VOUCHER_ISRESERVED'                            => 'Gutschein ist reserviert!',\n'ERROR_MESSAGE_VOUCHER_NOTALLOWEDOTHERSERIES'                 => 'Kombination mit Gutschein einer anderen Serie nicht erlaubt!',\n'ERROR_MESSAGE_VOUCHER_NOTALLOWEDSAMESERIES'                  => 'Gutschein der gleichen Serie kann nicht in diesem Einkauf benutzt werden!',\n'ERROR_MESSAGE_VOUCHER_NOTVALIDUSERGROUP'                     => 'Ihre Benutzergruppe erlaubt diesen Gutschein nicht!',\n'ERROR_MESSAGE_VOUCHER_NOVOUCHER'                             => 'Gutschein ungültig!',\n'ERROR_MESSAGE_COMPLETE_FIELDS_CORRECTLY'                     => 'Felder mit einem * müssen ausgefüllt werden.',\n'ERROR_MESSAGE_FILE_DOESNOT_EXIST'                            => 'Herunterladbare Datei existiert nicht mehr.',\n'ERROR_MESSAGE_FILE_DOWNLOAD_FAILED'                          => 'Fehler beim Herunterladen der Datei.',\n'ERROR_MESSAGE_WRONG_DOWNLOAD_LINK'                           => 'Downloadlink ist nicht korrekt.',\n'ERROR_MESSAGE_INCORRECT_DATE'                                => 'Falsches Datum',\n'EXCEPTION_NOT_VALID_CURL_CONSTANT'                           => 'Ungültiger cURL Konstantenname: %s',\n'EXCEPTION_CURL_ERROR'                                        => 'cURL Fehler: %s',\n'EXCEPTION_NON_MATCHING_CSRF_TOKEN'                           => 'CSRF-Token stimmt nicht überein!',\n'ERROR_MESSAGE_NON_MATCHING_CSRF_TOKEN'                       => 'Die Aktion konnte nicht abgeschlossen werden. Bitte versuchen Sie es noch einmal!',\n'EXPIRES_IN'                                                  => 'Läuft ab in',\n'FAX'                                                         => 'Telefax',\n'FIRST_LAST_NAME'                                             => 'Name',\n'FIRST_NAME'                                                  => 'Vorname',\n'FORGOT_PASSWORD'                                             => 'Passwort vergessen?',\n'FROM'                                                        => 'Von',\n'GIFT_OPTION'                                                 => 'Geschenkoption',\n'GIFT_REGISTRY'                                               => 'Wunschzettel',\n'GIFT_REGISTRY_EMPTY'                                         => 'Der Wunschzettel ist leer.',\n'GIFT_REGISTRY_OF'                                            => 'Wunschzettel von',\n'GIFT_REGISTRY_OF_2'                                          => 'Mein Wunschzettel bei',\n'GIFT_REGISTRY_OF_3'                                          => 'Herzlich willkommen zum Wunschzettel von',\n'GIFT_REGISTRY_SEARCH_RESULTS'                                => 'Wunschzettelsuchergebnis',\n'GIFT_WRAPPING'                                               => 'Geschenkverpackung',\n'GIFT_WRAPPING_GREETING_CARD'                                 => 'Geschenkverpackung/Grußkarte',\n'GO'                                                          => 'Los!',\n'GRAND_TOTAL'                                                 => 'Gesamtbetrag',\n'GREETING'                                                    => 'Hallo, ',\n'GREETING_CARD'                                               => 'Grußkarte',\n'GREETING_MESSAGE'                                            => 'Grußnachricht',\n'GROSS'                                                       => '(brutto)',\n'HAVE_A_LOOK'                                                 => 'Schau Dir das mal an',\n'HAVE_YOU_FORGOTTEN_PASSWORD'                                 => 'Sie haben Ihr Passwort vergessen?',\n'HAVE_YOU_SEEN'                                               => 'Schon gesehen?',\n'HELP'                                                        => 'Hilfe',\n'HERE_YOU_CAN_ENETER_MESSAGE'                                 => 'Hier können Sie uns noch etwas mitteilen.',\n'HERE_YOU_SET_UP_NEW_PASSWORD'                                => 'Kein Problem! Hier können Sie ein neues Passwort einrichten.',\n'HITS_FOR'                                                    => 'Treffer für',\n'HOME'                                                        => 'Startseite',\n'IBAN'                                                        => 'IBAN',\n'IF_DIFFERENT_FROM_BILLING_ADDRESS'                           => 'Falls abweichend von der Rechnungsadresse.',\n'IMPRESSUM'                                                   => 'Impressum',\n'INCL_TAX_AND_PLUS_SHIPPING'                                  => '* Alle Preise inkl. MwSt., zzgl. Versandkosten.',\n'INFORMATION'                                                 => 'Informationen',\n'INTRODUCTION'                                                => 'Einleitung',\n'INVITE_YOUR_FRIENDS'                                         => 'Freunde einladen',\n'ITEMS_IN_BASKET'                                             => 'Artikel im Warenkorb',\n'JUST_ARRIVED'                                                => 'Frisch eingetroffen!',\n'KEEP_LOGGED_IN'                                              => 'Angemeldet bleiben',\n'KG'                                                          => 'kg',\n'LABEL'                                                       => 'Beschriftung',\n'LAST_NAME'                                                   => 'Nachname',\n'LAST_SEEN_PRODUCTS'                                          => 'Zuletzt angesehene Artikel',\n'LINKS'                                                       => 'Links',\n'LISTMANIA'                                                   => 'Lieblingslisten',\n'LISTMANIA_2'                                                 => 'Lieblingsliste/%s',\n'LISTMANIA_LIST_FOR'                                          => 'Lieblingsliste für %s',\n'LISTMANIA_LIST_PRODUCTS'                                     => 'Artikel von Lieblingsliste %s',\n'LISTMANIA_LIST_SAVED'                                        => 'Änderungen der Lieblingsliste wurden gespeichert',\n'LISTS'                                                       => 'Listen',\n'LIST_BY'                                                     => 'Eine Liste von',\n'LOADING'                                                     => 'Laden...',\n'LOGIN'                                                       => 'Anmelden',\n'LOGIN_ALREADY_CUSTOMER'                                      => 'Falls Sie schon Kunde bei uns sind, melden Sie sich bitte hier mit Ihrer E-Mail-Adresse und Ihrem Passwort an.',\n'LOGIN_DESCRIPTION'                                           => 'Bitte mit E-Mail-Adresse und Passwort anmelden.',\n'LOGIN_TO_ACCESS_GIFT_REGISTRY'                               => 'Für den Wunschzettel bitte anmelden.',\n'LOGIN_TO_ACCESS_LISTMANIA'                                   => 'Für die Lieblingsliste bitte anmelden.',\n'LOGIN_TO_ACCESS_WISH_LIST'                                   => 'Für den Merkzettel bitte anmelden.',\n'LOGIN_WITH'                                                  => 'Anmelden mit',\n'LOGOUT'                                                      => 'Abmelden',\n'LOW_STOCK'                                                   => 'Wenige Exemplare auf Lager - schnell bestellen!',\n'MANUFACTURER'                                                => 'Hersteller',\n'MANUFACTURER_S'                                              => '| Hersteller: %s',\n'MANY_GREETINGS'                                              => 'Viele Grüße,',\n'MEDIA'                                                       => 'Medien',\n'MESSAGE'                                                     => 'Nachricht',\n'MESSAGE_ACCOUNT_REGISTRATION_CONFIRMED'                      => 'Das Konto wurde eröffnet.',\n'MESSAGE_ALREADY_RATED'                                       => 'Sie haben schon bewertet!',\n'MESSAGE_BASKET_EXCLUDE_INFO'                                 => 'Sie können nun zur Kasse gehen und Ihre Bestellung abschließen. Sie können auch weiter einkaufen, jedoch wird dann der Warenkorbinhalt geleert.',\n'MESSAGE_CONFIRMATION_NOT_SUCCEED'                            => 'Leider konnten wir Ihnen keine Bestellbestätigung per E-Mail zustellen.',\n'MESSAGE_CONFIRMING_REGISTRATION'                             => 'Sie haben eine E-Mail von uns erhalten, die Ihre Registrierung bestätigt.',\n'MESSAGE_COUPON_ACCUMULATION_SAME_SERIE'                      => 'Kombination mit Gutschein der gleichen Serie ist nicht erlaubt!',\n'MESSAGE_COUPON_EXPIRED'                                      => 'Gutschein abgelaufen!',\n'MESSAGE_COUPON_NOT_APPLIED_FOR_ARTICLES'                     => 'Diesen Artikeln ist kein Rabatt zugeordnet',\n'MESSAGE_DENIED_BY_SHOP_RULES'                                => 'Verweigert aufgrund von Shopregeln',\n'MESSAGE_EMAIL_ALREADY_IN_USE'                                => 'E-Mail-Adresse ist bereits vorhanden!',\n'MESSAGE_FROM'                                                => 'Nachricht von',\n'MESSAGE_GET_BONUS_POINTS'                                    => 'Holen Sie sich jetzt für Ihren Einkauf Bonuspunkte!',\n'MESSAGE_INVALID_EMAIL'                                       => 'Keine gültige E-Mail-Adresse!',\n'MESSAGE_LOGIN_TO_RATE'                                       => 'Für Bewertung bitte anmelden!',\n'MESSAGE_LOGIN_TO_WRITE_REVIEW'                               => 'Sie müssen angemeldet sein, um eine Bewertung schreiben zu können.',\n'MESSAGE_MAKE_GIFT_REGISTRY_PUBLISH'                          => 'Mein Wunschzettel soll von allen gesucht und angesehen werden können',\n'MESSAGE_NEGATIVE_TOTAL'                                      => 'Negativer Betrag ist nicht erlaubt.',\n'MESSAGE_NEWSLETTER_CONGRATULATIONS'                          => 'Herzlichen Glückwunsch!',\n'MESSAGE_NEWSLETTER_FOR_SUBSCRIPTION_BONUS'                   => 'Holen Sie sich jetzt für Ihr Newsletter-Abonnement Bonuspunkte!',\n'MESSAGE_NEWSLETTER_SUBSCRIPTION'                             => 'Sie können den Newsletter jederzeit abbestellen.',\n'MESSAGE_NEWSLETTER_SUBSCRIPTION_ACTIVATED'                   => 'Sie sind nun für den Empfang unseres Newsletters freigeschaltet.',\n'MESSAGE_NEWSLETTER_SUBSCRIPTION_CANCELED'                    => 'Sie haben den Newsletter erfolgreich abbestellt.',\n'MESSAGE_NEWSLETTER_SUBSCRIPTION_SUCCESS'                     => 'Der Newsletter wurde abonniert.',\n'MESSAGE_NOT_ABLE_TO_SEND_EMAIL'                              => 'Leider konnten wir Ihnen keine E-Mail zustellen.',\n'MESSAGE_NOT_ON_STOCK'                                        => 'Dieser Artikel ist nicht auf Lager und muss erst nachbestellt werden.',\n'MESSAGE_NO_SHIPPING_METHOD_FOUND'                            => 'Keine Versandarten gefunden. Bitte kontaktieren Sie uns telefonisch oder per E-Mail!',\n'MESSAGE_PASSWORD_CHANGED'                                    => 'Ihr Passwort wurde geändert.',\n'MESSAGE_PAYMENT_AUTHORIZATION_FAILED'                        => 'Autorisierung der Zahlung fehlgeschlagen. Bitte prüfen Sie Ihre Eingabe!',\n'MESSAGE_PAYMENT_SELECT_ANOTHER_PAYMENT'                      => 'Bitte wählen Sie eine andere Zahlungsart!',\n'MESSAGE_PAYMENT_BANK_CODE_INVALID'                           => 'Bitte geben Sie einen gültigen BIC-Code ein!',\n'MESSAGE_PAYMENT_ACCOUNT_NUMBER_INVALID'                      => 'Bitte geben Sie eine gültige IBAN an!',\n'MESSAGE_PAYMENT_UNAVAILABLE_PAYMENT'                         => 'Die Zahlungsweise ist aus technischen Gründen derzeit leider nicht möglich. Bitte wählen Sie eine andere Zahlungsart!',\n'MESSAGE_PAYMENT_UNAVAILABLE_PAYMENT_ERROR'                   => 'Die Zahlungsweise ist aus technischen Gründen derzeit leider nicht möglich. Bitte wählen Sie eine andere Zahlungsart! (Fehler',\n'MESSAGE_PLEASE_CONTACT_SUPPORT'                              => 'Bitte wenden Sie sich an den technischen Support!',\n'MESSAGE_PLEASE_DELETE_FOLLOWING_DIRECTORY'                   => 'Bitte löschen Sie folgendes Verzeichnis',\n'MESSAGE_PRICE_ALARM_PRICE_CHANGE'                            => 'Wir informieren Sie gern darüber, falls der Preis dieses Artikels Ihrem Wunschpreis entspricht.',\n'MESSAGE_RATE_THIS_ARTICLE'                                   => 'Bewerten Sie diesen Artikel!',\n'MESSAGE_READ_DETAILS'                                        => 'Lesen Sie Details zum',\n'MESSAGE_SELECT_AT_LEAST_ONE_PRODUCT'                         => 'Bitte wählen Sie mindestens einen Artikel aus!',\n'MESSAGE_SELECT_MORE_PRODUCTS'                                => 'Bitte wählen Sie Artikel zum Vergleichen aus!',\n'MESSAGE_SEND_GIFT_REGISTRY'                                  => 'Klicken Sie hier, um Ihren Wunschzettel an Ihre Freunde zu versenden!',\n'MESSAGE_SENT_CONFIRMATION_EMAIL'                             => 'Falls Ihre E-Mail-Adresse noch nicht abonniert ist, erhalten Sie eine Bestätigungs-E-Mail.',\n'MESSAGE_SORRY_NO_GIFT_REGISTRY'                              => 'Leider keine passenden Wunschzettel gefunden.',\n'MESSAGE_STOCK_LOW'                                           => 'Lagerbestand niedrig: Die eingestellte Lagerbestandsgrenze für diesen Artikel wurde erreicht.',\n'MESSAGE_SUBMIT_BOTTOM'                                       => 'Bitte prüfen Sie alle Daten, bevor Sie Ihre Bestellung abschließen!',\n'MESSAGE_THANKYOU_FOR_SUBSCRIBING_NEWSLETTERS'                => 'Vielen Dank für das Abonnement unseres Newsletters.',\n'MESSAGE_UNAVAILABLE_SHIPPING_METHOD'                         => 'Die von Ihnen gewählte Versandart ist nicht mehr verfügbar. Bitte wählen Sie eine andere Versandart aus!',\n'MESSAGE_VERIFY_YOUR_EMAIL'                                   => 'Bitte kontrollieren Sie Ihre E-Mail-Adresse!',\n'MESSAGE_WELCOME_REGISTERED_USER'                             => 'Herzlich willkommen als registrierter Kunde!',\n'MESSAGE_WE_WILL_INFORM_YOU'                                  => 'Sollte etwas nicht lieferbar sein, werden wir Sie sofort informieren.',\n'MESSAGE_WRONG_VERIFICATION_CODE'                             => 'Der Prüfcode, den Sie eingegeben haben, ist nicht korrekt. Bitte versuchen Sie es erneut!',\n'MESSAGE_YOU_RECEIVED_ORDER_CONFIRM'                          => 'Sie haben bereits eine Bestellbestätigung per E-Mail erhalten.',\n'MESSAGE_DOWNLOADABLE_PRODUCT'                                => 'Hinweis: Sie haben Download-Artikel im Warenkorb. Wenn Sie ohne Registrierung einkaufen, finden Sie Downloadlinks ausschließlich in Ihrer E-Mail zur Bestellbestätigung. Sind Sie registriert, werden die Downloadlinks unter KONTO -> MEINE DOWNLOADS angezeigt.',\n'MIN_ORDER_PRICE'                                             => 'Mindestbestellwert',\n'MONTH'                                                       => 'Monat',\n'MONTHS'                                                      => 'Monate',\n'DELIVERYTIME_MONTH'                                          => '%s Monat',\n'DELIVERYTIME_MONTHS'                                         => '%s Monate',\n'MORE'                                                        => 'Mehr',\n'MORE_INFO'                                                   => 'Mehr Informationen',\n'MOVE'                                                        => 'Artikel verschieben',\n'MR'                                                          => 'Herr',\n'MRS'                                                         => 'Frau',\n'MY_ACCOUNT'                                                  => 'Mein Konto',\n'MY_GIFT_REGISTRY'                                            => 'Mein Wunschzettel',\n'MY_LISTMANIA'                                                => 'Meine Lieblingslisten',\n'MY_ORDER_HISTORY'                                            => 'Meine Bestellhistorie',\n'MY_PRODUCT_COMPARISON'                                       => 'Mein Artikelvergleich',\n'MY_WISH_LIST'                                                => 'Mein Merkzettel',\n'NEWEST_SHOP_PRODUCTS'                                        => 'Neue Artikel im Shop',\n'NEWLIST'                                                     => 'Neue Lieblingsliste',\n'NEWSLETTER'                                                  => 'Newsletter',\n'NEWSLETTER_SETTINGS'                                         => 'Newslettereinstellungen',\n'NEWSLETTER_SUBSCRIBE_CANCEL'                                 => 'Newsletter abonnieren/abbestellen',\n'NEWSLETTER_SUBSCRIPTION'                                     => 'Newsletter abonnieren',\n'NEWSLETTER_EMAIL_NOT_EXIST'                                  => 'Unbekannte E-Mail-Adresse!',\n'NEW_ADDRESS'                                                 => 'Neue Adresse',\n'NEW_BASKET_ITEM_MSG'                                         => 'Neuer Artikel wurde in den Warenkorb gelegt',\n'NEW_PASSWORD'                                                => 'Neues Passwort',\n'NEXT'                                                        => 'Weiter',\n'NEXT_PRODUCT'                                                => 'nächster Artikel ',\n'NO'                                                          => 'Nein',\n'NONE'                                                        => 'Keine',\n'NOTE'                                                        => 'Hinweis',\n'NOT_SHIPPED_YET'                                             => 'Noch nicht versendet!',\n'NOTREGISTERED_ACCOUNTINFO'                                   => 'Kundeninformation',\n'NOW_ONLY'                                                    => 'Jetzt nur',\n'NO_GREETING_CARD'                                            => 'Keine Grußkarte',\n'NO_ITEMS_FOUND'                                              => 'Leider keine Artikel gefunden.',\n'NO_LISTMANIA_LIST_FOUND'                                     => 'Keine Lieblingslisten gefunden',\n'NO_RATINGS'                                                  => 'Keine Bewertungen',\n'NO_REVIEW_AVAILABLE'                                         => 'Es liegen keine Bewertungen zu diesem Artikel vor.',\n'NUMBER'                                                      => 'Nummer',\n'NUMBER_2'                                                    => 'Nr.',\n'OF'                                                          => 'VON',\n'OLD_PASSWORD'                                                => 'Altes Passwort',\n'ONLY_IN_PACKING_UNITS_OF'                                    => 'Nur in Verpackungseinheiten zu je ',\n'OPEN_ACCOUNT'                                                => 'Konto eröffnen',\n'OR'                                                          => 'oder',\n'ORDERS'                                                      => 'Bestellungen',\n'ORDER'                                                       => 'Bestellen',\n'ORDER_COMPLETED'                                             => 'Bestellung abgeschlossen',\n'ORDER_DATE'                                                  => 'Bestellung vom',\n'ORDER_EMPTY_HISTORY'                                         => 'Die Bestellhistorie ist leer',\n'ORDER_HISTORY'                                               => 'Bestellhistorie',\n'ORDER_IS_CANCELED'                                           => 'Bestellung wurde storniert!',\n'ORDER_NUMBER'                                                => 'Bestellnummer',\n'ORDER_REMARK'                                                => 'Bestellbemerkung',\n'BRAND'                                                       => 'Marke',\n'OUR_BRANDS'                                                  => 'Unsere Marken',\n'OUR_REGULAR_PRICE'                                           => '(Unser regulärer Preis)',\n'OXID_ESALES_URL'                                             => 'https://www.oxid-esales.com',\n'OXID_ESALES_URL_TITLE'                                       => 'Shopsoftware von OXID eSales',\n'PAGE'                                                        => 'Seite',\n'PASSWORD'                                                    => 'Passwort',\n'PASSWORD_CHANGED'                                            => 'Ihr Passwort wurde erfolgreich geändert.',\n'PASSWORD_WAS_SEND_TO'                                        => 'Bei bestehender Registrierung erhalten Sie eine E-Mail mit einem Link zur Passwortvergabe an',\n'PAY'                                                         => 'Bezahlen',\n'PAYMENT_INFORMATION'                                         => 'Bezahlinformation',\n'PAYMENT_METHOD'                                              => 'Zahlungsart',\n'PCS'                                                         => 'Stück',\n'PERSONAL_PHONE'                                              => 'Telefon (privat)',\n'PERSONAL_SETTINGS'                                           => 'Persönliche Einstellungen',\n'PHONE'                                                       => 'Telefon',\n'PLEASE_CHOOSE'                                               => 'Bitte wählen',\n'PLEASE_SELECT_STATE'                                         => 'Bitte wählen Sie ein Bundesland aus',\n'PLUS'                                                        => 'zzgl. ',\n'PLUS_SHIPPING'                                               => 'inkl. MwSt., zzgl. ',\n'PLUS_SHIPPING2'                                              => 'Versandkosten',\n'PLUS_SHIPPING3'                                              => '* zzgl. Versandkosten',\n'POSTAL_CODE_AND_CITY'                                        => 'PLZ, Ort',\n'POSTAL_CODE'                                                 => 'PLZ',\n'POSTAL_CITY'                                                 => 'Ort',\n'POST_CARD_FROM'                                              => 'Eine Postkarte von',\n'PREVIOUS'                                                    => 'Zurück',\n'PREVIOUS_STEP'                                               => 'Zurück',\n'PREVIOUS_PRODUCT'                                            => 'Artikel zurück',\n'PRICE'                                                       => 'Preis',\n'PRICE_ALERT'                                                 => '[!] Wunschpreis',\n'PRICE_ALERT_AT'                                              => 'Wunschpreis im',\n'PRICE_ALERT_FOR_PRODUCT'                                     => 'Wunschpreis für Artikel',\n'PRINT'                                                       => 'Diese Seite drucken',\n'PRODUCT'                                                     => 'Artikel',\n'PRODUCTS'                                                    => 'Artikel',\n'PRODUCTS_PER_PAGE'                                           => 'Artikel pro Seite',\n'PRODUCT_ATTRIBUTES'                                          => 'Artikelattribute',\n'PRODUCT_COMPARISON'                                          => 'Artikelvergleich',\n'PRODUCT_DETAILS'                                             => 'Artikeldetails',\n'PRODUCT_IMAGES'                                              => 'Artikelbilder',\n'PRODUCT_NO'                                                  => 'Art. Nr.',\n'PRODUCT_REVIEW'                                              => 'Artikel bewerten',\n'PUBLIC_GIFT_REGISTRIES'                                      => 'Öffentlicher Wunschzettel',\n'PUBLISH'                                                     => 'Veröffentlichen',\n'PURCHASE_WITHOUT_REGISTRATION'                               => 'Bestellen ohne Registrierung',\n'QNT'                                                         => 'stk.',\n'QUANTITY'                                                    => 'Menge',\n'QUESTIONS_ABOUT_THIS_PRODUCT'                                => 'Fragen zum Artikel',\n'QUESTIONS_ABOUT_THIS_PRODUCT_2'                              => '[?] Sie haben Fragen zu diesem Artikel?',\n'RATING'                                                      => 'Bewertung',\n'RATINGS'                                                     => 'Bewertungen',\n'READY'                                                       => 'Fertig!',\n'READY_FOR_SHIPPING'                                          => 'Sofort lieferbar',\n'READ_AND_CONFIRM_TERMS'                                      => 'Bitte bestätigen Sie unsere Allg. Geschäftsbedingungen!',\n'REASON'                                                      => 'Grund',\n'REBATE'                                                      => 'Nachlass',\n'RECIPIENT_EMAIL'                                             => 'E-Mail des Empfängers',\n'RECIPIENT_NAME'                                              => 'Name des Empfängers',\n'RECOMMEND'                                                   => 'Empfehlen',\n'RECOMMEND_PRODUCT'                                           => 'Artikel weiterempfehlen',\n'REDEEM_COUPON'                                               => 'Gutschein einlösen',\n'REDUCED_FROM'                                                => 'Statt',\n'REDUCED_FROM_2'                                              => 'UVP',\n'REGISTER'                                                    => 'Registrieren',\n'REMEMBER_ME'                                                 => 'Passwort merken',\n'REMOVE'                                                      => 'Entfernen',\n'REMOVE_FROM_COMPARE_LIST'                                    => 'Aus Vergl. entfernen',\n'REQUEST_PASSWORD'                                            => 'Passwort zurücksetzen',\n'REQUEST_PASSWORD_AFTERCLICK'                                 => 'Sie erhalten eine E-Mail mit einem Link, um ein neues Passwort zu vergeben.',\n'RESET_SELECTION'                                             => 'Auswahl zurücksetzen',\n'REVIEW'                                                      => 'Bewerten',\n'REVIEW_YOUR_ORDER'                                           => 'Bitte Bestellung kontrollieren!',\n'ROOT_CATEGORY_CHANGED'                                       => 'Hauptkategorie verändert',\n'SAVE'                                                        => 'Speichern',\n'SAVE_RATING_AND_REVIEW'                                      => 'Bewertung und Sterne-Rating speichern',\n'SEARCH'                                                      => 'Suche',\n'SEARCH_FOR_LISTS'                                            => 'Suche nach weiteren Listen',\n'SEARCH_FOR_PRODUCTS_CATEGORY_VENDOR'                         => 'Artikel aus Suche nach \"%s\" <TAG_CATEGORY> <TAG_VENDOR>',\n'SEARCH_FOR_PRODUCTS_CATEGORY_VENDOR_MANUFACTURER'            => 'Artikel aus Suche nach \"%s\" <TAG_CATEGORY> <TAG_VENDOR> <TAG_MANUFACTURER>',\n'SEARCH_GIFT_REGISTRY'                                        => 'Wunschzettel suchen',\n'SELECT'                                                      => 'Auswählen',\n'SELECTED_COMBINATION'                                        => 'Ausgewählte Kombination',\n'SELECTED_SHIPPING_CARRIER'                                   => 'Der Versand erfolgt mit',\n'SELECT_ALL'                                                  => 'Alle auswählen',\n'SELECT_LISTMANIA_LIST'                                       => 'Wählen Sie die Lieblingsliste',\n'SELECT_SHIPPING_METHOD'                                      => 'Bitte wählen Sie Ihre Versandart',\n'SEND'                                                        => 'Abschicken',\n'SENDER_EMAIL'                                                => 'E-Mail des Absenders',\n'SENDER_NAME'                                                 => 'Name des Absenders',\n'SEND_GIFT_REGISTRY'                                          => 'Wunschzettel versenden',\n'SEND_INVITE_TO'                                              => 'Einladung schicken an',\n'SHIPMENT_TO'                                                 => 'Lieferung an',\n'SHIPPED'                                                     => 'Versendet!',\n'SHIPPING'                                                    => 'Versand',\n'SHIPPING_ADDRESS'                                            => 'Lieferadresse',\n'SHIPPING_ADDRESSES'                                          => 'Lieferadressen',\n'SHIPPING_CARRIER'                                            => 'Versandart',\n'SHIPPING_COST'                                               => 'Versandkosten',\n'SHIPPING_NET'                                                => 'Versandkosten (netto)',\n'PLUS_VAT'                                                    => 'zzgl. MwSt.',\n'VAT_PLUS_PERCENT_AMOUNT'                                     => 'zzgl. %s%% MwSt., Betrag',\n'SURCHARGE_PLUS_PERCENT_AMOUNT'                               => 'Aufschlag %s%% MwSt., Betrag',\n'SHOPLUPE'                                                    => 'Shoplupe',\n'SIMILAR_PRODUCTS'                                            => 'Ähnliche Produkte',\n'SORT_BY'                                                     => 'Sortierung',\n'SPECIFICATION'                                               => 'Spezifikation',\n'STAR'                                                        => 'Stern',\n'STARS'                                                       => 'Sterne',\n'STATUS'                                                      => 'Status',\n'STAY_INFORMED'                                               => 'Lassen Sie sich informieren!',\n'STEPS_BASKET'                                                => '1. Warenkorbübersicht',\n'STEPS_ORDER'                                                 => '4. überprüfen & absenden',\n'STEPS_PAY'                                                   => '3. Versand & Zahlungsart',\n'STEPS_SEND'                                                  => '2. Adressen wählen ',\n'STOCK'                                                       => 'Lagerbestand',\n'STOCK_LOW'                                                   => 'Lagerbestand niedrig',\n'STREET_AND_STREETNO'                                         => 'Straße, Hausnummer',\n'SUBJECT'                                                     => 'Betreff',\n'SUBMIT'                                                      => 'Absenden',\n'SUBMIT_COUPON'                                               => 'Gutschein absenden',\n'SUBMIT_ORDER'                                                => 'Zahlungspflichtig bestellen',\n'SUBSCRIBE'                                                   => 'Abonnieren',\n'SUCCESS'                                                     => 'Erfolg!',\n'SUM'                                                         => 'Warenwert',\n'SURCHARGE'                                                   => 'Aufschlag',\n'S_CATEGORY_PRODUCTS'                                         => 'Artikel aus der Kategorie %s',\n'TERMS_AND_CONDITIONS'                                        => 'AGB',\n'THANK_YOU'                                                   => 'Vielen Dank',\n'TIME'                                                        => 'Uhrzeit',\n'TITLE'                                                       => 'Anrede',\n'TO'                                                          => 'An',\n'TOP_OF_THE_SHOP'                                             => 'Top of the Shop',\n'TOP_SHOP_PRODUCTS'                                           => 'Die beliebtesten Artikel des Shops',\n'TOTAL'                                                       => 'Gesamtbetrag',\n'TOTAL_GROSS'                                                 => 'Summe Artikel (brutto)',\n'TOTAL_NET'                                                   => 'Summe Artikel (netto)',\n'TOTAL_QUANTITY'                                              => 'Anzahl total',\n'TO_CART'                                                     => 'In den Warenkorb',\n'TO_MY_WISHLIST'                                              => 'Um zu meinem Wunschzettel zu kommen, bitte',\n'TRACKING_ID'                                                 => 'Tracking-ID',\n'TRACK_SHIPMENT'                                              => 'Wo ist meine Lieferung?',\n'RATE_OUR_SHOP'                                               => 'Bitte nehmen Sie sich eine Minute, um unseren Shop zu bewerten.',\n'UNIT_PRICE'                                                  => 'Einzelpreis',\n'UNSUBSCRIBE'                                                 => 'Abmelden',\n'UPDATE'                                                      => 'Aktualisieren',\n'UPDATE_SHIPPING_CARRIER'                                     => 'Versandart und -kosten aktualisieren',\n'UPDATE_YOUR_BILLING_SHIPPING_SETTINGS'                       => 'Rechnungs- und Lieferadressen bearbeiten',\n'USED_COUPONS'                                                => 'Folgende Gutscheine werden benutzt',\n'USED_COUPONS_2'                                              => 'Benutzte Gutscheine,',\n'USE_BILLINGADDRESS_FOR_SHIPPINGADDRESS'                      => 'Rechnungsadresse als Lieferadresse verwenden',\n'VALID_UNTIL'                                                 => 'Gültig bis',\n'VAT'                                                         => 'MwSt',\n'VAT_ID_NUMBER'                                               => 'USt-ID',\n'VAT_MESSAGE_ID_NOT_VALID'                                    => 'USt-ID ist ungültig',\n'VAT_MESSAGE_COMPANY_MISSING'                                 => 'Bitte geben Sie zur USt-ID auch Ihren Firmennamen an!',\n'VAT_MESSAGE_MISSING_COUNTRY_PREFIX'                          => 'Die Prüfung der Umsatzsteuer-ID ist für das gewählte Land derzeit nicht möglich. Bitte wenden Sie sich an den Shopbetreiber, um den Einkauf fortzusetzen.',\n'ERROR_MESSAGE_INPUT_VAT_PREFIX_EMPTY'                        => 'Bitte geben Sie ein gültiges Präfix zur Umsatzsteuer-Identifikationsnummer ein, wenn die Option \"Keine Mehrwertsteuer berechnen, wenn eine Mehrwertsteuer-ID angegeben ist\" ausgewählt ist.',\n'VENDOR'                                                      => 'Lieferant',\n'VENDOR_S'                                                    => '| Lieferant: %s',\n'VERIFICATION_CODE'                                           => 'Prüfcode',\n'VIEW_ALL_PRODUCTS'                                           => 'Alle Artikel ansehen',\n'WEEK'                                                        => 'Woche',\n'WEEKS'                                                       => 'Wochen',\n'DELIVERYTIME_WEEKS'                                          => '%s Wochen',\n'DELIVERYTIME_WEEK'                                           => '%s Woche',\n'WEEK_SPECIAL'                                                => 'Angebot der Woche',\n'WEIGHT'                                                      => 'Gewicht',\n'WHAT_I_WANTED_TO_SAY'                                        => 'Ihre Mitteilung an uns',\n'WHO_BOUGHT_ALSO_BOUGHT'                                      => 'Kunden, die diese Artikel gekauft haben, kauften auch',\n'WISH_LIST'                                                   => 'Merkzettel',\n'WISH_LIST_EMPTY'                                             => 'Der Merkzettel ist leer.',\n'WITH_LOVE'                                                   => 'Alles Liebe,',\n'WRAPPING'                                                    => 'Verpackung',\n'WRAPPING_DESCRIPTION'                                        => 'Wir verpacken gern Ihr Geschenk oder legen eine Karte mit Ihrer persönlichen Nachricht bei.',\n'WRAPPING_NET'                                                => 'Geschenkverpackung/Grußkarte (netto)',\n'WRITES'                                                      => 'schreibt',\n'WRITE_PRODUCT_REVIEW'                                        => 'Artikel bewerten',\n'WRITE_REVIEW'                                                => 'Eine Bewertung schreiben.',\n'WRITE_REVIEW_2'                                              => 'Bewerten Sie unseren Shop!',\n'YES'                                                         => 'Ja',\n'YOUR_EMAIL_ADDRESS'                                          => 'Ihre E-Mail-Adresse',\n'YOUR_GREETING_CARD'                                          => 'Ihre Grußkarte',\n'YOUR_PREVIOUS_ORDER'                                         => 'Ihre bisherigen Bestellungen',\n'YOUR_REVIEW'                                                 => 'Ihre Bewertung',\n'YOUR_PRICE'                                                  => 'Ihr Preis',\n'YOU_ARE_HERE'                                                => 'Sie sind hier',\n'YOU_CAN_GO'                                                  => 'Sie können nun',\n'YOUR_MESSAGE'                                                => 'Ihr Text',\n'YOUR_TEAM'                                                   => 'Ihr %s-Team',\n'ZOOM'                                                        => 'Zoom',\n//used as field translations\n'OXACTIVEFROM'                                                => 'Aktiv von',\n'OXACTIVETO'                                                  => 'Aktiv bis',\n'OXARTNUM'                                                    => 'Artikelnummer',\n'OXTITLE'                                                     => 'Titel',\n'OXID'                                                        => 'interne ID',\n'OXSHOPID'                                                    => 'Shop-ID',\n'OXPARENTID'                                                  => 'ID des Elternartikels',\n'OXACTIVE'                                                    => 'Aktiv',\n'OXSHORTDESC'                                                 => 'Kurzbeschreibung',\n'OXLONGDESC'                                                  => 'Langtext',\n'OXPRICE'                                                     => 'Preis',\n'OXPRICEA'                                                    => 'Preis A',\n'OXPRICEB'                                                    => 'Preis B',\n'OXPRICEC'                                                    => 'Preis C',\n'OXBPRICE'                                                    => 'Bruttopreis',\n'OXTPRICE'                                                    => 'Alter Preis',\n'OXEXTURL'                                                    => 'Externe URL',\n'OXUNITNAME'                                                  => 'Einheit',\n'OXUNITQUANTITY'                                              => 'Mengeneinheit',\n'OXURLDESC'                                                   => 'URL-Beschreibung',\n'OXURLIMG'                                                    => 'URL für externes Bild',\n'OXVAT'                                                       => 'Artikel-MwSt',\n'OXTHUMB'                                                     => 'Vorschaubild',\n'OXPIC1'                                                      => 'Bild 1',\n'OXPIC2'                                                      => 'Bild 2',\n'OXPIC3'                                                      => 'Bild 3',\n'OXPIC4'                                                      => 'Bild 4',\n'OXPIC5'                                                      => 'Bild 5',\n'OXPIC6'                                                      => 'Bild 6',\n'OXPIC7'                                                      => 'Bild 7',\n'OXPIC8'                                                      => 'Bild 8',\n'OXPIC9'                                                      => 'Bild 9',\n'OXPIC10'                                                     => 'Bild 10',\n'OXPIC11'                                                     => 'Bild 11',\n'OXPIC12'                                                     => 'Bild 12',\n'OXZOOM1'                                                     => 'Zoom-Bild 1',\n'OXZOOM2'                                                     => 'Zoom-Bild 2',\n'OXZOOM3'                                                     => 'Zoom-Bild 3',\n'OXZOOM4'                                                     => 'Zoom-Bild 4',\n'OXWEIGHT'                                                    => 'Gewicht',\n'OXSTOCK'                                                     => 'Lagerbestand',\n'OXSTOCKACTIVE'                                               => 'Lagerverwaltung aktiv',\n'OXSTOCKFLAG'                                                 => 'Lieferstatus',\n'OXDELIVERY'                                                  => 'Ausgeliefert am',\n'OXINSERT'                                                    => 'Angelegt am',\n'OXTIMESTAMP'                                                 => 'Letzte Änderung',\n'OXLENGTH'                                                    => 'Länge',\n'OXWIDTH'                                                     => 'Breite',\n'OXHEIGHT'                                                    => 'Höhe',\n'OXFILE'                                                      => 'Datei',\n'OXSEARCHKEYS'                                                => 'Suchbegriffe',\n'OXTEMPLATE'                                                  => 'Alt. Template',\n'OXQUESTIONEMAIL'                                             => 'E-Mail für Anfragen',\n'OXISSEARCH'                                                  => 'Kann gesucht werden',\n'OXISCONFIGURABLE'                                            => 'Artikel ist individualisierbar',\n'OXVARNAME'                                                   => 'Name der Variante',\n'OXVARMINPRICE'                                               => 'Preis',\n'OXFOLDER'                                                    => 'Ordner',\n'OXSORT'                                                      => 'Sortierung',\n'OXSOLDAMOUNT'                                                => 'Verkaufte Anzahl',\n'OXNONMATERIAL'                                               => 'Immateriell',\n'OXFREESHIPPING'                                              => 'Versandkostenfrei',\n'OXREMINDACTIVE'                                              => 'Benachrichtigung für geringen Lagerbestand aktiv',\n'OXREMINDAMOUNT'                                              => 'Mindestmenge für geringen Lagerbestand',\n'OXVENDORID'                                                  => 'Lieferanten-ID',\n'OXMANUFACTURERID'                                            => 'Hersteller-ID',\n'OXVARCOUNT'                                                  => 'Anzahl der Varianten',\n'OXSHOPINCL'                                                  => 'Shop einschließen',\n'OXSHOPEXCL'                                                  => 'Shop ausschließen',\n'OXEAN'                                                       => 'EAN',\n'OXMPN'                                                       => 'Hersteller-Artikelnummer (MPN)',\n'OXDISTEAN'                                                   => 'Hersteller-EAN',\n'OXSTOCKTEXT'                                                 => 'Info falls Artikel auf Lager',\n'OXNOSTOCKTEXT'                                               => 'Info falls Artikel nicht auf Lager',\n'OXSKIPDISCOUNTS'                                             => 'Nachlässe ignorieren',\n'OXRATINGCNT'                                                 => 'Bewertung Anzahl',\n'OXRATING'                                                    => 'Bewertung',\n'OXRRVIEW'                                                    => 'Ausschließlich sichtbar',\n'OXRRBUY'                                                     => 'Ausschließlich kaufbar',\n'OXORDERINFO'                                                 => 'Bestell. Info',\n'OXSEOID'                                                     => 'SEOID',\n'OXMINDELTIME'                                                => 'Min. Lieferzeit',\n'OXMAXDELTIME'                                                => 'Max. Lieferzeit',\n'OXDELTIMEUNIT'                                               => 'Lieferzeit Einheit',\n'OXVPE'                                                       => 'Verpackungseinheit',\n'OXBUNDLEID'                                                  => 'Bundle Identnr.',\n'OXVARSTOCK'                                                  => 'Varianten Lagerbestand',\n//used as field translations ^\n'ERROR_DELIVERY_ADDRESS_WAS_CHANGED_DURING_CHECKOUT'          => 'Rechnungs- oder Lieferadresse wurde während der Bestellung geändert. Bitte noch einmal prüfen.',\n'_UNIT_KG'                                                    => 'kg',\n'_UNIT_G'                                                     => 'g',\n'_UNIT_L'                                                     => 'l',\n'_UNIT_ML'                                                    => 'ml',\n'_UNIT_CM'                                                    => 'cm',\n'_UNIT_MM'                                                    => 'mm',\n'_UNIT_M'                                                     => 'm',\n'_UNIT_M2'                                                    => 'm²',\n'_UNIT_M3'                                                    => 'm³',\n'_UNIT_PIECE'                                                 => 'Stück',\n'_UNIT_ITEM'                                                  => 'Teil',\n'DOWNLOADS_EMPTY'                                             => 'Sie haben bisher noch keine Dateien bestellt.',\n'DOWNLOADS_PAYMENT_PENDING'                                   => 'Die Bezahlung der Bestellung ist noch nicht abgeschlossen.',\n'MY_DOWNLOADS'                                                => 'Meine Downloads',\n'MY_DOWNLOADS_DESC'                                           => 'Laden Sie Ihre bestellten Dateien hier herunter.',\n'MESSAGE_MY_DOWNLOADS_LINK_EXPIRED'                           => 'Downloadlink war abgelaufen. Bitte erneut versuchen.',\n'LINK_VALID_UNTIL'                                            => 'Downloadlink ist gültig bis',\n'LEFT_DOWNLOADS'                                              => 'Verbleibende Downloads',\n'START_DOWNLOADING_UNTIL'                                     => 'Bitte Download starten bis',\n'DOWNLOAD_LINK_EXPIRED_OR_MAX_COUNT_RECEIVED'                 => 'Downloadlink abgelaufen oder maximale Anzahl der Downloads erreicht.',\n'MONTH_NAME_1'                                                => 'Januar',\n'MONTH_NAME_2'                                                => 'Februar',\n'MONTH_NAME_3'                                                => 'März',\n'MONTH_NAME_4'                                                => 'April',\n'MONTH_NAME_5'                                                => 'Mai',\n'MONTH_NAME_6'                                                => 'Juni',\n'MONTH_NAME_7'                                                => 'Juli',\n'MONTH_NAME_8'                                                => 'August',\n'MONTH_NAME_9'                                                => 'September',\n'MONTH_NAME_10'                                               => 'Oktober',\n'MONTH_NAME_11'                                               => 'November',\n'MONTH_NAME_12'                                               => 'Dezember',\n'COOKIE_NOTE'                                                 => 'Dieser Online-Shop verwendet Cookies für ein optimales Einkaufserlebnis. Dabei werden beispielsweise die Session-Informationen oder die Spracheinstellung auf Ihrem Rechner gespeichert. Ohne Cookies ist der Funktionsumfang des Online-Shops eingeschränkt.',\n'COOKIE_NOTE_DISAGREE'                                        => 'Sind Sie damit nicht einverstanden, klicken Sie bitte hier.',\n\n'BASKET_TOTAL_WRAPPING_COSTS_NET'                             => 'Geschenkverpackung (netto)',\n'BASKET_TOTAL_GIFTCARD_COSTS_NET'                             => 'Grußkarte (netto)',\n'BASKET_TOTAL_PLUS_PROPORTIONAL_VAT'                          => 'plus MwSt. (anteilig berechnet)',\n'PROPORTIONALLY_CALCULATED'                                   => 'Anteilig berechnet',\n'PRICE_FROM'                                                  => 'ab',\n'PAGE_DETAILS_THANKYOUMESSAGE1'                               => 'Vielen Dank für Ihre Nachricht an',\n'PAGE_DETAILS_THANKYOUMESSAGE2'                               => '.',\n'PAGE_DETAILS_THANKYOUMESSAGE3'                               => 'Sie bekommen eine Nachricht von uns sobald der Preis unter',\n'PAGE_DETAILS_THANKYOUMESSAGE4'                               => 'fällt.',\n'PAGE_TITLE_START'                                            => 'Startseite',\n'PAGE_TITLE_BASKET'                                           => 'Warenkorb',\n'PAGE_TITLE_USER'                                             => 'Lieferadresse',\n'PAGE_TITLE_PAYMENT'                                          => 'Versand & Zahlungsart ',\n'PAGE_TITLE_ORDER'                                            => 'Bestellung',\n'PAGE_TITLE_THANKYOU'                                         => 'Vielen Dank',\n'PAGE_TITLE_REGISTER'                                         => 'Registrieren',\n'PAGE_TITLE_ACCOUNT'                                          => 'Mein Konto',\n'PAGE_TITLE_ACCOUNT_ORDER'                                    => 'Bestellhistorie',\n'PAGE_TITLE_ACCOUNT_DOWNLOADS'                                => 'Meine Downloads',\n'PAGE_TITLE_ACCOUNT_NOTICELIST'                               => 'Mein Merkzettel',\n'PAGE_TITLE_ACCOUNT_WISHLIST'                                 => 'Mein Wunschzettel',\n'PAGE_TITLE_ACCOUNT_RECOMMLIST'                               => 'Meine Lieblingslisten',\n'PAGE_TITLE_ACCOUNT_PASSWORD'                                 => 'Passwort ändern',\n'PAGE_TITLE_ACCOUNT_NEWSLETTER'                               => 'Newslettereinstellungen',\n'PAGE_TITLE_ACCOUNT_USER'                                     => 'Rechnungs- und Lieferadressen',\n'PAGE_TITLE_COMPARE'                                          => 'Artikelvergleich',\n'PAGE_TITLE_WISHLIST'                                         => 'Merkzettel',\n'PAGE_TITLE_CONTACT'                                          => 'Kontakt',\n'PAGE_TITLE_LINKS'                                            => 'Links',\n'PAGE_TITLE_SEARCH'                                           => 'Suche',\n'PAGE_TITLE_CLEARCOOKIES'                                     => 'Information über Cookies',\n'PAGE_TITLE_SUGGEST'                                          => 'Artikel weiterempfehlen',\n'PAGE_TITLE_INVITE'                                           => 'Freunde einladen',\n'PAGE_TITLE_REVIEW'                                           => 'Bewerten',\n\n'WISHLIST_PRODUCTS'                                           => 'Diese Artikel hat sich %s gewünscht. Wenn Sie ihr/ihm eine Freude machen wollen, dann kaufen Sie einen oder mehrere von diesen Artikeln.',\n\n'BETA_NOTE'                                                   => 'Willkommen ',\n'BETA_NOTE_RELEASE_BETA'                                      => 'zur Beta',\n'BETA_NOTE_RELEASE_RC'                                        => 'zum Release-Kandidaten',\n'BETA_NOTE_MIDDLE'                                            => ' des OXID eShop ',\n'BETA_NOTE_FAQ'                                               => '. Häufig gestellte Fragen und Antworten sind in der %s gelistet.',\n\n'NO_LISTMANIA_LIST'                                           => 'Es liegen zur Zeit keine Lieblingslisten vor. Um eine neue Lieblingsliste anzulegen, bitte ',\n'DETAILS_VPE_MESSAGE'                                         => 'Dieser Artikel kann nur in Verpackungseinheiten zu je %s erworben werden.',\n'DETAILS_CHOOSEVARIANT'                                       => 'Bitte wählen Sie eine Variante',\n'INVITE_TO_SHOP'                                              => 'Eine Einladung von %s %s zu besuchen.',\n'DISTRIBUTORS'                                                => 'Lieferanten',\n'MANUFACTURERS'                                               => 'Marken',\n'SERVICES'                                                    => 'Service',\n'FORM_FIELDSET_USER_SHIPPING_ADDITIONALINFO2_TOOLTIP'         => '',\n'FORM_FIELDSET_USER_BILLING_ADDITIONALINFO_TOOLTIP'           => '',\n'FORM_SUGGEST_MESSAGE1'                                       => 'Hallo, Heute habe ich den interessanten Shop',\n'FORM_SUGGEST_MESSAGE2'                                       => 'für dich gefunden. Einfach auf den Link unten klicken, und du gelangst direkt zum Shop.',\n'SHOP_SUGGEST_MESSAGE'                                        => 'Hallo, heute habe ich den interessanten Shop %s für Dich gefunden. Einfach auf den Link unten klicken und Du gelangst direkt zum Shop.',\n'SHOP_SUGGEST_BUY_FOR_ME'                                     => 'Hallo, ich habe mir hier bei %s einen Wunschzettel angelegt. Es wäre toll, wenn Du mir davon etwas kaufen könntest.',\n'GIFT_REGISTRY_SENT_SUCCESSFULLY'                             => 'Ihr Wunschzettel wurde erfolgreich an %s verschickt.',\n'INCLUDE_VAT'                                                 => 'inkl. MwSt.',\n'COD_CHARGE'                                                  => 'Nachnahmegebühr',\n'REGISTERED_YOUR_ORDER'                                       => 'Ihre Bestellung ist unter der Nummer %s bei uns registriert.',\n'THANK_YOU_FOR_ORDER'                                         => 'Vielen Dank für Ihre Bestellung im',\n'PRICE_ALERT_THANK_YOU_MESSAGE'                               => 'Vielen Dank für die Übermittlung Ihres Wunschpreises von %s %s. Sie erhalten eine E-Mail, sobald dieser erreicht ist.',\n'THANK_YOU_MESSAGE'                                           => 'Vielen Dank für Ihre Nachricht an %s.',\n\n'ALL_BRANDS'                                                  => 'Alle Marken',\n'BY_BRAND'                                                    => 'Nach Marke',\n'BY_MANUFACTURER'                                             => 'Nach Hersteller',\n'BY_VENDOR'                                                   => 'Nach Lieferant',\n'SEARCH_RESULT'                                               => 'Suchergebnis für \"%s\"',\n'EMAIL_SENDDOWNLOADS_GREETING'                                => 'Guten Tag',\n'DOWNLOAD_LINKS'                                              => 'Downloadlinks',\n'ORDER_SHIPPED_TO'                                            => 'Die Sendung geht an',\n'PRODUCT_RATING'                                              => 'Artikel bewerten',\n'SHIPMENT_TRACKING'                                           => 'Ihr Link zur Sendungsverfolgung',\n'INFO_ABOUT_COOKIES'                                          => 'Information über Cookies',\n'PARTNERS'                                                    => 'Partner',\n\n'MY_REVIEWS'                                                  => 'Meine Bewertungen',\n];\n"
  },
  {
    "path": "source/Application/translations/de/translit_lang.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\n/**\n * Character list for replacement in SEO URL's\n * @var array SEO replacement list\n */\n$aSeoReplaceChars = [\n    'ä' => 'ae',\n    'ö' => 'oe',\n    'ü' => 'ue',\n    'Ä' => 'Ae',\n    'Ö' => 'Oe',\n    'Ü' => 'Ue',\n    'ß' => 'ss',\n];\n\n$aLang = [\n    'charset' => \"UTF-8\",\n];\n"
  },
  {
    "path": "source/Application/translations/en/cust_lang.php.dist",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\n$sLangName  = \"English\";\n// -------------------------------\n// RESOURCE IDENTIFIER = STRING\n// -------------------------------\n$aLang = [\n\n'charset'                                   => 'UTF-8',\n\n];\n\n/*\n[{ oxmultilang ident=\"GENERAL_YOUWANTTODELETE\" }]\n*/\n"
  },
  {
    "path": "source/Application/translations/en/lang.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\n$sLangName  = \"English\";\n\n// -------------------------------\n// RESOURCE IDENTIFIER = STRING\n// -------------------------------\n$aLang = [\n'charset'                                                     => 'UTF-8',\n'fullDateFormat'                                              => 'Y-m-d H:i:s',\n'simpleDateFormat'                                            => 'Y-m-d',\n'grid'                                                        => 'Grid',\n'infogrid'                                                    => 'Double grid',\n'line'                                                        => 'Line',\n'LIST_DISPLAY_TYPE'                                           => 'View',\n\n'COLON'                                                       => ':',\n'ELLIPSIS'                                                    => '...',\n'ACCESSORIES'                                                 => 'Accessories',\n'ACCOUNT'                                                     => 'Account',\n'ACCOUNT_INFORMATION'                                         => 'Account information',\n'ADD'                                                         => 'add',\n'ADDITIONAL_INFO'                                             => 'Additional info',\n'ADDRESS'                                                     => 'Address',\n'ADDRESSES'                                                   => 'Addresses',\n'ADD_THIS_PAGE_TO'                                            => 'Add this page to',\n'ADD_TO_CART'                                                 => 'Add to cart',\n'ADD_TO_GIFT_REGISTRY'                                        => 'Add to gift registry',\n'ADD_TO_LISTMANIA_LIST'                                       => 'Add to listmania list',\n'ADD_TO_WISH_LIST'                                            => 'Add to wish list',\n'ADD_WRAPPING'                                                => 'Add gift wrap',\n'ADD_YOUR_COMMENTS'                                           => 'Add your comments',\n'ALL'                                                         => 'All',\n'ALL_LISTMANIA'                                               => 'All listmania',\n'ALREADY_CUSTOMER'                                            => 'I am already a customer',\n'APPLY'                                                       => 'Apply',\n'ARTNUM'                                                      => 'Product number',\n'ATENTION_GREETING_CARD'                                      => 'Attention - greeting card!',\n'AUTHOR'                                                      => 'Author',\n'AVAILABLE_ON'                                                => 'Available on',\n'BACK_TO_OVERVIEW'                                            => 'Back to overview',\n'BACK_TO_SHOP'                                                => 'Back to shop',\n'BACK_TO_START_PAGE'                                          => 'back to start page',\n'BANK'                                                        => 'Bank',\n'BANK_DETAILS'                                                => 'Bank details',\n'BANK_ACCOUNT_HOLDER'                                         => 'Account holder',\n'BANK_ACCOUNT_NUMBER'                                         => 'IBAN',\n'BANK_CODE'                                                   => 'BIC',\n'BARGAIN'                                                     => 'Bargain',\n'BARGAIN_PRODUCTS'                                            => 'Bargain products',\n'BASKET_EMPTY'                                                => 'The shopping cart is empty.',\n'BASKET_ITEMS_CHANGED_ERROR'                                  => 'The shopping cart items have been changed.',\n'BIC'                                                         => 'BIC',\n'BILLING_ADDRESS'                                             => 'Billing address',\n'BILLING_SHIPPING_SETTINGS'                                   => 'Billing and shipping addresses',\n'BIRTHDATE'                                                   => 'Date of birth',\n'BLOCK_PRICE'                                                 => 'Block price',\n'CANCEL'                                                      => 'Cancel',\n'CART'                                                        => 'Cart',\n'CATEGORIES'                                                  => 'Categories',\n'CATEGORY'                                                    => 'Category',\n// @deprecated oxmore feature will be removed in v8.0\n'CATEGORY_OVERVIEW'                                           => 'Category overview',\n// END deprecated\n'CATEGORY_PRODUCTS_S'                                         => 'Category/%s',\n'CATEGORY_S'                                                  => 'Category: %s',\n'CELLUAR_PHONE'                                               => 'Cell phone',\n'CHANGE'                                                      => 'Change',\n'CHANGE_ACCOUNT_PASSWORD'                                     => 'Change account password',\n'CHANGE_LANGUAGE_AND_CURRENCY'                                => 'Change language and currency',\n'CHANGE_PASSWORD'                                             => 'Change password',\n'CHARGES'                                                     => 'Charges',\n'CHECKOUT'                                                    => 'Checkout',\n'CHECK_YOUR_ORDER_HISTORY'                                    => 'check your order history',\n'CHOOSE'                                                      => 'Choose',\n'CHOOSE_VARIANT'                                              => 'Choose variant',\n'CLICK_HERE'                                                  => 'click here.',\n'CLOSE'                                                       => 'Close',\n'COMPANY'                                                     => 'Company',\n'COMPARE'                                                     => 'Compare',\n'COMPLETE_MARKED_FIELDS'                                      => 'Please fill in all mandatory fields labeled in bold.',\n'COMPLETE_ORDER'                                              => 'Complete order',\n'CONFIRM_PASSWORD'                                            => 'Confirm password',\n'CONTACT'                                                     => 'Contact',\n'CONTINUE_SHOPPING'                                           => 'Continue shopping',\n'CONTINUE_TO_NEXT_STEP'                                       => 'Continue to the next step',\n'COUNTRY'                                                     => 'Country',\n'COUPON'                                                      => 'Coupon',\n'COUPON_NOT_ACCEPTED'                                         => 'Your coupon \"%s\" couldn\\'t be accepted.',\n'CREATE_PASSWORD'                                             => 'Create password',\n'CURRENT_PRODUCT'                                             => 'Current product',\n'CUSTOMERS_ALSO_BOUGHT'                                       => 'Customers who bought this product also bought',\n'DATE'                                                        => 'Date',\n'DAY'                                                         => 'Day',\n'DELIVERYTIME_DAY'                                            => '%s day',\n'DAYS'                                                        => 'Days',\n'DELIVERYTIME_DAYS'                                           => '%s days',\n'YEAR'                                                        => 'Year',\n'DEDUCTION'                                                   => 'Deduction',\n'DELIVERABLE'                                                 => 'Deliverable',\n'DELIVERYTIME_DELIVERYTIME'                                   => 'Delivery time',\n'DELIVERY_STATUS'                                             => 'Delivery status',\n'DELIVERY_STATUS_ANG'                                         => 'Order received',\n'DELIVERY_STATUS_AUS'                                         => 'Sent',\n'DELIVERY_STATUS_BES'                                         => 'Ordered from distributor',\n'DELIVERY_STATUS_EIN'                                         => 'Picking',\n'DELIVERY_STATUS_HAL'                                         => 'Waiting for money',\n'DELIVERY_STATUS_NLB'                                         => 'Undeliverable',\n'DELIVERY_STATUS_STO'                                         => 'Canceled',\n'DESCRIPTION'                                                 => 'Description',\n'DETAILS'                                                     => 'Details',\n'DISCOUNT'                                                    => 'Discount',\n'DISPLAY_BASKET'                                              => 'Display cart',\n'DO_NOT_WANT_CREATE_ACCOUNT'                                  => '(I don\\'t want to create a customer account.)',\n'EDIT'                                                        => 'Edit',\n'EMAIL'                                                       => 'E-mail',\n'EMAIL_ADDRESS'                                               => 'E-mail address',\n'ENABLE'                                                      => 'Enable',\n'ENTER_COUPON_NUMBER'                                         => 'Enter coupon number',\n'ENTER_EMAIL_OR_NAME'                                         => 'Enter e-mail address or last name',\n'ENTER_NEW_PASSWORD'                                          => 'Please enter a new password.',\n'ERROR'                                                       => 'Error',\n'ERROR_404'                                                   => 'The requested page %s couldn\\'t be found.',\n'ERROR_MESSAGE_ACCESSRIGHT_ACCESSDENIED'                      => 'Access denied - insufficient rights!',\n'ERROR_MESSAGE_ACCESS_DENIED'                                 => 'Access denied.',\n'ERROR_MESSAGE_ARTICLE_ARTICLE_DOES_NOT_EXIST'                => 'Unfortunately the product \"%s\" is not longer available!',\n'ERROR_MESSAGE_ARTICLE_ARTICLE_NOT_BUYABLE'                   => 'Product is not purchasable',\n'ERROR_MESSAGE_ARTICLE_NOPRODUCTID'                           => 'No product ID given!',\n'ERROR_MESSAGE_CHECK_EMAIL'                                   => 'An error occurred sending the e-mail - please check the e-mail address.',\n'EXCEPTION_CONNECTION_NODB'                                   => 'No connection to the database!',\n'ERROR_MESSAGE_COOKIE_NOCOOKIE'                               => 'We are sorry. This action requires cookie support. Please enable cookies or use a browser with cookies enabled to access our site.',\n'ERROR_MESSAGE_FILE_ERRORINFILE'                              => 'Error in file!',\n'ERROR_MESSAGE_INPUT_EMPTYPASS'                               => 'Please enter a password.',\n'ERROR_MESSAGE_INPUT_INVALIDAMOUNT'                           => 'Please enter a valid amount for this product!',\n'ERROR_MESSAGE_INPUT_NOTALLFIELDS'                            => 'Specify a value for this required field.',\n'ERROR_MESSAGE_INPUT_NOVALIDEMAIL'                            => 'Please enter a valid e-mail address',\n'ERROR_MESSAGE_INVITE_INCORRECTEMAILADDRESS'                  => 'Incorrect e-mail address. Please check the e-mail addresses you entered.',\n'ERROR_MESSAGE_MANDATES_EXCEEDED'                             => 'The number of the licensed sub-shops exceeded. Please enter a valid license number or contact',\n'FOR_MORE_INFORMATION'                                        => 'for more information.',\n'ERROR_MESSAGE_NOFILE'                                        => 'No file uploaded',\n'EXCEPTION_NOTALLOWEDTYPE'                                    => 'File type not allowed. Please edit config.inc.php to allow this kind of files.',\n'ERROR_MESSAGE_OUTOFSTOCK_OUTOFSTOCK'                         => 'Not enough items of this product in stock! Available',\n'ERROR_MESSAGE_OXID_ESALES'                                   => 'OXID eSales',\n'ERROR_MESSAGE_OXID_SHOP_ERROR'                               => 'OXID eShop error',\n'ERROR_MESSAGE_PASSWORD_DO_NOT_MATCH'                         => 'Error: passwords don\\'t match.',\n// @deprecated will be removed in next major\n'ERROR_MESSAGE_PASSWORD_EMAIL_INVALID'                        => 'Please enter a valid e-mail address!',\n// END deprecated\n'ERROR_MESSAGE_PASSWORD_LINK_EXPIRED'                         => 'This page is expired. Please use the function \"Forgot password?\" once again.',\n'ERROR_MESSAGE_PASSWORD_TOO_SHORT'                            => 'Error: your password is too short.',\n'ERROR_REVIEW_AND_RATING_NOT_DELETED'                         => 'The review and the star rating could not be deleted',\n'ERROR_MESSAGE_CURRENT_PASSWORD_INVALID'                      => 'Error: your current password is incorrect.',\n'ERROR_MESSAGE_RECOMMLIST_NOTITLE'                            => 'Title field is empty',\n'ERROR_MESSAGE_SYSTEMCOMPONENT_CLASSNOTFOUND'                 => 'Class \"%s\" not found',\n'EXCEPTION_SYSTEMCOMPONENT_CLASSNOTFOUND'                     => 'Class \"%s\" not found!',\n'ERROR_MESSAGE_SYSTEMCOMPONENT_FUNCTIONNOTFOUND'              => 'Function \"%s\" not found',\n'EXCEPTION_SYSTEMCOMPONENT_TEMPLATENOTFOUND'                  => 'Template \"%s\" not found',\n'ERROR_MESSAGE_UNKNOWN_ERROR'                                 => 'Unknown Error',\n'ERROR_MESSAGE_UNLICENSED1'                                   => 'There\\'s something wrong with the license of this shop. Please enter a valid license key or contact',\n'ERROR_MESSAGE_USER_NOVALIDLOGIN'                             => 'Wrong e-mail address or password!',\n'ERROR_MESSAGE_USER_NOVALUES'                                 => 'E-mail address and password have to be entered!',\n'ERROR_MESSAGE_USER_USERCREATIONFAILED'                       => 'Error creating the user!',\n'ERROR_MESSAGE_USER_UPDATE_FAILED'                            => 'Error while updating the user data!',\n'ERROR_MESSAGE_USER_USEREXISTS'                               => 'An error has occurred with this e-mail address. Please contact our support.',\n'ERROR_MESSAGE_VERSION_EXPIRED1'                              => 'Your version is expired. Please contact',\n'ERROR_MESSAGE_VOUCHER_INCORRECTPRICE'                        => 'The total price is too low for this coupon!',\n'ERROR_MESSAGE_VOUCHER_ISRESERVED'                            => 'This coupon is reserved!',\n'ERROR_MESSAGE_VOUCHER_NOTALLOWEDOTHERSERIES'                 => 'Accumulation with coupons of other series is not allowed!',\n'ERROR_MESSAGE_VOUCHER_NOTALLOWEDSAMESERIES'                  => 'Coupon of the same series can not be used for this purchase!',\n'ERROR_MESSAGE_VOUCHER_NOTVALIDUSERGROUP'                     => 'The coupon is not valid for your user group!',\n'ERROR_MESSAGE_VOUCHER_NOVOUCHER'                             => 'Invalid coupon!',\n'ERROR_MESSAGE_COMPLETE_FIELDS_CORRECTLY'                     => 'Please complete all fields correctly!',\n'ERROR_MESSAGE_FILE_DOESNOT_EXIST'                            => 'Downloadable file does not exist any longer.',\n'ERROR_MESSAGE_FILE_DOWNLOAD_FAILED'                          => 'Error downloading file.',\n'ERROR_MESSAGE_WRONG_DOWNLOAD_LINK'                           => 'Download link is not correct.',\n'ERROR_MESSAGE_INCORRECT_DATE'                                => 'Incorrect date',\n'EXCEPTION_NOT_VALID_CURL_CONSTANT'                           => 'Not valid cURL constant name: %s',\n'EXCEPTION_CURL_ERROR'                                        => 'cURL error: %s',\n'EXCEPTION_NON_MATCHING_CSRF_TOKEN'                           => 'CSRF token does not match!',\n'ERROR_MESSAGE_NON_MATCHING_CSRF_TOKEN'                       => 'The action could not be completed. Please try again!',\n'EXPIRES_IN'                                                  => 'Expires in',\n'FAX'                                                         => 'Telefax',\n'FIRST_LAST_NAME'                                             => 'First/last Name',\n'FIRST_NAME'                                                  => 'First name',\n'FORGOT_PASSWORD'                                             => 'Forgot password?',\n'FROM'                                                        => 'From',\n'GIFT_OPTION'                                                 => 'Gift options',\n'GIFT_REGISTRY'                                               => 'Gift registry',\n'GIFT_REGISTRY_EMPTY'                                         => 'The gift registry is empty.',\n'GIFT_REGISTRY_OF'                                            => 'Gift registry of',\n'GIFT_REGISTRY_OF_2'                                          => 'My gift registry at',\n'GIFT_REGISTRY_OF_3'                                          => 'Welcome to the gift registry of',\n'GIFT_REGISTRY_SEARCH_RESULTS'                                => 'Gift registry search results',\n'GIFT_WRAPPING'                                               => 'Gift wrapping',\n'GIFT_WRAPPING_GREETING_CARD'                                 => 'Gift wrapping/greeting card',\n'GO'                                                          => 'GO!',\n'GRAND_TOTAL'                                                 => 'Grand total',\n'GREETING'                                                    => 'Hello, ',\n'GREETING_CARD'                                               => 'Greeting card',\n'GREETING_MESSAGE'                                            => 'Greeting message',\n'GROSS'                                                       => '(incl. tax)',\n'HAVE_A_LOOK'                                                 => 'Have a look at this',\n'HAVE_YOU_FORGOTTEN_PASSWORD'                                 => 'Did you forget your password?',\n'HAVE_YOU_SEEN'                                               => 'Have you seen?',\n'HELP'                                                        => 'Help',\n'HERE_YOU_CAN_ENETER_MESSAGE'                                 => 'Here you can enter an optional message.',\n'HERE_YOU_SET_UP_NEW_PASSWORD'                                => 'No problem! Here you can set up a new password.',\n'HITS_FOR'                                                    => 'Hits for',\n'HOME'                                                        => 'Home',\n'IBAN'                                                        => 'IBAN (International Bank Account Number)',\n'IF_DIFFERENT_FROM_BILLING_ADDRESS'                           => 'If different from billing address.',\n'IMPRESSUM'                                                   => 'Legal notice',\n'INCL_TAX_AND_PLUS_SHIPPING'                                  => '* All prices incl. tax, plus shipping',\n'INFORMATION'                                                 => 'Information',\n'INTRODUCTION'                                                => 'Introduction',\n'INVITE_YOUR_FRIENDS'                                         => 'Invite your friends',\n'ITEMS_IN_BASKET'                                             => 'Items in cart',\n'JUST_ARRIVED'                                                => 'Just arrived!',\n'KEEP_LOGGED_IN'                                              => 'Keep me logged in',\n'KG'                                                          => 'kg',\n'LABEL'                                                       => 'Label',\n'LAST_NAME'                                                   => 'Last name',\n'LAST_SEEN_PRODUCTS'                                          => 'Last seen products',\n'LINKS'                                                       => 'Links',\n'LISTMANIA'                                                   => 'Listmania',\n'LISTMANIA_2'                                                 => 'Listmania/%s',\n'LISTMANIA_LIST_FOR'                                          => 'Listmania list for %s',\n'LISTMANIA_LIST_PRODUCTS'                                     => 'Listmania list %s products',\n'LISTMANIA_LIST_SAVED'                                        => 'Recommendation list changes saved',\n'LISTS'                                                       => 'Lists',\n'LIST_BY'                                                     => 'A list by',\n'LOADING'                                                     => 'Loading...',\n'LOGIN'                                                       => 'Log in',\n'LOGIN_ALREADY_CUSTOMER'                                      => 'If you are already our customer, please log in using your e-mail address and your password.',\n'LOGIN_DESCRIPTION'                                           => 'Please log in using your e-mail address and your password.',\n'LOGIN_TO_ACCESS_GIFT_REGISTRY'                               => 'Please log in to access your gift registry.',\n'LOGIN_TO_ACCESS_LISTMANIA'                                   => 'Please log in to access the listmania list.',\n'LOGIN_TO_ACCESS_WISH_LIST'                                   => 'Please log in to access the wish list.',\n'LOGIN_WITH'                                                  => 'Log in with',\n'LOGOUT'                                                      => 'Log out',\n'LOW_STOCK'                                                   => 'Only some items on stock - order quickly!',\n'MANUFACTURER'                                                => 'Manufacturer',\n'MANUFACTURER_S'                                              => 'Manufacturer: %s',\n'MANY_GREETINGS'                                              => 'Kind regards!,',\n'MEDIA'                                                       => 'Media',\n'MESSAGE'                                                     => 'Message',\n'MESSAGE_ACCOUNT_REGISTRATION_CONFIRMED'                      => 'Your account was created successfully.',\n'MESSAGE_ALREADY_RATED'                                       => 'You already rated!',\n'MESSAGE_BASKET_EXCLUDE_INFO'                                 => 'You can now proceed to checkout and finalize your order. You can also continue shopping, but the content of your cart will be purged then.',\n'MESSAGE_CONFIRMATION_NOT_SUCCEED'                            => 'Unfortunately the e-mail for confirming your order couldn\\'t be sent.',\n'MESSAGE_CONFIRMING_REGISTRATION'                             => 'You should have received an e-mail confirming your registration.',\n'MESSAGE_COUPON_ACCUMULATION_SAME_SERIE'                      => 'An accumulation with another coupons of the same series is not permitted.',\n'MESSAGE_COUPON_EXPIRED'                                      => 'Coupon expired!',\n'MESSAGE_COUPON_NOT_APPLIED_FOR_ARTICLES'                     => 'No discounts assigned to these product items',\n'MESSAGE_DENIED_BY_SHOP_RULES'                                => 'Denied by store rules.',\n'MESSAGE_EMAIL_ALREADY_IN_USE'                                => 'The e-mail address you entered is already in use.',\n'MESSAGE_FROM'                                                => 'Message from',\n'MESSAGE_GET_BONUS_POINTS'                                    => 'Get bonus points for your purchase now!',\n'MESSAGE_INVALID_EMAIL'                                       => 'Invalid e-mail address!',\n'MESSAGE_LOGIN_TO_RATE'                                       => 'Please log in for rating!',\n'MESSAGE_LOGIN_TO_WRITE_REVIEW'                               => 'You have to be logged in to write a review.',\n'MESSAGE_MAKE_GIFT_REGISTRY_PUBLISH'                          => 'Everyone shall be able to search and display my gift registry',\n'MESSAGE_NEGATIVE_TOTAL'                                      => 'A negative amount is not permitted.',\n'MESSAGE_NEWSLETTER_CONGRATULATIONS'                          => 'Congratulations!',\n'MESSAGE_NEWSLETTER_FOR_SUBSCRIPTION_BONUS'                   => 'Just now! Get bonus points for your newsletter subscription!',\n'MESSAGE_NEWSLETTER_SUBSCRIPTION'                             => 'It\\'s possible to cancel the newsletter at any time.',\n'MESSAGE_NEWSLETTER_SUBSCRIPTION_ACTIVATED'                   => 'Your subscription to our newsletter is now active.',\n'MESSAGE_NEWSLETTER_SUBSCRIPTION_CANCELED'                    => 'You unsubscribed successfully from our newsletter.',\n'MESSAGE_NEWSLETTER_SUBSCRIPTION_SUCCESS'                     => 'The successfully subscribed to our newsletter.',\n'MESSAGE_NOT_ABLE_TO_SEND_EMAIL'                              => 'Unfortunately we couldn\\'t send you an e-mail.',\n'MESSAGE_NOT_ON_STOCK'                                        => 'This item is not on stock and has to be re-ordered.',\n'MESSAGE_NO_SHIPPING_METHOD_FOUND'                            => 'No shipping methods found. Please contact us by phone or e-mail!',\n'MESSAGE_PASSWORD_CHANGED'                                    => 'Your password has been changed.',\n'MESSAGE_PAYMENT_AUTHORIZATION_FAILED'                        => 'The payment authorization failed. Please verify your input!',\n'MESSAGE_PAYMENT_SELECT_ANOTHER_PAYMENT'                      => 'Please select a different payment method!',\n'MESSAGE_PAYMENT_BANK_CODE_INVALID'                           => 'Please provide a valid BIC code!',\n'MESSAGE_PAYMENT_ACCOUNT_NUMBER_INVALID'                      => 'Please provide a valid IBAN!',\n'MESSAGE_PAYMENT_UNAVAILABLE_PAYMENT'                         => 'Unfortunately, this payment method is not available due to technical problems. Please choose a different payment method.',\n'MESSAGE_PAYMENT_UNAVAILABLE_PAYMENT_ERROR'                   => 'Unfortunately, this payment method is not available due to technical problems. Please choose a different payment method. (Error',\n'MESSAGE_PLEASE_CONTACT_SUPPORT'                              => 'Please contact OXID eSales support staff.',\n'MESSAGE_PLEASE_DELETE_FOLLOWING_DIRECTORY'                   => 'Please delete the following directory',\n'MESSAGE_PRICE_ALARM_PRICE_CHANGE'                            => 'We will inform you if the price of this product will be changed according to your wished price.',\n'MESSAGE_RATE_THIS_ARTICLE'                                   => 'Rate this product!',\n'MESSAGE_READ_DETAILS'                                        => 'Read our',\n'MESSAGE_SELECT_AT_LEAST_ONE_PRODUCT'                         => 'Please select at least one product!',\n'MESSAGE_SELECT_MORE_PRODUCTS'                                => 'Please select products for comparison!',\n'MESSAGE_SEND_GIFT_REGISTRY'                                  => 'Please click here to send your gift registry to your friends!',\n'MESSAGE_SENT_CONFIRMATION_EMAIL'                             => 'If your e-mail address is not yet subscribed, you will receive a confirmation e-mail.',\n'MESSAGE_SORRY_NO_GIFT_REGISTRY'                              => 'Sorry - no gift registry found!',\n'MESSAGE_STOCK_LOW'                                           => 'Stock low: the specified stock limit for this product has been reached.',\n'MESSAGE_SUBMIT_BOTTOM'                                       => 'Please check all data on this overview before submitting your order!',\n'MESSAGE_THANKYOU_FOR_SUBSCRIBING_NEWSLETTERS'                => 'Thank you for subscribing to our newsletter.',\n'MESSAGE_UNAVAILABLE_SHIPPING_METHOD'                         => 'The shipping method you selected isn\\'t available any more. Please choose another one!',\n'MESSAGE_VERIFY_YOUR_EMAIL'                                   => 'Please verify your e-mail address!',\n'MESSAGE_WELCOME_REGISTERED_USER'                             => 'Welcome as a registered customer!',\n'MESSAGE_WE_WILL_INFORM_YOU'                                  => 'We will inform you immediately if an item is not deliverable.',\n'MESSAGE_WRONG_VERIFICATION_CODE'                             => 'The verification code you entered is not correct. Please try again!',\n'MESSAGE_YOU_RECEIVED_ORDER_CONFIRM'                          => 'You\\'ve already received an e-mail with an order confirmation.',\n'MESSAGE_DOWNLOADABLE_PRODUCT'                                => 'Hint: there are downloadable products in your shopping cart. If you buy without a registration you\\'ll find product download links only in the e-mail with the order confirmation. If you are registered links will appear in the section ACCOUNT -> MY DOWNLOADS.',\n'MIN_ORDER_PRICE'                                             => 'Minimum order price',\n'MONTH'                                                       => 'month',\n'DELIVERYTIME_MONTH'                                          => '%s month',\n'MONTHS'                                                      => 'months',\n'DELIVERYTIME_MONTHS'                                         => '%s months',\n'MORE'                                                        => 'More',\n'MORE_INFO'                                                   => 'More information',\n'MOVE'                                                        => 'Move product',\n'MR'                                                          => 'Mr',\n'MRS'                                                         => 'Mrs',\n'MY_ACCOUNT'                                                  => 'My account',\n'MY_GIFT_REGISTRY'                                            => 'My gift registry',\n'MY_LISTMANIA'                                                => 'My listmania list',\n'MY_ORDER_HISTORY'                                            => 'My order history',\n'MY_PRODUCT_COMPARISON'                                       => 'My product comparison',\n'MY_WISH_LIST'                                                => 'My wish list',\n'NEWEST_SHOP_PRODUCTS'                                        => 'Recent products in shop',\n'NEWLIST'                                                     => 'New listmania list',\n'NEWSLETTER'                                                  => 'Newsletter',\n'NEWSLETTER_SETTINGS'                                         => 'Newsletter settings',\n'NEWSLETTER_SUBSCRIBE_CANCEL'                                 => 'Subscribe/unsubscribe newsletter',\n'NEWSLETTER_SUBSCRIPTION'                                     => 'Subscribe to the newsletter',\n'NEWSLETTER_EMAIL_NOT_EXIST'                                  => 'Unknown e-mail address!',\n'NEW_ADDRESS'                                                 => 'New address',\n'NEW_BASKET_ITEM_MSG'                                         => 'New item was added to cart',\n'NEW_PASSWORD'                                                => 'New password',\n'NEXT'                                                        => 'Next',\n'NEXT_PRODUCT'                                                => 'Next product',\n'NO'                                                          => 'No',\n'NONE'                                                        => 'None',\n'NOTE'                                                        => 'Note',\n'NOT_SHIPPED_YET'                                             => 'Not shipped yet!',\n'NOTREGISTERED_ACCOUNTINFO'                                   => 'Customer information',\n'NOW_ONLY'                                                    => 'Now only',\n'NO_GREETING_CARD'                                            => 'No greeting card',\n'NO_ITEMS_FOUND'                                              => 'Sorry - no items found.',\n'NO_LISTMANIA_LIST_FOUND'                                     => 'No listmania lists found',\n'NO_RATINGS'                                                  => 'No ratings',\n'NO_REVIEW_AVAILABLE'                                         => 'No review available for this product.',\n'NUMBER'                                                      => 'Number',\n'NUMBER_2'                                                    => 'No.',\n'OF'                                                          => 'of',\n'OLD_PASSWORD'                                                => 'Previous password',\n'ONLY_IN_PACKING_UNITS_OF'                                    => 'Only in packaging units of ',\n'OPEN_ACCOUNT'                                                => 'Open account',\n'OR'                                                          => 'or',\n'ORDERS'                                                      => 'Orders',\n'ORDER'                                                       => 'Order',\n'ORDER_COMPLETED'                                             => 'Order completed',\n'ORDER_DATE'                                                  => 'Order date',\n'ORDER_EMPTY_HISTORY'                                         => 'Order history is empty',\n'ORDER_HISTORY'                                               => 'Order history',\n'ORDER_IS_CANCELED'                                           => 'Order is cancelled.',\n'ORDER_NUMBER'                                                => 'Order No.',\n'ORDER_REMARK'                                                => 'Order remark',\n'BRAND'                                                       => 'Brand',\n'OUR_BRANDS'                                                  => 'Our brands',\n'OUR_REGULAR_PRICE'                                           => '(Our regular price)',\n'OXID_ESALES_URL'                                             => 'https://www.oxid-esales.com',\n'OXID_ESALES_URL_TITLE'                                       => 'Shopping cart software by OXID eSales',\n'PAGE'                                                        => 'Page',\n'PASSWORD'                                                    => 'Password',\n'PASSWORD_CHANGED'                                            => 'Your password was changed successfully.',\n'PASSWORD_WAS_SEND_TO'                                        => 'If you have registered, you will receive an e-mail with a link to the password assignment to',\n'PAY'                                                         => 'Pay',\n'PAYMENT_INFORMATION'                                         => 'Payment information',\n'PAYMENT_METHOD'                                              => 'Payment method',\n'PCS'                                                         => 'pcs',\n'PERSONAL_PHONE'                                              => 'Personal phone',\n'PERSONAL_SETTINGS'                                           => 'Personal settings',\n'PHONE'                                                       => 'Phone',\n'PLEASE_CHOOSE'                                               => 'Please choose',\n'PLEASE_SELECT_STATE'                                         => 'Please select a state',\n'PLUS'                                                        => 'plus ',\n'PLUS_SHIPPING'                                               => 'incl. tax, plus ',\n'PLUS_SHIPPING2'                                              => 'shipping',\n'PLUS_SHIPPING3'                                              => '* plus shipping',\n'POSTAL_CODE_AND_CITY'                                        => 'Postal code, city',\n'POSTAL_CODE'                                                 => 'Postal code',\n'POSTAL_CITY'                                                 => 'City',\n'POST_CARD_FROM'                                              => 'A postcard from',\n'PREVIOUS'                                                    => 'Previous',\n'PREVIOUS_STEP'                                               => 'Previous step',\n'PREVIOUS_PRODUCT'                                            => 'Previous product',\n'PRICE'                                                       => 'Price',\n'PRICE_ALERT'                                                 => '[!] Wished price',\n'PRICE_ALERT_AT'                                              => 'Wished price at',\n'PRICE_ALERT_FOR_PRODUCT'                                     => 'Wished price for product',\n'PRINT'                                                       => 'Print this page',\n'PRODUCT'                                                     => 'Product',\n'PRODUCTS'                                                    => ' products',\n'PRODUCTS_PER_PAGE'                                           => 'Products per page',\n'PRODUCT_ATTRIBUTES'                                          => 'Product attributes',\n'PRODUCT_COMPARISON'                                          => 'Product comparison',\n'PRODUCT_DETAILS'                                             => 'Product details',\n'PRODUCT_IMAGES'                                              => 'Product images',\n'PRODUCT_NO'                                                  => 'Item #',\n'PRODUCT_REVIEW'                                              => 'Product review',\n'PUBLIC_GIFT_REGISTRIES'                                      => 'Public gift registries',\n'PUBLISH'                                                     => 'Publish',\n'PURCHASE_WITHOUT_REGISTRATION'                               => 'Purchase without registration',\n'QNT'                                                         => 'qty.',\n'QUANTITY'                                                    => 'Quantity',\n'QUESTIONS_ABOUT_THIS_PRODUCT'                                => 'Questions about product',\n'QUESTIONS_ABOUT_THIS_PRODUCT_2'                              => '[?] Have questions about this product?',\n'RATING'                                                      => 'Rating',\n'RATINGS'                                                     => 'Ratings',\n'READY'                                                       => 'Ready!',\n'READY_FOR_SHIPPING'                                          => 'Ready for shipping',\n'READ_AND_CONFIRM_TERMS'                                      => 'Please read and confirm our terms and conditions!',\n'REASON'                                                      => 'Reason',\n'REBATE'                                                      => 'Discount',\n'RECIPIENT_EMAIL'                                             => 'Recipient\\'s e-mail',\n'RECIPIENT_NAME'                                              => 'Recipient\\'s name',\n'RECOMMEND'                                                   => 'Recommend',\n'RECOMMEND_PRODUCT'                                           => 'Recommend product',\n'REDEEM_COUPON'                                               => 'Redeem coupon',\n'REDUCED_FROM'                                                => 'Reduced from',\n'REDUCED_FROM_2'                                              => 'RRP',\n'REGISTER'                                                    => 'Register',\n'REMEMBER_ME'                                                 => 'Remember password',\n'REMOVE'                                                      => 'Remove',\n'REMOVE_FROM_COMPARE_LIST'                                    => 'Remove from compare list',\n'REQUEST_PASSWORD'                                            => 'Reset password',\n'REQUEST_PASSWORD_AFTERCLICK'                                 => 'You\\'ll receive an e-mail with a link for resetting your password.',\n'RESET_SELECTION'                                             => 'Reset selection',\n'REVIEW'                                                      => 'Review',\n'REVIEW_YOUR_ORDER'                                           => 'Please check your order!',\n'ROOT_CATEGORY_CHANGED'                                       => 'Main category changed',\n'SAVE'                                                        => 'Save',\n'SAVE_RATING_AND_REVIEW'                                      => 'Save review and star rating',\n'SEARCH'                                                      => 'Search',\n'SEARCH_FOR_LISTS'                                            => 'Search for more lists',\n'SEARCH_FOR_PRODUCTS_CATEGORY_VENDOR'                         => 'Products by search for \"%s\" <TAG_CATEGORY> <TAG_VENDOR>',\n'SEARCH_FOR_PRODUCTS_CATEGORY_VENDOR_MANUFACTURER'            => 'Products by search for \"%s\" <TAG_CATEGORY> <TAG_VENDOR> <TAG_MANUFACTURER>',\n'SEARCH_GIFT_REGISTRY'                                        => 'Search gift registry',\n'SELECT'                                                      => 'Select',\n'SELECTED_COMBINATION'                                        => 'Selected combination',\n'SELECTED_SHIPPING_CARRIER'                                   => 'Selected shipping carrier is',\n'SELECT_ALL'                                                  => 'Select all',\n'SELECT_LISTMANIA_LIST'                                       => 'Select listmania list',\n'SELECT_SHIPPING_METHOD'                                      => 'Please select your shipping method',\n'SEND'                                                        => 'Send',\n'SENDER_EMAIL'                                                => 'Sender\\'s e-mail',\n'SENDER_NAME'                                                 => 'Sender\\'s name',\n'SEND_GIFT_REGISTRY'                                          => 'Send gift registry',\n'SEND_INVITE_TO'                                              => 'Send invitation to',\n'SHIPMENT_TO'                                                 => 'Shipment to',\n'SHIPPED'                                                     => 'Shipped',\n'SHIPPING'                                                    => 'Shipping',\n'SHIPPING_ADDRESS'                                            => 'Shipping address',\n'SHIPPING_ADDRESSES'                                          => 'Shipping addresses',\n'SHIPPING_CARRIER'                                            => 'Shipping method',\n'SHIPPING_COST'                                               => 'Shipping costs',\n'SHIPPING_NET'                                                => 'Shipping (excl. tax)',\n'PLUS_VAT'                                                    => 'plus tax',\n'VAT_PLUS_PERCENT_AMOUNT'                                     => 'plus %s%% tax, amount',\n'SURCHARGE_PLUS_PERCENT_AMOUNT'                               => 'Surcharge %s%% tax, amount',\n'SHOPLUPE'                                                    => 'Shoplupe',\n'SIMILAR_PRODUCTS'                                            => 'Similar products',\n'SORT_BY'                                                     => 'Sort by',\n'SPECIFICATION'                                               => 'Specification',\n'STAR'                                                        => 'Star',\n'STARS'                                                       => 'Stars',\n'STATUS'                                                      => 'Status',\n'STAY_INFORMED'                                               => 'Stay informed!',\n'STEPS_BASKET'                                                => '1. Cart',\n'STEPS_ORDER'                                                 => '4. Order',\n'STEPS_PAY'                                                   => '3. Pay',\n'STEPS_SEND'                                                  => '2. Address',\n'STOCK'                                                       => 'Stock',\n'STOCK_LOW'                                                   => 'Stock low',\n'STREET_AND_STREETNO'                                         => 'Street, street#',\n'SUBJECT'                                                     => 'Subject',\n'SUBMIT'                                                      => 'Submit',\n'SUBMIT_COUPON'                                               => 'Submit coupon',\n'SUBMIT_ORDER'                                                => 'Order now',\n'SUBSCRIBE'                                                   => 'Subscribe',\n'SUCCESS'                                                     => 'Success!',\n'SUM'                                                         => 'Sum',\n'SURCHARGE'                                                   => 'Surcharge',\n'S_CATEGORY_PRODUCTS'                                         => 'products in category %s',\n'TERMS_AND_CONDITIONS'                                        => 'Terms and conditions',\n'THANK_YOU'                                                   => 'Thank you',\n'TIME'                                                        => 'Time',\n'TITLE'                                                       => 'Salutation',\n'TO'                                                          => 'To',\n'TOP_OF_THE_SHOP'                                             => 'Top of the shop',\n'TOP_SHOP_PRODUCTS'                                           => 'Top products in the shop',\n'TOTAL'                                                       => 'Total',\n'TOTAL_GROSS'                                                 => 'Total products (incl. tax)',\n'TOTAL_NET'                                                   => 'Total products (excl. tax)',\n'TOTAL_QUANTITY'                                              => 'Total quantity',\n'TO_CART'                                                     => 'To cart',\n'TO_MY_WISHLIST'                                              => 'To go to my wishlist',\n'TRACKING_ID'                                                 => 'Tracking ID',\n'TRACK_SHIPMENT'                                              => 'Where is my shipment?',\n'RATE_OUR_SHOP'                                               => 'Please take a minute to rate our shop.',\n'UNIT_PRICE'                                                  => 'Unit price',\n'UNSUBSCRIBE'                                                 => 'Unsubscribe',\n'UPDATE'                                                      => 'Update',\n'UPDATE_SHIPPING_CARRIER'                                     => 'Update shipping method and costs',\n'UPDATE_YOUR_BILLING_SHIPPING_SETTINGS'                       => 'Update your billing and delivery addresses',\n'USED_COUPONS'                                                => 'The following coupons are used',\n'USED_COUPONS_2'                                              => 'Used coupons,',\n'USE_BILLINGADDRESS_FOR_SHIPPINGADDRESS'                      => 'Use billing address for shipping',\n'VALID_UNTIL'                                                 => 'Valid until',\n'VAT'                                                         => 'VAT',\n'VAT_ID_NUMBER'                                               => 'VAT ID',\n'VAT_MESSAGE_ID_NOT_VALID'                                    => 'VAT ID is invalid',\n'VAT_MESSAGE_COMPANY_MISSING'                                 => 'Please enter your company name along with your VAT ID!',\n'VAT_MESSAGE_MISSING_COUNTRY_PREFIX'                          => 'It is currently not possible to check the VAT ID for the selected country. Please contact the store operator to continue shopping.',\n'ERROR_MESSAGE_INPUT_VAT_PREFIX_EMPTY'                        => 'Please provide a valid VAT Identification Number Prefix when \"Do not bill VAT if VAT ID number provided\" is selected.',\n'VENDOR'                                                      => 'Vendor',\n'VENDOR_S'                                                    => '|Vendor: %s',\n'VERIFICATION_CODE'                                           => 'Verification code',\n'VIEW_ALL_PRODUCTS'                                           => 'View all products',\n'WEEK'                                                        => 'Week',\n'DELIVERYTIME_WEEKS'                                          => '%s weeks',\n'DELIVERYTIME_WEEK'                                           => '%s week',\n'WEEKS'                                                       => 'Weeks',\n'WEEK_SPECIAL'                                                => 'This week\\'s special',\n'WEIGHT'                                                      => 'Weight',\n'WHAT_I_WANTED_TO_SAY'                                        => 'Drop us a message',\n'WHO_BOUGHT_ALSO_BOUGHT'                                      => 'Customers who bought these products, also bought',\n'WISH_LIST'                                                   => 'Wish list',\n'WISH_LIST_EMPTY'                                             => 'Wish list is empty.',\n'WITH_LOVE'                                                   => 'With love,',\n'WRAPPING'                                                    => 'Wrapping',\n'WRAPPING_DESCRIPTION'                                        => 'We\\'re happy to wrap your gift or to add a card with your personal message.',\n'WRAPPING_NET'                                                => 'Gift wrapping/greeting card (w/o tax)',\n'WRITES'                                                      => 'writes',\n'WRITE_PRODUCT_REVIEW'                                        => 'Review product',\n'WRITE_REVIEW'                                                => 'Write a review.',\n'WRITE_REVIEW_2'                                              => 'Review our shop!',\n'YES'                                                         => 'Yes',\n'YOUR_EMAIL_ADDRESS'                                          => 'Your e-mail address',\n'YOUR_GREETING_CARD'                                          => 'Your greeting card',\n'YOUR_PREVIOUS_ORDER'                                         => 'Your previous orders',\n'YOUR_PRICE'                                                  => 'Your price',\n'YOUR_REVIEW'                                                 => 'Your review',\n'YOU_ARE_HERE'                                                => 'You are here',\n'YOU_CAN_GO'                                                  => 'You can go',\n'YOUR_MESSAGE'                                                => 'Your message',\n'YOUR_TEAM'                                                   => 'Your %s team',\n'ZOOM'                                                        => 'Zoom',\n'OXACTIVEFROM'                                                => 'Active from',\n'OXACTIVETO'                                                  => 'Active until',\n'OXARTNUM'                                                    => 'Product number',\n'OXTITLE'                                                     => 'Title',\n'OXID'                                                        => 'Internal ID',\n'OXSHOPID'                                                    => 'Shop ID',\n'OXPARENTID'                                                  => 'Parent product ID',\n'OXACTIVE'                                                    => 'Active',\n'OXSHORTDESC'                                                 => 'Short description',\n'OXLONGDESC'                                                  => 'Long description',\n'OXPRICE'                                                     => 'Price',\n'OXPRICEA'                                                    => 'Price A',\n'OXPRICEB'                                                    => 'Price B',\n'OXPRICEC'                                                    => 'Price C',\n'OXBPRICE'                                                    => 'Price incl. tax',\n'OXTPRICE'                                                    => 'Old price',\n'OXEXTURL'                                                    => 'External URL',\n'OXUNITNAME'                                                  => 'Unit',\n'OXUNITQUANTITY'                                              => 'Quantity unit',\n'OXURLDESC'                                                   => 'URL description',\n'OXURLIMG'                                                    => 'External URL image',\n'OXVAT'                                                       => 'Product VAT',\n'OXTHUMB'                                                     => 'Preview picture',\n'OXPIC1'                                                      => 'Picture 1',\n'OXPIC2'                                                      => 'Picture 2',\n'OXPIC3'                                                      => 'Picture 3',\n'OXPIC4'                                                      => 'Picture 4',\n'OXPIC5'                                                      => 'Picture 5',\n'OXPIC6'                                                      => 'Picture 6',\n'OXPIC7'                                                      => 'Picture 7',\n'OXPIC8'                                                      => 'Picture 8',\n'OXPIC9'                                                      => 'Picture 9',\n'OXPIC10'                                                     => 'Picture 10',\n'OXPIC11'                                                     => 'Picture 11',\n'OXPIC12'                                                     => 'Picture 12',\n'OXZOOM1'                                                     => 'Zoom picture 1',\n'OXZOOM2'                                                     => 'Zoom picture 2',\n'OXZOOM3'                                                     => 'Zoom picture 3',\n'OXZOOM4'                                                     => 'Zoom picture 4',\n'OXWEIGHT'                                                    => 'Weight',\n'OXSTOCK'                                                     => 'Stock',\n'OXSTOCKACTIVE'                                               => 'Stock control active',\n'OXSTOCKFLAG'                                                 => 'Delivery status',\n'OXDELIVERY'                                                  => 'Delivered on',\n'OXINSERT'                                                    => 'Created on',\n'OXTIMESTAMP'                                                 => 'Last modification',\n'OXLENGTH'                                                    => 'Length',\n'OXWIDTH'                                                     => 'Width',\n'OXHEIGHT'                                                    => 'Height',\n'OXFILE'                                                      => 'File',\n'OXSEARCHKEYS'                                                => 'Search keys',\n'OXTEMPLATE'                                                  => 'Alt. template',\n'OXQUESTIONEMAIL'                                             => 'E-mail for requests',\n'OXISSEARCH'                                                  => 'Is searchable',\n'OXISCONFIGURABLE'                                            => 'Product is customizable',\n'OXVARNAME'                                                   => 'Variant name',\n'OXVARMINPRICE'                                               => 'Price',\n'OXFOLDER'                                                    => 'Folder',\n'OXSORT'                                                      => 'Sorting',\n'OXSOLDAMOUNT'                                                => 'Quantity sold',\n'OXNONMATERIAL'                                               => 'Immaterial',\n'OXFREESHIPPING'                                              => 'Free shipping',\n'OXREMINDACTIVE'                                              => 'Notification active if stock is low',\n'OXREMINDAMOUNT'                                              => 'Threshold for low stock',\n'OXVENDORID'                                                  => 'Vendor ID',\n'OXMANUFACTURERID'                                            => 'Manufacturer ID',\n'OXVARCOUNT'                                                  => 'Number of variants',\n'OXSHOPINCL'                                                  => 'Include shop',\n'OXSHOPEXCL'                                                  => 'Exclude shop',\n'OXEAN'                                                       => 'EAN',\n'OXMPN'                                                       => 'MPN',\n'OXDISTEAN'                                                   => 'Vendor EAN',\n'OXSTOCKTEXT'                                                 => 'In-Stock Message',\n'OXNOSTOCKTEXT'                                               => 'Out-Of-Stock Mess.',\n'OXSKIPDISCOUNTS'                                             => 'Skip discounts',\n'OXRATINGCNT'                                                 => 'Rating Count',\n'OXRATING'                                                    => 'Rating',\n'OXRRVIEW'                                                    => 'Exclusive viewable',\n'OXRRBUY'                                                     => 'Exclusive buyable',\n'OXORDERINFO'                                                 => 'Order info',\n'OXSEOID'                                                     => 'SEOID',\n'OXMINDELTIME'                                                => 'Min. delivery time',\n'OXMAXDELTIME'                                                => 'Max. delivery time',\n'OXDELTIMEUNIT'                                               => 'Delivery time unit',\n'OXVPE'                                                       => 'Packingunit',\n'OXBUNDLEID'                                                  => 'Bundle Identno',\n'OXVARSTOCK'                                                  => 'Variant Stock',\n'ERROR_DELIVERY_ADDRESS_WAS_CHANGED_DURING_CHECKOUT'          => 'Billing or shipping address have been changed during checkout. Please double check.',\n'_UNIT_KG'                                                    => 'kg',\n'_UNIT_G'                                                     => 'g',\n'_UNIT_L'                                                     => 'l',\n'_UNIT_ML'                                                    => 'ml',\n'_UNIT_CM'                                                    => 'cm',\n'_UNIT_MM'                                                    => 'mm',\n'_UNIT_M'                                                     => 'm',\n'_UNIT_M2'                                                    => 'm²',\n'_UNIT_M3'                                                    => 'm³',\n'_UNIT_PIECE'                                                 => 'piece',\n'_UNIT_ITEM'                                                  => 'item',\n'DOWNLOADS_EMPTY'                                             => 'You have not ordered any files yet.',\n'DOWNLOADS_PAYMENT_PENDING'                                   => 'Payment of the order is not yet complete.',\n'MY_DOWNLOADS'                                                => 'My downloads',\n'MY_DOWNLOADS_DESC'                                           => 'Download your ordered files here.',\n'MESSAGE_MY_DOWNLOADS_LINK_EXPIRED'                           => 'Download link has been expired, try it now.',\n'LINK_VALID_UNTIL'                                            => 'Download link is valid until',\n'LEFT_DOWNLOADS'                                              => 'Remaining downloads',\n'START_DOWNLOADING_UNTIL'                                     => 'Please, start downloading until',\n'DOWNLOAD_LINK_EXPIRED_OR_MAX_COUNT_RECEIVED'                 => 'Download link expired or max. number of downloads reached.',\n'MONTH_NAME_1'                                                => 'January',\n'MONTH_NAME_2'                                                => 'February',\n'MONTH_NAME_3'                                                => 'March',\n'MONTH_NAME_4'                                                => 'April',\n'MONTH_NAME_5'                                                => 'May',\n'MONTH_NAME_6'                                                => 'June',\n'MONTH_NAME_7'                                                => 'July',\n'MONTH_NAME_8'                                                => 'August',\n'MONTH_NAME_9'                                                => 'September',\n'MONTH_NAME_10'                                               => 'October',\n'MONTH_NAME_11'                                               => 'November',\n'MONTH_NAME_12'                                               => 'December',\n'COOKIE_NOTE'                                                 => 'This online shop is using cookies to give you the best shopping experience. Thereby for example the session information or language setting are stored on your computer. Without cookies the range of the online shop\\'s functionality is limited.',\n'COOKIE_NOTE_DISAGREE'                                        => 'If you don\\'t agree, please click here.',\n'BASKET_TOTAL_WRAPPING_COSTS_NET'                             => 'Gift wrapping (excl. tax)',\n'BASKET_TOTAL_GIFTCARD_COSTS_NET'                             => 'Greeting card (excl. tax)',\n'BASKET_TOTAL_PLUS_PROPORTIONAL_VAT'                          => 'plus tax (calculated proportionally)',\n'PROPORTIONALLY_CALCULATED'                                   => 'Calculated proportionally',\n'PRICE_FROM'                                                  => 'from',\n'PAGE_DETAILS_THANKYOUMESSAGE1'                               => 'Thank you.',\n'PAGE_DETAILS_THANKYOUMESSAGE2'                               => ' appreciates your comments.',\n'PAGE_DETAILS_THANKYOUMESSAGE3'                               => 'We will inform you as soon as the price falls below',\n'PAGE_DETAILS_THANKYOUMESSAGE4'                               => '.',\n'PAGE_TITLE_START'                                            => 'Home page',\n'PAGE_TITLE_BASKET'                                           => 'Cart',\n'PAGE_TITLE_USER'                                             => 'Shipping address',\n'PAGE_TITLE_PAYMENT'                                          => 'Shipping & payment',\n'PAGE_TITLE_ORDER'                                            => 'Order',\n'PAGE_TITLE_THANKYOU'                                         => 'Thank you',\n'PAGE_TITLE_REGISTER'                                         => 'Register',\n'PAGE_TITLE_ACCOUNT'                                          => 'My account',\n'PAGE_TITLE_ACCOUNT_ORDER'                                    => 'My order history',\n'PAGE_TITLE_ACCOUNT_DOWNLOADS'                                => 'My downloads',\n'PAGE_TITLE_ACCOUNT_NOTICELIST'                               => 'My wish list',\n'PAGE_TITLE_ACCOUNT_WISHLIST'                                 => 'My gift registry',\n'PAGE_TITLE_ACCOUNT_RECOMMLIST'                               => 'Listmania',\n'PAGE_TITLE_ACCOUNT_PASSWORD'                                 => 'Change password',\n'PAGE_TITLE_ACCOUNT_NEWSLETTER'                               => 'Newsletter settings',\n'PAGE_TITLE_ACCOUNT_USER'                                     => 'Billing and shipping addresses',\n'PAGE_TITLE_COMPARE'                                          => 'Product comparison',\n'PAGE_TITLE_WISHLIST'                                         => 'Wish list',\n'PAGE_TITLE_CONTACT'                                          => 'Contact',\n'PAGE_TITLE_LINKS'                                            => 'Links',\n'PAGE_TITLE_SEARCH'                                           => 'Search',\n'PAGE_TITLE_CLEARCOOKIES'                                     => 'Information about cookies',\n'PAGE_TITLE_SUGGEST'                                          => 'Recommend product',\n'PAGE_TITLE_INVITE'                                           => 'Invite your friends',\n'PAGE_TITLE_REVIEW'                                           => 'Review',\n\n'WISHLIST_PRODUCTS'                                           => 'These products are on the wish list of %s. If you want to please him/her, purchase one or more of these products.',\n\n\n'BETA_NOTE'                                                   => 'Welcome to ',\n'BETA_NOTE_RELEASE_BETA'                                      => 'Beta',\n'BETA_NOTE_RELEASE_RC'                                        => 'Release candidate',\n'BETA_NOTE_MIDDLE'                                            => ' of OXID eShop ',\n'BETA_NOTE_FAQ'                                               => '. Please refer to our %s if you have any questions.',\n\n\n'NO_LISTMANIA_LIST'                                           => 'There is no listmania lists at the moment. To create a new list, please ',\n'DETAILS_VPE_MESSAGE'                                         => 'This product can only be ordered in packaging units of %s',\n'DETAILS_CHOOSEVARIANT'                                       => 'Please select a variant',\n'INVITE_TO_SHOP'                                              => 'An invitation from %s to visit %s',\n'DISTRIBUTORS'                                                => 'Distributors',\n'MANUFACTURERS'                                               => 'Brands',\n'SERVICES'                                                    => 'Service',\n'FORM_FIELDSET_USER_SHIPPING_ADDITIONALINFO2_TOOLTIP'         => '',\n'FORM_FIELDSET_USER_BILLING_ADDITIONALINFO_TOOLTIP'           => '',\n'FORM_SUGGEST_MESSAGE1'                                       => 'Hello, I was looking at',\n'FORM_SUGGEST_MESSAGE2'                                       => 'today and found something that might be interesting for you. Just click on the link below and you will be directed to the shop.',\n'SHOP_SUGGEST_MESSAGE'                                        => 'Hello, I was looking at %s today and found something that might be interesting for you. Just click on the link below and you will be directed to the shop.',\n'SHOP_SUGGEST_BUY_FOR_ME'                                     => 'Hi, I created a gift registry at %s . Thank you for your chosen gift purchase!',\n'GIFT_REGISTRY_SENT_SUCCESSFULLY'                             => 'Your gift registry was sent successfully to %s.',\n'INCLUDE_VAT'                                                 => 'incl. tax',\n'COD_CHARGE'                                                  => 'C.O.D. charge',\n'REGISTERED_YOUR_ORDER'                                       => 'We registered your order with number %s ',\n'THANK_YOU_FOR_ORDER'                                         => 'Thank you for ordering at',\n'PRICE_ALERT_THANK_YOU_MESSAGE'                               => 'Many thanks for the transmission of your wished price of %s %s. You will receive an e-mail as soon as this is reached.',\n'THANK_YOU_MESSAGE'                                           => 'Thank you for your message to %s.',\n\n\n'ALL_BRANDS'                                                  => 'All brands',\n'BY_BRAND'                                                    => 'By brand',\n'BY_MANUFACTURER'                                             => 'By manufacturer',\n'BY_VENDOR'                                                   => 'By distributor',\n'SEARCH_RESULT'                                               => 'Search result for \"%s\"',\n'EMAIL_SENDDOWNLOADS_GREETING'                                => 'Hello',\n'DOWNLOAD_LINKS'                                              => 'Download links',\n'ORDER_SHIPPED_TO'                                            => 'The order is shipped to',\n'PRODUCT_RATING'                                              => 'Product rating',\n'SHIPMENT_TRACKING'                                           => 'Your shipment tracking URL',\n'INFO_ABOUT_COOKIES'                                          => 'Information about cookies',\n'PARTNERS'                                                    => 'Partners',\n\n'MY_REVIEWS'                                                  => 'My reviews',\n];\n"
  },
  {
    "path": "source/Application/translations/en/translit_lang.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\n/**\n * Character list for replacement in SEO URL's\n * @var array SEO replacement list\n */\n$aSeoReplaceChars = [\n    'ä' => 'ae',\n    'ö' => 'oe',\n    'ü' => 'ue',\n    'Ä' => 'Ae',\n    'Ö' => 'Oe',\n    'Ü' => 'Ue',\n    'ß' => 'ss',\n];\n\n$aLang = [\n    'charset' => \"UTF-8\",\n];\n"
  },
  {
    "path": "source/Core/AdminLogSqlDecorator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Decorator for\n */\nclass AdminLogSqlDecorator\n{\n    protected $table = 'oxadminlog';\n\n    /**\n     * Injects argument to admin log insert sql.\n     *\n     * @param string $originalSql\n     * @return string\n     */\n    public function prepareSqlForLogging($originalSql)\n    {\n        $userId = $this->getUserId();\n\n        return \"insert into {$this->table} (oxuserid, oxsql) values ('{$userId}', \" . $this->quote($originalSql) . \")\";\n    }\n\n    /**\n     * Get currently logged in admin user id\n     *\n     * @return string\n     */\n    protected function getUserId()\n    {\n        $user = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n        if ($user->loadAdminUser()) {\n            return $user->getId();\n        }\n    }\n\n    /**\n     * Quotes the string for saving in database field;\n     *\n     * @param string $str\n     *\n     * @return string\n     */\n    protected function quote($str)\n    {\n        return \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->quote($str);\n    }\n}\n"
  },
  {
    "path": "source/Core/Autoload/BackwardsCompatibilityAutoload.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Autoload;\n\n/**\n * This class autoloads backwards compatible classes by triggering the composer autoloader via a unified namespace\n * class.\n *\n * @internal Do not make a module extension for this class.\n */\nclass BackwardsCompatibilityAutoload\n{\n    /**\n     * Autoload method.\n     *\n     * @param string $class Name of the class to be loaded\n     *\n     * @return bool\n     */\n    public static function autoload($class)\n    {\n        /**\n         * Classes from unified namespace canot be loaded by this auto loader.\n         * Do not try to load them in order to avoid strange errors in edge cases.\n         */\n        if (false !== strpos($class, 'OxidEsales\\Eshop\\\\')) {\n            return false;\n        }\n\n        $unifiedNamespaceClassName = static::getUnifiedNamespaceClassForBcAlias($class);\n        if (!empty($unifiedNamespaceClassName)) {\n            static::forceBackwardsCompatiblityClassLoading($unifiedNamespaceClassName);\n        }\n    }\n\n    /**\n     * Return the name of a Unified Namespace class for a given backwards compatible class\n     *\n     * @param string $bcAlias Name of the backwards compatible class like oxArticle\n     *\n     * @return string Name of the unified namespace class like OxidEsales\\Eshop\\Application\\Model\\Article\n     */\n    private static function getUnifiedNamespaceClassForBcAlias($bcAlias)\n    {\n        $classMap = static::getBackwardsCompatibilityClassMap();\n        $bcAlias = strtolower($bcAlias);\n        $result = isset($classMap[$bcAlias]) ? $classMap[$bcAlias] : \"\";\n\n        return $result;\n    }\n\n    /**\n     * This triggers loading the unified namespace class via composer autoloader and also the\n     * aliasing of the backwards compatible class.\n     *\n     * @param string $class Name of the class to load\n     */\n    private static function forceBackwardsCompatiblityClassLoading($class)\n    {\n        class_exists($class);\n    }\n\n    /**\n     * Return the backwards compatible class map.\n     *\n     * @return array Mapping of Unified Namespace to backwards compatible classes.\n     */\n    private static function getBackwardsCompatibilityClassMap()\n    {\n        return (new BackwardsCompatibilityClassMapProvider())->getMap();\n    }\n}\n"
  },
  {
    "path": "source/Core/Autoload/BackwardsCompatibilityClassMap.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\n/**\n * @internal Do not make a module extension based on this file.\n */\nreturn [\n    'oxcmp_basket' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\BasketComponent',\n    'oxcmp_categories' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\CategoriesComponent',\n    'oxcmp_cur' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\CurrencyComponent',\n    'oxcmp_lang' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\LanguageComponent',\n    'oxlocator' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\Locator',\n    'oxelement2shoprelationsservice' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\Service\\\\Element2ShopRelationsService',\n    'oxcmp_shop' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\ShopComponent',\n    'oxcmp_user' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\UserComponent',\n    'oxcmp_utils' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\UtilsComponent',\n    'oxwactions' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\Widget\\\\Actions',\n    'oxwarticlebox' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\Widget\\\\ArticleBox',\n    'oxwarticledetails' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\Widget\\\\ArticleDetails',\n    'oxwcategorytree' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\Widget\\\\CategoryTree',\n    'oxwcookienote' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\Widget\\\\CookieNote',\n    'oxwcurrencylist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\Widget\\\\CurrencyList',\n    'oxwinformation' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\Widget\\\\Information',\n    'oxwlanguagelist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\Widget\\\\LanguageList',\n    'oxwmanufacturerlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\Widget\\\\ManufacturerList',\n    'oxwminibasket' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\Widget\\\\MiniBasket',\n    'oxwrating' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\Widget\\\\Rating',\n    'oxwrecommendation' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\Widget\\\\Recommendation',\n    'oxwreview' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\Widget\\\\Review',\n    'oxwservicelist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\Widget\\\\ServiceList',\n    'oxwservicemenu' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\Widget\\\\ServiceMenu',\n    'oxwvendorlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\Widget\\\\VendorList',\n    'oxwidget' => 'OxidEsales\\\\Eshop\\\\Application\\\\Component\\\\Widget\\\\WidgetController',\n    'account' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\AccountController',\n    'account_downloads' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\AccountDownloadsController',\n    'account_newsletter' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\AccountNewsletterController',\n    'account_noticelist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\AccountNoticeListController',\n    'account_order' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\AccountOrderController',\n    'account_password' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\AccountPasswordController',\n    'account_recommlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\AccountRecommlistController',\n    'account_user' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\AccountUserController',\n    'account_wishlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\AccountWishlistController',\n    'actions_article_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ActionsArticleAjax',\n    'actions' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ActionsController',\n    'actions_groups_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ActionsGroupsAjax',\n    'actions_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ActionsList',\n    'actions_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ActionsMain',\n    'actions_main_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ActionsMainAjax',\n    'actions_order_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ActionsOrderAjax',\n    'admin_beroles' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AdminBackEndRoles',\n    'admin_content' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AdminContent',\n    'oxadminview' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AdminController',\n    'oxadmindetails' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AdminDetailsController',\n    'admin_feroles' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AdminFrontEndRoles',\n    'admin_links' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AdminLinks',\n    'adminlinks_mall' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AdminLinksMall',\n    'oxadminlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AdminListController',\n    'admin_mall' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AdminMall',\n    'admin_newsletter' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AdminNewsletter',\n    'admin_order' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AdminOrder',\n    'admin_payment' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AdminPayment',\n    'admin_pricealarm' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AdminPriceAlarm',\n    'admin_start' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AdminStart',\n    'admin_user' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AdminUser',\n    'admin_wrapping' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AdminWrapping',\n    'adminlinks_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AdminlinksList',\n    'adminlinks_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AdminlinksMain',\n    'article_accessories_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleAccessoriesAjax',\n    'article_attribute' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleAttribute',\n    'article_attribute_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleAttributeAjax',\n    'article_bundle_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleBundleAjax',\n    'article' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleController',\n    'article_crossselling' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleCrossselling',\n    'article_crossselling_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleCrosssellingAjax',\n    'article_extend' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleExtend',\n    'article_extend_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleExtendAjax',\n    'article_files' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleFiles',\n    'article_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleList',\n    'article_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleMain',\n    'article_mall' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleMall',\n    'article_overview' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleOverview',\n    'article_pictures' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticlePictures',\n    'article_review' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleReview',\n    'article_rights' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleRights',\n    'article_rights_buyable_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleRightsBuyableAjax',\n    'article_rights_visible_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleRightsVisibleAjax',\n    'article_selection_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleSelectionAjax',\n    'article_seo' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleSeo',\n    'article_stock' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleStock',\n    'article_userdef' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleUserdef',\n    'article_variant' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ArticleVariant',\n    'attribute_category' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AttributeCategory',\n    'attribute_category_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AttributeCategoryAjax',\n    'attribute_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AttributeList',\n    'attribute_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AttributeMain',\n    'attribute_main_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AttributeMainAjax',\n    'attribute_mall' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AttributeMall',\n    'attribute_order_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\AttributeOrderAjax',\n    'category' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\CategoryController',\n    'category_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\CategoryList',\n    'category_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\CategoryMain',\n    'category_main_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\CategoryMainAjax',\n    'category_mall' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\CategoryMall',\n    'category_order' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\CategoryOrder',\n    'category_order_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\CategoryOrderAjax',\n    'category_pictures' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\CategoryPictures',\n    'category_rights' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\CategoryRights',\n    'category_rights_buyable_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\CategoryRightsBuyableAjax',\n    'category_rights_visible_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\CategoryRightsVisibleAjax',\n    'category_seo' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\CategorySeo',\n    'category_text' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\CategoryText',\n    'category_update' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\CategoryUpdate',\n    'content_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ContentList',\n    'content_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ContentMain',\n    'content_seo' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ContentSeo',\n    'country' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\CountryController',\n    'country_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\CountryList',\n    'country_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\CountryMain',\n    'delivery_articles' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliveryArticles',\n    'delivery_articles_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliveryArticlesAjax',\n    'delivery_categories_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliveryCategoriesAjax',\n    'delivery' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliveryController',\n    'delivery_groups_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliveryGroupsAjax',\n    'delivery_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliveryList',\n    'delivery_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliveryMain',\n    'delivery_main_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliveryMainAjax',\n    'delivery_mall' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliveryMall',\n    'deliveryset' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliverySetController',\n    'deliveryset_country_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliverySetCountryAjax',\n    'deliveryset_groups_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliverySetGroupsAjax',\n    'deliveryset_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliverySetList',\n    'deliveryset_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliverySetMain',\n    'deliveryset_main_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliverySetMainAjax',\n    'deliveryset_mall' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliverySetMall',\n    'deliveryset_payment' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliverySetPayment',\n    'deliveryset_payment_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliverySetPaymentAjax',\n    'deliveryset_rdfa' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliverySetRdfa',\n    'deliveryset_users' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliverySetUsers',\n    'deliveryset_users_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliverySetUsersAjax',\n    'delivery_users' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliveryUsers',\n    'delivery_users_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DeliveryUsersAjax',\n    'diagnostics' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DiagnosticsController',\n    'diagnostics_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DiagnosticsList',\n    'diagnostics_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DiagnosticsMain',\n    'discount_articles' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DiscountArticles',\n    'discount_articles_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DiscountArticlesAjax',\n    'discount_categories_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DiscountCategoriesAjax',\n    'discount' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DiscountController',\n    'discount_groups_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DiscountGroupsAjax',\n    'discount_item_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DiscountItemAjax',\n    'discount_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DiscountList',\n    'discount_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DiscountMain',\n    'discount_main_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DiscountMainAjax',\n    'discount_mall' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DiscountMall',\n    'discount_users' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DiscountUsers',\n    'discount_users_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DiscountUsersAjax',\n    'dynexportbase' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\DynamicExportBaseController',\n    'genexport' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\GenericExport',\n    'genexport_do' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\GenericExportDo',\n    'genexport_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\GenericExportMain',\n    'genimport' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\GenericImport',\n    'genimport_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\GenericImportMain',\n    'language' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\LanguageController',\n    'language_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\LanguageList',\n    'language_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\LanguageMain',\n    'ajaxlistcomponent' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ListComponentAjax',\n    'list_review' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ListReview',\n    'list_user' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ListUser',\n    'login' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\LoginController',\n    'manufacturer' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ManufacturerController',\n    'manufacturer_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ManufacturerList',\n    'manufacturer_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ManufacturerMain',\n    'manufacturer_main_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ManufacturerMainAjax',\n    'manufacturer_mall' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ManufacturerMall',\n    'manufacturer_seo' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ManufacturerSeo',\n    'module_config' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ModuleConfiguration',\n    'module' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ModuleController',\n    'module_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ModuleList',\n    'module_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ModuleMain',\n    'module_sortlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ModuleSortList',\n    'navigation' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\NavigationController',\n    'oxnavigationtree' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\NavigationTree',\n    'object_seo' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ObjectSeo',\n    'order_address' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\OrderAddress',\n    'order_article' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\OrderArticle',\n    'order_downloads' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\OrderDownloads',\n    'order_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\OrderList',\n    'order_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\OrderMain',\n    'order_overview' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\OrderOverview',\n    'order_remark' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\OrderRemark',\n    'payment_country' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\PaymentCountry',\n    'payment_country_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\PaymentCountryAjax',\n    'payment_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\PaymentList',\n    'payment_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\PaymentMain',\n    'payment_main_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\PaymentMainAjax',\n    'payment_rdfa' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\PaymentRdfa',\n    'pricealarm_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\PriceAlarmList',\n    'pricealarm_mail' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\PriceAlarmMail',\n    'pricealarm_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\PriceAlarmMain',\n    'pricealarm_send' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\PriceAlarmSend',\n    'roles_begroups_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\RolesBackendGroupsAjax',\n    'roles_belist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\RolesBackendList',\n    'roles_bemain' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\RolesBackendMain',\n    'roles_beobject' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\RolesBackendObject',\n    'roles_beuser' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\RolesBackendUser',\n    'roles_beuser_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\RolesBackendUserAjax',\n    'roles_fegroups_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\RolesFrontendGroupsAjax',\n    'roles_felist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\RolesFrontendList',\n    'roles_femain' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\RolesFrontendMain',\n    'roles_feuser' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\RolesFrontendUser',\n    'selectlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\SelectListController',\n    'selectlist_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\SelectListList',\n    'selectlist_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\SelectListMain',\n    'selectlist_main_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\SelectListMainAjax',\n    'selectlist_mall' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\SelectListMall',\n    'selectlist_order_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\SelectListOrderAjax',\n    'shop_cache' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ShopCache',\n    'shop_config' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ShopConfiguration',\n    'shop' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ShopController',\n    'shop_default_category_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ShopDefaultCategoryAjax',\n    'shop_license' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ShopLicense',\n    'shop_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ShopList',\n    'shop_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ShopMain',\n    'shop_mall' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ShopMall',\n    'shop_performance' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ShopPerformance',\n    'shop_rdfa' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ShopRdfa',\n    'shop_seo' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ShopSeo',\n    'shop_system' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ShopSystem',\n    'systeminfo' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\SystemInfoController',\n    'sysreq' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\SystemRequirements',\n    'sysreq_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\SystemRequirementsList',\n    'sysreq_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\SystemRequirementsMain',\n    'theme_config' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ThemeConfiguration',\n    'theme' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ThemeController',\n    'theme_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ThemeList',\n    'theme_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ThemeMain',\n    'tools' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ToolsController',\n    'tools_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ToolsList',\n    'tools_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\ToolsMain',\n    'user_address' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\UserAddress',\n    'user_article' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\UserArticle',\n    'user_extend' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\UserExtend',\n    'usergroup' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\UserGroupController',\n    'usergroup_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\UserGroupList',\n    'usergroup_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\UserGroupMain',\n    'usergroup_main_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\UserGroupMainAjax',\n    'user_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\UserList',\n    'user_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\UserMain',\n    'user_main_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\UserMainAjax',\n    'user_overview' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\UserOverview',\n    'user_payment' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\UserPayment',\n    'user_remark' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\UserRemark',\n    'vendor' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\VendorController',\n    'vendor_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\VendorList',\n    'vendor_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\VendorMain',\n    'vendor_main_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\VendorMainAjax',\n    'vendor_mall' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\VendorMall',\n    'vendor_seo' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\VendorSeo',\n    'voucherserie' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\VoucherSerieController',\n    'voucherserie_export' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\VoucherSerieExport',\n    'voucherserie_generate' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\VoucherSerieGenerate',\n    'voucherserie_groups' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\VoucherSerieGroups',\n    'voucherserie_groups_ajax' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\VoucherSerieGroupsAjax',\n    'voucherserie_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\VoucherSerieList',\n    'voucherserie_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\VoucherSerieMain',\n    'voucherserie_mall' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\VoucherSerieMall',\n    'wrapping_list' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\WrappingList',\n    'wrapping_main' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\WrappingMain',\n    'wrapping_mall' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\Admin\\\\WrappingMall',\n    'details' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\ArticleDetailsController',\n    'alist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\ArticleListController',\n    'basket' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\BasketController',\n    'clearcookies' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\ClearCookiesController',\n    'compare' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\CompareController',\n    'contact' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\ContactController',\n    'content' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\ContentController',\n    'credits' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\CreditsController',\n    'download' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\DownloadController',\n    'exceptionerror' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\ExceptionErrorController',\n    'forgotpwd' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\ForgotPasswordController',\n    'oxubase' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\FrontendController',\n    'invite' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\InviteController',\n    'links' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\LinksController',\n    'mallstart' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\MallStartController',\n    'manufacturerlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\ManufacturerListController',\n    'moredetails' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\MoreDetailsController',\n    'newsletter' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\NewsletterController',\n    'order' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\OrderController',\n    'oxstart' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\OxidStartController',\n    'payment' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\PaymentController',\n    'pricealarm' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\PriceAlarmController',\n    'recommlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\RecommListController',\n    'recommadd' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\RecommendationAddController',\n    'register' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\RegisterController',\n    'review' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\ReviewController',\n    'search' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\SearchController',\n    'start' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\StartController',\n    'tpl' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\TemplateController',\n    'thankyou' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\ThankYouController',\n    'user' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\UserController',\n    'vendorlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\VendorListController',\n    'wishlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\WishListController',\n    'wrapping' => 'OxidEsales\\\\Eshop\\\\Application\\\\Controller\\\\WrappingController',\n    'oxactionlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\ActionList',\n    'oxactions' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Actions',\n    'oxaddress' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Address',\n    'oxamountpricelist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\AmountPriceList',\n    'oxarticle' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Article',\n    'oxarticlelist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\ArticleList',\n    'oxattribute' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Attribute',\n    'oxattributelist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\AttributeList',\n    'oxbasket' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Basket',\n    'oxbasketcontentmarkgenerator' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\BasketContentMarkGenerator',\n    'oxbasketitem' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\BasketItem',\n    'oxbasketreservation' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\BasketReservation',\n    'oxcategory' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Category',\n    'oxcategorylist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\CategoryList',\n    'oxcompanyvatin' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\CompanyVatIn',\n    'oxcontent' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Content',\n    'oxcontentlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\ContentList',\n    'oxcountry' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Country',\n    'oxcountrylist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\CountryList',\n    'oxdelivery' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Delivery',\n    'oxdeliverylist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\DeliveryList',\n    'oxdeliveryset' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\DeliverySet',\n    'oxdeliverysetlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\DeliverySetList',\n    'oxdiagnostics' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Diagnostics',\n    'oxdiagnosticsoutput' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\DiagnosticsOutput',\n    'oxdiscount' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Discount',\n    'oxdiscountlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\DiscountList',\n    'oxfield2shop' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Field2Shop',\n    'oxfile' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\File',\n    'oxfilecollector' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\FileCollector',\n    'oxgroups' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Groups',\n    'oxlinks' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Links',\n    'oxlistobject' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\ListObject',\n    'oxmaintenance' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Maintenance',\n    'oxmanufacturer' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Manufacturer',\n    'oxmanufacturerlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\ManufacturerList',\n    'oxmdvariant' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\MdVariant',\n    'oxmediaurl' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\MediaUrl',\n    'oxnewssubscribed' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\NewsSubscribed',\n    'oxobject2category' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Object2Category',\n    'oxobject2group' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Object2Group',\n    'oxorder' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Order',\n    'oxorderarticle' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\OrderArticle',\n    'oxorderarticlelist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\OrderArticleList',\n    'oxorderfile' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\OrderFile',\n    'oxorderfilelist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\OrderFileList',\n    'oxpayment' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Payment',\n    'oxpaymentgateway' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\PaymentGateway',\n    'oxpaymentlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\PaymentList',\n    'oxpricealarm' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\PriceAlarm',\n    'oxrating' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Rating',\n    'oxrecommlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\RecommendationList',\n    'oxremark' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Remark',\n    'oxrequiredaddressfields' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\RequiredAddressFields',\n    'oxrequiredfieldvalidator' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\RequiredFieldValidator',\n    'oxrequiredfieldsvalidator' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\RequiredFieldsValidator',\n    'oxreview' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Review',\n    'oxrights' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Rights',\n    'oxrole' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Role',\n    'oxsearch' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Search',\n    'oxselectlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\SelectList',\n    'oxselection' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Selection',\n    'oxseoencoderarticle' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\SeoEncoderArticle',\n    'oxseoencodercategory' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\SeoEncoderCategory',\n    'oxseoencodercontent' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\SeoEncoderContent',\n    'oxseoencodermanufacturer' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\SeoEncoderManufacturer',\n    'oxseoencoderrecomm' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\SeoEncoderRecomm',\n    'oxseoencodervendor' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\SeoEncoderVendor',\n    'oxshop' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Shop',\n    'oxshoplist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\ShopList',\n    'oxshoprelations' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\ShopRelations',\n    'oxshopviewvalidator' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\ShopViewValidator',\n    'oxsimplevariant' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\SimpleVariant',\n    'oxsimplevariantlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\SimpleVariantList',\n    'oxstate' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\State',\n    'oxuser' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\User',\n    'oxuseraddresslist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\UserAddressList',\n    'oxuserbasket' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\UserBasket',\n    'oxuserbasketitem' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\UserBasketItem',\n    'oxuserlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\UserList',\n    'oxuserpayment' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\UserPayment',\n    'oxvarianthandler' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\VariantHandler',\n    'oxvariantselectlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\VariantSelectList',\n    'oxvatselector' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\VatSelector',\n    'oxvendor' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Vendor',\n    'oxvendorlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\VendorList',\n    'oxvoucher' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Voucher',\n    'oxvoucherlist' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\VoucherList',\n    'oxvoucherserie' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\VoucherSerie',\n    'oxwrapping' => 'OxidEsales\\\\Eshop\\\\Application\\\\Model\\\\Wrapping',\n    'oxadminrights' => 'OxidEsales\\\\Eshop\\\\Core\\\\AdminRights',\n    'oxapplicationserver' => 'OxidEsales\\\\Eshop\\\\Core\\\\DataObject\\\\ApplicationServer',\n    'oxarticle2shoprelations' => 'OxidEsales\\\\Eshop\\\\Core\\\\Article2ShopRelations',\n    'oxsupercfg' => 'OxidEsales\\\\Eshop\\\\Core\\\\Base',\n    'oxcache' => 'OxidEsales\\\\Eshop\\\\Core\\\\Cache\\\\DynamicContent\\\\ContentCache',\n    'oxcategory2shoprelations' => 'OxidEsales\\\\Eshop\\\\Core\\\\Category2ShopRelations',\n    'oxcompanyvatinchecker' => 'OxidEsales\\\\Eshop\\\\Core\\\\CompanyVatInChecker',\n    'oxcompanyvatincountrychecker' => 'OxidEsales\\\\Eshop\\\\Core\\\\CompanyVatInCountryChecker',\n    'oxcompanyvatinvalidator' => 'OxidEsales\\\\Eshop\\\\Core\\\\CompanyVatInValidator',\n    'oxconfig' => 'OxidEsales\\\\Eshop\\\\Core\\\\Config',\n    'oxconfigfile' => 'OxidEsales\\\\Eshop\\\\Core\\\\ConfigFile',\n    'oxiconfigurable' => 'OxidEsales\\\\Eshop\\\\Core\\\\Contract\\\\IConfigurable',\n    'oxicountryaware' => 'OxidEsales\\\\Eshop\\\\Core\\\\Contract\\\\ICountryAware',\n    'oxidisplayerror' => 'OxidEsales\\\\Eshop\\\\Core\\\\Contract\\\\IDisplayError',\n    'oximodulevalidator' => 'OxidEsales\\\\Eshop\\\\Core\\\\Contract\\\\IModuleValidator',\n    'oxiselectlist' => 'OxidEsales\\\\Eshop\\\\Core\\\\Contract\\\\ISelectList',\n    'oxiurl' => 'OxidEsales\\\\Eshop\\\\Core\\\\Contract\\\\IUrl',\n    'oxview' => 'OxidEsales\\\\Eshop\\\\Core\\\\Controller\\\\BaseController',\n    'oxcounter' => 'OxidEsales\\\\Eshop\\\\Core\\\\Counter',\n    'oxcurl' => 'OxidEsales\\\\Eshop\\\\Core\\\\Curl',\n    'oxdb' => 'OxidEsales\\\\Eshop\\\\Core\\\\DatabaseProvider',\n    'DatabaseInterface' => 'OxidEsales\\\\Eshop\\\\Core\\\\Database\\\\Adapter\\\\DatabaseInterface',\n    'oxdbmetadatahandler' => 'OxidEsales\\\\Eshop\\\\Core\\\\DbMetaDataHandler',\n    'oxdebuginfo' => 'OxidEsales\\\\Eshop\\\\Core\\\\DebugInfo',\n    'oxdecryptor' => 'OxidEsales\\\\Eshop\\\\Core\\\\Decryptor',\n    'oxdisplayerror' => 'OxidEsales\\\\Eshop\\\\Core\\\\DisplayError',\n    'oxdynimggenerator' => 'OxidEsales\\\\Eshop\\\\Core\\\\DynamicImageGenerator',\n    'oxelement2shoprelations' => 'OxidEsales\\\\Eshop\\\\Core\\\\Element2ShopRelations',\n    'oxelement2shoprelationsdbgateway' => 'OxidEsales\\\\Eshop\\\\Core\\\\Element2ShopRelationsDbGateway',\n    'oxelement2shoprelationssqlgenerator' => 'OxidEsales\\\\Eshop\\\\Core\\\\Element2ShopRelationsSqlGenerator',\n    'oxemail' => 'OxidEsales\\\\Eshop\\\\Core\\\\Email',\n    'oxencryptor' => 'OxidEsales\\\\Eshop\\\\Core\\\\Encryptor',\n    'oxaccessrightexception' => 'OxidEsales\\\\Eshop\\\\Core\\\\Exception\\\\AccessRightException',\n    'oxarticleexception' => 'OxidEsales\\\\Eshop\\\\Core\\\\Exception\\\\ArticleException',\n    'oxarticleinputexception' => 'OxidEsales\\\\Eshop\\\\Core\\\\Exception\\\\ArticleInputException',\n    'oxconnectionexception' => 'OxidEsales\\\\Eshop\\\\Core\\\\Exception\\\\ConnectionException',\n    'oxcookieexception' => 'OxidEsales\\\\Eshop\\\\Core\\\\Exception\\\\CookieException',\n    'oxexceptionhandler' => 'OxidEsales\\\\Eshop\\\\Core\\\\Exception\\\\ExceptionHandler',\n    'oxexceptiontodisplay' => 'OxidEsales\\\\Eshop\\\\Core\\\\Exception\\\\ExceptionToDisplay',\n    'oxfileexception' => 'OxidEsales\\\\Eshop\\\\Core\\\\Exception\\\\FileException',\n    'oxinputexception' => 'OxidEsales\\\\Eshop\\\\Core\\\\Exception\\\\InputException',\n    'oxnoarticleexception' => 'OxidEsales\\\\Eshop\\\\Core\\\\Exception\\\\NoArticleException',\n    'oxobjectexception' => 'OxidEsales\\\\Eshop\\\\Core\\\\Exception\\\\ObjectException',\n    'oxoutofstockexception' => 'OxidEsales\\\\Eshop\\\\Core\\\\Exception\\\\OutOfStockException',\n    'oxshopexception' => 'OxidEsales\\\\Eshop\\\\Core\\\\Exception\\\\ShopException',\n    'oxexception' => 'OxidEsales\\\\Eshop\\\\Core\\\\Exception\\\\StandardException',\n    'oxsystemcomponentexception' => 'OxidEsales\\\\Eshop\\\\Core\\\\Exception\\\\SystemComponentException',\n    'oxuserexception' => 'OxidEsales\\\\Eshop\\\\Core\\\\Exception\\\\UserException',\n    'oxvoucherexception' => 'OxidEsales\\\\Eshop\\\\Core\\\\Exception\\\\VoucherException',\n    'oxexpirationemailbuilder' => 'OxidEsales\\\\Eshop\\\\Core\\\\ExpirationEmailBuilder',\n    'oxfield' => 'OxidEsales\\\\Eshop\\\\Core\\\\Field',\n    'oxfilecache' => 'OxidEsales\\\\Eshop\\\\Core\\\\FileCache',\n    'oxhasher' => 'OxidEsales\\\\Eshop\\\\Core\\\\Hasher',\n    'oxheader' => 'OxidEsales\\\\Eshop\\\\Core\\\\Header',\n    'oxinputvalidator' => 'OxidEsales\\\\Eshop\\\\Core\\\\InputValidator',\n    'oxlang' => 'OxidEsales\\\\Eshop\\\\Core\\\\Language',\n    'oxbase' => 'OxidEsales\\\\Eshop\\\\Core\\\\Model\\\\BaseModel',\n    'oxlist' => 'OxidEsales\\\\Eshop\\\\Core\\\\Model\\\\ListModel',\n    'oxi18n' => 'OxidEsales\\\\Eshop\\\\Core\\\\Model\\\\MultiLanguageModel',\n    'oxmodule' => 'OxidEsales\\\\Eshop\\\\Core\\\\Module\\\\Module',\n    'oxmodulechainsgenerator' => 'OxidEsales\\\\Eshop\\\\Core\\\\Module\\\\ModuleChainsGenerator',\n    'oxmodulelist' => 'OxidEsales\\\\Eshop\\\\Core\\\\Module\\\\ModuleList',\n    'oxmodulemetadatavalidator' => 'OxidEsales\\\\Eshop\\\\Core\\\\Module\\\\ModuleMetadataValidator',\n    'oxmodulevalidatorfactory' => 'OxidEsales\\\\Eshop\\\\Core\\\\Module\\\\ModuleValidatorFactory',\n    'oxnojsvalidator' => 'OxidEsales\\\\Eshop\\\\Core\\\\NoJsValidator',\n    'oxonlinecaller' => 'OxidEsales\\\\Eshop\\\\Core\\\\OnlineCaller',\n    'oxonlinelicensecheck' => 'OxidEsales\\\\Eshop\\\\Core\\\\OnlineLicenseCheck',\n    'oxonlinelicensecheckcaller' => 'OxidEsales\\\\Eshop\\\\Core\\\\OnlineLicenseCheckCaller',\n    'oxonlinelicensecheckrequest' => 'OxidEsales\\\\Eshop\\\\Core\\\\OnlineLicenseCheckRequest',\n    'oxonlinelicensecheckresponse' => 'OxidEsales\\\\Eshop\\\\Core\\\\OnlineLicenseCheckResponse',\n    'oxonlinemoduleversionnotifier' => 'OxidEsales\\\\Eshop\\\\Core\\\\OnlineModuleVersionNotifier',\n    'oxonlinemoduleversionnotifiercaller' => 'OxidEsales\\\\Eshop\\\\Core\\\\OnlineModuleVersionNotifierCaller',\n    'oxonlinemodulesnotifierrequest' => 'OxidEsales\\\\Eshop\\\\Core\\\\OnlineModulesNotifierRequest',\n    'oxonlinerequest' => 'OxidEsales\\\\Eshop\\\\Core\\\\OnlineRequest',\n    'oxonlineserveremailbuilder' => 'OxidEsales\\\\Eshop\\\\Core\\\\OnlineServerEmailBuilder',\n    'oxonlinevatidcheck' => 'OxidEsales\\\\Eshop\\\\Core\\\\OnlineVatIdCheck',\n    'oxopensslfunctionalitychecker' => 'OxidEsales\\\\Eshop\\\\Core\\\\OpenSSLFunctionalityChecker',\n    'oxoutput' => 'OxidEsales\\\\Eshop\\\\Core\\\\Output',\n    'oxpasswordhasher' => 'OxidEsales\\\\Eshop\\\\Core\\\\PasswordHasher',\n    'oxpicturehandler' => 'OxidEsales\\\\Eshop\\\\Core\\\\PictureHandler',\n    'oxprice' => 'OxidEsales\\\\Eshop\\\\Core\\\\Price',\n    'oxpricelist' => 'OxidEsales\\\\Eshop\\\\Core\\\\PriceList',\n    'oxregistry' => 'OxidEsales\\\\Eshop\\\\Core\\\\Registry',\n    'oxseodecoder' => 'OxidEsales\\\\Eshop\\\\Core\\\\SeoDecoder',\n    'oxseoencoder' => 'OxidEsales\\\\Eshop\\\\Core\\\\SeoEncoder',\n    'oxsepabicvalidator' => 'OxidEsales\\\\Eshop\\\\Core\\\\SepaBICValidator',\n    'oxsepaibanvalidator' => 'OxidEsales\\\\Eshop\\\\Core\\\\SepaIBANValidator',\n    'oxsepavalidator' => 'OxidEsales\\\\Eshop\\\\Core\\\\SepaValidator',\n    'oxserial' => 'OxidEsales\\\\Eshop\\\\Core\\\\Serial',\n    'oxsession' => 'OxidEsales\\\\Eshop\\\\Core\\\\Session',\n    'oxsha512hasher' => 'OxidEsales\\\\Eshop\\\\Core\\\\Sha512Hasher',\n    'oxshopcontrol' => 'OxidEsales\\\\Eshop\\\\Core\\\\ShopControl',\n    'oxshopidcalculator' => 'OxidEsales\\\\Eshop\\\\Core\\\\ShopIdCalculator',\n    'oxsimplexml' => 'OxidEsales\\\\Eshop\\\\Core\\\\SimpleXml',\n    'oxstr' => 'OxidEsales\\\\Eshop\\\\Core\\\\Str',\n    'oxstrmb' => 'OxidEsales\\\\Eshop\\\\Core\\\\StrMb',\n    'oxstrregular' => 'OxidEsales\\\\Eshop\\\\Core\\\\StrRegular',\n    'oxsubshopspecificfilecache' => 'OxidEsales\\\\Eshop\\\\Core\\\\SubShopSpecificFileCache',\n    'oxsystemeventhandler' => 'OxidEsales\\\\Eshop\\\\Core\\\\SystemEventHandler',\n    'oxsysrequirements' => 'OxidEsales\\\\Eshop\\\\Core\\\\SystemRequirements',\n    'oxtableviewnamegenerator' => 'OxidEsales\\\\Eshop\\\\Core\\\\TableViewNameGenerator',\n    'oxtheme' => 'OxidEsales\\\\Eshop\\\\Core\\\\Theme',\n    'oxuniversallyuniqueidgenerator' => 'OxidEsales\\\\Eshop\\\\Core\\\\UniversallyUniqueIdGenerator',\n    'oxusercounter' => 'OxidEsales\\\\Eshop\\\\Core\\\\UserCounter',\n    'oxutils' => 'OxidEsales\\\\Eshop\\\\Core\\\\Utils',\n    'oxutilscount' => 'OxidEsales\\\\Eshop\\\\Core\\\\UtilsCount',\n    'oxutilsdate' => 'OxidEsales\\\\Eshop\\\\Core\\\\UtilsDate',\n    'oxutilsfile' => 'OxidEsales\\\\Eshop\\\\Core\\\\UtilsFile',\n    'oxutilsobject' => 'OxidEsales\\\\Eshop\\\\Core\\\\UtilsObject',\n    'oxutilspic' => 'OxidEsales\\\\Eshop\\\\Core\\\\UtilsPic',\n    'oxutilsserver' => 'OxidEsales\\\\Eshop\\\\Core\\\\UtilsServer',\n    'oxutilsstring' => 'OxidEsales\\\\Eshop\\\\Core\\\\UtilsString',\n    'oxutilsurl' => 'OxidEsales\\\\Eshop\\\\Core\\\\UtilsUrl',\n    'oxutilsview' => 'OxidEsales\\\\Eshop\\\\Core\\\\UtilsView',\n    'oxutilsxml' => 'OxidEsales\\\\Eshop\\\\Core\\\\UtilsXml',\n    'oxviewconfig' => 'OxidEsales\\\\Eshop\\\\Core\\\\ViewConfig',\n    'oxwidgetcontrol' => 'OxidEsales\\\\Eshop\\\\Core\\\\WidgetControl',\n];\n"
  },
  {
    "path": "source/Core/Autoload/BackwardsCompatibilityClassMapProvider.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Autoload;\n\n/**\n * @internal Do not make a module extension for this class.\n */\nclass BackwardsCompatibilityClassMapProvider\n{\n    /**\n     * @return array\n     */\n    public function getMap(): array\n    {\n        $classMap = include __DIR__ . DIRECTORY_SEPARATOR . 'BackwardsCompatibilityClassMap.php';\n\n        return $classMap;\n    }\n}\n"
  },
  {
    "path": "source/Core/Autoload/ModuleAutoload.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Autoload;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ActiveModulesDataProviderBridgeInterface;\n\n/**\n * Autoloader for module classes and extensions.\n *\n * @internal Do not make a module extension for this class.\n */\nclass ModuleAutoload\n{\n    /** @var array Classes, for which extension class chain was created. */\n    public $triedClasses = [];\n\n    /**\n     * @var null|ModuleAutoload A singleton instance of this class or a sub class of this class\n     */\n    private static $instance = null;\n\n    /**\n     * ModuleAutoload constructor.\n     *\n     * Make constructor protected to ensure Singleton pattern\n     */\n    protected function __construct()\n    {\n    }\n\n    /**\n     * Magic clone method.\n     *\n     * Make method private to ensure Singleton pattern\n     */\n    private function __clone()\n    {\n    }\n\n    /**\n     * Tries to autoload given class. Searches for the class in module extensions.\n     *\n     * @param string $class Class name.\n     *\n     * @return bool\n     */\n    public static function autoload($class)\n    {\n        /**\n         * Classes from unified namespace cannot be loaded by this auto loader.\n         * Do not try to load them in order to avoid strange errors in edge cases.\n         */\n        if (false !== strpos($class, 'OxidEsales\\Eshop\\\\')) {\n            return false;\n        }\n\n        $instance = static::getInstance();\n        $class = strtolower(basename($class));\n        $class = preg_replace('/_parent$/i', '', $class);\n\n        if (!in_array($class, $instance->triedClasses)) {\n            $instance->triedClasses[] = $class;\n            $instance->createExtensionClassChain($class);\n        }\n    }\n\n    /**\n     * Returns the singleton instance of this class or of a sub class of this class.\n     *\n     * @return ModuleAutoload The singleton instance.\n     */\n    public static function getInstance()\n    {\n        if (null === static::$instance) {\n            static::$instance = new static();\n        }\n\n        return static::$instance;\n    }\n\n    /**\n     * When module is extending other module's extension (module class, which is extending shop class),\n     * this class comes to autoload and class chain has to be created.\n     *\n     * @param string $class\n     */\n    protected function createExtensionClassChain($class)\n    {\n        $extensions = ContainerFacade::get(ActiveModulesDataProviderBridgeInterface::class)->getClassExtensions();\n\n        if (is_array($extensions)) {\n            $class = preg_quote($class, '/');\n\n            foreach ($extensions as $parentClass => $extensionPaths) {\n                foreach ($extensionPaths as $extensionPath) {\n                    if (preg_match('/\\b' . $class . '($|\\&)/i', $extensionPath)) {\n                        Registry::getUtilsObject()->getClassName($parentClass);\n                        break;\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Core/Autoload/UnifiedNameSpaceClassMap.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\n/*\n * @internal Do not make a module extension based on this file.\n */\nreturn [\n    'OxidEsales\\Eshop\\Application\\Component\\BasketComponent'                     => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\BasketComponent::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\CategoriesComponent'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\CategoriesComponent::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\CurrencyComponent'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\CurrencyComponent::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\LanguageComponent'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\LanguageComponent::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\Locator'                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\Locator::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\ShopComponent'                       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\ShopComponent::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\UserComponent'                       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\UserComponent::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\UtilsComponent'                      => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\UtilsComponent::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\Widget\\Actions'                      => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\Widget\\Actions::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\Widget\\ArticleBox'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\Widget\\ArticleBox::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\Widget\\ArticleDetails'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\Widget\\ArticleDetails::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\Widget\\CategoryTree'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\Widget\\CategoryTree::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\Widget\\CookieNote'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\Widget\\CookieNote::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\Widget\\CurrencyList'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\Widget\\CurrencyList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\Widget\\Information'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\Widget\\Information::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\Widget\\LanguageList'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\Widget\\LanguageList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\Widget\\ManufacturerList'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\Widget\\ManufacturerList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\Widget\\MiniBasket'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\Widget\\MiniBasket::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\Widget\\Rating'                       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\Widget\\Rating::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\Widget\\Recommendation'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\Widget\\Recommendation::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => true\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\Widget\\Review'                       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\Widget\\Review::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\Widget\\ServiceList'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\Widget\\ServiceList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\Widget\\ServiceMenu'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\Widget\\ServiceMenu::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\Widget\\VendorList'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\Widget\\VendorList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Component\\Widget\\WidgetController'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Component\\Widget\\WidgetController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\AccountController'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\AccountController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\AccountDownloadsController'         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\AccountDownloadsController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\AccountNewsletterController'        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\AccountNewsletterController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\AccountNoticeListController'        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\AccountNoticeListController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\AccountOrderController'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\AccountOrderController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\AccountPasswordController'          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\AccountPasswordController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\AccountRecommlistController'        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\AccountRecommlistController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => true\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\AccountReviewController'            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\AccountReviewController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\AccountUserController'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\AccountUserController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\AccountWishlistController'          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\AccountWishlistController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ActionsArticleAjax'           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ActionsArticleAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ActionsController'            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ActionsController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ActionsGroupsAjax'            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ActionsGroupsAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ActionsList'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ActionsList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ActionsMain'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ActionsMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ActionsMainAjax'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ActionsMainAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ActionsOrderAjax'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ActionsOrderAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminContent'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AdminContent::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminController'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AdminController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminDetailsController'       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AdminDetailsController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminLinks'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AdminLinks::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminListController'          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AdminListController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminNewsletter'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AdminNewsletter::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminOrder'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AdminOrder::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminPayment'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AdminPayment::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminPriceAlarm'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AdminPriceAlarm::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminStart'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AdminStart::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminUser'                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AdminUser::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminWrapping'                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AdminWrapping::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminlinksList'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AdminlinksList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AdminlinksMain'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AdminlinksMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleAccessoriesAjax'       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticleAccessoriesAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleAttribute'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticleAttribute::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleAttributeAjax'         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticleAttributeAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleBundleAjax'            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticleBundleAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleController'            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticleController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleCrossselling'          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticleCrossselling::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleCrosssellingAjax'      => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticleCrosssellingAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleExtend'                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticleExtend::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleExtendAjax'            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticleExtendAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleFiles'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticleFiles::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleList'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticleList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleMain'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticleMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleOverview'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticleOverview::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticlePictures'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticlePictures::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticlePicturesAjax'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticlePicturesAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleReview'                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticleReview::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleSelectionAjax'         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticleSelectionAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleSeo'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticleSeo::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleStock'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticleStock::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleUserdef'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticleUserdef::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleVariant'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticleVariant::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AttributeCategory'            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AttributeCategory::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AttributeCategoryAjax'        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AttributeCategoryAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AttributeController'          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AttributeController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AttributeList'                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AttributeList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AttributeMain'                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AttributeMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AttributeMainAjax'            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AttributeMainAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\AttributeOrderAjax'           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\AttributeOrderAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\CategoryController'           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\CategoryController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\CategoryList'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\CategoryList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\CategoryMain'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\CategoryMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\CategoryMainAjax'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\CategoryMainAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\CategoryOrder'                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\CategoryOrder::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\CategoryOrderAjax'            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\CategoryOrderAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\CategoryPictures'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\CategoryPictures::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\CategorySeo'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\CategorySeo::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\CategoryText'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\CategoryText::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\CategoryUpdate'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\CategoryUpdate::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ContentList'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ContentList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ContentMain'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ContentMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ContentSeo'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ContentSeo::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\CountryController'            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\CountryController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\CountryList'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\CountryList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\CountryMain'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\CountryMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliveryArticles'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliveryArticles::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliveryArticlesAjax'         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliveryArticlesAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliveryCategoriesAjax'       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliveryCategoriesAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliveryController'           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliveryController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliveryGroupsAjax'           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliveryGroupsAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliveryList'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliveryList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliveryMain'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliveryMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliveryMainAjax'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliveryMainAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliverySetController'        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliverySetController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliverySetCountryAjax'       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliverySetCountryAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliverySetGroupsAjax'        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliverySetGroupsAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliverySetList'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliverySetList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliverySetMain'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliverySetMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliverySetMainAjax'          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliverySetMainAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliverySetPayment'           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliverySetPayment::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliverySetPaymentAjax'       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliverySetPaymentAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliverySetRdfa'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliverySetRdfa::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliverySetUsers'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliverySetUsers::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliverySetUsersAjax'         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliverySetUsersAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliveryUsers'                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliveryUsers::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DeliveryUsersAjax'            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DeliveryUsersAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DiagnosticsController'        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DiagnosticsController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DiagnosticsList'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DiagnosticsList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DiagnosticsMain'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DiagnosticsMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DiscountArticles'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DiscountArticles::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DiscountArticlesAjax'         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DiscountArticlesAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DiscountCategoriesAjax'       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DiscountCategoriesAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DiscountController'           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DiscountController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DiscountGroupsAjax'           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DiscountGroupsAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DiscountItemAjax'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DiscountItemAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DiscountList'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DiscountList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DiscountMain'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DiscountMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DiscountMainAjax'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DiscountMainAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DiscountUsers'                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DiscountUsers::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DiscountUsersAjax'            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DiscountUsersAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\DynamicExportBaseController'  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\DynamicExportBaseController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\GenericExport'                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\GenericExport::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\GenericExportDo'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\GenericExportDo::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\GenericExportMain'            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\GenericExportMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\GenericImport'                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\GenericImport::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\GenericImportMain'            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\GenericImportMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\LanguageController'           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\LanguageController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\LanguageList'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\LanguageList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\LanguageMain'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\LanguageMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListComponentAjax'            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ListComponentAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListReview'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ListReview::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ListUser'                     => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ListUser::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\LoginController'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\LoginController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ManufacturerController'       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ManufacturerController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ManufacturerList'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ManufacturerList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ManufacturerMain'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ManufacturerMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ManufacturerPicture'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ManufacturerPicture::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ManufacturerMainAjax'         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ManufacturerMainAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ManufacturerSeo'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ManufacturerSeo::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ModuleConfiguration'          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ModuleConfiguration::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ModuleController'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ModuleController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ModuleList'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ModuleList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ModuleMain'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ModuleMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ModuleSortList'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ModuleSortList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\NavigationController'         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\NavigationController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\NavigationTree'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\NavigationTree::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ObjectSeo'                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ObjectSeo::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\OrderAddress'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\OrderAddress::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\OrderArticle'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\OrderArticle::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\OrderDownloads'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\OrderDownloads::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\OrderList'                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\OrderList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\OrderMain'                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\OrderMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\OrderOverview'                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\OrderOverview::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\OrderRemark'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\OrderRemark::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\PaymentCountry'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\PaymentCountry::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\PaymentCountryAjax'           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\PaymentCountryAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\PaymentList'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\PaymentList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\PaymentMain'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\PaymentMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\PaymentMainAjax'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\PaymentMainAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\PaymentRdfa'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\PaymentRdfa::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\PriceAlarmList'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\PriceAlarmList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\PriceAlarmMail'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\PriceAlarmMail::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\PriceAlarmMain'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\PriceAlarmMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\PriceAlarmSend'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\PriceAlarmSend::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\SelectListController'         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\SelectListController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\SelectListList'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\SelectListList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\SelectListMain'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\SelectListMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\SelectListMainAjax'           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\SelectListMainAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\SelectListOrderAjax'          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\SelectListOrderAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ShopConfiguration'            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ShopConfiguration::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ShopController'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ShopController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ShopDefaultCategoryAjax'      => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ShopDefaultCategoryAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ShopLicense'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ShopLicense::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ShopList'                     => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ShopList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ShopMain'                     => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ShopMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ShopPerformance'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ShopPerformance::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ShopRdfa'                     => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ShopRdfa::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ShopSeo'                      => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ShopSeo::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ShopSystem'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ShopSystem::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\SystemInfoController'         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\SystemInfoController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\SystemRequirements'           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\SystemRequirements::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\SystemRequirementsList'       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\SystemRequirementsList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\SystemRequirementsMain'       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\SystemRequirementsMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ThemeConfiguration'           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ThemeConfiguration::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ThemeController'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ThemeController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ThemeList'                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ThemeList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ThemeMain'                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ThemeMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ToolsController'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ToolsController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ToolsList'                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ToolsList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\ToolsMain'                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ToolsMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\UserAddress'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\UserAddress::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\UserArticle'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\UserArticle::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\UserExtend'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\UserExtend::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\UserGroupController'          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\UserGroupController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\UserGroupList'                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\UserGroupList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\UserGroupMain'                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\UserGroupMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\UserGroupMainAjax'            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\UserGroupMainAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\UserList'                     => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\UserList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\UserMain'                     => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\UserMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\UserMainAjax'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\UserMainAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\UserOverview'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\UserOverview::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\UserPayment'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\UserPayment::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\UserRemark'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\UserRemark::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\VendorController'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\VendorController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\VendorList'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\VendorList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\VendorMain'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\VendorMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\VendorMainAjax'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\VendorMainAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\VendorSeo'                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\VendorSeo::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\VoucherSerieController'       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\VoucherSerieController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\VoucherSerieExport'           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\VoucherSerieExport::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\VoucherSerieGenerate'         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\VoucherSerieGenerate::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\VoucherSerieGroups'           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\VoucherSerieGroups::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\VoucherSerieGroupsAjax'       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\VoucherSerieGroupsAjax::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\VoucherSerieList'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\VoucherSerieList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\VoucherSerieMain'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\VoucherSerieMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\WrappingList'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\WrappingList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\Admin\\WrappingMain'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\WrappingMain::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\ArticleDetailsController'           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\ArticleDetailsController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\ArticleListController'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\ArticleListController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\BasketController'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\BasketController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\ClearCookiesController'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\ClearCookiesController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\CompareController'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\CompareController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\ContactController'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\ContactController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\ContentController'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\ContentController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\CreditsController'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\CreditsController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\DownloadController'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\DownloadController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\ExceptionErrorController'           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\ExceptionErrorController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\ForgotPasswordController'           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\ForgotPasswordController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\FrontendController'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\FrontendController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\InviteController'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\InviteController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\LinksController'                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\LinksController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\ManufacturerListController'         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\ManufacturerListController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\MoreDetailsController'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\MoreDetailsController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\NewsletterController'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\NewsletterController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\OrderController'                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\OrderController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\OxidStartController'                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\OxidStartController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\PaymentController'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\PaymentController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\PriceAlarmController'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\PriceAlarmController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\RecommListController'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\RecommListController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => true\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\RecommendationAddController'        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\RecommendationAddController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => true\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\RegisterController'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\RegisterController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\ReviewController'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\ReviewController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\SearchController'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\SearchController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\StartController'                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\StartController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\TemplateController'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\TemplateController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\TextEditorHandler'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\TextEditorHandler::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\ThankYouController'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\ThankYouController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\UserController'                     => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\UserController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\VendorListController'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\VendorListController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\WishListController'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\WishListController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Controller\\WrappingController'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Controller\\WrappingController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\ActionList'                              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\ActionList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Actions'                                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Actions::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Address'                                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Address::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\AmountPriceList'                         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\AmountPriceList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Article'                                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Article::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\ArticleList'                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\ArticleList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Attribute'                               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Attribute::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\AttributeList'                           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\AttributeList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Basket'                                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Basket::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\BasketContentMarkGenerator'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\BasketContentMarkGenerator::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\BasketItem'                              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\BasketItem::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\BasketReservation'                       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\BasketReservation::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Category'                                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Category::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\CategoryList'                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\CategoryList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\CompanyVatIn'                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\CompanyVatIn::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Content'                                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Content::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\ContentList'                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\ContentList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Contract\\ArticleInterface'               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Contract\\ArticleInterface::class,\n        'isAbstract'       => false,\n        'isInterface'      => true,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Country'                                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Country::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\CountryList'                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\CountryList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Delivery'                                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Delivery::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\DeliveryList'                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\DeliveryList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\DeliverySet'                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\DeliverySet::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\DeliverySetList'                         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\DeliverySetList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Diagnostics'                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Diagnostics::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\DiagnosticsOutput'                       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\DiagnosticsOutput::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Discount'                                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Discount::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\DiscountList'                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\DiscountList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\File'                                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\File::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\FileCollector'                           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\FileCollector::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Groups'                                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Groups::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Links'                                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Links::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\ListObject'                              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\ListObject::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Maintenance'                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Maintenance::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Manufacturer'                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Manufacturer::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\ManufacturerList'                        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\ManufacturerList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\MdVariant'                               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\MdVariant::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\MediaUrl'                                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\MediaUrl::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\NewsSubscribed'                          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\NewsSubscribed::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Object2Category'                         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Object2Category::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Object2Group'                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Object2Group::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Order'                                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Order::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\OrderArticle'                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\OrderArticle::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\OrderArticleList'                        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\OrderArticleList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\OrderFile'                               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\OrderFile::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\OrderFileList'                           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\OrderFileList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Payment'                                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Payment::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\PaymentGateway'                          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\PaymentGateway::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\PaymentList'                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\PaymentList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\PriceAlarm'                              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\PriceAlarm::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Rating'                                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Rating::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\RecommendationList'                      => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\RecommendationList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => true\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Remark'                                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Remark::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\RequiredAddressFields'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\RequiredAddressFields::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\RequiredFieldValidator'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\RequiredFieldValidator::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\RequiredFieldsValidator'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\RequiredFieldsValidator::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Review'                                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Review::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Search'                                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Search::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\SelectList'                              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\SelectList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Selection'                               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Selection::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\SeoEncoderArticle'                       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\SeoEncoderArticle::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\SeoEncoderCategory'                      => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\SeoEncoderCategory::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\SeoEncoderContent'                       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\SeoEncoderContent::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\SeoEncoderManufacturer'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\SeoEncoderManufacturer::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\SeoEncoderRecomm'                        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\SeoEncoderRecomm::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => true\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\SeoEncoderVendor'                        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\SeoEncoderVendor::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Shop'                                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Shop::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\ShopList'                                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\ShopList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\ShopViewValidator'                       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\ShopViewValidator::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\SimpleVariant'                           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\SimpleVariant::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\SimpleVariantList'                       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\SimpleVariantList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\State'                                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\State::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\User'                                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\User::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\UserAddressList'                         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\UserAddressList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\UserBasket'                              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\UserBasket::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\UserBasketItem'                          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\UserBasketItem::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\UserList'                                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\UserList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\UserPayment'                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\UserPayment::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\User\\UserShippingAddressUpdatableFields' => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\User\\UserShippingAddressUpdatableFields::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\User\\UserUpdatableFields'                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\User\\UserUpdatableFields::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\VariantHandler'                          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\VariantHandler::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\VariantSelectList'                       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\VariantSelectList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\VatSelector'                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\VatSelector::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Vendor'                                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Vendor::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\VendorList'                              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\VendorList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Voucher'                                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Voucher::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\VoucherList'                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\VoucherList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\VoucherSerie'                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\VoucherSerie::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Application\\Model\\Wrapping'                                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Application\\Model\\Wrapping::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\AdminLogSqlDecorator'                                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\AdminLogSqlDecorator::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer'                         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\DataObject\\ApplicationServer::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Dao\\ApplicationServerDao'                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Dao\\ApplicationServerDao::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Dao\\BaseDaoInterface'                                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Dao\\BaseDaoInterface::class,\n        'isAbstract'       => false,\n        'isInterface'      => true,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Dao\\ApplicationServerDaoInterface'                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Dao\\ApplicationServerDaoInterface::class,\n        'isAbstract'       => false,\n        'isInterface'      => true,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Service\\ApplicationServerExporter'                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Service\\ApplicationServerExporter::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Service\\ApplicationServerExporterInterface'           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Service\\ApplicationServerExporterInterface::class,\n        'isAbstract'       => false,\n        'isInterface'      => true,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Service\\ApplicationServerService'                     => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Service\\ApplicationServerService::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Service\\ApplicationServerServiceInterface'            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Service\\ApplicationServerServiceInterface::class,\n        'isAbstract'       => false,\n        'isInterface'      => true,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Autoload\\BackwardsCompatibilityAutoload'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Autoload\\BackwardsCompatibilityAutoload::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\BackwardsCompatibleClassNameProvider'                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\BackwardsCompatibleClassNameProvider::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Base'                                                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Base::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\CompanyVatInChecker'                                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\CompanyVatInChecker::class,\n        'isAbstract'       => true,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\CompanyVatInCountryChecker'                           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\CompanyVatInCountryChecker::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\CompanyVatInValidator'                                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\CompanyVatInValidator::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Config'                                               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Config::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\ConfigFile'                                           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\ConfigFile::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Contract\\AbstractUpdatableFields'                     => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Contract\\AbstractUpdatableFields::class,\n        'isAbstract'       => true,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Contract\\ClassNameResolverInterface'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Contract\\ClassNameResolverInterface::class,\n        'isAbstract'       => false,\n        'isInterface'      => true,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Contract\\ControllerMapProviderInterface'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Contract\\ControllerMapProviderInterface::class,\n        'isAbstract'       => false,\n        'isInterface'      => true,\n        'isDeprecated'     => true\n    ],\n    'OxidEsales\\Eshop\\Core\\Contract\\IConfigurable'                               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Contract\\IConfigurable::class,\n        'isAbstract'       => false,\n        'isInterface'      => true,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Contract\\ICountryAware'                               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Contract\\ICountryAware::class,\n        'isAbstract'       => false,\n        'isInterface'      => true,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Contract\\IDisplayError'                               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Contract\\IDisplayError::class,\n        'isAbstract'       => false,\n        'isInterface'      => true,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Contract\\ISelectList'                                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Contract\\ISelectList::class,\n        'isAbstract'       => false,\n        'isInterface'      => true,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Contract\\IUrl'                                        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Contract\\IUrl::class,\n        'isAbstract'       => false,\n        'isInterface'      => true,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Controller\\BaseController'                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Controller\\BaseController::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Counter'                                              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Counter::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Curl'                                                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Curl::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\DatabaseProvider'                                     => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\DatabaseProvider::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => true\n    ],\n    'OxidEsales\\Eshop\\Core\\Database\\Adapter\\DatabaseInterface'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Database\\Adapter\\DatabaseInterface::class,\n        'isAbstract'       => false,\n        'isInterface'      => true,\n        'isDeprecated'     => true\n    ],\n    'OxidEsales\\Eshop\\Core\\Database\\Adapter\\Doctrine\\Database'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Database\\Adapter\\Doctrine\\Database::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => true\n    ],\n    'OxidEsales\\Eshop\\Core\\Database\\Adapter\\Doctrine\\ResultSet'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Database\\Adapter\\Doctrine\\ResultSet::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => true\n    ],\n    'OxidEsales\\Eshop\\Core\\Database\\Adapter\\ResultSetInterface'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Database\\Adapter\\ResultSetInterface::class,\n        'isAbstract'       => false,\n        'isInterface'      => true,\n        'isDeprecated'     => true\n    ],\n    'OxidEsales\\Eshop\\Core\\DbMetaDataHandler'                                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\DbMetaDataHandler::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\DebugInfo'                                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\DebugInfo::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Decryptor'                                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Decryptor::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\DisplayError'                                         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\DisplayError::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\DynamicImageGenerator'                                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\DynamicImageGenerator::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Email'                                                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Email::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\EmailBuilder'                                         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\EmailBuilder::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Encryptor'                                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Encryptor::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\ArticleException'                           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\ArticleException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\ArticleInputException'                      => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\ArticleInputException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\ConnectionException'                        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\ConnectionException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\CookieException'                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\CookieException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\DatabaseConnectionException'                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\DatabaseConnectionException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\DatabaseErrorException'                     => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\DatabaseErrorException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\DatabaseException'                          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\DatabaseException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\DatabaseNotConfiguredException'             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\DatabaseNotConfiguredException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\ExceptionHandler'                           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\ExceptionHandler::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay'                         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\ExceptionToDisplay::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\FileException'                              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\FileException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\InputException'                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\InputException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\LanguageNotFoundException'                          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\LanguageNotFoundException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\ModuleValidationException'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\ModuleValidationException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\NoArticleException'                         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\NoArticleException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\ObjectException'                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\ObjectException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\OutOfStockException'                        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\OutOfStockException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\RoutingException'                           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\RoutingException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\ShopException'                              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\ShopException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\StandardException'                          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\StandardException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\SystemComponentException'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\SystemComponentException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\UserException'                              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\UserException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\VoucherException'                           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\VoucherException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Exception\\NoResultException'                          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Exception\\NoResultException::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Field'                                                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Field::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\FileCache'                                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\FileCache::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\FileSystem\\FileSystem'                                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\FileSystem\\FileSystem::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Form\\FormFields'                                      => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Form\\FormFields::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Form\\FormFieldsCleaner'                               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Form\\FormFieldsCleaner::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Form\\FormFieldsTrimmerInterface'                      => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Form\\FormFieldsTrimmerInterface::class,\n        'isAbstract'       => false,\n        'isInterface'      => true,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Form\\FormFieldsTrimmer'                               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Form\\FormFieldsTrimmer::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Form\\UpdatableFieldsConstructor'                      => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Form\\UpdatableFieldsConstructor::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\GenericImport\\GenericImport'                          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\GenericImport\\GenericImport::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\Accessories2Article'       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject\\Accessories2Article::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\Article'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject\\Article::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\Article2Action'            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject\\Article2Action::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\Article2Category'          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject\\Article2Category::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\ArticleExtends'            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject\\ArticleExtends::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\Category'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject\\Category::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\Country'                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject\\Country::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\CrossSelling'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject\\CrossSelling::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\ImportObject'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject\\ImportObject::class,\n        'isAbstract'       => true,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\Order'                     => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject\\Order::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\OrderArticle'              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject\\OrderArticle::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\ScalePrice'                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject\\ScalePrice::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\User'                      => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject\\User::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\Vendor'                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject\\Vendor::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Hasher'                                               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Hasher::class,\n        'isAbstract'       => true,\n        'isInterface'      => false,\n        'isDeprecated'     => true\n    ],\n    'OxidEsales\\Eshop\\Core\\Header'                                               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Header::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\InputValidator'                                       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\InputValidator::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Language'                                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Language::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Model\\BaseModel'                                      => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Model\\BaseModel::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Model\\FieldNameHelper'                                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Model\\FieldNameHelper::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Model\\ListModel'                                      => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Model\\ListModel::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel'                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Model\\MultiLanguageModel::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Module\\Module'                                        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Module\\Module::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => true\n    ],\n    'OxidEsales\\Eshop\\Core\\Module\\ModuleChainsGenerator'                         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Module\\ModuleChainsGenerator::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Module\\ModuleList'                                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Module\\ModuleList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => true\n    ],\n    'OxidEsales\\Eshop\\Core\\NamespaceInformationProvider'                         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\NamespaceInformationProvider::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\NoJsValidator'                                        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\NoJsValidator::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\OnlineCaller'                                         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\OnlineCaller::class,\n        'isAbstract'       => true,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\OnlineLicenseCheck'                                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\OnlineLicenseCheck::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\OnlineLicenseCheckCaller'                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\OnlineLicenseCheckCaller::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\OnlineLicenseCheckRequest'                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\OnlineLicenseCheckRequest::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\OnlineLicenseCheckResponse'                           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\OnlineLicenseCheckResponse::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\OnlineModuleVersionNotifier'                          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\OnlineModuleVersionNotifier::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\OnlineModuleVersionNotifierCaller'                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\OnlineModuleVersionNotifierCaller::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\OnlineModulesNotifierRequest'                         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\OnlineModulesNotifierRequest::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\OnlineRequest'                                        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\OnlineRequest::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\OnlineServerEmailBuilder'                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\OnlineServerEmailBuilder::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\OnlineVatIdCheck'                                     => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\OnlineVatIdCheck::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\OpenSSLFunctionalityChecker'                          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\OpenSSLFunctionalityChecker::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Output'                                               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Output::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Oxid'                                                 => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Oxid::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\PasswordHasher'                                       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\PasswordHasher::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => true\n    ],\n    'OxidEsales\\Eshop\\Core\\PictureHandler'                                       => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\PictureHandler::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Price'                                                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Price::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\PriceList'                                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\PriceList::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Registry'                                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Registry::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Request'                                              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Request::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Routing\\ControllerClassNameResolver'                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Routing\\ControllerClassNameResolver::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Routing\\ShopControllerMapProvider'                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Routing\\ShopControllerMapProvider::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\SeoDecoder'                                           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\SeoDecoder::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\SeoEncoder'                                           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\SeoEncoder::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\SepaBICValidator'                                     => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\SepaBICValidator::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\SepaIBANValidator'                                    => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\SepaIBANValidator::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\SepaValidator'                                        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\SepaValidator::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Session'                                              => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Session::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\SettingsHandler'                                      => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\SettingsHandler::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Sha512Hasher'                                         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Sha512Hasher::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => true\n    ],\n    'OxidEsales\\Eshop\\Core\\ShopControl'                                          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\ShopControl::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\ShopIdCalculator'                                     => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\ShopIdCalculator::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\ShopVersion'                                          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\ShopVersion::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\SimpleXml'                                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\SimpleXml::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Str'                                                  => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Str::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\StrMb'                                                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\StrMb::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\StrRegular'                                           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\StrRegular::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\SubShopSpecificFileCache'                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\SubShopSpecificFileCache::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\SystemEventHandler'                                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\SystemEventHandler::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\SystemRequirements'                                   => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\SystemRequirements::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\TableViewNameGenerator'                               => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\TableViewNameGenerator::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Theme'                                                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Theme::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\UniversallyUniqueIdGenerator'                         => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\UniversallyUniqueIdGenerator::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\UserCounter'                                          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\UserCounter::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\Utils'                                                => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\Utils::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\UtilsCount'                                           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\UtilsCount::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\UtilsDate'                                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\UtilsDate::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\UtilsFile'                                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\UtilsFile::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\UtilsObject'                                          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\UtilsObject::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\UtilsPic'                                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\UtilsPic::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\UtilsServer'                                          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\UtilsServer::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\UtilsString'                                          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\UtilsString::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\UtilsUrl'                                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\UtilsUrl::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\UtilsView'                                            => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\UtilsView::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\UtilsXml'                                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\UtilsXml::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\ViewConfig'                                           => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\ViewConfig::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\ViewHelper\\JavaScriptRegistrator'                     => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\ViewHelper\\JavaScriptRegistrator::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\ViewHelper\\JavaScriptRenderer'                        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\ViewHelper\\JavaScriptRenderer::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\ViewHelper\\StyleRegistrator'                          => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\ViewHelper\\StyleRegistrator::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\ViewHelper\\StyleRenderer'                             => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\ViewHelper\\StyleRenderer::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n    'OxidEsales\\Eshop\\Core\\WidgetControl'                                        => [\n        'editionClassName' => \\OxidEsales\\EshopCommunity\\Core\\WidgetControl::class,\n        'isAbstract'       => false,\n        'isInterface'      => false,\n        'isDeprecated'     => false\n    ],\n];\n"
  },
  {
    "path": "source/Core/BackwardsCompatibleClassNameProvider.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Forms real class name for edition based classes.\n *\n * @internal Do not make a module extension for this class.\n */\nclass BackwardsCompatibleClassNameProvider\n{\n    /** @var array */\n    private $classMap;\n\n    /**\n     * @param array $classMap\n     */\n    public function __construct($classMap)\n    {\n        $this->classMap = $classMap;\n    }\n\n    /**\n     * Returns real class name from given alias. If class alias is not found,\n     * given class alias is thought to be a real class and is returned.\n     *\n     * @param string $classAlias\n     *\n     * @return mixed\n     */\n    public function getClassName($classAlias)\n    {\n        $className = $classAlias;\n        if (array_key_exists($classAlias, $this->classMap)) {\n            $className = $this->classMap[$classAlias];\n        }\n\n        return $className;\n    }\n\n    /**\n     * Method returns class alias by given class name.\n     *\n     * @param string $className with namespace.\n     *\n     * @return string|null\n     */\n    public function getClassAliasName($className)\n    {\n        /*\n         * Sanitize input: class names in namespaces should not, but may include a leading backslash\n         */\n        $className = ltrim($className, '\\\\');\n        $classAlias = array_search($className, $this->classMap);\n\n        if ($classAlias === false) {\n            $classAlias = null;\n        }\n\n        return $classAlias;\n    }\n}\n"
  },
  {
    "path": "source/Core/Base.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Exception\\SystemComponentException;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\n\n/**\n * Basic class which is used as parent class by other OXID eShop classes.\n * It provides access to some basic objects and some basic functionality.\n */\nclass Base\n{\n    /**\n     * oxuser object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\User\n     */\n    protected static $_oActUser = null;\n\n    /**\n     * Admin mode marker\n     *\n     * @var bool\n     */\n    protected static $_blIsAdmin = null;\n\n    /**\n     * Only used for convenience in UNIT tests by doing so we avoid\n     * writing extended classes for testing protected or private methods\n     *\n     * @param string $method Methods name\n     * @param array  $arguments Argument array\n     * @throws SystemComponentException\n     * @return false|mixed\n     */\n    public function __call($method, $arguments)\n    {\n        if (method_exists($this, $method)) {\n            return call_user_func_array([& $this, $method], $arguments);\n        }\n        throw new SystemComponentException(\n            \"Function '$method' does not exist or is not accessible! (\" . get_class($this) . \")\" . PHP_EOL\n        );\n    }\n\n    /**\n     * Class constructor. The constructor is defined in order to be possible to call parent::__construct() in modules.\n     *\n     * @return null\n     */\n    public function __construct()\n    {\n    }\n\n    /**\n     * Active user getter\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\User\n     */\n    public function getUser()\n    {\n        if (self::$_oActUser === null) {\n            self::$_oActUser = false;\n            $user = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n            if ($user->loadActiveUser()) {\n                self::$_oActUser = $user;\n            }\n        }\n\n        return self::$_oActUser;\n    }\n\n    /**\n     * Active oxuser object setter\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $user user object\n     */\n    public function setUser($user)\n    {\n        self::$_oActUser = $user;\n    }\n\n    /**\n     * Admin mode status getter\n     *\n     * @return bool\n     */\n    public function isAdmin()\n    {\n        if (self::$_blIsAdmin === null) {\n            self::$_blIsAdmin = isAdmin();\n        }\n\n        return self::$_blIsAdmin;\n    }\n\n    /**\n     * Admin mode setter\n     *\n     * @param bool $isAdmin admin mode\n     */\n    public function setAdminMode($isAdmin)\n    {\n        self::$_blIsAdmin = $isAdmin;\n    }\n\n    /**\n     * @template T of object\n     * @param class-string<T> $id\n     *\n     * @return T\n     */\n    protected function getService(string $id): object\n    {\n        return ContainerFacade::get($id);\n    }\n}\n"
  },
  {
    "path": "source/Core/CompanyVatInChecker.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Company VAT identification number (VATIN) checker\n */\nabstract class CompanyVatInChecker\n{\n    /**\n     * Error message\n     *\n     * @var string\n     */\n    protected $_sError = '';\n\n    /**\n     * Error message setter\n     *\n     * @param string $error\n     */\n    public function setError($error)\n    {\n        $this->_sError = $error;\n    }\n\n    /**\n     * Error message getter\n     *\n     * @return string\n     */\n    public function getError()\n    {\n        return $this->_sError;\n    }\n\n    /**\n     * Validates company VAT identification number\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\CompanyVatIn $vatIn\n     *\n     * @return mixed\n     */\n    abstract public function validate(\\OxidEsales\\Eshop\\Application\\Model\\CompanyVatIn $vatIn);\n}\n"
  },
  {
    "path": "source/Core/CompanyVatInCountryChecker.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Application\\Model\\CompanyVatIn;\nuse OxidEsales\\Eshop\\Application\\Model\\Country;\n\n/**\n * Company VAT identification number checker. Check if number belongs to the country.\n */\nclass CompanyVatInCountryChecker extends \\OxidEsales\\Eshop\\Core\\CompanyVatInChecker implements \\OxidEsales\\Eshop\\Core\\Contract\\ICountryAware\n{\n    /**\n     * Error string if country mismatch\n     */\n    const ERROR_ID_NOT_VALID = 'ID_NOT_VALID';\n\n    /**\n     * Country\n     *\n     * @var Country\n     */\n    private $_oCountry = null;\n\n    /**\n     * Country setter\n     */\n    public function setCountry(Country $country)\n    {\n        $this->_oCountry = $country;\n    }\n\n    /**\n     * Country getter\n     *\n     * @return Country\n     */\n    public function getCountry()\n    {\n        return $this->_oCountry;\n    }\n\n    public function validate(CompanyVatIn $vatIn)\n    {\n        $country = $this->getCountry();\n\n        if (is_null($country)) {\n            return false;\n        }\n\n        if (!$this->hasValidVatPrefix($country)) {\n            $this->setError('MISSING_COUNTRY_PREFIX');\n            return false;\n        }\n\n        return $this->isVatPrefixMatching($country, $vatIn);\n    }\n\n    private function hasValidVatPrefix(Country $country): bool\n    {\n        return !empty($country->getVATIdentificationNumberPrefix());\n    }\n\n    private function isVatPrefixMatching(Country $country, CompanyVatIn $companyVatIn): bool\n    {\n        $prefix = $country->getVATIdentificationNumberPrefix();\n        $isValid = ($prefix === $companyVatIn->getCountryCode());\n\n        if (!$isValid) {\n            $this->setError(self::ERROR_ID_NOT_VALID);\n        }\n\n        return $isValid;\n    }\n}\n"
  },
  {
    "path": "source/Core/CompanyVatInValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Company VAT identification number validator. Executes added validators on given VATIN.\n */\nclass CompanyVatInValidator\n{\n    /**\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Country\n     */\n    private $_oCountry = null;\n\n    /**\n     * Array of validators (checkers)\n     *\n     * @var array\n     */\n    private $_aCheckers = [];\n\n    /**\n     * Error message\n     *\n     * @var string\n     */\n    private $_sError = '';\n\n    /**\n     * Country setter\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Country $country\n     */\n    public function setCountry(\\OxidEsales\\Eshop\\Application\\Model\\Country $country)\n    {\n        $this->_oCountry = $country;\n    }\n\n    /**\n     * Country getter\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Country\n     */\n    public function getCountry()\n    {\n        return $this->_oCountry;\n    }\n\n    /**\n     * Error setter\n     *\n     * @param string $error\n     */\n    public function setError($error)\n    {\n        $this->_sError = $error;\n    }\n\n    /**\n     * Error getter\n     *\n     * @return string\n     */\n    public function getError()\n    {\n        return $this->_sError;\n    }\n\n    /**\n     * Constructor\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Country $country\n     */\n    public function __construct(\\OxidEsales\\Eshop\\Application\\Model\\Country $country)\n    {\n        $this->setCountry($country);\n    }\n\n    /**\n     * Adds validator\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\CompanyVatInChecker $validator\n     */\n    public function addChecker(\\OxidEsales\\Eshop\\Core\\CompanyVatInChecker $validator)\n    {\n        $this->_aCheckers[] = $validator;\n    }\n\n    /**\n     * Returns added validators\n     *\n     * @return array\n     */\n    public function getCheckers()\n    {\n        return $this->_aCheckers;\n    }\n\n    /**\n     * Validate company VAT identification number.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\CompanyVatIn $companyVatNumber\n     *\n     * @return bool\n     */\n    public function validate(\\OxidEsales\\Eshop\\Application\\Model\\CompanyVatIn $companyVatNumber)\n    {\n        $result = false;\n        $validators = $this->getCheckers();\n\n        foreach ($validators as $validator) {\n            $result = true;\n            if ($validator instanceof \\OxidEsales\\Eshop\\Core\\Contract\\ICountryAware) {\n                $validator->setCountry($this->getCountry());\n            }\n\n            if (!$validator->validate($companyVatNumber)) {\n                $result = false;\n                $this->setError($validator->getError());\n                break;\n            }\n        }\n\n        return $result;\n    }\n}\n"
  },
  {
    "path": "source/Core/Config.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Application\\Controller\\FrontendController;\nuse OxidEsales\\Eshop\\Application\\Controller\\OxidStartController;\nuse OxidEsales\\Eshop\\Application\\Model\\Shop;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Event\\ShopConfigurationChangedEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition\\Edition;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Bridge\\AdminThemeBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Event\\ThemeSettingChangedEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse stdClass;\nuse Symfony\\Component\\Filesystem\\Path;\n\n//max integer\ndefine('MAX_64BIT_INTEGER', '18446744073709551615');\n\n/**\n * Main shop configuration class.\n */\n#[\\AllowDynamicProperties]\nclass Config extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Application starter instance\n     *\n     * @var OxidStartController\n     */\n    private $_oStart;\n\n    /**\n     * Active shop object.\n     *\n     * @var object\n     */\n    protected $_oActShop;\n\n    /**\n     * Active Views object array. Object has setters/getters for these properties:\n     *   _sClass - name of current view class\n     *   _sFnc   - name of current action function\n     *\n     * @var array\n     */\n    protected $_aActiveViews = [];\n\n    /**\n     * Array of global parameters.\n     *\n     * @var array\n     */\n    protected $_aGlobalParams = [];\n\n    /**\n     * Shop config parameters storage array\n     *\n     * @var array\n     */\n    protected $_aConfigParams = [];\n\n    /**\n     * Theme config parameters storage array\n     *\n     * @var array\n     */\n    protected $_aThemeConfigParams = [];\n\n    /**\n     * Current language Id\n     *\n     * @var int\n     */\n    protected $_iLanguageId = null;\n\n    /**\n     * Current shop Id\n     *\n     * @var int\n     */\n    protected $_iShopId = null;\n\n    /**\n     * Out dir name\n     *\n     * @var string\n     */\n    protected $_sOutDir = 'out';\n\n    /**\n     * Image dir name\n     *\n     * @var string\n     */\n    protected $_sImageDir = 'img';\n\n    /**\n     * Dyn Image dir name\n     *\n     * @var string\n     */\n    protected $_sPictureDir = 'pictures';\n\n    /**\n     * Master pictures dir name\n     *\n     * @var string\n     */\n    protected $_sMasterPictureDir = 'master';\n\n    /**\n     * Template dir name\n     *\n     * @var string\n     */\n    protected $_sTemplateDir = 'tpl';\n\n    /**\n     * Resource dir name\n     *\n     * @var string\n     */\n    protected $_sResourceDir = 'src';\n\n    /**\n     * Modules dir name\n     *\n     * @var string\n     */\n    protected $_sModulesDir = 'modules';\n\n    /**\n     * Whether shop is in SSL mode\n     *\n     * @var bool\n     */\n    protected $_blIsSsl = null;\n\n    /**\n     * Absolute image dirs for each shops\n     *\n     * @var array\n     */\n    protected $_aAbsDynImageDir = [];\n\n    /**\n     * Active currency object\n     *\n     * @var array\n     */\n    protected $_oActCurrencyObject = null;\n\n    /**\n     * Indicates if Config::init() method has been already run.\n     * Is checked for loading config variables on demand.\n     *\n     * @var bool\n     */\n    protected $_blInit = false;\n\n    private bool $initVars = false;\n\n    /**\n     * prefix for oxModule field for themes in oxConfig and oxConfigDisplay tables\n     *\n     * @var string\n     */\n    const OXMODULE_THEME_PREFIX = 'theme:';\n\n    /**\n     * Returns config parameter value if such parameter exists\n     *\n     * @param string $name    config parameter name\n     * @param mixed  $default default value if no config var is found default null\n     *\n     * @return mixed\n     */\n    public function getConfigParam($name, $default = null)\n    {\n        $this->initVars($this->getShopId());\n\n        if (isset($this->_aConfigParams[$name])) {\n            $value = $this->_aConfigParams[$name];\n        } elseif (isset($this->$name)) {\n            $value = $this->$name;\n        } else {\n            $value = $default;\n        }\n\n        return $value;\n    }\n\n    /**\n     * Stores config parameter value in config\n     *\n     * @param string $name  config parameter name\n     * @param mixed  $value config parameter value\n     */\n    public function setConfigParam($name, $value)\n    {\n        if (isset($this->_aConfigParams[$name])) {\n            $this->_aConfigParams[$name] = $value;\n        } elseif (isset($this->$name)) {\n            $this->$name = $value;\n        } else {\n            $this->_aConfigParams[$name] = $value;\n        }\n    }\n\n    /**\n     * Parse SEO url parameters.\n     */\n    protected function processSeoCall()\n    {\n        // TODO: refactor shop bootstrap and parse url params as soon as possible\n        if (isSearchEngineUrl()) {\n            oxNew(\\OxidEsales\\Eshop\\Core\\SeoDecoder::class)->processSeoCall();\n        }\n    }\n\n    /**\n     * Initialize configuration variables\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\DatabaseException\n     * @param int $shopId\n     */\n    public function initVars($shopId)\n    {\n        if ($this->initVars === true) {\n            return;\n        }\n        $this->initVars = true;\n\n        $this->setDefaults();\n\n        $configLoaded = $this->loadVarsFromDb($shopId);\n        // loading shop config\n        if (empty($shopId) || !$configLoaded) {\n            // if no config values where loaded (some problems with DB), throwing an exception\n            $exception = new \\OxidEsales\\Eshop\\Core\\Exception\\DatabaseException(\n                \"Unable to load shop config values from database\",\n                0,\n                new \\Exception()\n            );\n            throw $exception;\n        }\n\n        // loading theme config options\n        $this->loadVarsFromDb($shopId, null, Config::OXMODULE_THEME_PREFIX . $this->getConfigParam('sTheme'));\n\n        // checking if custom theme (which has defined parent theme) config options should be loaded over parent theme (#3362)\n        if ($this->getConfigParam('sCustomTheme')) {\n            $this->loadVarsFromDb($shopId, null, Config::OXMODULE_THEME_PREFIX . $this->getConfigParam('sCustomTheme'));\n        }\n\n        $this->loadAdditionalConfiguration();\n\n        // Admin handling\n        $this->setConfigParam('blAdmin', isAdmin());\n\n        if (defined('OX_ADMIN_DIR')) {\n            $this->setConfigParam('sAdminDir', OX_ADMIN_DIR);\n        }\n    }\n\n    /**\n     * Starts session manager\n     *\n     * @return null\n     */\n    public function init()\n    {\n        // Duplicated init protection\n        if ($this->_blInit) {\n            return;\n        }\n        $this->_blInit = true;\n        $this->initVars = false;\n\n        try {\n            // config params initialization\n            $this->initVars($this->getShopId());\n\n            // application initialization\n            $this->initializeShop();\n            $this->_oStart = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\OxidStartController::class);\n            $this->_oStart->appInit();\n        } catch (\\OxidEsales\\Eshop\\Core\\Exception\\DatabaseException $exception) {\n            $this->handleDbConnectionException($exception);\n        } catch (\\OxidEsales\\Eshop\\Core\\Exception\\CookieException $exception) {\n            $this->handleCookieException($exception);\n        }\n    }\n\n    /**\n     * Reloads all configuration.\n     */\n    public function reinitialize()\n    {\n        $this->_blInit = false;\n        $this->initVars = false;\n        $this->init();\n    }\n\n    /**\n     * Load any additional configuration on Config::init.\n     */\n    protected function loadAdditionalConfiguration()\n    {\n    }\n\n    /**\n     * Initializes main shop tasks - processing of SEO calls, starting of session.\n     */\n    protected function initializeShop()\n    {\n        $this->processSeoCall();\n        $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n        $session->start();\n    }\n\n    /**\n     * Set important defaults.\n     */\n    protected function setDefaults()\n    {\n        if (is_null($this->getConfigParam('sDefaultLang'))) {\n            $this->setConfigParam('sDefaultLang', 0);\n        }\n\n        if (is_null($this->getConfigParam('blCheckTemplates'))) {\n            $this->setConfigParam('blCheckTemplates', false);\n        }\n\n        if (is_null($this->getConfigParam('blAllowArticlesubclass'))) {\n            $this->setConfigParam('blAllowArticlesubclass', false);\n        }\n\n        if (is_null($this->getConfigParam('iAdminListSize'))) {\n            $this->setConfigParam('iAdminListSize', 9);\n        }\n\n        if (is_null($this->getConfigParam('iZoomPicCount'))) {\n            $this->setConfigParam('iZoomPicCount', 4);\n        }\n\n        $this->setConfigParam('sCoreDir', __DIR__ . DIRECTORY_SEPARATOR);\n    }\n\n    /**\n     * Load config values from DB\n     *\n     * @param int    $shopId   shop ID to load parameters\n     * @param array  $onlyVars array of params to load (optional)\n     * @param string $module   module vars to load, empty for base options\n     *\n     * @return bool\n     */\n    protected function loadVarsFromDb($shopId, $onlyVars = null, $module = '')\n    {\n        $db = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $params = [\n            'oxshopid' => $shopId,\n            'oxmodule' => $module\n        ];\n\n        $select = \"\n            SELECT oxvarname, oxvartype, oxvarvalue\n            FROM oxconfig\n            WHERE oxshopid = :oxshopid AND oxmodule LIKE :oxmodule\n        \";\n        $select .= $this->getConfigParamsSelectSnippet($onlyVars);\n\n        $result = $db->getAll($select, $params);\n\n        foreach ($result as $value) {\n            $varName = $value['oxvarname'];\n            $varType = $value['oxvartype'];\n            $varVal = $value['oxvarvalue'];\n\n            $this->setConfVarFromDb($varName, $varType, $varVal);\n\n            //setting theme options array\n            if ($module) {\n                $this->_aThemeConfigParams[$varName] = $module;\n            }\n        }\n\n        return (bool) count($result);\n    }\n\n    /**\n     * Allow loading from some vars only from baseshop\n     *\n     * @param array $vars\n     *\n     * @return string\n     */\n    protected function getConfigParamsSelectSnippet($vars)\n    {\n        $select = '';\n        if (is_array($vars) && !empty($vars)) {\n            foreach ($vars as &$field) {\n                $field = '\"' . $field . '\"';\n            }\n            $select = ' and oxvarname in ( ' . implode(', ', $vars) . ' ) ';\n        }\n\n        return $select;\n    }\n\n    /**\n     * Sets config variable to config object, first unserializing it by given type.\n     *\n     * @param string $varName variable name\n     * @param string $varType variable type - arr, aarr, bool or str\n     * @param string $varVal  serialized by type value\n     *\n     * @return null\n     */\n    protected function setConfVarFromDb($varName, $varType, $varVal)\n    {\n        switch ($varType) {\n            case 'arr':\n            case 'aarr':\n                $this->setConfigParam($varName, unserialize($varVal, ['allowed_classes' => false]));\n                break;\n            case 'bool':\n                $this->setConfigParam($varName, ($varVal == 'true' || $varVal == '1'));\n                break;\n            default:\n                $this->setConfigParam($varName, $varVal);\n                break;\n        }\n    }\n\n    /**\n     * Unsets all session data.\n     *\n     * @return null\n     */\n    public function pageClose()\n    {\n        if ($this->hasActiveViewsChain()) {\n            // do not commit session until active views chain exists\n            return;\n        }\n\n        return $this->_oStart->pageClose();\n    }\n\n    /**\n     * Get request 'cl' parameter which is the controller id.\n     *\n     * @return string|null\n     */\n    public function getRequestControllerId()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('cl');\n    }\n\n    /**\n     * Use this function to get the controller class hidden behind the request's 'cl' parameter.\n     *\n     * @return mixed\n     */\n    public function getRequestControllerClass()\n    {\n        $controllerId = $this->getRequestControllerId();\n        $controllerClass = Registry::getControllerClassNameResolver()->getClassNameById($controllerId);\n\n        return $controllerClass;\n    }\n\n    /**\n     * Returns uploaded file parameter\n     *\n     * @param string $paramName param name\n     *\n     * @return null\n     */\n    public function getUploadedFile($paramName)\n    {\n        return $_FILES[$paramName];\n    }\n\n    /**\n     * Sets global parameter value\n     *\n     * @param string $name  name of parameter\n     * @param mixed  $value value to store\n     */\n    public function setGlobalParameter($name, $value)\n    {\n        $this->_aGlobalParams[$name] = $value;\n    }\n\n    /**\n     * Returns global parameter value\n     *\n     * @param string $name name of cached parameter\n     *\n     * @return mixed\n     */\n    public function getGlobalParameter($name)\n    {\n        return $this->_aGlobalParams[$name] ?? null;\n    }\n\n    /**\n     * Checks if passed parameter has special chars and replaces them.\n     * Returns checked value.\n     *\n     * @param mixed $value value to process escaping\n     * @param array $raw   keys of unescaped values\n     *\n     * @return mixed\n     */\n    public function checkParamSpecialChars(&$value, $raw = null)\n    {\n        return Registry::get(\\OxidEsales\\Eshop\\Core\\Request::class)->checkParamSpecialChars($value, $raw);\n    }\n\n    /**\n     * Active Shop id setter\n     *\n     * @param int    $shopId shop id\n     */\n    public function setShopId($shopId)\n    {\n        $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n        $session->setVariable('actshop', $shopId);\n        $this->_iShopId = $shopId;\n    }\n\n    /**\n     * Returns active shop ID.\n     *\n     * @deprecated\n     * @see ContextInterface::getCurrentShopId()\n     *\n     * @return int\n     */\n    public function getShopId()\n    {\n        if (is_null($this->_iShopId)) {\n            $shopId = $this->calculateActiveShopId();\n            $this->setShopId($shopId);\n\n            if (!$this->isValidShopId($shopId)) {\n                $shopId = $this->getBaseShopId();\n            }\n            $this->setShopId($shopId);\n        }\n\n        return $this->_iShopId;\n    }\n\n    /**\n     * Set is shop url\n     *\n     * @param bool $isSsl - state bool value\n     */\n    public function setIsSsl($isSsl = false)\n    {\n        $this->_blIsSsl = $isSsl;\n    }\n\n    /**\n     * Checks if WEB session is SSL.\n     */\n    protected function checkSsl()\n    {\n        $myUtilsServer = Registry::getUtilsServer();\n        $serverVars = $myUtilsServer->getServerVar();\n        $httpsServerVar = $myUtilsServer->getServerVar('HTTPS');\n\n        $this->setIsSsl();\n        if ($httpsServerVar === 'on' || $httpsServerVar === 'ON' || $httpsServerVar == '1') {\n            $this->setIsSsl(\n                ContainerFacade::getParameter('oxid_esales.shop_url') || $this->getConfigParam('sMallSSLShopURL')\n            );\n            if (!$this->_blIsSsl && $this->isAdmin()) {\n                $this->setIsSsl(\n                    ContainerFacade::getParameter('oxid_esales.shop_admin_url') !== null\n                );\n            }\n        }\n\n        //additional special handling for profihost customers\n        if (\n            isset($serverVars['HTTP_X_FORWARDED_SERVER']) &&\n            (\n                str_contains($serverVars['HTTP_X_FORWARDED_SERVER'], 'ssl') ||\n                str_contains($serverVars['HTTP_X_FORWARDED_SERVER'], 'secure-online-shopping.de')\n            )\n        ) {\n            $this->setIsSsl(true);\n        }\n    }\n\n\n    /**\n     * Checks if WEB session is SSL. Returns true if yes.\n     *\n     * @return bool\n     */\n    public function isSsl()\n    {\n        if (is_null($this->_blIsSsl)) {\n            $this->checkSsl();\n        }\n\n        return $this->_blIsSsl;\n    }\n\n    /**\n     * Checks if shop runs in https only mode\n     * https only mode means there is no http url but only a https url\n     *\n     * @return bool\n     */\n    public function isHttpsOnly()\n    {\n        return $this->isSsl();\n    }\n\n    /**\n     * Compares current URL to supplied string\n     *\n     * @param string $url URL\n     *\n     * @return bool true if $url is equal to current page URL\n     */\n    public function isCurrentUrl($url)\n    {\n        /** @var UtilsServer $utilsServer */\n        $utilsServer = Registry::getUtilsServer();\n        return $utilsServer->isCurrentUrl($url);\n    }\n\n    /**\n     * Compares current protocol to supplied url string\n     *\n     * @param string $url URL\n     *\n     * @return bool true if $url is equal to current page URL\n     */\n    public function isCurrentProtocol($url)\n    {\n        // Missing protocol, cannot proceed, assuming true.\n        if (!$url || (strpos($url, \"http\") !== 0)) {\n            return true;\n        }\n\n        return (strpos($url, \"https:\") === 0) == $this->isSsl();\n    }\n\n    /**\n     * Returns config sShopURL or sMallShopURL if secondary shop\n     *\n     * @param int  $lang  language\n     * @param bool $admin if set true, function returns shop url without checking language/subshops for different url.\n     *\n     * @return string\n     */\n    public function getShopUrl($lang = null, $admin = null)\n    {\n        $url = null;\n        $admin = isset($admin) ? $admin : $this->isAdmin();\n\n        if (!$admin) {\n            $url = $this->getShopUrlByLanguage($lang);\n            if (!$url) {\n                $url = $this->getMallShopUrl();\n            }\n        }\n\n        if (!$url) {\n            $url = ContainerFacade::getParameter('oxid_esales.shop_url');\n        }\n\n        return $url;\n    }\n\n    /**\n     * Returns utils dir URL\n     *\n     * @return string\n     */\n    public function getCoreUtilsUrl()\n    {\n        return $this->getCurrentShopUrl() . 'Core/utils/';\n    }\n\n    /**\n     * Returns SSL or non SSL shop URL without index.php depending on Mall\n     * affecting environment is admin mode and current ssl usage status\n     *\n     * @param bool $admin if admin\n     *\n     * @return string\n     */\n    public function getCurrentShopUrl($admin = null)\n    {\n        if ($admin === null) {\n            $admin = $this->isAdmin();\n        }\n        if ($admin) {\n            $url = ContainerFacade::getParameter('oxid_esales.shop_admin_url');\n            if (!$url) {\n                return $this->getShopUrl() . $this->getConfigParam('sAdminDir') . '/';\n            }\n\n            return $url;\n        } else {\n            return $this->getShopUrl();\n        }\n    }\n\n    /**\n     * Returns SSL or not SSL shop URL with index.php and sid\n     *\n     * @param int $lang language (optional)\n     *\n     * @return string\n     */\n    public function getShopCurrentUrl($lang = null)\n    {\n        return Registry::getUtilsUrl()->processUrl($this->getShopURL($lang) . 'index.php', false);\n    }\n\n    /**\n     * Returns shop non SSL URL including index.php and sid.\n     *\n     * @param int  $lang  language\n     * @param bool $admin if admin\n     *\n     * @return string\n     */\n    public function getShopHomeUrl($lang = null, $admin = null)\n    {\n        return Registry::getUtilsUrl()->processUrl($this->getShopUrl($lang, $admin) . 'index.php', false);\n    }\n\n    /**\n     * Returns widget start non SSL URL including widget.php and sid.\n     *\n     * @param int   $languageId    language\n     * @param bool  $inAdmin       if admin\n     * @param array $urlParameters parameters which should be added to URL.\n     *\n     * @return string\n     */\n    public function getWidgetUrl($languageId = null, $inAdmin = null, $urlParameters = [])\n    {\n        $utilsUrl = Registry::getUtilsUrl();\n        $widgetUrl = $this->getShopUrl($languageId, $inAdmin);\n        $widgetUrl = $utilsUrl->processUrl($widgetUrl . 'widget.php', false);\n\n        if (!isset($languageId)) {\n            $language = Registry::getLang();\n            $languageId = $language->getBaseLanguage();\n        }\n        $urlLang = $utilsUrl->getUrlLanguageParameter($languageId);\n\n        $widgetUrl = $utilsUrl->appendUrl($widgetUrl, $urlLang, true);\n\n        return $utilsUrl->appendUrl($widgetUrl, $urlParameters, true, true);\n    }\n\n    /**\n     * Returns shop SSL URL with index.php and sid.\n     *\n     * @return string\n     */\n    public function getShopSecureHomeUrl()\n    {\n        return Registry::getUtilsUrl()->processUrl($this->getShopUrl() . 'index.php', false);\n    }\n\n    /**\n     * Returns active shop currency.\n     *\n     * @return string\n     */\n    public function getShopCurrency()\n    {\n        if ((null === ($curr = Registry::getRequest()->getRequestEscapedParameter('cur')))) {\n            if (null === ($curr = Registry::getRequest()->getRequestEscapedParameter('currency'))) {\n                $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n                $curr = $session->getVariable('currency');\n            }\n        }\n\n        return (int) $curr;\n    }\n\n    /**\n     * Returns active shop currency object.\n     *\n     * @return stdClass\n     */\n    public function getActShopCurrencyObject()\n    {\n        if ($this->_oActCurrencyObject === null) {\n            $cur = $this->getShopCurrency();\n            $currencies = $this->getCurrencyArray();\n            if (!isset($currencies[$cur])) {\n                $this->_oActCurrencyObject = reset($currencies); // reset() returns the first element\n            } else {\n                $this->_oActCurrencyObject = $currencies[$cur];\n            }\n        }\n\n        return $this->_oActCurrencyObject;\n    }\n\n    /**\n     * Sets the actual currency\n     *\n     * @param int $cur 0 = EUR, 1 = GBP, 2 = CHF\n     */\n    public function setActShopCurrency($cur)\n    {\n        $currencies = $this->getCurrencyArray();\n        if (isset($currencies[$cur])) {\n            $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n            $session->setVariable('currency', $cur);\n            $this->_oActCurrencyObject = null;\n        }\n    }\n\n    /**\n     * Returns path to out dir\n     *\n     * @param bool $absolute mode - absolute/relative path\n     *\n     * @return string\n     */\n    public function getOutDir($absolute = true)\n    {\n        if ($absolute) {\n\n            return Path::join(ContainerFacade::getParameter('oxid_esales.shop_source_directory'), $this->_sOutDir)\n                . DIRECTORY_SEPARATOR;\n        } else {\n\n            return $this->_sOutDir . DIRECTORY_SEPARATOR;\n        }\n    }\n\n    /**\n     * Returns path to out dir\n     *\n     * @param bool $absolute mode - absolute/relative path\n     *\n     * @return string\n     */\n    public function getViewsDir($absolute = true)\n    {\n        return Path::join($this->getAppDir($absolute), 'views') . DIRECTORY_SEPARATOR;\n    }\n\n    /**\n     * Returns path to translations dir\n     *\n     * @param string $file     File name\n     * @param string $dir      Directory name\n     * @param bool   $absolute mode - absolute/relative path\n     *\n     * @return string\n     */\n    public function getTranslationsDir($file, $dir, $absolute = true)\n    {\n        $path = Path::join($this->getAppDir($absolute), 'translations', $dir, $file);\n\n        return is_readable($path) ? $path : false;\n    }\n\n    /**\n     * Returns path to out dir\n     *\n     * @param bool $absolute mode - absolute/relative path\n     *\n     * @return string\n     */\n    public function getAppDir($absolute = true)\n    {\n        if ($absolute) {\n\n            return Path::join(ContainerFacade::getParameter('oxid_esales.shop_source_directory'), 'Application')\n                . DIRECTORY_SEPARATOR;\n        } else {\n\n            return 'Application' . DIRECTORY_SEPARATOR;\n        }\n    }\n\n    /**\n     * Returns url to out dir\n     *\n     * @param bool $ssl       Whether to force ssl\n     * @param bool $admin     Whether to force admin\n     * @param bool $nativeImg Whether to force native image dirs\n     *\n     * @return string\n     */\n    public function getOutUrl($ssl = null, $admin = null, $nativeImg = false)\n    {\n        $admin = is_null($admin) ? $this->isAdmin() : $admin;\n\n        if ($nativeImg && !$admin) {\n            $url = $this->getShopUrl();\n        } else {\n            $url = ContainerFacade::getParameter('oxid_esales.shop_url');\n            if (!$url && $admin) {\n                $url = ContainerFacade::getParameter('oxid_esales.shop_admin_url') . '../';\n            }\n        }\n\n        return $url . $this->_sOutDir . '/';\n    }\n\n    /**\n     * Finds and returns files or folders path in out dir\n     *\n     * @param string $file       File name\n     * @param string $dir        Directory name\n     * @param bool   $admin      Whether to force admin\n     * @param int    $lang       Language id\n     * @param int    $shop       Shop id\n     * @param string $theme      Theme name\n     * @param bool   $absolute   mode - absolute/relative path\n     * @param bool   $ignoreCust Ignore custom theme\n     *\n     * @return string\n     */\n    public function getDir($file, $dir, $admin, $lang = null, $shop = null, $theme = null, $absolute = true, $ignoreCust = false)\n    {\n        if (is_null($theme)) {\n            $theme = $this->getConfigParam('sTheme');\n        }\n\n        if ($admin) {\n            $theme = ContainerFacade::get(AdminThemeBridgeInterface::class)\n                ->getActiveTheme();\n        }\n\n        if ($dir != $this->_sTemplateDir) {\n            $base = $this->getOutDir($absolute);\n            $absBase = $this->getOutDir();\n        } else {\n            $base = $this->getViewsDir($absolute);\n            $absBase = $this->getViewsDir();\n        }\n\n        $langAbbr = '-';\n        // false means skip language folder check\n        if ($lang !== false) {\n            $language = Registry::getLang();\n\n            if (is_null($lang)) {\n                $lang = $language->getEditLanguage();\n            }\n\n            $langAbbr = $language->getLanguageAbbr($lang);\n        }\n\n        if (is_null($shop)) {\n            $shop = $this->getShopId();\n        }\n\n        //Load from\n        $path = \"{$theme}/{$shop}/{$langAbbr}/{$dir}/{$file}\";\n        $cacheKey = $path . \"_{$ignoreCust}{$absolute}\";\n\n        if (($return = Registry::getUtils()->fromStaticCache($cacheKey)) !== null) {\n            return $return;\n        }\n\n        $return = $this->getEditionTemplate(\"{$theme}/{$dir}/{$file}\");\n\n        // Check for custom template\n        $customTheme = $this->getConfigParam('sCustomTheme');\n        if (!$return && !$admin && !$ignoreCust && $customTheme && $customTheme != $theme) {\n            $return = $this->getDir($file, $dir, $admin, $lang, $shop, $customTheme, $absolute, $ignoreCust);\n        }\n\n        //test lang level ..\n        if (!$return && !$admin && is_readable($absBase . $path)) {\n            $return = $base . $path;\n        }\n\n        //test shop level ..\n        if (!$return && !$admin) {\n            $return = $this->getShopLevelDir($base, $absBase, $file, $dir, $admin, $lang, $shop, $theme, $absolute, $ignoreCust);\n        }\n\n        //test theme language level ..\n        $path = \"$theme/$langAbbr/$dir/$file\";\n        if (!$return && $lang !== false && is_readable($absBase . $path)) {\n            $return = $base . $path;\n        }\n\n        //test theme level ..\n        $path = \"$theme/$dir/$file\";\n        if (!$return && is_readable($absBase . $path)) {\n            $return = $base . $path;\n        }\n\n        //test out language level ..\n        $path = \"$langAbbr/$dir/$file\";\n        if (!$return && $lang !== false && is_readable($absBase . $path)) {\n            $return = $base . $path;\n        }\n\n        //test out level ..\n        $path = \"$dir/$file\";\n        if (!$return && is_readable($absBase . $path)) {\n            $return = $base . $path;\n        }\n\n        // TODO: implement logic to log missing paths\n\n        Registry::getUtils()->toStaticCache($cacheKey, $return);\n\n        return $return;\n    }\n\n    /**\n     * @param string $base\n     * @param string $absBase\n     * @param string $file\n     * @param string $dir\n     * @param bool   $admin\n     * @param int    $lang\n     * @param int    $shop\n     * @param string $theme\n     * @param bool   $absolute\n     * @param bool   $ignoreCust\n     *\n     * @return bool|string\n     */\n    protected function getShopLevelDir($base, $absBase, $file, $dir, $admin, $lang, $shop, $theme, $absolute, $ignoreCust)\n    {\n        $return = false;\n\n        $path = \"$theme/$shop/$dir/$file\";\n        if (is_readable($absBase . $path)) {\n            $return = $base . $path;\n        }\n\n        return $return;\n    }\n\n    /**\n     * Finds and returns file or folder url in out dir\n     *\n     * @param string $file      File name\n     * @param string $dir       Directory name\n     * @param bool   $admin     Whether to force admin\n     * @param bool   $ssl       Whether to force ssl\n     * @param bool   $nativeImg Whether to force native image dirs\n     * @param int    $lang      Language id\n     * @param int    $shop      Shop id\n     * @param string $theme     Theme name\n     *\n     * @return string\n     */\n    public function getUrl($file, $dir, $admin = null, $ssl = null, $nativeImg = false, $lang = null, $shop = null, $theme = null)\n    {\n        return str_replace(\n            $this->getOutDir(),\n            $this->getOutUrl($ssl, $admin, $nativeImg),\n            $this->getDir($file, $dir, $admin, $lang, $shop, $theme)\n        );\n    }\n\n    /**\n     * Finds and returns image files or folders path\n     *\n     * @param string $file  File name\n     * @param bool   $admin Whether to force admin\n     *\n     * @return string\n     */\n    public function getImagePath($file, $admin = false)\n    {\n        return $this->getDir($file, $this->_sImageDir, $admin);\n    }\n\n    /**\n     * Finds and returns image folder url\n     *\n     * @param bool   $admin     Whether to force admin\n     * @param bool   $ssl       Whether to force ssl\n     * @param bool   $nativeImg Whether to force native image dirs\n     * @param string $file      Image file name\n     *\n     * @return string\n     */\n    public function getImageUrl($admin = false, $ssl = null, $nativeImg = null, $file = null)\n    {\n        $nativeImg = is_null($nativeImg) ? $this->getConfigParam('blNativeImages') : $nativeImg;\n\n        return $this->getUrl($file, $this->_sImageDir, $admin, $ssl, $nativeImg);\n    }\n\n    /**\n     * Finds and returns image folders path\n     *\n     * @param bool $admin Whether to force admin\n     *\n     * @return string\n     */\n    public function getImageDir($admin = false)\n    {\n        return $this->getDir(null, $this->_sImageDir, $admin);\n    }\n\n    /**\n     * Finds and returns product pictures files or folders path\n     *\n     * @param string $file  File name\n     * @param bool   $admin Whether to force admin\n     * @param int    $lang  Language\n     * @param int    $shop  Shop id\n     * @param string $theme theme name\n     *\n     * @return string\n     */\n    public function getPicturePath($file, $admin = false, $lang = null, $shop = null, $theme = null)\n    {\n        return $this->getDir($file, $this->_sPictureDir, $admin, $lang, $shop, $theme);\n    }\n\n    /**\n     * Finds and returns master pictures folder path\n     *\n     * @param bool $admin Whether to force admin\n     *\n     * @return string\n     */\n    public function getMasterPictureDir($admin = false)\n    {\n        return $this->getDir(null, $this->_sPictureDir . \"/\" . $this->_sMasterPictureDir, $admin);\n    }\n\n    /**\n     * Finds and returns master picture path\n     *\n     * @param string $file  File name\n     * @param bool   $admin Whether to force admin\n     *\n     * @return string\n     */\n    public function getMasterPicturePath($file, $admin = false)\n    {\n        return $this->getDir($file, $this->_sPictureDir . \"/\" . $this->_sMasterPictureDir, $admin);\n    }\n\n    /**\n     * Finds and returns product picture file or folder url\n     *\n     * @param string $file   File name\n     * @param bool   $admin  Whether to force admin\n     * @param bool   $ssl    Whether to force ssl\n     * @param int    $lang   Language\n     * @param int    $shopId Shop id\n     * @param string $defPic Default (nopic) image path [\"0/nopic.jpg\"]\n     *\n     * @return string\n     */\n    public function getPictureUrl($file, $admin = false, $ssl = null, $lang = null, $shopId = null, $defPic = \"master/nopic.jpg\")\n    {\n        if ($altUrl = Registry::getPictureHandler()->getAltImageUrl('', $file)) {\n            return $altUrl;\n        }\n\n        $nativeImg = $this->getConfigParam('blNativeImages');\n        $url = $this->getUrl($file, $this->_sPictureDir, $admin, $ssl, $nativeImg, $lang, $shopId);\n\n        //anything is better than empty name, because <img src=\"\"> calls shop once more = x2 SLOW.\n        if (!$url && $defPic) {\n            $url = $this->getUrl($defPic, $this->_sPictureDir, $admin, $ssl, $nativeImg, $lang, $shopId);\n        }\n\n        return $url;\n    }\n\n    /**\n     * Finds and returns product pictures folders path\n     *\n     * @param bool $admin Whether to force admin\n     *\n     * @return string\n     */\n    public function getPictureDir($admin)\n    {\n        return $this->getDir(null, $this->_sPictureDir, $admin);\n    }\n\n    /**\n     * Calculates and returns full path to template.\n     *\n     * @param string $templateName Template name\n     * @param bool   $isAdmin      Whether to force admin\n     *\n     * @return string\n     */\n    public function getTemplatePath($templateName, $isAdmin)\n    {\n        return $this->getDir($templateName, $this->_sTemplateDir, $isAdmin);\n    }\n\n    /**\n     * Finds and returns templates folders path\n     *\n     * @param bool $admin Whether to force admin\n     *\n     * @return string\n     */\n    public function getTemplateDir($admin = false)\n    {\n        return $this->getDir(null, $this->_sTemplateDir, $admin);\n    }\n\n    /**\n     * Finds and returns template file or folder url\n     *\n     * @param string $file  File name\n     * @param bool   $admin Whether to force admin\n     * @param bool   $ssl   Whether to force ssl\n     * @param int    $lang  Language id\n     *\n     * @return string\n     */\n    public function getTemplateUrl($file = null, $admin = false, $ssl = null, $lang = null)\n    {\n        return $this->getShopMainUrl() . $this->getDir($file, $this->_sTemplateDir, $admin, $lang, null, null, false);\n    }\n\n    /**\n     * Finds and returns base template folder url\n     *\n     * @param bool $admin Whether to force admin\n     *\n     * @return string\n     */\n    public function getTemplateBase($admin = false)\n    {\n        // Base template dir is the parent dir of template dir\n        return str_replace($this->_sTemplateDir . '/', '', $this->getDir(null, $this->_sTemplateDir, $admin, null, null, null, false));\n    }\n\n    /**\n     * Finds and returns resource (css, js, etc..) files or folders path\n     *\n     * @param string $file  File name\n     * @param bool   $admin Whether to force admin\n     *\n     * @return string\n     */\n    public function getResourcePath($file = '', $admin = false)\n    {\n        return $this->getDir($file, $this->_sResourceDir, $admin);\n    }\n\n    /**\n     * Finds and returns resource (css, js, etc..) file or folder url\n     *\n     * @param string $file  File name\n     * @param bool   $admin Whether to force admin\n     * @param bool   $ssl   Whether to force ssl\n     * @param int    $lang  Language id\n     *\n     * @return string\n     */\n    public function getResourceUrl($file = '', $admin = false, $ssl = null, $lang = null)\n    {\n        $nativeImg = $this->getConfigParam('blNativeImages');\n\n        return $this->getUrl($file, $this->_sResourceDir, $admin, $ssl, $nativeImg, $lang);\n    }\n\n    /**\n     * Finds and returns resource (css, js, etc..) folders path\n     *\n     * @param bool $admin Whether to force admin\n     *\n     * @return string\n     */\n    public function getResourceDir($admin)\n    {\n        return $this->getDir(null, $this->_sResourceDir, $admin);\n    }\n\n    /**\n     * Returns array of available currencies\n     *\n     * @param integer $currency Active currency number (default null)\n     *\n     * @return stdClass[]\n     */\n    public function getCurrencyArray($currency = null)\n    {\n        $confCurrencies = $this->getConfigParam('aCurrencies');\n        if (!is_array($confCurrencies)) {\n            return [];\n        }\n\n        // processing currency configuration data\n        $currencies = [];\n        reset($confCurrencies);\n        foreach ($confCurrencies as $key => $val) {\n            if ($val) {\n                $cur = new stdClass();\n                $cur->id = $key;\n                $curValues = explode('@', $val);\n                $cur->name = trim($curValues[0]);\n                $cur->rate = trim($curValues[1]);\n                $cur->dec = trim($curValues[2]);\n                $cur->thousand = trim($curValues[3]);\n                $cur->sign = trim($curValues[4]);\n                $cur->decimal = trim($curValues[5]);\n\n                // change for US version\n                if (isset($curValues[6])) {\n                    $cur->side = trim($curValues[6]);\n                }\n\n                if (isset($currency) && $key == $currency) {\n                    $cur->selected = 1;\n                } else {\n                    $cur->selected = 0;\n                }\n                $currencies[$key] = $cur;\n            }\n\n            // #861C -  performance, do not load other currencies\n            if (!$this->getConfigParam('bl_perfLoadCurrency')) {\n                break;\n            }\n        }\n\n        return $currencies;\n    }\n\n    /**\n     * Returns currency object.\n     *\n     * @param string $name Name of active currency\n     *\n     * @return stdClass|null\n     */\n    public function getCurrencyObject($name)\n    {\n        $search = $this->getCurrencyArray();\n        foreach ($search as $cur) {\n            if ($cur->name == $name) {\n                return $cur;\n            }\n        }\n    }\n\n    /**\n     * Checks if the shop is in demo mode.\n     *\n     * @return bool\n     */\n    public function isDemoShop()\n    {\n        return ContainerFacade::getParameter('oxid_esales.demo_shop_mode');\n    }\n\n    public function getEdition(): Edition\n    {\n        return ContainerFacade::get(BasicContextInterface::class)->getEdition();\n    }\n\n    /**\n     * Returns full eShop edition name\n     */\n    public function getFullEdition(): string\n    {\n        return $this->getEdition()->getFullEditionName();\n    }\n\n    /**\n     * Returns build package info file content.\n     *\n     * @return bool|string\n     */\n    public function getPackageInfo()\n    {\n        $fileName = Path::join(ContainerFacade::getParameter('oxid_esales.shop_source_directory'), 'pkg.info');\n        $rev = @file_get_contents($fileName);\n        $rev = str_replace(\"\\n\", \"<br>\", $rev);\n\n        if (!$rev) {\n            return false;\n        }\n\n        return $rev;\n    }\n\n    /**\n     * Counts OXID mandates\n     *\n     * @return int\n     */\n    public function getMandateCount()\n    {\n        return 1;\n    }\n\n    /**\n     * Checks if shop is MALL. Returns true on success.\n     *\n     * @return bool\n     */\n    public function isMall()\n    {\n        return false;\n    }\n\n    /**\n     * Checks version of shop, returns:\n     *  0 - version is bellow 2.2\n     *  1 - Demo or unlicensed\n     *  2 - Pro\n     *  3 - Enterprise\n     */\n    public function detectVersion()\n    {\n    }\n\n    /**\n     * Updates or adds new shop configuration parameters to DB.\n     * Arrays must be passed not serialized, serialized values are supported just for backward compatibility.\n     *\n     * @param string $varType Variable Type\n     * @param string $varName Variable name\n     * @param mixed  $varVal  Variable value (can be string, integer or array)\n     * @param int    $shopId  Shop ID, default is current shop\n     * @param string $module  Module name (empty for base options)\n     */\n    public function saveShopConfVar($varType, $varName, $varVal, $shopId = null, $module = '')\n    {\n        switch ($varType) {\n            case 'arr':\n            case 'aarr':\n                $value = serialize($varVal);\n                break;\n            case 'bool':\n                //config param\n                $varVal = (($varVal == 'true' || $varVal) && $varVal && strcasecmp($varVal, \"false\"));\n                //db value\n                $value = $varVal ? \"1\" : \"\";\n                break;\n            case 'num':\n                //config param\n                $varVal = $varVal != '' ? Registry::getUtils()->string2Float($varVal) : '';\n                $value = $varVal;\n                break;\n            default:\n                $value = $varVal;\n                break;\n        }\n\n        if (!$shopId) {\n            $shopId = $this->getShopId();\n        }\n\n        // Update value only for current shop\n        if ($shopId == $this->getShopId()) {\n            $this->setConfigParam($varName, $varVal);\n        }\n\n        $db = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $newOXID = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsObject()->generateUID();\n\n        $query = \"delete from oxconfig where oxshopid = :oxshopid and oxvarname = :oxvarname and oxmodule = :oxmodule\";\n        $db->execute($query, [\n            'oxshopid' => $shopId,\n            'oxvarname' => $varName,\n            'oxmodule' => $module ?: ''\n        ]);\n\n        $query = \"insert into oxconfig (oxid, oxshopid, oxmodule, oxvarname, oxvartype, oxvarvalue)\n                  values (:oxid, :oxshopid, :oxmodule, :oxvarname, :oxvartype, :value)\";\n        $db->execute($query, [\n            'oxid' => $newOXID,\n            'oxshopid' => $shopId,\n            'oxmodule' => $module ?: '',\n            'oxvarname' => $varName,\n            'oxvartype' => $varType,\n            'value' => $value ?? '',\n        ]);\n\n        $this->informServicesAfterConfigurationChanged($varName, $shopId, $module);\n    }\n\n    /**\n     * Retrieves shop configuration parameters from DB.\n     *\n     * @param string $varName Variable name\n     * @param int    $shopId  Shop ID\n     * @param string $module  module identifier\n     *\n     * @return object - raw configuration value in DB\n     */\n    public function getShopConfVar($varName, $shopId = null, $module = '')\n    {\n        if (!$shopId) {\n            $shopId = $this->getShopId();\n        }\n\n        if ($shopId == $this->getShopId() && (!$module || $module == Config::OXMODULE_THEME_PREFIX . $this->getConfigParam('sTheme'))) {\n            $varValue = $this->getConfigParam($varName);\n            if ($varValue !== null) {\n                return $varValue;\n            }\n        }\n\n        $db = DatabaseProvider::getDb();\n\n        $query = \"select oxvartype, oxvarvalue from oxconfig where oxshopid = :oxshopid and oxmodule = :oxmodule and oxvarname = :oxvarname\";\n        $result = $db->select($query, [\n            'oxshopid' => $shopId,\n            'oxmodule' => $module,\n            'oxvarname' => $varName\n        ]);\n\n        if ($result != false && $result->count() > 0) {\n            return $this->decodeValue($result->fields['oxvartype'], $result->fields['oxvarvalue']);\n        }\n    }\n\n    /**\n     * Decodes and returns database value\n     *\n     * @param string $type       parameter type\n     * @param mixed  $mOrigValue parameter db value\n     *\n     * @return mixed\n     */\n    public function decodeValue($type, $mOrigValue)\n    {\n        $value = $mOrigValue;\n        switch ($type) {\n            case 'arr':\n            case 'aarr':\n                $value = unserialize($mOrigValue, ['allowed_classes' => false]);\n                break;\n            case 'bool':\n                $value = ($mOrigValue == 'true' || $mOrigValue == '1');\n                break;\n        }\n\n        return $value;\n    }\n\n    /**\n     * Returns true if current active shop is in productive mode or false if not\n     *\n     * @return bool\n     */\n    public function isProductiveMode()\n    {\n        $productive = $this->getConfigParam('blProductive');\n        if (!isset($productive)) {\n            $query = 'select oxproductive from oxshops where oxid = :oxid';\n            $productive = (bool) \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->getOne($query, [\n                'oxid' => $this->getShopId()\n            ]);\n            $this->setConfigParam('blProductive', $productive);\n        }\n\n        return $productive;\n    }\n\n    /**\n     * Function returns default shop ID\n     *\n     * @deprecated\n     * @see BasicContextInterface::getDefaultShopId()\n     *\n     * @return string\n     */\n    public function getBaseShopId()\n    {\n        return \\OxidEsales\\Eshop\\Core\\ShopIdCalculator::BASE_SHOP_ID;\n    }\n\n    /**\n     * Loads and returns active shop object\n     *\n     * @return Shop\n     */\n    public function getActiveShop()\n    {\n        if (\n            $this->_oActShop && $this->_iShopId == $this->_oActShop->getId() &&\n            $this->_oActShop->getLanguage() == Registry::getLang()->getBaseLanguage()\n        ) {\n            return $this->_oActShop;\n        }\n\n        $this->_oActShop = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Shop::class);\n        $this->_oActShop->load($this->getShopId());\n\n        return $this->_oActShop;\n    }\n\n    /**\n     * Returns active view object. If this object was not defined - returns oxubase object\n     *\n     * @return FrontendController\n     */\n    public function getActiveView()\n    {\n        if (count($this->_aActiveViews)) {\n            $actView = end($this->_aActiveViews);\n        }\n        if (!isset($actView) || $actView == null) {\n            $actView = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::class);\n            $this->_aActiveViews[] = $actView;\n        }\n\n        return $actView;\n    }\n\n    /**\n     * Returns top active view object from views chain.\n     *\n     * @return FrontendController\n     */\n    public function getTopActiveView()\n    {\n        if (count($this->_aActiveViews)) {\n            return reset($this->_aActiveViews);\n        } else {\n            return $this->getActiveView();\n        }\n    }\n\n    /**\n     * Returns all active views objects list.\n     *\n     * @return array\n     */\n    public function getActiveViewsList()\n    {\n        return $this->_aActiveViews;\n    }\n\n    /**\n     * View object setter\n     *\n     * @param object $view view object\n     */\n    public function setActiveView($view)\n    {\n        $this->_aActiveViews[] = $view;\n    }\n\n    /**\n     * Drop last active view object\n     */\n    public function dropLastActiveView()\n    {\n        array_pop($this->_aActiveViews);\n    }\n\n    /**\n     * Check if there is more than one active view\n     *\n     * @return null\n     */\n    public function hasActiveViewsChain()\n    {\n        return (count($this->_aActiveViews) > 1);\n    }\n\n    /**\n     * Get active views class id list\n     *\n     * @return array\n     */\n    public function getActiveViewsIds()\n    {\n        $ids = [];\n\n        if (is_array($this->getActiveViewsList())) {\n            foreach ($this->getActiveViewsList() as $view) {\n                $ids[] = $view->getClassKey();\n            }\n        }\n\n        return $ids;\n    }\n\n    /**\n     * Returns log files storage path\n     *\n     * @return string\n     */\n    public function getLogsDir()\n    {\n        return Path::join(ContainerFacade::getParameter('oxid_esales.shop_source_directory'), 'log');\n    }\n\n    /**\n     * Returns true if option is theme option\n     *\n     * @param string $name option name\n     *\n     * @return bool\n     */\n    public function isThemeOption($name)\n    {\n        return (bool) isset($this->_aThemeConfigParams[$name]);\n    }\n\n    /**\n     * Returns  SSL or non SSL shop main URL without index.php\n     *\n     * @return string\n     */\n    public function getShopMainUrl()\n    {\n        return ContainerFacade::getParameter('oxid_esales.shop_url');\n    }\n\n    /**\n     * Return active shop ids\n     *\n     * @deprecated\n     * @see ContextInterface::getAllShopIds()\n     *\n     * @return array\n     */\n    public function getShopIds()\n    {\n        return \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->getCol(\"SELECT `oxid` FROM `oxshops`\");\n    }\n\n    /**\n     * Function returns shop url by given language.\n     * #680 per language another URL\n     *\n     * @param integer $lang Language id.\n     * @param bool    $ssl  Whether to use ssl.\n     *\n     * @return null|string\n     */\n    public function getShopUrlByLanguage($lang, $ssl = false)\n    {\n        $configParameter = $ssl ? 'aLanguageSSLURLs' : 'aLanguageURLs';\n        $lang = isset($lang) ? $lang : Registry::getLang()->getBaseLanguage();\n        $languageURLs = $this->getConfigParam($configParameter);\n        if (isset($lang) && isset($languageURLs[$lang]) && !empty($languageURLs[$lang])) {\n            $languageURLs[$lang] = Registry::getUtils()->checkUrlEndingSlash($languageURLs[$lang]);\n            return $languageURLs[$lang];\n        }\n    }\n\n    /**\n     * Function returns mall shop url.\n     *\n     * @return null|string\n     */\n    public function getMallShopUrl()\n    {\n        $mallShopUrl = $this->getConfigParam('sMallSSLShopURL');\n        if ($mallShopUrl) {\n            return Registry::getUtils()->checkUrlEndingSlash($mallShopUrl);\n        }\n    }\n\n    /**\n     * Handle database exception.\n     * At this point everything has crashed already and not much of shop business logic is left to call.\n     * So just go straight and call the ExceptionHandler.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Exception\\DatabaseException $exception\n     */\n    protected function handleDbConnectionException(\\OxidEsales\\Eshop\\Core\\Exception\\DatabaseException $exception)\n    {\n        $this->getExceptionHandler()->handleUncaughtException($exception);\n    }\n\n    /**\n     * Redirect to start page and display the error\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Exception\\StandardException $ex message to show on exit\n     */\n    protected function handleCookieException($ex)\n    {\n        $this->processSeoCall();\n\n        //starting up the session\n        $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n        $session->start();\n\n        // redirect to start page and display the error\n        Registry::getUtilsView()->addErrorToDisplay($ex);\n        Registry::getUtils()->redirect($this->getShopHomeUrl() . 'cl=start', true, 302);\n    }\n\n    /**\n     * Save system configuration parameters, which is the same for sub-shops.\n     *\n     * @param string $parameterType  Type\n     * @param string $parameterName  Name\n     * @param mixed  $parameterValue Value (can be string, integer or array)\n     */\n    public function saveSystemConfigParameter($parameterType, $parameterName, $parameterValue)\n    {\n        $this->saveShopConfVar($parameterType, $parameterName, $parameterValue, $this->getBaseShopId());\n    }\n\n    /**\n     * Retrieves system configuration parameters, which is the same for sub-shops.\n     *\n     * @param string $parameterName Variable name\n     *\n     * @return mixed\n     */\n    public function getSystemConfigParameter($parameterName)\n    {\n        return $this->getShopConfVar($parameterName, $this->getBaseShopId());\n    }\n\n    /**\n     * Returns whether given shop id is valid.\n     *\n     * @param int    $shopId\n     *\n     * @return bool\n     */\n    protected function isValidShopId($shopId)\n    {\n        return !empty($shopId);\n    }\n\n    /**\n     * Returns active shop id.\n     *\n     * @return string\n     */\n    protected function calculateActiveShopId()\n    {\n        return $this->getBaseShopId();\n    }\n\n    /**\n     * Check and get template path by Edition if exists\n     *\n     * @param string $templateName\n     *\n     * @return false|string\n     */\n    protected function getEditionTemplate($templateName)\n    {\n        return false;\n    }\n\n    /**\n     * @return \\OxidEsales\\Eshop\\Core\\Exception\\ExceptionHandler\n     */\n    protected function getExceptionHandler()\n    {\n        return new \\OxidEsales\\Eshop\\Core\\Exception\\ExceptionHandler();\n    }\n\n    /**\n     * Inform respective services if shop/module/theme related configuration data was changed in database.\n     *\n     * @param string  $varName   Variable name\n     * @param integer $shopId    Shop id\n     * @param string  $extension Module or theme name in case of extension config change\n     */\n    protected function informServicesAfterConfigurationChanged($varName, $shopId, $extension = '')\n    {\n        if (empty($extension)) {\n            ContainerFacade::dispatch(new ShopConfigurationChangedEvent($varName, (int) $shopId));\n        } elseif (str_contains($extension, self::OXMODULE_THEME_PREFIX)) {\n            ContainerFacade::dispatch(new ThemeSettingChangedEvent($varName, (int) $shopId, $extension));\n        }\n    }\n}\n"
  },
  {
    "path": "source/Core/Contract/AbstractUpdatableFields.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Contract;\n\n/**\n * Abstraction for handling fields which could be modified by shop customer.\n */\nabstract class AbstractUpdatableFields\n{\n    /** @var string */\n    protected $tableName;\n\n    /**\n     * Return list of fields which could be updated by shop customer.\n     */\n    abstract public function getUpdatableFields();\n\n    /**\n     * Get table name of a model.\n     * Table name could be used to form full name together with field.\n     *\n     * @return string\n     */\n    public function getTableName()\n    {\n        return $this->tableName;\n    }\n}\n"
  },
  {
    "path": "source/Core/Contract/ClassNameResolverInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Contract;\n\n/**\n * The implementation of this class maps className to classId and vice versa.\n */\ninterface ClassNameResolverInterface\n{\n    /**\n     * Map argument classId to related className.\n     *\n     * @param string $classId Class id.\n     *\n     * @return string|null\n     */\n    public function getClassNameById($classId);\n\n    /**\n     * Map argument className to related classId.\n     *\n     * @param string $className Class name.\n     *\n     * @return string|null\n     */\n    public function getIdByClassName($className);\n}\n"
  },
  {
    "path": "source/Core/Contract/ControllerMapProviderInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Contract;\n\n/**\n * The implementation of this class determines the controllers which should be allowed to be called directly via\n * HTTP GET/POST Parameters, inside form actions or with oxid_include_widget.\n * Those controllers are specified e.g. inside a form action with a controller key which is mapped to its class.\n */\ninterface ControllerMapProviderInterface\n{\n    /**\n     * Get all controller keys and their assigned classes\n     *\n     * @return array\n     */\n    public function getControllerMap();\n}\n"
  },
  {
    "path": "source/Core/Contract/IConfigurable.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Contract;\n\n/**\n * The interface methods should be implemented by classes which need a configuration object\n * (usually OxConfig) manually set.\n */\ninterface IConfigurable\n{\n\n    /**\n     * Sets configuration object\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Config $oConfig Configraution object\n     *\n     * @abstract\n     *\n     * @return mixed\n     */\n    public function setConfig(\\OxidEsales\\Eshop\\Core\\Config $oConfig);\n\n    /**\n     * Returns active configuration object\n     *\n     * @abstract\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Config\n     */\n    public function getConfig();\n}\n"
  },
  {
    "path": "source/Core/Contract/ICountryAware.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Contract;\n\n/**\n * Interface for country getter and setter\n */\ninterface ICountryAware\n{\n\n    /**\n     * Country setter\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Country $oCountry\n     */\n    public function setCountry(\\OxidEsales\\Eshop\\Application\\Model\\Country $oCountry);\n\n    /**\n     * Country getter\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Country\n     */\n    public function getCountry();\n}\n"
  },
  {
    "path": "source/Core/Contract/IDisplayError.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Contract;\n\n/**\n * DisplayError interface\n */\ninterface IDisplayError\n{\n\n    /**\n     * This method should return a localized message for displaying\n     *\n     * @return string A string to display to the user\n     */\n    public function getOxMessage();\n\n    /**\n     * Returns a type of the error, e.g. the class of the exception or whatever class\n     * implemented this interface\n     *\n     * @return string The error type\n     */\n    public function getErrorClassType();\n\n    /**\n     * Possibility to access additional values\n     *\n     * @param string $sName Value name\n     *\n     * @return string An additional value (string) by its name\n     */\n    public function getValue($sName);\n}\n"
  },
  {
    "path": "source/Core/Contract/ISelectList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Contract;\n\n/**\n * Interface for selection list based objects\n */\ninterface ISelectList\n{\n\n    /**\n     * Returns selection list label\n     *\n     * @return string\n     */\n    public function getLabel();\n\n    /**\n     * Returns array of oxSelection's\n     *\n     * @return array\n     */\n    public function getSelections();\n\n    /**\n     * Returns active selection object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Selection\n     */\n    public function getActiveSelection();\n}\n"
  },
  {
    "path": "source/Core/Contract/IUrl.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Contract;\n\n/**\n * Interface for object URLs getters\n */\ninterface IUrl\n{\n\n    /**\n     * Returns object URL. If SEO if ON returned link will be in SEO form,\n     * else URL will have dynamic form\n     *\n     * @param int $iLang language id [optional]\n     *\n     * @return string\n     */\n    public function getLink($iLang = null);\n\n    /**\n     * Returns standard (dynamic) object URL\n     *\n     * @param int   $iLang   language id [optional]\n     * @param array $aParams additional params to use [optional]\n     *\n     * @return string\n     */\n    public function getStdLink($iLang = null, $aParams = []);\n\n    /**\n     * Returns base dynamic url: e.g. shopurl/index.php?cl=details&anid=artid\n     *\n     * @param int  $iLang   language id\n     * @param bool $blAddId add current object id to url or not\n     * @param bool $blFull  return full including domain name [optional]\n     *\n     * @return string\n     */\n    public function getBaseStdLink($iLang, $blAddId = true, $blFull = true);\n}\n"
  },
  {
    "path": "source/Core/Controller/BaseController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Core\\ShopVersion;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Controller\\ViewControllerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents\\AfterRequestProcessedEvent;\n\n/**\n * Base view class. Collects and passes data to template engine, sets some global\n * configuration parameters.\n */\nclass BaseController extends \\OxidEsales\\Eshop\\Core\\Base implements ViewControllerInterface\n{\n    /**\n     * Array of data that is passed to template engine - array( \"varName\" => \"varValue\").\n     *\n     * @var array\n     */\n    protected $_aViewData = [];\n\n    /**\n     * View parameters array\n     *\n     * @var array\n     */\n    protected $_aViewParams = [];\n\n    /**\n     * Location of a executed class file.\n     *\n     * @var string\n     */\n    protected $_sClassLocation = null;\n\n    /**\n     * Name of running class method.\n     *\n     * @var string\n     */\n    protected $_sThisAction = null;\n\n    /**\n     * If this is a component we will have our parent view here.\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Controller\\BaseController|null\n     */\n    protected $_oParent = null;\n\n    /**\n     * Flag if this object is a component or not\n     *\n     * @var bool|null\n     */\n    protected $_blIsComponent = false;\n\n    /**\n     * Name of template file to render.\n     *\n     * @var string\n     */\n    protected $_sThisTemplate = null;\n\n    /**\n     * ID of current view - generated php file.\n     *\n     * @var string\n     */\n    protected $_sViewId = null;\n\n    /**\n     * Current view class name\n     *\n     * @var string\n     */\n    protected $_sClass = null;\n\n    /**\n     * Current view class key\n     *\n     * @var string\n     */\n    protected $classKey = null;\n\n    /**\n     * Action function name\n     *\n     * @var string\n     */\n    protected $_sFnc = null;\n\n    /**\n     * Marker if user defined function was executed\n     *\n     * @var bool\n     */\n    protected static $_blExecuted = false;\n\n    /**\n     * Active charset\n     *\n     * @var string\n     */\n    protected $_sCharSet = null;\n\n    /**\n     * Shop version\n     *\n     * @var string\n     */\n    protected $_sVersion = null;\n\n    /**\n     * If current shop has demo version\n     *\n     * @var bool\n     */\n    protected $_blDemoVersion = null;\n\n    /**\n     * If current shop has demo shop\n     *\n     * @var bool\n     */\n    protected $_blDemoShop = null;\n\n    /**\n     * Display if newsletter must be displayed\n     *\n     * @var bool\n     */\n    protected $_iNewsStatus = null;\n\n    /**\n     * Shop logo\n     *\n     * @var string\n     */\n    protected $_sShopLogo = null;\n\n    /**\n     * Category ID\n     *\n     * @var string\n     */\n    protected $_sCategoryId = null;\n\n    /**\n     * Active category object.\n     *\n     * @var object\n     */\n    protected $_oClickCat = null;\n\n    /**\n     * Cache sign to enable/disable use of cache.\n     *\n     * @var bool\n     */\n    protected $_blIsCallForCache = false;\n\n    /**\n     * \\OxidEsales\\Eshop\\Core\\ViewConfig instance\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\ViewConfig\n     */\n    protected $_oViewConf = null;\n\n    /**\n     * Initiates all components stored, executes \\OxidEsales\\Eshop\\Core\\Controller\\BaseController::addGlobalParams.\n     */\n    public function init()\n    {\n        // setting current view class name\n        $this->_sThisAction = strtolower(get_class($this));\n\n        if (!$this->_blIsComponent) {\n            // assume that cached components does not affect this method ...\n            $this->addGlobalParams();\n        }\n    }\n\n    /**\n     * Add parameters to controllers\n     *\n     * @param array $aParams view parameters array.\n     */\n    public function setViewParameters($aParams = null)\n    {\n        $this->_aViewParams = $aParams;\n    }\n\n    /**\n     * Get parameters to controllers\n     *\n     * @param string $sKey parameter key\n     *\n     * @return string\n     */\n    public function getViewParameter($sKey)\n    {\n        return (isset($this->_aViewParams[$sKey])) ? $this->_aViewParams[$sKey] : Registry::getRequest()->getRequestEscapedParameter($sKey);\n    }\n\n    /**\n     * Set cache sign to enable/disable use of cache\n     *\n     * @param bool $blIsCallForCache cache sign to enable/disable use of cache\n     */\n    public function setIsCallForCache($blIsCallForCache = null)\n    {\n        $this->_blIsCallForCache = $blIsCallForCache;\n    }\n\n    /**\n     * Get cache sign to enable/disable use of cache\n     *\n     * @return bool\n     */\n    public function getIsCallForCache()\n    {\n        return $this->_blIsCallForCache;\n    }\n\n    /**\n     * Returns view ID (currently it returns NULL)\n     */\n    public function getViewId()\n    {\n    }\n\n    /**\n     * Entry point to pass controller-specific data to the view.\n     * @return string current view template file name\n     */\n    public function render()\n    {\n        return $this->getTemplateName();\n    }\n\n    /**\n     * Sets and caches default parameters for shop object and returns it.\n     *\n     * Template variables:\n     * <b>isdemoversion</b>, <b>shop</b>, <b>isdemoversion</b>,\n     * <b>version</b>,\n     * <b>urlsign</b>\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Shop $oShop current shop object\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\ViewConfig $oShop current shop object\n     */\n    public function addGlobalParams($oShop = null)\n    {\n        // by default we always display newsletter bar\n        $this->_iNewsStatus = 1;\n\n        // assigning shop to view config ..\n        $oViewConf = $this->getViewConfig();\n        if ($oShop) {\n            $oViewConf->setViewShop($oShop, $this->_aViewData);\n        }\n\n        //sending all view to template engine\n        $this->_aViewData['oView'] = $this;\n        $this->_aViewData['oViewConf'] = $this->getViewConfig();\n\n        return $oViewConf;\n    }\n\n    /**\n     * Sets value to parameter used by template engine.\n     *\n     * @param string $sPara  name of parameter to pass\n     * @param mixed  $sValue value of parameter\n     */\n    public function addTplParam($sPara, $sValue)\n    {\n        $this->_aViewData[$sPara] = $sValue;\n    }\n\n    /**\n     * Returns view config object\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\ViewConfig\n     */\n    public function getViewConfig()\n    {\n        if ($this->_oViewConf === null) {\n            $this->_oViewConf = oxNew(\\OxidEsales\\Eshop\\Core\\ViewConfig::class);\n        }\n\n        return $this->_oViewConf;\n    }\n\n    /**\n     * Returns current view template file name\n     *\n     * @return string\n     */\n    public function getTemplateName()\n    {\n        return $this->_sThisTemplate;\n    }\n\n    /**\n     * Sets current view template file name\n     *\n     * @param string $sTemplate template name\n     */\n    public function setTemplateName($sTemplate)\n    {\n        $this->_sThisTemplate = $sTemplate;\n    }\n\n    /**\n     * Current view class key setter.\n     *\n     * @param string $classKey current view class key\n     */\n    public function setClassKey($classKey)\n    {\n        $this->_sClass = $classKey;\n        $this->classKey = $classKey;\n    }\n\n    /**\n     * Returns class key of current view\n     *\n     * @return string\n     */\n    public function getClassKey()\n    {\n        return $this->classKey;\n    }\n\n    /**\n     * Set current view action function name\n     *\n     * @param string $sFncName action function name\n     */\n    public function setFncName($sFncName)\n    {\n        $this->_sFnc = $sFncName;\n    }\n\n    /**\n     * Returns name of current action function\n     *\n     * @return string\n     */\n    public function getFncName()\n    {\n        return $this->_sFnc;\n    }\n\n    /**\n     * Set array of data that is passed to template engine - array( \"varName\" => \"varValue\")\n     *\n     * @param array $aViewData array of data that is passed to template engine\n     */\n    public function setViewData($aViewData = null)\n    {\n        $this->_aViewData = $aViewData;\n    }\n\n    /**\n     * Get view data\n     *\n     * @return array\n     */\n    public function getViewData()\n    {\n        return $this->_aViewData;\n    }\n\n    /**\n     * Get view data single array element\n     *\n     * @param string $sParamId view data array key\n     *\n     * @return mixed\n     */\n    public function getViewDataElement($sParamId = null)\n    {\n        $return = null;\n        if ($sParamId && isset($this->_aViewData[$sParamId])) {\n            $return = $this->_aViewData[$sParamId];\n        }\n\n        return $return;\n    }\n\n    /**\n     * Set location of a executed class file\n     *\n     * @param string $sClassLocation location of a executed class file\n     */\n    public function setClassLocation($sClassLocation = null)\n    {\n        $this->_sClassLocation = $sClassLocation;\n    }\n\n    /**\n     * Get location of a executed class file\n     *\n     * @return string\n     */\n    public function getClassLocation()\n    {\n        return $this->_sClassLocation;\n    }\n\n    /**\n     * Set name of running class method\n     *\n     * @param string $sThisAction name of running class method\n     */\n    public function setThisAction($sThisAction = null)\n    {\n        $this->_sThisAction = $sThisAction;\n    }\n\n    /**\n     * Get name of running class method\n     *\n     * @return string\n     */\n    public function getThisAction()\n    {\n        return $this->_sThisAction;\n    }\n\n    /**\n     * Set parent object. If this is a component we will have our parent view here.\n     * @param \\OxidEsales\\Eshop\\Core\\Controller\\BaseController $oParent parent object\n     */\n    public function setParent($oParent = null)\n    {\n        $this->_oParent = $oParent;\n    }\n\n    /**\n     * Get parent object\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Controller\\BaseController|null\n     */\n    public function getParent()\n    {\n        return $this->_oParent;\n    }\n\n    /**\n     * Set flag if this object is a component or not\n     *\n     * @param bool|null $blIsComponent flag if this object is a component\n     */\n    public function setIsComponent($blIsComponent = null)\n    {\n        $this->_blIsComponent = $blIsComponent;\n    }\n\n    /**\n     * Get flag if this object is a component\n     *\n     * @return bool|null\n     */\n    public function getIsComponent()\n    {\n        return $this->_blIsComponent;\n    }\n\n    /**\n     * Executes method (creates class and then executes). Returns executed\n     * function result.\n     *\n     * @param string $sFunction name of function to execute\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\SystemComponentException system component exception\n     */\n    public function executeFunction($sFunction)\n    {\n        // execute\n        if ($sFunction && !self::$_blExecuted) {\n            if (method_exists($this, $sFunction)) {\n                $sNewAction = $this->$sFunction();\n                self::$_blExecuted = true;\n                ContainerFacade::dispatch(new AfterRequestProcessedEvent());\n\n                if (isset($sNewAction)) {\n                    $this->executeNewAction($sNewAction);\n                }\n            } elseif (!$this->_blIsComponent) {\n                throw new \\OxidEsales\\Eshop\\Core\\Exception\\RoutingException(\n                    sprintf(\"Controller method is not accessible: %s::%s\", self::class, $sFunction)\n                );\n            }\n        }\n    }\n\n    /**\n     * Formats header for new controller action\n     *\n     * Input example: \"view_name?param1=val1&param2=val2\" => \"cl=view_name&param1=val1&param2=val2\"\n     *\n     * @param string $sNewAction new action params\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\SystemComponentException system component exception\n     */\n    protected function executeNewAction($sNewAction)\n    {\n        if ($sNewAction) {\n            $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n            // page parameters is the part which goes after '?'\n            $params = explode('?', $sNewAction);\n\n            // action parameters is the part before '?'\n            $pageParams = isset($params[1]) ? $params[1] : null;\n\n            // looking for function name\n            $params = explode('/', $params[0]);\n            $className = $params[0];\n            $resolvedClassName = \\OxidEsales\\Eshop\\Core\\Registry::getControllerClassNameResolver()->getClassNameById($className);\n            $realClassName = $resolvedClassName ? \\OxidEsales\\Eshop\\Core\\Registry::getUtilsObject()->getClassName($resolvedClassName) : \\OxidEsales\\Eshop\\Core\\Registry::getUtilsObject()->getClassName($className);\n\n            if (false === class_exists($realClassName)) {\n                //If redirect tries to use a not existing class throw an exception.\n                //we'll be redirected to start page directly.\n                $exception =  new \\OxidEsales\\Eshop\\Core\\Exception\\SystemComponentException();\n                /** Use setMessage here instead of passing it in constructor in order to test exception message */\n                $exception->setMessage('ERROR_MESSAGE_SYSTEMCOMPONENT_CLASSNOTFOUND' . ' ' . $className);\n                $exception->setComponent($className);\n                throw $exception;\n            }\n\n            $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n\n            // building redirect path ...\n            $header = ($className) ? \"cl=$className&\" : ''; // adding view name\n            $header .= ($pageParams) ? \"$pageParams&\" : ''; // adding page params\n            $header .= $session->sid(); // adding session Id\n\n            $url = $myConfig->getCurrentShopUrl($this->isAdmin());\n\n            $url = \"{$url}index.php?{$header}\";\n\n            $url = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsUrl()->processUrl($url);\n\n            if (\\OxidEsales\\Eshop\\Core\\Registry::getUtils()->seoIsActive() && $seoUrl = \\OxidEsales\\Eshop\\Core\\Registry::getSeoEncoder()->getStaticUrl($url)) {\n                $url = $seoUrl;\n            }\n\n            $this->onExecuteNewAction();\n\n            ContainerFacade::dispatch(new AfterRequestProcessedEvent());\n\n            //#M341 do not add redirect parameter\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->redirect($url, (bool) Registry::getRequest()->getRequestEscapedParameter('redirected'), 302);\n        }\n    }\n\n    /**\n     * Method for overwriting if any additional actions on _executeNewAction is needed\n     */\n    protected function onExecuteNewAction()\n    {\n    }\n\n    /**\n     * Template variable getter. Returns additional params for url\n     *\n     * @return string\n     */\n    public function getAdditionalParams()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getUtilsUrl()->processUrl('', false);\n    }\n\n    /**\n     * Returns active charset\n     *\n     * @return string\n     */\n    public function getCharSet()\n    {\n        if ($this->_sCharSet == null) {\n            $this->_sCharSet = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('charset');\n        }\n\n        return $this->_sCharSet;\n    }\n\n    /**\n     * Returns shop version\n     *\n     * @return string\n     */\n    public function getShopVersion()\n    {\n        return ShopVersion::getVersion();\n    }\n\n    /**\n     * Returns shop edition\n     *\n     * @return string\n     */\n    public function getShopEdition()\n    {\n        return Registry::getConfig()->getEdition()->value;\n    }\n\n    /**\n     * Returns shop package info\n     *\n     * @return string\n     */\n    public function getPackageInfo()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getPackageInfo();\n    }\n\n    /**\n     * Returns shop full edition\n     *\n     * @return string\n     */\n    public function getShopFullEdition()\n    {\n        $sEdition = $this->getShopEdition();\n        $sFullEdition = \"Community Edition\";\n        if ($sEdition == \"PE\") {\n            $sFullEdition = \"Professional Edition\";\n        }\n\n        if ($sEdition == \"EE\") {\n            $sFullEdition = \"Enterprise Edition\";\n        }\n\n        return $sFullEdition;\n    }\n\n\n    /**\n     * Returns if current shop is demo version\n     *\n     * @return string\n     */\n    public function isDemoVersion()\n    {\n        if ($this->_blDemoVersion == null) {\n            $this->_blDemoVersion = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->detectVersion() == 1;\n        }\n\n        return $this->_blDemoVersion;\n    }\n\n    /**\n     * Returns if current shop is beta version.\n     *\n     * @return bool\n     */\n    public function isBetaVersion()\n    {\n        return (stripos($this->getShopVersion(), 'beta') !== false);\n    }\n\n    /**\n     * Returns if current shop is release candidate version.\n     *\n     * @return bool\n     */\n    public function isRCVersion()\n    {\n        return (stripos($this->getShopVersion(), 'rc') !== false);\n    }\n\n    /**\n     * Returns if current shop is demo shop\n     *\n     * @return string\n     */\n    public function isDemoShop()\n    {\n        if ($this->_blDemoShop == null) {\n            $this->_blDemoShop = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->isDemoShop();\n        }\n\n        return $this->_blDemoShop;\n    }\n\n    /**\n     * Template variable getter. Returns if newsletter can be displayed (for _right)\n     *\n     * @return integer\n     */\n    public function showNewsletter()\n    {\n        return $this->_iNewsStatus === null ? 1 : $this->_iNewsStatus;\n    }\n\n    /**\n     * Sets if to show newsletter\n     *\n     * @param bool $blShow if TRUE - newsletter subscription box will be shown\n     */\n    public function setShowNewsletter($blShow)\n    {\n        $this->_iNewsStatus = $blShow;\n    }\n\n    /**\n     * Returns active category set by categories component; if category is\n     * not set by component - will create category object and will try to\n     * load by id passed by request\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Category\n     */\n    public function getActCategory()\n    {\n        // if active category is not set yet - trying to load it from request params\n        // this may be usefull when category component was unable to load active category\n        // and we still need some object to mount navigation info\n        if ($this->_oClickCat === null) {\n            $this->_oClickCat = false;\n            $oCategory = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Category::class);\n            if ($oCategory->load($this->getCategoryId())) {\n                $this->_oClickCat = $oCategory;\n            }\n        }\n\n        return $this->_oClickCat;\n    }\n\n    /**\n     * Active category setter\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Category $oCategory active category\n     */\n    public function setActCategory($oCategory)\n    {\n        $this->_oClickCat = $oCategory;\n    }\n\n    /**\n     * Get category ID\n     *\n     * @return string\n     */\n    public function getCategoryId()\n    {\n        if ($this->_sCategoryId == null && ($sCatId = Registry::getRequest()->getRequestEscapedParameter('cnid'))) {\n            $this->_sCategoryId = $sCatId;\n        }\n\n        return $this->_sCategoryId;\n    }\n\n    /**\n     * Category ID setter\n     *\n     * @param string $sCategoryId Id of category to cache\n     */\n    public function setCategoryId($sCategoryId)\n    {\n        $this->_sCategoryId = $sCategoryId;\n    }\n\n    /**\n     * Returns a name of the view variable containing the error/exception messages\n     */\n    public function getErrorDestination()\n    {\n    }\n\n    /**\n     * Returns name of a view class, which will be active for an action\n     * (given a generic fnc, e.g. logout)\n     *\n     * @return string\n     */\n    public function getActionClassName()\n    {\n        return $this->getClassKey();\n    }\n\n    /**\n     * Returns if shop is mall\n     *\n     * @return bool\n     */\n    public function isMall()\n    {\n        return false;\n    }\n\n    /**\n     * Returns if page has rdfa\n     *\n     * @return bool\n     */\n    public function showRdfa()\n    {\n        return false;\n    }\n\n    /**\n     * Returns session ID, but only in case it is needed to be included for widget calls.\n     * This basically happens on session change,\n     * when session cookie is not equals to the actual session ID.\n     *\n     * @return string|null\n     */\n    public function getSidForWidget()\n    {\n        $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n        $sid = null;\n        if (!$session->isActualSidInCookie()) {\n            $sid = $session->getId();\n        }\n\n        return $sid;\n    }\n\n    /**\n     * Returns whether to show persistent parameter. Returns true as a default.\n     *\n     * @param string $persParamKey\n     *\n     * @return bool\n     */\n    public function showPersParam($persParamKey)\n    {\n        return true;\n    }\n}\n"
  },
  {
    "path": "source/Core/Counter.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse Exception;\n\n/**\n * Counter class\n */\nclass Counter\n{\n    /**\n     * Return the next counter value for a given type of counter\n     *\n     * @param string $ident Identifies the type of counter. E.g. 'oxOrder'\n     *\n     * @throws Exception\n     *\n     * @return int Next counter value\n     */\n    public function getNext($ident)\n    {\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        /** Current counter retrieval needs to be encapsulated in transaction */\n        $database->startTransaction();\n        try {\n            /** Block row for reading until the counter is updated */\n            $query = \"SELECT `oxcount` FROM `oxcounters` WHERE `oxident` = :oxident FOR UPDATE\";\n            $currentCounter = (int) $database->getOne($query, [\n                'oxident' => $ident\n            ]);\n            $nextCounter = $currentCounter + 1;\n\n            /** Insert or increment the the counter */\n            $query = \"INSERT INTO `oxcounters` (`oxident`, `oxcount`) VALUES (:oxident, 1) ON DUPLICATE KEY UPDATE `oxcount` = `oxcount` + 1\";\n            $database->execute($query, ['oxident' => $ident]);\n\n            $database->commitTransaction();\n        } catch (Exception $exception) {\n            $database->rollbackTransaction();\n\n            throw $exception;\n        }\n\n        return $nextCounter;\n    }\n\n    /**\n     * Update the counter value for a given type of counter, but only when it is greater than the current value\n     *\n     * @param string  $ident Identifies the type of counter. E.g. 'oxOrder'\n     * @param integer $count New counter value\n     *\n     * @throws Exception\n     *\n     * @return int Number of affected rows\n     */\n    public function update($ident, $count)\n    {\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        /** Current counter retrieval needs to be encapsulated in transaction */\n        $database->startTransaction();\n        try {\n            /** Block row for reading until the counter is updated */\n            $query = \"SELECT `oxcount` FROM `oxcounters` WHERE `oxident` = :oxident FOR UPDATE\";\n            $database->getOne($query, [\n                'oxident' => $ident\n            ]);\n\n            /** Insert or update the counter, if the value to be updated is greater, than the current value */\n            $query = \"INSERT INTO `oxcounters` (`oxident`, `oxcount`) VALUES (:oxident, :oxcount) ON DUPLICATE KEY UPDATE `oxcount` = IF(:oxcount > oxcount, :oxcount, oxcount)\";\n            $result = $database->execute($query, ['oxident' => $ident, 'oxcount' => $count]);\n\n            $database->commitTransaction();\n        } catch (Exception $exception) {\n            $database->rollbackTransaction();\n\n            throw $exception;\n        }\n\n        return $result;\n    }\n}\n"
  },
  {
    "path": "source/Core/Curl.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * CURL request handler.\n * Handles CURL calls\n */\nclass Curl\n{\n    /** Curl option for setting the timeout of whole execution process. */\n    const EXECUTION_TIMEOUT_OPTION = 'CURLOPT_TIMEOUT';\n\n    /** Curl option for setting the timeout for connect. */\n    const CONNECT_TIMEOUT_OPTION = 'CURLOPT_CONNECTTIMEOUT';\n\n    /**\n     * Curl instance.\n     *\n     * @var resource\n     */\n    protected $_rCurl = null;\n\n    /**\n     * URL to call\n     *\n     * @var string|null\n     */\n    protected $_sUrl = null;\n\n    /**\n     * Query like \"param1=value1&param2=values2..\"\n     *\n     * @return string\n     */\n    protected $_sQuery = null;\n\n    /**\n     * Set CURL method\n     *\n     * @return string\n     */\n    protected $_sMethod = 'POST';\n\n    /**\n     * Parameter to be added to call url\n     *\n     * @var array|null\n     */\n    protected $_aParameters = null;\n\n    /**\n     * Connection Charset.\n     *\n     * @var string\n     */\n    protected $_sConnectionCharset = \"UTF-8\";\n\n    /**\n     * Curl call header.\n     *\n     * @var array\n     */\n    protected $_aHeader = null;\n\n    /**\n     * Host for header.\n     *\n     * @var string\n     */\n    protected $_sHost = null;\n\n    /**\n     * Curl Options\n     *\n     * @var array\n     */\n    protected $_aOptions = ['CURLOPT_RETURNTRANSFER' => 1];\n\n    /**\n     * Request HTTP status call code.\n     *\n     * @var int|null\n     */\n    protected $_sStatusCode = null;\n\n    /**\n     * Sets url to call\n     *\n     * @param string $url URL to call.\n     */\n    public function setUrl($url)\n    {\n        $this->_sUrl = $url;\n    }\n\n    /**\n     * Get url\n     *\n     * @return string\n     */\n    public function getUrl()\n    {\n        if ($this->getMethod() == \"GET\" && $this->getQuery()) {\n            $this->_sUrl = $this->_sUrl . \"?\" . $this->getQuery();\n        }\n\n        return $this->_sUrl;\n    }\n\n    /**\n     * Set query like \"param1=value1&param2=values2..\"\n     *\n     * @param string $query Request query.\n     */\n    public function setQuery($query)\n    {\n        $this->_sQuery = $query;\n    }\n\n    /**\n     * Builds query like \"param1=value1&param2=values2..\"\n     *\n     * @return string\n     */\n    public function getQuery()\n    {\n        if (is_null($this->_sQuery)) {\n            $query = \"\";\n            if ($params = $this->getParameters()) {\n                $params = $this->prepareQueryParameters($params);\n                $query = http_build_query($params, \"\", \"&\");\n            }\n            $this->setQuery($query);\n        }\n\n        return $this->_sQuery;\n    }\n\n    /**\n     * Sets parameters to be added to call url.\n     *\n     * @param array $parameters parameters\n     */\n    public function setParameters($parameters)\n    {\n        $this->setQuery(null);\n        $this->_aParameters = $parameters;\n    }\n\n    /**\n     * Return parameters to be added to call url.\n     *\n     * @return array\n     */\n    public function getParameters()\n    {\n        return $this->_aParameters;\n    }\n\n    /**\n     * Sets host.\n     *\n     * @param string $host\n     */\n    public function setHost($host)\n    {\n        $this->_sHost = $host;\n    }\n\n    /**\n     * Returns host.\n     *\n     * @return string\n     */\n    public function getHost()\n    {\n        return $this->_sHost;\n    }\n\n    /**\n     * Set header.\n     *\n     * @param array $header\n     */\n    public function setHeader($header = null)\n    {\n        if (is_null($header) && $this->getMethod() == \"POST\") {\n            $host = $this->getHost();\n\n            $header = [];\n            $header[] = 'POST /cgi-bin/webscr HTTP/1.1';\n            $header[] = 'Content-Type: application/x-www-form-urlencoded';\n            if (isset($host)) {\n                $header[] = 'Host: ' . $host;\n            }\n            $header[] = 'Connection: close';\n        }\n        $this->_aHeader = $header;\n    }\n\n    /**\n     * Forms header from host.\n     *\n     * @return array\n     */\n    public function getHeader()\n    {\n        if (is_null($this->_aHeader)) {\n            $this->setHeader();\n        }\n\n        return $this->_aHeader;\n    }\n\n    /**\n     * Set method to send (POST/GET)\n     *\n     * @param string $method method to send (POST/GET)\n     */\n    public function setMethod($method)\n    {\n        $this->_sMethod = strtoupper($method);\n    }\n\n    /**\n     * Return method to send\n     *\n     * @return string\n     */\n    public function getMethod()\n    {\n        return $this->_sMethod;\n    }\n\n    /**\n     * Sets an option for a cURL transfer\n     *\n     * @param string $name  curl option name to set value to.\n     * @param string $value curl option value to set.\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\StandardException curl errors\n     */\n    public function setOption($name, $value)\n    {\n        if (strpos($name, 'CURLOPT_') !== 0 || !defined($constant  = strtoupper($name))) {\n            $exception = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\StandardException::class);\n            $lang = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n            $exception->setMessage(sprintf($lang->translateString('EXCEPTION_NOT_VALID_CURL_CONSTANT', $lang->getTplLanguage()), $name));\n            throw $exception;\n        }\n\n        $this->_aOptions[$name] = $value;\n    }\n\n    /**\n     * Gets all options for a cURL transfer\n     *\n     * @return array\n     */\n    public function getOptions()\n    {\n        return $this->_aOptions;\n    }\n\n    /**\n     * Executes curl call and returns response data as associative array.\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\StandardException on curl errors\n     *\n     * @return string\n     */\n    public function execute()\n    {\n        $this->setOptions();\n\n        $response = $this->executeCurl();\n        $this->saveStatusCode();\n\n        $curlErrorNumber = $this->getErrorNumber();\n\n        $this->close();\n\n        if ($curlErrorNumber) {\n            $exception = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\StandardException::class);\n            $lang = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n            $exception->setMessage(sprintf($lang->translateString('EXCEPTION_CURL_ERROR', $lang->getTplLanguage()), $curlErrorNumber));\n            throw $exception;\n        }\n\n        return $response;\n    }\n\n    /**\n     * Set connection charset\n     *\n     * @param string $charset charset\n     */\n    public function setConnectionCharset($charset)\n    {\n        $this->_sConnectionCharset = $charset;\n    }\n\n    /**\n     * Return connection charset\n     *\n     * @return string\n     */\n    public function getConnectionCharset()\n    {\n        return $this->_sConnectionCharset;\n    }\n\n    /**\n     * Return HTTP status code.\n     *\n     * @return int HTTP status code.\n     */\n    public function getStatusCode()\n    {\n        return $this->_sStatusCode;\n    }\n\n    /**\n     * Sets resource\n     *\n     * @param resource $rCurl curl.\n     */\n    protected function setResource($rCurl)\n    {\n        $this->_rCurl = $rCurl;\n    }\n\n    /**\n     * Returns curl resource\n     *\n     * @return resource\n     */\n    protected function getResource()\n    {\n        if (is_null($this->_rCurl)) {\n            $this->setResource(curl_init());\n        }\n\n        return $this->_rCurl;\n    }\n\n    /**\n     * Set Curl Options\n     */\n    protected function setOptions()\n    {\n        if (!is_null($this->getHeader())) {\n            $this->setOpt(CURLOPT_HTTPHEADER, $this->getHeader());\n        }\n        $this->setOpt(CURLOPT_URL, $this->getUrl());\n\n        if ($this->getMethod() == \"POST\") {\n            $this->setOpt(CURLOPT_POST, 1);\n            $this->setOpt(CURLOPT_POSTFIELDS, $this->getQuery());\n        }\n\n        $options = $this->getOptions();\n        if (count($options)) {\n            foreach ($options as $name => $mValue) {\n                $this->setOpt(constant($name), $mValue);\n            }\n        }\n    }\n\n    /**\n     * Wrapper function to be mocked for testing.\n     *\n     * @return string\n     */\n    protected function executeCurl(): string\n    {\n        return curl_exec($this->getResource());\n    }\n\n    /**\n     * Wrapper function to be mocked for testing.\n     */\n    protected function close()\n    {\n        $this->setResource(null);\n    }\n\n    /**\n     * Wrapper function to be mocked for testing.\n     *\n     * @param string $name  curl option name to set value to.\n     * @param string $value curl option value to set.\n     */\n    protected function setOpt($name, $value)\n    {\n        curl_setopt($this->getResource(), $name, $value);\n    }\n\n    /**\n     * Check if curl has errors. Set error message if has.\n     *\n     * @return int\n     */\n    protected function getErrorNumber()\n    {\n        return curl_errno($this->getResource());\n    }\n\n    /**\n     * Sets current request HTTP status code.\n     */\n    protected function saveStatusCode()\n    {\n        $this->_sStatusCode = curl_getinfo($this->getResource(), CURLINFO_HTTP_CODE);\n    }\n\n    /**\n     * Decodes html entities.\n     *\n     * @param array $params Parameters.\n     *\n     * @return array\n     */\n    protected function prepareQueryParameters($params)\n    {\n        return array_map([$this, 'htmlDecode'], array_filter($params));\n    }\n\n    /**\n     * Decode (if needed) html entity.\n     *\n     * @param mixed $mParam query\n     *\n     * @return string\n     */\n    protected function htmlDecode($mParam)\n    {\n        if (is_array($mParam)) {\n            $mParam = $this->prepareQueryParameters($mParam);\n        } else {\n            $mParam = html_entity_decode(stripslashes($mParam), ENT_QUOTES, $this->getConnectionCharset());\n        }\n\n        return $mParam;\n    }\n}\n"
  },
  {
    "path": "source/Core/Dao/ApplicationServerDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Dao;\n\n/**\n * Application server data access manager.\n *\n * @internal Do not make a module extension for this class.\n */\nclass ApplicationServerDao implements \\OxidEsales\\Eshop\\Core\\Dao\\ApplicationServerDaoInterface\n{\n    /**\n     * The name of config option for saving servers data information.\n     */\n    const CONFIG_NAME_FOR_SERVER_INFO = 'aServersData_';\n\n    /**\n     * @var \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer[]\n     */\n    private $appServer = [];\n\n    /**\n     * @var \\OxidEsales\\Eshop\\Core\\Config Main shop configuration class.\n     */\n    private $config;\n\n    /**\n     * @var \\OxidEsales\\Eshop\\Core\\Database\\Adapter\\DatabaseInterface\n     */\n    protected $database;\n\n    /**\n     * ApplicationServerDao constructor.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Database\\Adapter\\DatabaseInterface $database Database connection class.\n     * @param \\OxidEsales\\Eshop\\Core\\Config                             $config   Main shop configuration class.\n     */\n    public function __construct($database, $config)\n    {\n        $this->database = $database;\n        $this->config = $config;\n    }\n\n    /**\n     * Finds all application servers.\n     *\n     * @return array\n     */\n    public function findAll()\n    {\n        $appServerList = [];\n\n        /** @var \\OxidEsales\\Eshop\\Core\\Database\\Adapter\\ResultSetInterface $resultList */\n        $resultList = $this->selectAllData();\n        if ($resultList != false && $resultList->count() > 0) {\n            $result = $resultList->getFields();\n            $serverId = $this->getServerIdFromConfig($result['oxvarname']);\n            $information = $this->getValueFromConfig($result['oxvarvalue']);\n            $appServerList[$serverId] = $this->createServer($information);\n            while ($result = $resultList->fetchRow()) {\n                $serverId = $this->getServerIdFromConfig($result['oxvarname']);\n                $information = $this->getValueFromConfig($result['oxvarvalue']);\n                $appServerList[$serverId] = $this->createServer($information);\n            }\n        }\n        return $appServerList;\n    }\n\n    /**\n     * Deletes the entity with the given id.\n     *\n     * @param string $id An id of the entity to delete.\n     */\n    public function delete($id)\n    {\n        unset($this->appServer[$id]);\n\n        $query = \"DELETE FROM oxconfig WHERE oxvarname = :oxvarname and oxshopid = :oxshopid\";\n        $this->database->execute($query, [\n            'oxvarname' => self::CONFIG_NAME_FOR_SERVER_INFO . $id,\n            'oxshopid' => $this->config->getBaseShopId()\n        ]);\n    }\n\n    /**\n     * Finds an application server by given id, null if none is found.\n     *\n     * @param string $id An id of the entity to find.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer|null\n     */\n    public function findAppServer($id)\n    {\n        if (!isset($this->appServer[$id])) {\n            $serverData = $this->selectDataById($id);\n\n            if ($serverData != false) {\n                $appServerProperties = (array)unserialize($serverData);\n            } else {\n                return null;\n            }\n\n            $this->appServer[$id] = $this->createServer($appServerProperties);\n        }\n        return $this->appServer[$id];\n    }\n\n    /**\n     * Updates or insert the given entity.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer $appServer\n     */\n    public function save($appServer)\n    {\n        $id = $appServer->getId();\n        if ($this->findAppServer($id)) {\n            $this->update($appServer);\n            unset($this->appServer[$id]);\n        } else {\n            $this->insert($appServer);\n        }\n    }\n\n    /**\n     * Start a database transaction.\n     */\n    public function startTransaction()\n    {\n        $this->database->startTransaction();\n    }\n\n    /**\n     * Commit a database transaction.\n     */\n    public function commitTransaction()\n    {\n        $this->database->commitTransaction();\n    }\n\n    /**\n     * RollBack a database transaction.\n     */\n    public function rollbackTransaction()\n    {\n        $this->database->rollbackTransaction();\n    }\n\n    /**\n     * Updates the given entity.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer $appServer\n     */\n    protected function update($appServer)\n    {\n        $query = \"UPDATE oxconfig SET oxvarvalue = :value\n                  WHERE oxvarname = :oxvarname and oxshopid = :oxshopid\";\n\n        $parameter = [\n            'value' => $this->convertAppServerToConfigOption($appServer),\n            'oxvarname' => self::CONFIG_NAME_FOR_SERVER_INFO . $appServer->getId(),\n            'oxshopid' => $this->config->getBaseShopId()\n        ];\n\n        $this->database->execute($query, $parameter);\n    }\n\n    /**\n     * Insert new application server entity.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer $appServer\n     */\n    protected function insert($appServer)\n    {\n        $query = \"insert into oxconfig (oxid, oxshopid, oxmodule, oxvarname, oxvartype, oxvarvalue)\n                  values (:oxid, :oxshopid, '', :oxvarname, :oxvartype, :value)\";\n\n        $parameter = [\n            'oxid' => \\OxidEsales\\Eshop\\Core\\Registry::getUtilsObject()->generateUID(),\n            'oxshopid' => $this->config->getBaseShopId(),\n            'oxvarname' => self::CONFIG_NAME_FOR_SERVER_INFO . $appServer->getId(),\n            'oxvartype' => 'arr',\n            'value' => $this->convertAppServerToConfigOption($appServer),\n        ];\n\n        $this->database->execute($query, $parameter);\n    }\n\n    /**\n     * Returns all application server entities from database.\n     *\n     * @param string $id An id of the entity to find.\n     *\n     * @return string\n     */\n    private function selectDataById($id)\n    {\n        $query = \"SELECT oxvarvalue FROM oxconfig \n            WHERE oxvarname = :oxvarname \n              AND oxshopid = :oxshopid FOR UPDATE\";\n\n        $parameter = [\n            \"oxvarname\" => self::CONFIG_NAME_FOR_SERVER_INFO . $id,\n            \"oxshopid\" => $this->config->getBaseShopId()\n        ];\n        return $this->database->getOne($query, $parameter);\n    }\n\n    /**\n     * Returns all application server entities from database.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Database\\Adapter\\ResultSetInterface\n     */\n    private function selectAllData()\n    {\n        $query = \"SELECT oxvarname, oxvarvalue\n                    FROM oxconfig\n                    WHERE oxvarname like :oxvarname AND oxshopid = :oxshopid\";\n\n        $parameter = [\n            'oxvarname' => self::CONFIG_NAME_FOR_SERVER_INFO . \"%\",\n            'oxshopid' => $this->config->getBaseShopId()\n        ];\n\n        return $this->database->select($query, $parameter);\n    }\n\n    /**\n     * Parses config option name to get the server id.\n     *\n     * @param string $varName The name of the config option.\n     *\n     * @return string The id of server.\n     */\n    private function getServerIdFromConfig($varName)\n    {\n        $constNameLength = strlen(self::CONFIG_NAME_FOR_SERVER_INFO);\n        $id = substr($varName, $constNameLength);\n        return $id;\n    }\n\n    /**\n     * Unserializes config option value.\n     *\n     * @param string $varValue The serialized value of the config option.\n     *\n     * @return array The information of server.\n     */\n    private function getValueFromConfig($varValue)\n    {\n        return (array) unserialize($varValue);\n    }\n\n    /**\n     * Creates ApplicationServer from given server id and data.\n     *\n     * @param array $data The array of server data.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer\n     */\n    protected function createServer($data)\n    {\n        /** @var \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer $appServer */\n        $appServer = oxNew(\\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer::class);\n\n        $appServer->setId($this->getServerParameter($data, 'id'));\n        $appServer->setTimestamp($this->getServerParameter($data, 'timestamp'));\n        $appServer->setIp($this->getServerParameter($data, 'ip'));\n        $appServer->setLastFrontendUsage($this->getServerParameter($data, 'lastFrontendUsage'));\n        $appServer->setLastAdminUsage($this->getServerParameter($data, 'lastAdminUsage'));\n\n        return $appServer;\n    }\n\n    /**\n     * Gets server parameter.\n     *\n     * @param array  $data The array of server data.\n     * @param string $name The name of searched parameter.\n     *\n     * @return mixed\n     */\n    private function getServerParameter($data, $name)\n    {\n        return array_key_exists($name, $data) ? $data[$name] : null;\n    }\n\n    /**\n     * Convert ApplicationServer object into simple array for saving into database oxconfig table.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer $appServer An application server object.\n     *\n     * @return array\n     */\n    private function convertAppServerToConfigOption($appServer)\n    {\n        $serverData = [\n            'id'                => $appServer->getId(),\n            'timestamp'         => $appServer->getTimestamp(),\n            'ip'                => $appServer->getIp(),\n            'lastFrontendUsage' => $appServer->getLastFrontendUsage(),\n            'lastAdminUsage'    => $appServer->getLastAdminUsage()\n        ];\n\n        return serialize($serverData);\n    }\n}\n"
  },
  {
    "path": "source/Core/Dao/ApplicationServerDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Dao;\n\n/**\n * Application server data access manager.\n *\n * @internal Do not make a module extension for this class.\n */\ninterface ApplicationServerDaoInterface extends \\OxidEsales\\Eshop\\Core\\Dao\\BaseDaoInterface\n{\n    /**\n     * Finds an application server by given id, null if none is found.\n     *\n     * @param string $id An id of the entity to find.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer|null\n     */\n    public function findAppServer($id);\n}\n"
  },
  {
    "path": "source/Core/Dao/BaseDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Dao;\n\n/**\n * Data access object interface.\n *\n * @internal Do not make a module extension for this class.\n */\ninterface BaseDaoInterface\n{\n    /**\n     * Finds all entities.\n     *\n     * @return array\n     */\n    public function findAll();\n\n    /**\n     * Deletes the entity with the given id.\n     *\n     * @param string $id An id of the entity to delete.\n     */\n    public function delete($id);\n\n    /**\n     * Updates or insert the given entity.\n     *\n     * @param object $object\n     */\n    public function save($object);\n\n    /**\n     * Start a database transaction.\n     */\n    public function startTransaction();\n\n    /**\n     * Commit a database transaction.\n     */\n    public function commitTransaction();\n\n    /**\n     * RollBack a database transaction.\n     */\n    public function rollbackTransaction();\n}\n"
  },
  {
    "path": "source/Core/DataObject/ApplicationServer.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\DataObject;\n\n/**\n * Class used as entity for server node information.\n *\n * @internal Do not make a module extension for this class.\n *\n * @ignore   This class will not be included in documentation.\n */\nclass ApplicationServer\n{\n    /**\n     * Time in seconds, active server information life time.\n     */\n    const SERVER_INFORMATION_TIME_LIFE = 86400;\n\n    /**\n     * Time in seconds, how long inactive server information will be stored.\n     */\n    const INACTIVE_SERVER_STORAGE_PERIOD = 259200;\n\n    /**\n     * Time in seconds, how often server information must be updated.\n     */\n    const SERVER_INFO_UPDATE_PERIOD = 86400;\n\n    /**\n     * @var string\n     */\n    private $id;\n\n    /**\n     * @var string\n     */\n    private $ip;\n\n    /**\n     * @var int\n     */\n    private $timestamp;\n\n    /**\n     * Flag which stores timestamp.\n     *\n     * @var int\n     */\n    private $lastFrontendUsage;\n\n    /**\n     * Flag which stores timestamp.\n     *\n     * @var int\n     */\n    private $lastAdminUsage;\n\n    /**\n     * Sets id.\n     *\n     * @param string $id\n     */\n    public function setId($id)\n    {\n        $this->id = $id;\n    }\n\n    /**\n     * Gets id\n     *\n     * @return string\n     */\n    public function getId()\n    {\n        return $this->id;\n    }\n\n    /**\n     * Sets ip.\n     *\n     * @param string $ip\n     */\n    public function setIp($ip)\n    {\n        $this->ip = $ip;\n    }\n\n    /**\n     * Gets ip.\n     *\n     * @return string\n     */\n    public function getIp()\n    {\n        return $this->ip;\n    }\n\n    /**\n     * Sets timestamp.\n     *\n     * @param int $timestamp\n     */\n    public function setTimestamp($timestamp)\n    {\n        $this->timestamp = $timestamp;\n    }\n\n    /**\n     * Gets timestamp.\n     *\n     * @return int\n     */\n    public function getTimestamp()\n    {\n        return $this->timestamp;\n    }\n\n    /**\n     * Sets last admin usage.\n     *\n     * @param int|null $lastAdminUsage\n     */\n    public function setLastAdminUsage($lastAdminUsage)\n    {\n        $this->lastAdminUsage = $lastAdminUsage;\n    }\n\n    /**\n     * Gets last admin usage.\n     *\n     * @return int|null\n     */\n    public function getLastAdminUsage()\n    {\n        return $this->lastAdminUsage;\n    }\n\n    /**\n     * Sets last frontend usage.\n     *\n     * @param int|null $lastFrontendUsage Admin server flag which stores timestamp.\n     */\n    public function setLastFrontendUsage($lastFrontendUsage)\n    {\n        $this->lastFrontendUsage = $lastFrontendUsage;\n    }\n\n    /**\n     * Gets last frontend usage.\n     *\n     * @return int|null Frontend server flag which stores timestamp.\n     */\n    public function getLastFrontendUsage()\n    {\n        return $this->lastFrontendUsage;\n    }\n\n    /**\n     * Check if application server was in use during 24h period.\n     *\n     * @param int $currentTimestamp The current timestamp.\n     *\n     * @return bool\n     */\n    public function isInUse($currentTimestamp)\n    {\n        return !$this->hasLifetimeExpired($currentTimestamp, self::SERVER_INFORMATION_TIME_LIFE);\n    }\n\n    /**\n     * Check if application server availability check period is over.\n     *\n     * @param int $currentTimestamp The current timestamp.\n     *\n     * @return bool\n     */\n    public function needToDelete($currentTimestamp)\n    {\n        return $this->hasLifetimeExpired($currentTimestamp, self::INACTIVE_SERVER_STORAGE_PERIOD);\n    }\n\n    /**\n     * Check if application server information must be updated.\n     *\n     * @param int $currentTimestamp The current timestamp.\n     *\n     * @return bool\n     */\n    public function needToUpdate($currentTimestamp)\n    {\n        return ($this->hasLifetimeExpired($currentTimestamp, self::SERVER_INFO_UPDATE_PERIOD)\n            || !$this->isServerTimeValid($currentTimestamp));\n    }\n\n    /**\n     * Method checks if the hardware time was not rolled back.\n     *\n     * @param int $currentTimestamp The current timestamp.\n     *\n     * @return bool\n     */\n    private function isServerTimeValid($currentTimestamp)\n    {\n        $timestamp = $this->getTimestamp();\n        return ($currentTimestamp - $timestamp) >= 0;\n    }\n\n    /**\n     * Compare if the application server lifetime has exceeded given period.\n     *\n     * @param int $currentTimestamp The current timestamp.\n     * @param int $periodTimestamp  The timestamp of period to check.\n     *\n     * @return bool\n     */\n    private function hasLifetimeExpired($currentTimestamp, $periodTimestamp)\n    {\n        $timestamp = $this->getTimestamp();\n        return (bool) ($currentTimestamp - $timestamp >= $periodTimestamp);\n    }\n}\n"
  },
  {
    "path": "source/Core/Database/Adapter/DatabaseInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Database\\Adapter;\n\nuse OxidEsales\\Eshop\\Core\\Exception\\DatabaseConnectionException;\nuse OxidEsales\\Eshop\\Core\\Exception\\DatabaseErrorException;\n\n/**\n * @deprecated since v6.5.0 (2019-09-24);\n * Use OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface\n */\ninterface DatabaseInterface\n{\n    /** @var int code of exception to check against. Use same number as MySQL to avoid duplications. */\n    public const DUPLICATE_KEY_ERROR_CODE = 1062;\n\n    /**\n     * Set the necessary connection parameters to connect to the database.\n     * The parameter array must at least contain the key 'default'. E.g.\n     * [\n     *  'default' => [\n     *      'databaseDriver'    => '', // string At the moment only 'pdo_mysql' is supported\n     *      'databaseHost'      => '', // string\n     *      'databasePort'      => '', // integer Optional, defaults to port 3306\n     *      'databaseName'      => '', // string\n     *      'databaseUser'      => '', // string\n     *      'databasePassword'  => '', // string\n     *      'connectionCharset' => '', // string Optional, defaults to the servers connection character set\n     *      ]\n     * ]\n     *\n     * @param array $connectionParameters\n     */\n    public function setConnectionParameters(array $connectionParameters);\n\n    /**\n     * Connects to the database using the connection parameters set in DatabaseInterface::setConnectionParameters().\n     *\n     * @throws DatabaseConnectionException If a connection to the database cannot be established\n     */\n    public function connect();\n\n    /**\n     * Force database master connection.\n     *\n     * @return null\n     */\n    public function forceMasterConnection();\n\n    /**\n     * Force database slave connection. Do not use this function unless\n     * you know exactly what you are doing. Usage of this function\n     * can lead to write access to a MySQL slave and getting replication out\n     * of sync.\n     *\n     * @return null\n     */\n    public function forceSlaveConnection();\n\n    /**\n     * Closes an open connection\n     *\n     * @return null\n     */\n    public function closeConnection();\n\n    /**\n     * Get the first value of the first row of the result set of a given sql SELECT or SHOW statement.\n     * Returns false for any other statement.\n     *\n     * NOTE: Although you might pass any SELECT or SHOW statement to this method, try to limit the result of the\n     * statement to one single row, as the rest of the rows is simply discarded.\n     *\n     * @param string $query      The sql SELECT or SHOW statement.\n     * @param array  $parameters Array of parameters for the given sql statement.\n     *\n     * @return string|false      Returns a string for SELECT or SHOW statements and FALSE for any other statement.\n     */\n    public function getOne($query, $parameters = []);\n\n    /**\n     * Get an array with the values of the first row of a given sql SELECT or SHOW statement .\n     * Returns an empty array for any other statement.\n     *\n     * NOTE: Although you might pass any SELECT or SHOW statement to this method, try to limit the result of the\n     * statement to one single row, as the rest of the rows is simply discarded.\n     *\n     * IMPORTANT:\n     * You are strongly encouraged to use prepared statements like this:\n     * $result = DatabaseProvider::getDb->getOne(\n     *   'SELECT ´id´ FROM ´mytable´ WHERE ´id´ = ? LIMIT 0, 1',\n     *   array($id1)\n     * );\n     * If you do not use prepared statements, you MUST quote variables the values with quote(), otherwise you create a\n     * SQL injection vulnerability.\n     *\n     * @param string $query      The sql select statement to be executed.\n     * @param array  $parameters Array of parameters, for the given sql statement.\n     *\n     * @return array The row, we selected with the given sql statement.\n     */\n    public function getRow($query, $parameters = []);\n\n    /**\n     * Return the first column of all rows of the results of a given sql SELECT or SHOW statement as an numeric array.\n     * Throws an exception for any other statement.\n     *\n     * IMPORTANT:\n     * You are strongly encouraged to use prepared statements like this:\n     * $result = DatabaseProvider::getDb->getRow(\n     *   'SELECT * FROM ´mytable´ WHERE ´id´ = ? LIMIT 0, 1',\n     *   array($id1)\n     * );\n     * If you do not use prepared statements, you MUST quote variables the values with quote(), otherwise you create a\n     * SQL injection vulnerability.\n     *\n     * @param string $query      The sql select statement to be executed.\n     * @param array  $parameters The parameters array.\n     *\n     * @throws DatabaseErrorException\n     *\n     * @return array The values of the first column of a corresponding sql query.\n     */\n    public function getCol($query, $parameters = []);\n\n    /**\n     * Get an multi-dimensional array of arrays with the values of the all rows of a given sql SELECT or SHOW statement.\n     * Returns an empty array for any other statement.\n     *\n     * IMPORTANT:\n     * You are strongly encouraged to use prepared statements like this:\n     * $result = DatabaseProvider::getDb->getAll(\n     *   'SELECT * FROM ´mytable´ WHERE ´id´ = ? OR ´id´ = ? LIMIT 0, 1',\n     *   array($id1, $id2)\n     * );\n     * If you do not use prepared statements, you MUST quote variables the values with quote(), otherwise you create a\n     * SQL injection vulnerability.\n     *\n     * @param string $query If parameters are given, the \"?\" in the string will be replaced by the values in the array\n     * @param array  $parameters Array of parameters, for the given sql statement.\n     *\n     * @throws DatabaseErrorException\n     * @throws \\InvalidArgumentException\n     *\n     * @return array\n     */\n    public function getAll($query, $parameters = []);\n\n    /**\n     * Return the results of a given sql SELECT or SHOW statement as a ResultSet.\n     * Throws an exception for any other statement.\n     *\n     * The values of first row of the result may be via resultSet's fields property.\n     * All further rows can be accessed via the specific methods of ResultSet.\n     *\n     * IMPORTANT:\n     * You are strongly encouraged to use prepared statements like this:\n     * $resultSet = DatabaseProvider::getDb->select(\n     *   'SELECT * FROM ´mytable´ WHERE ´id´ = ? OR ´id´ = ?',\n     *   array($id1, $id2)\n     * );\n     * If you do not use prepared statements, you MUST quote variables the values with quote(), otherwise you create a\n     * SQL injection vulnerability.\n     *\n     * @param string $query      The sql select statement to be executed.\n     * @param array  $parameters The parameters array for the given query.\n     *\n     * @throws DatabaseErrorException The exception, that can occur while executing the sql statement.\n     *\n     * @return ResultSetInterface The result of the given query.\n     */\n    public function select($query, $parameters = []);\n\n    /**\n     * Return the results of a given SQL SELECT or SHOW statement limited by a LIMIT clause as a ResultSet.\n     * Throws an exception for any other statement.\n     *\n     * To access the values of the first row from the result, use the 'fields' property of the resultSet object.\n     * This property is an array, which keys are strings\n     * All further rows can be accessed via the specific methods of ResultSet.\n     *\n     * IMPORTANT:\n     * You are strongly encouraged to use prepared statements like this:\n     * $resultSet = DatabaseProvider::getDb->selectLimit(\n     *   'SELECT * FROM ´mytable´ WHERE ´id´ = ? OR ´id´ = ?',\n     *   $rowCount,\n     *   $offset,\n     *   array($id1, $id2)\n     * );\n     * If you do not use prepared statements, you MUST quote variables the values with quote(), otherwise you create a\n     * SQL injection vulnerability.\n     *\n     * @param string $query      The sql select statement to be executed.\n     * @param int    $rowCount   Maximum number of rows to return\n     * @param int    $offset     Offset of the first row to return\n     * @param array  $parameters The parameters array.\n     *\n     * @return ResultSetInterface The result of the given query.\n     *@throws DatabaseErrorException The exception, that can occur while executing the sql statement.\n     *\n     */\n    public function selectLimit($query, $rowCount = -1, $offset = 0, $parameters = []);\n\n    /**\n     * Execute non read statements like INSERT, UPDATE, DELETE and return the number of rows affected by the statement.\n     * This method has to be used EXCLUSIVELY for non read statements.\n     *\n     * IMPORTANT:\n     * You are strongly encouraged to use prepared statements like this:\n     * $resultSet = DatabaseProvider::getDb->execute(\n     *   'DELETE * FROM `mytable` WHERE `id` = ? OR `id` = ?',\n     *   array($id1, $id2)\n     * );\n     * If you do not use prepared statements, you MUST quote variables the values with quote(), otherwise you create a\n     * SQL injection vulnerability.\n     *\n     * @param string $query      The sql select statement to be executed.\n     * @param array  $parameters The parameters array.\n     *\n     * @throws DatabaseErrorException\n     *\n     * @return integer Number of rows affected by the SQL statement\n     */\n    public function execute($query, $parameters = []);\n\n    /**\n     * Quote a string, that it might be used as a value in a sql statement.\n     * Returns false for values that cannot be quoted.\n     *\n     * NOTE: It is not safe to use the return value of this function in a query. There will be no risk of SQL injection,\n     * but when the statement is executed and the value could not have been quoted, a DatabaseException is thrown.\n     * You are strongly encouraged to always use prepared statements instead of quoting the values on your own.\n     * E.g. use\n     * $resultSet = DatabaseProvider::getDb->select(\n     *   'SELECT * FROM ´mytable´ WHERE ´id´ = ? OR ´id´ = ?',\n     *   array($id1, $id2)\n     * );\n     * instead of\n     * $resultSet = DatabaseProvider::getDb->select(\n     *  'SELECT * FROM ´mytable´ WHERE ´id´ = ' . DatabaseProvider::getDb->quote($id1) . ' OR ´id´ = '\n     * . DatabaseProvider::getDb->quote($id1)\n     * );\n     *\n     * @param mixed $value The string or numeric value to be quoted.\n     *\n     * @return false|string The given string or numeric value converted to a string surrounded by single quotes\n     * or set to false, if the value could not have been quoted.\n     */\n    public function quote($value);\n\n    /**\n     * Quote every value in a given array in a way, that it might be used as a value in a sql statement and return the\n     * result as a new array. Numeric values will be converted to strings which quotes.\n     * The keys and their order of the returned array will be the same as of the input array.\n     *\n     * NOTE: It is not safe to use the return value of this function in a query. There will be no risk of SQL injection,\n     * but when the statement is executed and the value could not have been quoted, a DatabaseException is thrown.\n     * You are strongly encouraged to always use prepared statements instead of quoting the values on your own.\n     *\n     * @param array $array The strings to quote as an array.\n     *\n     * @return array Array with all string and numeric values quoted with single quotes or set to false,\n     * if the value could not have been quoted.\n     */\n    public function quoteArray($array);\n\n    /**\n     * Quote a string in a way, that it can be used as a identifier (i.e. table name or field name) in a sql statement.\n     * You are strongly encouraged to always use quote identifiers.\n     *\n     * @param string $string The string to be quoted.\n     *\n     * @return string\n     */\n    public function quoteIdentifier($string);\n\n    /**\n     * Get the meta information about all the columns of the given table.\n     * This is kind of a poor man's schema manager, which only works for MySQL.\n     *\n     * @param string $table The name of the table.\n     *\n     * @return array Array of objects with meta information of each column.\n     */\n    public function metaColumns($table);\n\n    /**\n     * Start a database transaction.\n     *\n     * @throws DatabaseErrorException\n     */\n    public function startTransaction();\n\n    /**\n     * Commit a database transaction.\n     *\n     * @throws DatabaseErrorException\n     */\n    public function commitTransaction();\n\n    /**\n     * RollBack a database transaction.\n     *\n     * @throws DatabaseErrorException\n     */\n    public function rollbackTransaction();\n\n    /**\n     * @inheritdoc\n     *\n     * Note: This method is MySQL specific, as we use the MySQL syntax for setting the transaction isolation level.\n     *\n     * @see Doctrine::transactionIsolationLevelMap\n     *\n     * @return bool|integer\n     */\n    /**\n     * Set the transaction isolation level.\n     * Allowed values 'READ UNCOMMITTED', 'READ COMMITTED', 'REPEATABLE READ' and 'SERIALIZABLE'.\n     *\n     * NOTE: Currently the transaction isolation level is set on the database session and not globally.\n     * Setting the transaction isolation level globally requires root privileges in MySQL an this application should not\n     * be executed with root privileges.\n     * If you need to set the transaction isolation level globally, ask your database administrator to do so,\n     *\n     * @param string $level The transaction isolation level\n     *\n     * @throws \\InvalidArgumentException|DatabaseErrorException\n     */\n    public function setTransactionIsolationLevel($level);\n\n    /**\n     * Return true, if the connection is marked rollbackOnly.\n     *\n     * @return bool\n     */\n    public function isRollbackOnly();\n\n    /**\n     * Checks whether a transaction is currently active.\n     *\n     * @return boolean TRUE if a transaction is currently active, FALSE otherwise.\n     */\n    public function isTransactionActive();\n\n    /**\n     * Return string representing the row ID of the last row that was inserted into\n     * the database.\n     * @throws DatabaseErrorException for tables without autoincrement field.\n     *\n     * @return string|int Row ID\n     */\n    public function getLastInsertId();\n}\n"
  },
  {
    "path": "source/Core/Database/Adapter/Doctrine/Database.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Database\\Adapter\\Doctrine;\n\nuse Doctrine\\DBAL\\ConnectionException;\nuse Doctrine\\DBAL\\Exception as DBALException;\nuse Doctrine\\DBAL\\TransactionIsolationLevel;\nuse InvalidArgumentException;\nuse oxException;\nuse OxidEsales\\Eshop\\Core\\Database\\Adapter\\DatabaseInterface;\nuse OxidEsales\\Eshop\\Core\\Exception\\DatabaseConnectionException;\nuse OxidEsales\\Eshop\\Core\\Exception\\DatabaseErrorException;\nuse OxidEsales\\Eshop\\Core\\Exception\\StandardException;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\ConnectionFactoryInterface;\nuse PDOException;\nuse stdClass;\n\n/**\n * The doctrine implementation of our database.\n *\n * @deprecated since v6.5.0 (2019-09-24);\n *             Use OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface\n */\nclass Database implements DatabaseInterface\n{\n    private const MYSQL_DUPLICATE_KEY_ERROR_CODE = 1062;\n\n    protected $connectionParameters = [];\n\n    protected $connection = null;\n\n    /**\n     * @var array Map strings used in the shop to Doctrine constants\n     */\n    protected $transactionIsolationLevelMap = [\n        'READ UNCOMMITTED' => TransactionIsolationLevel::READ_UNCOMMITTED,\n        'READ COMMITTED' => TransactionIsolationLevel::READ_COMMITTED,\n        'REPEATABLE READ' => TransactionIsolationLevel::REPEATABLE_READ,\n        'SERIALIZABLE' => TransactionIsolationLevel::SERIALIZABLE\n    ];\n\n    public function setConnectionParameters(array $connectionParameters)\n    {\n        if (array_key_exists('default', $connectionParameters)) {\n            $this->connectionParameters = $connectionParameters['default'];\n        }\n    }\n\n    public function connect()\n    {\n        try {\n            $connection = ContainerFacade::get(ConnectionFactoryInterface::class)->create();\n            $this->setConnection($connection);\n            $this->ensureConnectionIsEstablished($connection);\n        } catch (DBALException | PDOException $exception) {\n            $exception = $this->convertException($exception);\n            $this->handleException($exception);\n        }\n    }\n\n    public function forceMasterConnection()\n    {\n        if (is_null($this->connection)) {\n            $this->connect();\n        }\n    }\n\n    public function forceSlaveConnection()\n    {\n        if (is_null($this->connection)) {\n            $this->connect();\n        }\n    }\n\n    public function closeConnection()\n    {\n        $this->connection->close();\n        gc_collect_cycles();\n    }\n\n    protected function setConnection($connection)\n    {\n        $this->connection = $connection;\n    }\n\n    public function getOne($query, $parameters = [])\n    {\n        if ($this->doesStatementProduceOutput($query)) {\n            try {\n                return $this->getConnection()->fetchOne($query, $parameters);\n            } catch (DBALException | PDOException $exception) {\n                $exception = $this->convertException($exception);\n                $this->handleException($exception);\n            }\n        } else {\n            Registry::getLogger()->warning(\n                'Given statement does not produce output and was not executed',\n                [debug_backtrace()]\n            );\n        }\n\n        return false;\n    }\n\n    public function getRow($query, $parameters = [])\n    {\n        try {\n            $resultSet = $this->select($query, $parameters);\n            $result = $resultSet->fields;\n        } catch (DatabaseErrorException $exception) {\n            $this->logException($exception);\n            $result = [];\n        } catch (PDOException $exception) {\n            $exception = $this->convertException($exception);\n            $this->logException($exception);\n            $result = [];\n        }\n\n        if ($result === false) {\n            $result = [];\n        }\n\n        return $result;\n    }\n\n    public function quoteIdentifier($string)\n    {\n        $string = trim(str_replace('`', '', $string));\n        try {\n            $result = $this->getConnection()->quoteIdentifier($string);\n        } catch (DBALException | PDOException $exception) {\n            $exception = $this->convertException($exception);\n            $this->handleException($exception);\n        }\n\n        return $result;\n    }\n\n    public function quote($value)\n    {\n        if (!is_scalar($value) && $value !== null) {\n            return false;\n        }\n\n        try {\n            return $this->getConnection()->quote((string) $value);\n        } catch (DBALException | PDOException $exception) {\n            $exception = $this->convertException($exception);\n            $this->handleException($exception);\n        }\n    }\n\n    public function quoteArray($array)\n    {\n        return array_map(function ($item) {\n            return $this->quote($item);\n        }, $array);\n    }\n\n    public function startTransaction()\n    {\n        try {\n            $this->getConnection()->beginTransaction();\n        } catch (DBALException | PDOException $exception) {\n            $exception = $this->convertException($exception);\n            $this->handleException($exception);\n        }\n    }\n\n    public function commitTransaction()\n    {\n        try {\n            $this->getConnection()->commit();\n        } catch (DBALException | PDOException $exception) {\n            $exception = $this->convertException($exception);\n            $this->handleException($exception);\n        }\n    }\n\n    public function rollbackTransaction()\n    {\n        try {\n            $this->getConnection()->rollBack();\n        } catch (DBALException | PDOException $exception) {\n            $exception = $this->convertException($exception);\n            $this->handleException($exception);\n        }\n    }\n\n    public function setTransactionIsolationLevel($level)\n    {\n        $level = strtoupper($level);\n\n        if (!array_key_exists($level, $this->transactionIsolationLevelMap)) {\n            throw new InvalidArgumentException('Transaction isolation level is invalid');\n        }\n\n        return $this->getConnection()->setTransactionIsolation($this->transactionIsolationLevelMap[$level]);\n    }\n\n    public function execute($query, $parameters = [])\n    {\n        return $this->executeUpdate($query, $parameters);\n    }\n\n    public function select($query, $parameters = [])\n    {\n        $this->checkIfSqlIsReadOnly($query);\n        try {\n            $parameters = $this->ensureParametersWithIntegerKeysStartWithOne($parameters);\n\n            $statement = $this->getConnection()->prepare($this->checkForMultipleQueries($query, $parameters));\n            foreach ($parameters as $key => $value) {\n                $statement->bindValue($key, $value);\n            }\n\n            return new ResultSet($statement);\n        } catch (DBALException | PDOException $exception) {\n            $exception = $this->convertException($exception);\n            $this->handleException($exception);\n        }\n    }\n\n    /**\n     * @throws InvalidArgumentException\n     */\n    private function checkIfSqlIsReadOnly($query): void\n    {\n        $check = ltrim($query, \" \\t\\n\\r\\0\\x0B(\");\n        if (!(stripos($check, 'select') === 0 || stripos($check, 'show') === 0)) {\n            throw new InvalidArgumentException(\"Function is only for read operations select or show\");\n        }\n    }\n\n    private function checkForMultipleQueries($query, $parameters): string\n    {\n        if ($parameters !== [] || strrpos($query, ';', -1) === false) {\n            return $query;\n        }\n        $queries = preg_split(\n            '~(\\\"[^\\\\\\\\\"]*\\\"|' . \"\\'[^\\\\\\\\']*\\'|\\'.+\\'|`[^\\\\`]*`)(*SKIP)(*F)|(?<=;)(?![ ]*$)~\",\n            $query\n        );\n        if (count($queries) > 1) {\n            Registry::getLogger()->error('More than one query within one statement', [$query]);\n        }\n\n        return $queries[0];\n    }\n\n    public function selectLimit($query, $rowCount = -1, $offset = 0, $parameters = [])\n    {\n        /**\n         * Parameter validation.\n         * At the moment there will be no InvalidArgumentException thrown on non numeric values as this may break\n         * too many things.\n         */\n        if (!is_numeric($rowCount) || !is_numeric($offset)) {\n            trigger_error(\n                'Parameters rowCount and offset have to be numeric in DatabaseInterface::selectLimit(). ' .\n                'Please fix your code as this error may trigger an exception in future versions of OXID eShop.',\n                E_USER_DEPRECATED\n            );\n        }\n\n        if (0 > $offset) {\n            throw new InvalidArgumentException('Argument $offset must not be smaller than zero.');\n        }\n\n        /**\n         * Cast the parameters limit and offset to integer in in order to avoid SQL injection.\n         */\n        $rowCount = (int)$rowCount;\n        $offset = (int)$offset;\n        $limitClause = '';\n\n        if ($rowCount >= 0 && $offset >= 0) {\n            $limitClause = \"LIMIT $rowCount OFFSET $offset\";\n        }\n\n        return $this->select($query . \" $limitClause \", $parameters);\n    }\n\n    public function getCol($query, $parameters = [])\n    {\n        $this->checkIfSqlIsReadOnly($query);\n        $result = [];\n\n        try {\n            $result = $this->getConnection()->fetchFirstColumn($query, $parameters);\n        } catch (DBALException | PDOException $exception) {\n            $exception = $this->convertException($exception);\n            $this->handleException($exception);\n        }\n\n        return $result;\n    }\n\n    public function executeUpdate($query, $parameters = [], $types = [])\n    {\n        try {\n            return $this->getConnection()->executeStatement($query, $parameters, $types);\n        } catch (DBALException | PDOException $exception) {\n            $exception = $this->convertException($exception);\n            $this->handleException($exception);\n        }\n    }\n\n    protected function getConnection()\n    {\n        return $this->connection;\n    }\n\n    /**\n     * @deprecated\n     * @internal\n     */\n    public function getPublicConnection()\n    {\n        return $this->connection;\n    }\n\n    private function doesStatementProduceOutput($query)\n    {\n        return in_array(\n            $this->getFirstCommandInStatement($query),\n            [\n                'SELECT',\n                'EXECUTE',\n                'GET',\n                'SHOW',\n                'CHECKSUM',\n                'DESCRIBE',\n                'EXPLAIN',\n                'HELP',\n            ]\n        );\n    }\n\n    protected function convertException(\\Exception $exception)\n    {\n        $message = $exception->getMessage();\n        $code = $exception->getCode();\n        $exceptionClass = DatabaseErrorException::class;\n\n        switch (true) {\n            case $exception instanceof DBALException\\ConnectionException:\n                // ConnectionException will be mapped to DatabaseConnectionException::class\n            case $exception instanceof ConnectionException:\n                /**\n                 * Doctrine does not recognise \"SQLSTATE[HY000] [2003] Can't connect to MySQL server on 'mysql.example'\"\n                 * as a connection error, as the error code 2003 is simply not treated in\n                 * Doctrine\\DBAL\\Driver\\AbstractMySQLDriver::convertException.\n                 * We fix this here.\n                 */\n                // ConnectionException will be mapped to DatabaseConnectionException::class\n                // no break\n            case is_a($exception->getPrevious(), '\\Exception')\n                && in_array($exception->getPrevious()->getCode(), ['2003']):\n                $exceptionClass = DatabaseConnectionException::class;\n                break;\n            case $exception instanceof DBALException:\n                /**\n                 * Doctrine passes the message and the code of the PDO Exception, which would break backward\n                 * compatibility as it uses SQLSTATE error code (string),\n                 * but the shop used to the (My)SQL errors (int)\n                 * See http://php.net/manual/de/class.pdoexception.php For details and discussion.\n                 * Fortunately we can access PDOException and recover the original SQL error code and message.\n                 */\n                /** @var $pdoException PDOException */\n                $pdoException = $exception->getPrevious();\n\n                if ($pdoException instanceof PDOException) {\n                    $code = $this->convertErrorCode($pdoException->errorInfo[1]);\n                    $message = $pdoException->errorInfo[2];\n                }\n\n                break;\n            case $exception instanceof PDOException:\n                /**\n                 * The shop uses the (My)SQL errors (int) in the error code,\n                 * but $pdoException uses SQLSTATE error code (string)\n                 * See http://php.net/manual/de/class.pdoexception.php For details and discussion.\n                 * Fortunately in some cases we can access PDOException and recover the original SQL error.\n                 */\n                $code = $this->convertErrorCode($exception->errorInfo[1]);\n                $message = $exception->errorInfo[2];\n\n                /** In case the original code (int) cannot be recovered, code is set to 0 */\n                if (!is_integer($code)) {\n                    $code = 0;\n                }\n\n                break;\n        }\n\n        /** @var oxException $convertedException */\n        $convertedException = new $exceptionClass($message, $code, $exception);\n\n        return $convertedException;\n    }\n\n    protected function handleException(StandardException $exception)\n    {\n        throw $exception;\n    }\n\n    protected function logException(\\Exception $exception)\n    {\n        /** The exception has to be converted into an instance of oxException in order to be logged like this */\n        $exception = $this->convertException($exception);\n        Registry::getLogger()->error($exception->getMessage(), [$exception]);\n    }\n\n    public function getAll($query, $parameters = [])\n    {\n        try {\n            $result = $this->getConnection()->fetchAllAssociative($query, $parameters);\n        } catch (DBALException | PDOException $exception) {\n            $exception = $this->convertException($exception);\n            $this->handleException($exception);\n        }\n\n        if ($this->doesStatementProduceOutput($query)) {\n            return $result;\n        }\n\n        Registry::getLogger()\n            ->warning(\n                'Given statement does not produce an output',\n                [debug_backtrace()]\n            );\n\n        return [];\n    }\n\n    public function getLastInsertId()\n    {\n        try {\n            $lastInsertId = $this->getConnection()->lastInsertId();\n        } catch (DBALException | PDOException $exception) {\n            $exception = $this->convertException($exception);\n            $this->handleException($exception);\n        }\n\n        return $lastInsertId;\n    }\n\n    public function metaColumns($table)\n    {\n        $databaseName = $this->getConnection()->getDatabase();\n        $query = \"SELECT\n              COLUMN_NAME AS `Field`,\n              COLUMN_TYPE AS `Type`,\n              IS_NULLABLE AS `Null`,\n              COLUMN_KEY AS `Key`,\n              COLUMN_DEFAULT AS `Default`,\n              EXTRA AS `Extra`,\n              COLUMN_COMMENT AS `Comment`,\n              CHARACTER_SET_NAME AS `CharacterSet`,\n              COLLATION_NAME AS `Collation`\n            FROM information_schema.COLUMNS\n            WHERE\n              TABLE_SCHEMA = '$databaseName'\n              AND\n              TABLE_NAME = '$table'\n            ORDER BY ORDINAL_POSITION ASC\";\n\n        try {\n            $columns = $this->getConnection()->fetchAllAssociative($query);\n        } catch (DBALException | PDOException $exception) {\n            $exception = $this->convertException($exception);\n            $this->handleException($exception);\n        }\n\n        $result = [];\n\n        foreach ($columns as $column) {\n            $type = $this->getMetaColumnValueByKey($column, 'Type');\n            $field = $this->getMetaColumnValueByKey($column, 'Field');\n            $null = $this->getMetaColumnValueByKey($column, 'Null');\n            $key = $this->getMetaColumnValueByKey($column, 'Key');\n            $default = $this->getMetaColumnValueByKey($column, 'Default');\n            $extra = $this->getMetaColumnValueByKey($column, 'Extra');\n            $comment = $this->getMetaColumnValueByKey($column, 'Comment');\n            $characterSet = $this->getMetaColumnValueByKey($column, 'CharacterSet');\n            $collation = $this->getMetaColumnValueByKey($column, 'Collation');\n\n            if ($default !== null) {\n                // MariaDB puts quotes around default values:\n                $default = trim($default, \"'\");\n            }\n\n            $typeInformation = explode('(', $type);\n            $typeName = trim($typeInformation[0]);\n\n            $item = new stdClass();\n            $item->name = $field;\n            $item->type = $typeName;\n            $item->not_null = ('no' === strtolower($null));\n            $item->primary_key = (strtolower($key) == 'pri');\n            $item->auto_increment = strtolower($extra) == 'auto_increment';\n            $item->binary = (false !== strpos(strtolower($type), 'blob'));\n            $item->unsigned = (false !== strpos(strtolower($type), 'unsigned'));\n            $item->has_default = ((is_null($default)) || ($default === '')) ? false : true;\n            if ($item->has_default) {\n                $item->default_value = $default;\n            }\n\n            /**\n             * These variables were set only when there was a value in the previous implementation with ADOdb Lite.\n             * We do it the same way here for compatibility.\n             */\n            list($max_length, $scale) = $this->getColumnMaxLengthAndScale($column, $item->type);\n            if (-1 !== $max_length) {\n                $item->max_length = (string)$max_length;\n            } else {\n                $item->max_length = $max_length;\n            }\n            if (-1 !== $scale) {\n                $item->scale = (string)$scale;\n            } else {\n                $item->scale = null;\n            }\n\n            /** Unset has_default and default_value for binary types */\n            if ($item->binary) {\n                unset($item->has_default, $item->default_value);\n            }\n\n            /** Additional properties not found in ADODB lite */\n            $item->comment = $comment;\n            $item->characterSet = $characterSet;\n            $item->collation = $collation;\n\n            /**\n             * ADODB lite properties not implemented\n             *\n             * @todo: implement the enums property for SET and ENUM fields\n             */\n            // $item->enums\n\n            if (array_key_exists('Field', $column)) {\n                $result[$item->name] = $item;\n            } else {\n                $result[] = $item;\n            }\n        }\n\n        return $result;\n    }\n\n    public function isRollbackOnly()\n    {\n        try {\n            $isRollbackOnly = $this->connection->isRollbackOnly();\n        } catch (DBALException | PDOException $exception) {\n            $exception = $this->convertException($exception);\n            $this->handleException($exception);\n        }\n\n        return $isRollbackOnly;\n    }\n\n    public function isTransactionActive()\n    {\n        try {\n            $isTransactionActive = $this->connection->isTransactionActive();\n        } catch (DBALException | PDOException $exception) {\n            $exception = $this->convertException($exception);\n            $this->handleException($exception);\n        }\n\n        return $isTransactionActive;\n    }\n\n    protected function getMetaColumnValueByKey(array $column, $key)\n    {\n        if (array_key_exists('Field', $column)) {\n            $keyMap = [\n                'Field' => 'Field',\n                'Type' => 'Type',\n                'Null' => 'Null',\n                'Key' => 'Key',\n                'Default' => 'Default',\n                'Extra' => 'Extra',\n                'Comment' => 'Comment',\n                'CharacterSet' => 'CharacterSet',\n                'Collation' => 'Collation',\n            ];\n        } else {\n            $keyMap = [\n                'Field' => 0,\n                'Type' => 1,\n                'Null' => 2,\n                'Key' => 3,\n                'Default' => 4,\n                'Extra' => 5,\n                'Comment' => 6,\n                'CharacterSet' => 7,\n                'Collation' => 8,\n            ];\n        }\n\n        return $column[$keyMap[$key]];\n    }\n\n    protected function getColumnMaxLengthAndScale(array $column, $assignedType)\n    {\n        /** @var int $maxLength The max length of a field. For floating point type or fixed point type fields the precision of the field */\n        $maxLength = -1;\n        /** @var int $scale The scale of floating point type or fixed point type fields */\n        $scale = -1;\n\n        /** @var string $mySqlType E.g. \"CHAR(4)\" or \"DECIMAL(5,2)\" or \"tinyint(1) unsigned\" */\n        $mySqlType = $this->getMetaColumnValueByKey($column, 'Type');\n        /** Get the maximum display width for the type */\n\n        /** Match Precision an scale E.g DECIMAL(5,2) */\n        if (preg_match(\"/^(.+)\\((\\d+),(\\d+)/\", $mySqlType, $matches)) {\n            if (is_numeric($matches[2])) {\n                $maxLength = $matches[2];\n            }\n            if (is_numeric($matches[3])) {\n                $scale = $matches[3];\n            }\n            /** Match max length E.g CHAR(4) */\n        } elseif (preg_match(\"/^(.+)\\((\\d+)/\", $mySqlType, $matches)) {\n            if (is_numeric($matches[2])) {\n                $maxLength = $matches[2];\n            }\n            /**\n             * Match List type E.g. SET('A', 'B', 'CDE)\n             * In this case the length will be the string length of the longest element\n             */\n        } elseif (preg_match(\"/^(enum|set)\\((.*)\\)$/i\", strtolower($mySqlType), $matches)) {\n            if ($matches[2]) {\n                $pieces = explode(\",\", $matches[2]);\n                /** The array values contain 2 quotes, so we have to subtract 2 from the strlen */\n                $maxLength = max(array_map(\"strlen\", $pieces)) - 2;\n                if ($maxLength <= 0) {\n                    $maxLength = 1;\n                }\n            }\n        }\n\n        /** Numeric types, which may have a maximum length */\n        $integerTypes = ['INTEGER', 'INT', 'SMALLINT', 'TINYINT', 'MEDIUMINT', 'BIGINT'];\n        $fixedPointTypes = ['DECIMAL', 'NUMERIC'];\n        $floatingPointTypes = ['FLOAT', 'DOUBLE'];\n\n        /** Text types, which may have a maximum length */\n        $textTypes = ['CHAR', 'VARCHAR'];\n\n        /** Date types, which may have a maximum length */\n        $dateTypes = ['YEAR'];\n\n        $assignedType = strtoupper($assignedType);\n        if (\n            (in_array($assignedType, $integerTypes) ||\n                in_array($assignedType, $fixedPointTypes) ||\n                in_array($assignedType, $floatingPointTypes) ||\n                in_array($assignedType, $textTypes) ||\n                in_array($assignedType, $dateTypes)) && -1 == $maxLength\n        ) {\n            /**\n             * @todo: If the assigned type is one of the following and maxLength is -1, then,\n             * if applicable the default max length ot that type should be assigned.\n             */\n        }\n\n        return [(int)$maxLength, (int)$scale];\n    }\n\n    protected function getFirstCommandInStatement($query)\n    {\n        $singleLineQuery = str_replace([\"\\r\", \"\\n\"], ' ', $query);\n        $sqlComments = '@(([\\'\"]).*?[^\\\\\\]\\2)|((?:\\#|--).*?$|/\\*(?:[^/*]|/(?!\\*)|\\*(?!/)|(?R))*\\*\\/)\\s*|(?<=;)\\s+@ms';\n        $uncommentedQuery = preg_replace($sqlComments, '$1', $singleLineQuery);\n\n        return strtoupper(\n            trim(\n                explode(' ', trim($uncommentedQuery))[0]\n            )\n        );\n    }\n\n    protected function ensureConnectionIsEstablished($connection)\n    {\n        if (!$this->isConnectionEstablished($connection)) {\n            $message = $this->createConnectionErrorMessage($connection);\n\n            throw new ConnectionException($message);\n        }\n    }\n\n    protected function isConnectionEstablished($connection)\n    {\n        try {\n            $connection->getServerVersion();\n        } catch (DBALException) {\n            return false;\n        }\n\n        return true;\n    }\n\n    protected function createConnectionErrorMessage($connection)\n    {\n        $params = $connection->getParams();\n        return sprintf(\n            \"Could not connect to the database. Please check your database status and configuration. \" .\n            \"driver: '%s', host: '%s'\",\n            $params['driver'] ?? '',\n            $params['host'] ?? ''\n        );\n    }\n\n    private function convertErrorCode($code)\n    {\n        return $code === self::MYSQL_DUPLICATE_KEY_ERROR_CODE\n            ? self::DUPLICATE_KEY_ERROR_CODE\n            : $code;\n    }\n\n    /**\n     * Doctrine's DBAL requires that arrays with integer keys for positional\n     * parameters must start from index 1. This method checks if the provided\n     * parameter array keys are integers and if the lowest index is 0. If so,\n     * it shifts all keys to begin from 1. Associative arrays are left untouched.\n     */\n    private function ensureParametersWithIntegerKeysStartWithOne(array $parameters): array\n    {\n        if (array_key_exists(0, $parameters)) {\n            array_unshift($parameters, '');\n            unset($parameters[0]);\n        }\n\n        return $parameters;\n    }\n}\n"
  },
  {
    "path": "source/Core/Database/Adapter/Doctrine/ResultSet.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Database\\Adapter\\Doctrine;\n\nuse Doctrine\\DBAL\\Result;\nuse Doctrine\\DBAL\\Statement;\nuse OxidEsales\\Eshop\\Core\\Database\\Adapter\\ResultSetInterface;\nuse Traversable;\n\n/**\n * The doctrine statement wrapper, to support the old adodblite interface.\n *\n * @package OxidEsales\\EshopCommunity\\Core\\Database\\Adapter\n *\n * @deprecated since v6.5.0 (2019-09-24); Use OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface\n */\nclass ResultSet implements \\IteratorAggregate, ResultSetInterface\n{\n    public $fields;\n\n    public $EOF;\n\n    private Result $result;\n\n    public function __construct(private Statement $statement)\n    {\n        $this->fields = [];\n        $this->EOF = false;\n        $this->result = $this->statement->executeQuery();\n\n        if ($this->count() === 0) {\n            $this->setToEmptyState();\n        }\n\n        $this->fetchRow();\n    }\n\n    public function close()\n    {\n        $this->result->free();\n        $this->fields = [];\n    }\n\n    public function fetchRow()\n    {\n        $this->fields = $this->result->fetchAssociative();\n\n        if (false === $this->fields) {\n            $this->EOF = true;\n        }\n\n        return $this->fields;\n    }\n\n    public function fetchAll()\n    {\n        $this->close();\n        $this->statement->executeQuery();\n\n        return $this->result->fetchAllAssociative();\n    }\n\n    public function fieldCount()\n    {\n        return $this->result->columnCount();\n    }\n    public function getIterator(): Traversable\n    {\n        $this->close();\n        $this->statement->executeQuery();\n\n        return $this->result->iterateAssociative();\n    }\n\n    public function getFields()\n    {\n        return $this->fields;\n    }\n\n    protected function getStatement()\n    {\n        return $this->statement;\n    }\n\n    protected function setStatement(Statement $statement)\n    {\n        $this->statement = $statement;\n    }\n\n    protected function setToEmptyState()\n    {\n        $this->EOF = true;\n    }\n\n    public function count(): int\n    {\n        return $this->result->rowCount();\n    }\n}\n"
  },
  {
    "path": "source/Core/Database/Adapter/ResultSetInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Database\\Adapter;\n\n/**\n * Interface ResultSetInterface\n *\n * @deprecated since v6.5.0 (2019-09-24);\n * Use OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface\n */\ninterface ResultSetInterface extends \\Traversable, \\Countable\n{\n    /**\n     * Closes the cursor, enabling the statement to be executed again.\n     *\n     * @return boolean Returns true on success or false on failure.\n     */\n    public function close();\n\n    /**\n     * Returns an array containing all of the result set rows\n     *\n     * @return array\n     */\n    public function fetchAll();\n\n    /**\n     * Returns the next row from a result set.\n     *\n     * @return mixed The return value of this function on success depends on the fetch type.\n     *               In all cases, FALSE is returned on failure.\n     */\n    public function fetchRow();\n\n    /**\n     * Returns the number of columns in the result set\n     *\n     * @return integer Returns the number of columns in the result set represented by the PDOStatement object.\n     */\n    public function fieldCount();\n}\n"
  },
  {
    "path": "source/Core/DatabaseProvider.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse Exception;\nuse OxidEsales\\Eshop\\Core\\Database\\Adapter\\DatabaseInterface;\nuse OxidEsales\\Eshop\\Core\\Database\\Adapter\\Doctrine\\Database;\nuse OxidEsales\\Eshop\\Core\\Exception\\DatabaseConnectionException;\n\n/**\n * @deprecated since v6.4.0 (2019-09-24) use QueryBuilderFactoryInterface\n * @see \\OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface\n */\nclass DatabaseProvider\n{\n\n    /**\n     * @var ?DatabaseProvider\n     */\n    protected static $instance = null;\n\n    /**\n     * @var null|DatabaseInterface Database connection object\n     */\n    protected static $db = null;\n\n    /**\n     * @var array Database tables descriptions cache array\n     */\n    protected static $tblDescCache = [];\n\n    /**\n     * This class is a singleton and should be instantiated with getInstance().\n     */\n    private function __construct()\n    {\n    }\n\n    /**\n     * @throws Exception\n     */\n    public function __clone()\n    {\n        throw new Exception(\"This object is a singleton, thou shalt not clone.\");\n    }\n\n    /**\n     * Returns the singleton instance of this class or of a subclass of this class.\n     *\n     * @return DatabaseProvider The singleton instance.\n     */\n    public static function getInstance()\n    {\n        if (null === static::$instance) {\n            static::$instance = new static();\n        }\n\n        return static::$instance;\n    }\n\n    /**\n     * Return the database connection instance as a singleton.\n     *\n     * @param int $fetchMode The fetch mode. Default is numeric (0).\n     *\n     * @return DatabaseInterface\n     * @throws DatabaseConnectionException Error while initiating connection to DB.\n     *\n     */\n    public static function getDb()\n    {\n        if (null === static::$db) {\n            $databaseFactory = static::getInstance();\n            static::$db = $databaseFactory->createDatabase();\n\n            /** Post connect actions will be taken only once per connection */\n            $databaseFactory->onPostConnect();\n        }\n\n        return static::$db;\n    }\n\n    /**\n     * Return the database master connection instance as a singleton.\n     * In case the shop is not allowed a master/slave setup, this function\n     * is simply a wrapper for DatabaseProvider::getDb.\n     *\n     * @param int $fetchMode The fetch mode. Default is numeric (0).\n     *\n     * @return DatabaseInterface\n     * @throws DatabaseConnectionException Error while initiating connection to DB\n     *\n     */\n    public static function getMaster()\n    {\n        static::getDb()->forceMasterConnection();\n\n        return static::getDb();\n    }\n\n    /**\n     * @param string $tableName Name of table to invest.\n     *\n     * @return array\n     */\n    public function getTableDescription($tableName)\n    {\n        if (!isset(self::$tblDescCache[$tableName])) {\n            self::$tblDescCache[$tableName] = $this->fetchTableDescription($tableName);\n        }\n\n        return self::$tblDescCache[$tableName];\n    }\n\n    /**\n     * Extracts and returns table metadata from DB.\n     * This method is extended in the Enterprise Edition.\n     *\n     * @param string $tableName\n     *\n     * @return array\n     */\n    protected function fetchTableDescription($tableName)\n    {\n        return static::getDb()->metaColumns($tableName);\n    }\n\n    /**\n     * @return DatabaseInterface\n     * @throws DatabaseConnectionException\n     *\n     */\n    protected function createDatabase()\n    {\n        $databaseAdapter = new Database();\n        $databaseAdapter->connect();\n\n        return $databaseAdapter;\n    }\n\n    /**\n     * Post connect hook. This method is called only once per connection right after the connection to the database has\n     * been established.\n     */\n    protected function onPostConnect()\n    {\n    }\n}\n"
  },
  {
    "path": "source/Core/DbMetaDataHandler.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Shop;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\n\n/**\n * Class for handling database related operations\n */\nclass DbMetaDataHandler extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     *\n     * @var array\n     */\n    protected $_aTables = null;\n\n    /**\n     *\n     * @var int\n     */\n    protected $_iCurrentMaxLangId;\n\n    /**\n     *\n     * @var array Tables which should be skipped from resetting\n     */\n    protected $_aSkipTablesOnReset = [\"oxcountry\"];\n\n    /**\n     * When creating views, always use those fields from core table.\n     *\n     * @var array\n     */\n    protected $forceOriginalFields = ['OXID'];\n\n    /**\n     *  Get table fields\n     *\n     * @param string $tableName table name\n     *\n     * @return array\n     */\n    public function getFields($tableName)\n    {\n        $fields = [];\n        $rawFields = DatabaseProvider::getDb()->MetaColumns($tableName);\n        if (is_array($rawFields)) {\n            foreach ($rawFields as $field) {\n                $fields[$field->name] = \"{$tableName}.{$field->name}\";\n            }\n        }\n\n        return $fields;\n    }\n\n    /**\n     * Check if table exists\n     *\n     * @param string $tableName table name\n     *\n     * @return bool\n     */\n    public function tableExists($tableName)\n    {\n        $db = DatabaseProvider::getDb();\n        $tables = $db->getAll(\"show tables like \" . $db->quote($tableName));\n\n        return count($tables) > 0;\n    }\n\n    /**\n     * Check if field exists in table\n     *\n     * @param string $fieldName field name\n     * @param string $tableName table name\n     *\n     * @return bool\n     */\n    public function fieldExists($fieldName, $tableName)\n    {\n        $tableFields = $this->getFields($tableName);\n        $tableName = strtoupper($tableName);\n        if (is_array($tableFields)) {\n            $fieldName = strtoupper($fieldName);\n            $tableFields = array_map('strtoupper', $tableFields);\n            if (in_array(\"{$tableName}.{$fieldName}\", $tableFields)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Get the indices of a table\n     *\n     * @param string $tableName The name of the table for which we want the\n     *\n     * @return array The indices of the given table\n     */\n    public function getIndices($tableName)\n    {\n        $result = [];\n\n        if ($this->tableExists($tableName)) {\n            $result = DatabaseProvider::getDb()->getAll(\"SHOW INDEX FROM $tableName\");\n        }\n\n        return $result;\n    }\n\n    /**\n     * Check, if the table has an index with the given name\n     *\n     * @param string $indexName The name of the index we want to check\n     * @param string $tableName The table to check for the index\n     *\n     * @return bool Has the table the given index?\n     */\n    public function hasIndex($indexName, $tableName)\n    {\n        $result = false;\n\n        foreach ($this->getIndices($tableName) as $index) {\n            if ($indexName === $index['Column_name']) {\n                $result = true;\n            }\n        }\n\n        return $result;\n    }\n\n    /**\n     * Get the index of a given table by its name\n     *\n     * @param string $indexName The name of the index\n     * @param string $tableName The name of the table from which we want the index\n     *\n     * @return null|array The index with the given name\n     */\n    public function getIndexByName($indexName, $tableName)\n    {\n        $indices = $this->getIndices($tableName);\n\n        $result = null;\n\n        foreach ($indices as $index) {\n            if ($indexName === $index['Column_name']) {\n                $result = $index;\n            }\n        }\n\n        return $result;\n    }\n\n    /**\n     * Get all tables names from db. Views tables are not included in\n     * this list.\n     *\n     * @return array\n     */\n    public function getAllTables()\n    {\n        if (empty($this->_aTables)) {\n            $tablesNames = DatabaseProvider::getDb()->getCol(\"show tables\");\n\n            foreach ($tablesNames as $tableName) {\n                if ($this->validateTableName($tableName)) {\n                    $this->_aTables[] = $tableName;\n                }\n            }\n        }\n\n        return $this->_aTables;\n    }\n\n    /**\n     * return all DB tables for the language sets\n     *\n     * @param string $table table name to check\n     *\n     * @return array\n     */\n    public function getAllMultiTables($table)\n    {\n        $mLTables = [];\n        foreach (array_keys(Registry::getLang()->getLanguageIds()) as $langId) {\n            $langTableName = getLangTableName($table, $langId);\n            if ($table != $langTableName && !in_array($langTableName, $mLTables)) {\n                $mLTables[] = $langTableName;\n            }\n        }\n\n        return $mLTables;\n    }\n\n    /**\n     * Get sql for new multi-language table set creation\n     *\n     * @param string $table core table name\n     * @param string $lang  language id\n     *\n     * @return string\n     */\n    protected function getCreateTableSetSql($table, $lang)\n    {\n        $tableSet = getLangTableName($table, $lang);\n\n        $tableStatus = DatabaseProvider::getDb()->getRow(\n            'SHOW TABLE STATUS LIKE :tablename',\n            [\n                'tablename' => $table\n            ]\n        );\n        $tableStatus = array_values($tableStatus);\n\n        return \"CREATE TABLE `{$tableSet}` (\" .\n               \"`OXID` char(32) NOT NULL, \" .\n               \"PRIMARY KEY (`OXID`)) \" .\n               \"DEFAULT CHARACTER SET latin1 COLLATE latin1_general_ci ENGINE= \" . $tableStatus[1] . \" \" .\n               \"COMMENT='\" . $tableStatus[17] . \"'\";\n    }\n\n    /**\n     * Get sql for new multi-language field creation\n     *\n     * @param string $table     core table name\n     * @param string $field     field name\n     * @param string $newField  new field name\n     * @param string $prevField previous field in table\n     * @param string $tableSet  table to change (if not set take core table)\n     *\n     * @return string\n     */\n    public function getAddFieldSql($table, $field, $newField, $prevField, $tableSet = null)\n    {\n        if (!$tableSet) {\n            $tableSet = $table;\n        }\n        $tableSql = $this->getTableCreationSql($table);\n\n        // removing comments;\n        $tableSql = preg_replace('/COMMENT \\\\\\'.*?\\\\\\'/', '', $tableSql);\n        preg_match(\"/.*,\\s+(['`]?\" . preg_quote($field, '/') . \"['`]?\\s+[^,]+),.*/\", $tableSql, $match);\n        $fieldSql = $match[1] ?? '';\n\n        $sql = \"\";\n        if (!empty($fieldSql)) {\n            $fieldSql = preg_replace(\"/\" . preg_quote($field, '/') . \"/\", $newField, $fieldSql);\n            $sql = \"ALTER TABLE `$tableSet` ADD \" . $fieldSql;\n            if ($this->tableExists($tableSet) && $this->fieldExists($prevField, $tableSet)) {\n                $sql .= \" AFTER `$prevField`\";\n            }\n        }\n\n        return $sql;\n    }\n\n\n    /**\n     * Get sql for new multi-language field index creation\n     *\n     * @param string $table    core table name\n     * @param string $field    field name\n     * @param string $newField new field name\n     * @param string $tableSet table to change (if not set take core table)\n     *\n     * @return string\n     */\n    public function getAddFieldIndexSql($table, $field, $newField, $tableSet = null)\n    {\n        $tableSql = $this->getTableCreationSql($table);\n\n        preg_match_all(\"/([\\w]+\\s+)?\\bKEY\\s+(`[^`]+`)?\\s*\\([^)]+(\\(\\d++\\))*\\)/iU\", $tableSql, $match);\n        $index = $match[0];\n\n        $usingTableSet = $tableSet ? true : false;\n\n        if (!$tableSet) {\n            $tableSet = $table;\n        }\n\n        $indexQueries = [];\n        $sql = [];\n        if (count($index)) {\n            foreach ($index as $key => $indexQuery) {\n                if (preg_match(\"/\\([^)]*\\b\" . $field . \"\\b[^)]*\\)/i\", $indexQuery)) {\n                    //removing index name - new will be added automaticly\n                    $indexQuery = preg_replace(\"/(.*\\bKEY\\s+)`[^`]+`/\", \"$1\", $indexQuery);\n\n                    if ($usingTableSet) {\n                        // replacing multiple fields to one (#3269)\n                        $indexQuery = preg_replace(\"/\\([^\\)]+\\)+/\", \"(`$newField`{$match[3][$key]})\", $indexQuery);\n                    } else {\n                        //replacing previous field name with new one\n                        $indexQuery = preg_replace(\"/\\b\" . $field . \"\\b/\", $newField, $indexQuery);\n                    }\n                    $indexQueries[] = \"ADD \" . $indexQuery;\n                }\n            }\n            if (count($indexQueries)) {\n                $sql = [\"ALTER TABLE `$tableSet` \" . implode(\", \", $indexQueries)];\n            }\n        }\n\n        return $sql;\n    }\n\n    /**\n     * Get max language ID used in shop. For checking is used table \"oxarticle\"\n     * field \"oxtitle\"\n     *\n     * @return int\n     */\n    public function getCurrentMaxLangId()\n    {\n        if (isset($this->_iCurrentMaxLangId)) {\n            return $this->_iCurrentMaxLangId;\n        }\n\n        $table = $tableSet = \"oxarticles\";\n        $field = $fieldSet = \"oxtitle\";\n        $lang = 0;\n        while ($this->tableExists($tableSet) && $this->fieldExists($fieldSet, $tableSet)) {\n            $lang++;\n            $tableSet = getLangTableName($table, $lang);\n            $fieldSet = $field . '_' . $lang;\n        }\n\n        return $this->_iCurrentMaxLangId = --$lang;\n    }\n\n    /**\n     * Get next available language ID\n     *\n     * @return int\n     */\n    public function getNextLangId()\n    {\n        return $this->getCurrentMaxLangId() + 1;\n    }\n\n    /**\n     * Get table multi-language fields\n     *\n     * @param string $table table name\n     *\n     * @return array\n     */\n    public function getMultilangFields($table)\n    {\n        $fields = $this->getFields($table);\n        $multiLangFields = [];\n\n        foreach ($fields as $field) {\n            if (preg_match(\"/({$table}\\.)?(?<field>.+)_1$/\", $field, $matches)) {\n                $multiLangFields[] = $matches['field'];\n            }\n        }\n\n        return $multiLangFields;\n    }\n\n    /**\n     * Get single language fields\n     *\n     * @param string $table table name\n     * @param int    $lang  language id\n     *\n     * @return array\n     */\n    public function getSinglelangFields($table, $lang)\n    {\n        $langTable = getLangTableName($table, $lang);\n\n        $baseFields = $this->getFields($table);\n        $langFields = $this->getFields($langTable);\n\n        //Some fields (for example OXID) must be taken from core table.\n        $langFields = $this->filterCoreFields($langFields);\n\n        $fields = array_merge($baseFields, $langFields);\n        $singleLangFields = [];\n\n        foreach ($fields as $fieldName => $field) {\n            if (preg_match(\"/(({$table}|{$langTable})\\.)?(?<field>.+)_(?<lang>[0-9]+)$/\", $field, $matches)) {\n                if ($matches['lang'] == $lang) {\n                    $singleLangFields[$matches['field']] = $field;\n                }\n            } else {\n                $singleLangFields[$fieldName] = $field;\n            }\n        }\n\n        return $singleLangFields;\n    }\n\n    /**\n     * Add new multi-languages fields to table. Duplicates all multi-language\n     * fields and fields indexes with next available language ID\n     *\n     * @param string $table table name\n     */\n    public function addNewMultilangField($table)\n    {\n        $newLang = $this->getNextLangId();\n\n        $this->ensureMultiLanguageFields($table, $newLang);\n    }\n\n    /**\n     * Ensure, that all multi language fields of the given table are present.\n     *\n     * @param string $table The table we want to assure, that the multi language fields are present.\n     */\n    public function ensureAllMultiLanguageFields($table)\n    {\n        $max = $this->getCurrentMaxLangId();\n\n        for ($index = 1; $index <= $max; $index++) {\n            $this->ensureMultiLanguageFields($table, $index);\n        }\n    }\n\n    /**\n     * Resetting all multi-language fields with specific language id\n     * to default value in selected table\n     *\n     * @param int    $langId    Language id\n     * @param string $tableName Table name\n     *\n     * @return null\n     */\n    public function resetMultilangFields($langId, $tableName)\n    {\n        $langId = (int) $langId;\n\n        if ($langId === 0) {\n            return;\n        }\n\n        $sql = [];\n\n        $fields = $this->getMultilangFields($tableName);\n        if (is_array($fields) && count($fields) > 0) {\n            foreach ($fields as $fieldName) {\n                $fieldName = $fieldName . \"_\" . $langId;\n\n                if ($this->fieldExists($fieldName, $tableName)) {\n                    //resetting field value to default\n                    $sql[] = \"UPDATE {$tableName} SET {$fieldName} = DEFAULT;\";\n                }\n            }\n        }\n\n        if (!empty($sql)) {\n            $this->executeSql($sql);\n        }\n    }\n\n    /**\n     * Add new language to database. Scans all tables and adds new\n     * multi-language fields\n     */\n    public function addNewLangToDb()\n    {\n        //reset max count\n        $this->_iCurrentMaxLangId = null;\n\n        $table = $this->getAllTables();\n\n        foreach ($table as $tableName) {\n            $this->addNewMultilangField($tableName);\n        }\n\n        //updating views\n        $this->updateViews();\n    }\n\n    /**\n     * Resetting all multi-language fields with specific language id\n     * to default value in all tables. Only if language ID > 0.\n     *\n     * @param int $langId Language id\n     *\n     * @return null\n     */\n    public function resetLanguage($langId)\n    {\n        if ((int) $langId === 0) {\n            return;\n        }\n\n        $tables = $this->getAllTables();\n\n        // removing tables which does not requires reset\n        foreach ($this->_aSkipTablesOnReset as $skipTable) {\n            if (($skipId = array_search($skipTable, $tables)) !== false) {\n                unset($tables[$skipId]);\n            }\n        }\n\n        foreach ($tables as $tableName) {\n            $this->resetMultilangFields($langId, $tableName);\n        }\n    }\n\n    /**\n     * Executes array of sql strings\n     *\n     * @param array $queries SQL query array\n     */\n    public function executeSql($queries)\n    {\n        $db = DatabaseProvider::getDb();\n\n        if (is_array($queries) && !empty($queries)) {\n            foreach ($queries as $query) {\n                $query = trim($query);\n                if (!empty($query)) {\n                    $db->execute($query);\n                }\n            }\n        }\n    }\n\n    /**\n     * Updates all views\n     *\n     * @param array|null $tables array of DB table name that can store different data per shop like oxArticle\n     *\n     * @return bool\n     */\n    public function updateViews(?array $tables = null): bool\n    {\n        set_time_limit(0);\n\n        Shop::disableViews();\n\n        $db = DatabaseProvider::getDb();\n        $config = Registry::getConfig();\n\n        $this->safeGuardAdditionalMultiLanguageTables();\n\n        $shops = $db->getAll('select * from oxshops');\n\n        $tables = $tables ?: ContainerFacade::getParameter('oxid_esales.multi_shop_tables');\n\n        $success = true;\n        foreach ($shops as $shopValues) {\n            $shopId = $shopValues['OXID'];\n            $shop = oxNew(Shop::class);\n            $shop->load($shopId);\n            $shop->setMultiShopTables($tables);\n            $mallInherit = [];\n            foreach ($tables as $table) {\n                $mallInherit[$table] = $config->getShopConfVar('blMallInherit_' . $table, $shopId);\n            }\n            if (!$shop->generateViews(false, $mallInherit) && $success) {\n                $success = false;\n            }\n        }\n\n        return $success;\n    }\n\n\n    /**\n     * Make sure that e.g. OXID is always used from core table when creating views.\n     * Otherwise we might have unwanted side effects from rows with OXIDs null in view tables.\n     *\n     * @param array $fields Language fields array we need to filter for core fields.\n     *\n     * @return array\n     */\n    protected function filterCoreFields($fields)\n    {\n        foreach ($this->forceOriginalFields as $fieldname) {\n            if (array_key_exists($fieldname, $fields)) {\n                unset($fields[$fieldname]);\n            }\n        }\n        return $fields;\n    }\n\n    /**\n     * Ensure that all *_set* tables for all tables in container parameter 'oxid_multilingual_tables'\n     * are created.\n     *\n     * @return null\n     */\n    protected function safeGuardAdditionalMultiLanguageTables()\n    {\n        $maxLang = $this->getCurrentMaxLangId();\n        $multiLanguageTables = ContainerFacade::getParameter('oxid_esales.multilingual_tables');\n\n        if (!is_array($multiLanguageTables) || empty($multiLanguageTables)) {\n            return; //nothing to do\n        }\n\n        foreach ($multiLanguageTables as $table) {\n            if ($this->tableExists($table)) {\n                //We start with language id 1 and rely on that all fields for language 0 exists.\n                //For language id 0 we have e.g. OXTITLE and logic here would expect it to\n                //be OXTITLE_0, add that as new field, leading to incorrect data in views later on.\n                for ($i = 1; $i <= $maxLang; $i++) {\n                    $this->ensureMultiLanguageFields($table, $i);\n                }\n            }\n        }\n    }\n\n    /**\n     * Make sure that all *_set* tables with all required multilanguage fields are created.\n     *\n     * @param string $table\n     * @param int    $languageId\n     */\n    protected function ensureMultiLanguageFields($table, $languageId)\n    {\n        $fields = $this->getMultilangFields($table);\n        $sql = [];\n\n        $tableSet = getLangTableName($table, $languageId);\n        if (!$this->tableExists($tableSet)) {\n            $sql[] = $this->getCreateTableSetSql($table, $languageId);\n        }\n\n        if (is_array($fields) && count($fields) > 0) {\n            foreach ($fields as $field) {\n                $newFieldName = $field . \"_\" . $languageId;\n                if ($languageId > 1) {\n                    $previousLanguage = $languageId - 1;\n                    $previousField = $field . '_' . $previousLanguage;\n                } else {\n                    $previousField = $field;\n                }\n\n                if (!$this->tableExists($tableSet) || !$this->fieldExists($newFieldName, $tableSet)) {\n                    //getting add field sql\n                    $sql[] = $this->getAddFieldSql($table, $field, $newFieldName, $previousField, $tableSet);\n\n                    //getting add index sql on added field\n                    $sql = array_merge($sql, (array) $this->getAddFieldIndexSql($table, $field, $newFieldName, $tableSet));\n                }\n            }\n        }\n\n        $this->executeSql($sql);\n    }\n\n    /**\n     * Adds possibility to validate table names.\n     *\n     * @param string $tableName\n     *\n     * @return bool\n     */\n    protected function validateTableName($tableName)\n    {\n        return true;\n    }\n\n    private function getTableCreationSql(string $tableName): string\n    {\n        $result = DatabaseProvider::getDb()->getRow(\n            sprintf(\"show create table %s\", DatabaseProvider::getDb()->quoteIdentifier($tableName))\n        );\n\n        return array_values($result)[1];\n    }\n}\n"
  },
  {
    "path": "source/Core/DebugInfo.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Debug information formatter\n */\nclass DebugInfo\n{\n    /**\n     * format template data for debug view\n     *\n     * @param array $viewData template data\n     *\n     * @return string\n     */\n    public function formatTemplateData($viewData = [])\n    {\n        $log = '';\n        reset($viewData);\n        foreach ($viewData as $viewName => $viewDataObject) {\n            // show debbuging information\n            $log .= \"TemplateData[$viewName] : <br />\\n\";\n            $log .= print_r($viewDataObject, 1);\n        }\n\n        return $log;\n    }\n\n    /**\n     * format memory usage\n     *\n     * @return string\n     */\n    public function formatMemoryUsage()\n    {\n        $log = '';\n        if (function_exists('memory_get_usage')) {\n            $kb = (int) (memory_get_usage() / 1024);\n            $mb = round($kb / 1024, 3);\n            $log .= 'Memory usage: ' . $mb . ' MB';\n\n            if (function_exists('memory_get_peak_usage')) {\n                $peakKb = (int) (memory_get_peak_usage() / 1024);\n                $peakMb = round($peakKb / 1024, 3);\n                $log .= ' (peak: ' . $peakMb . ' MB)';\n            }\n            $log .= '<br />';\n\n            if (version_compare(PHP_VERSION, '5.2.0', '>=')) {\n                $kb = (int) (memory_get_usage(true) / 1024);\n                $mb = round($kb / 1024, 3);\n                $log .= 'System memory usage: ' . $mb . ' MB';\n\n                if (function_exists('memory_get_peak_usage')) {\n                    $peakKb = (int) (memory_get_peak_usage(true) / 1024);\n                    $peakMb = round($peakKb / 1024, 3);\n                    $log .= ' (peak: ' . $peakMb . ' MB)';\n                }\n                $log .= '<br />';\n            }\n        }\n\n        return $log;\n    }\n\n    /**\n     * format execution times\n     *\n     * @param double $dTotalTime total time\n     *\n     * @return string\n     */\n    public function formatExecutionTime($dTotalTime)\n    {\n        $log = 'Execution time:' . round($dTotalTime, 4) . '<br />';\n        global $aProfileTimes;\n        global $executionCounts;\n        if (is_array($aProfileTimes)) {\n            $log .= \"----------------------------------------------------------<br>\" . PHP_EOL;\n            arsort($aProfileTimes);\n            $log .= \"<table cellspacing='10px' style='border: 1px solid #000'>\";\n            foreach ($aProfileTimes as $key => $val) {\n                $log .= \"<tr><td style='border-bottom: 1px dotted #000;min-width:300px;'>Profile $key: </td><td style='border-bottom: 1px dotted #000;min-width:100px;'>\" . round($val, 5) . \"s</td>\";\n                if ($dTotalTime) {\n                    $log .= \"<td style='border-bottom: 1px dotted #000;min-width:100px;'>\" . round($val * 100 / $dTotalTime, 2) . \"%</td>\";\n                }\n                if ($executionCounts[$key]) {\n                    $log .= \" <td style='border-bottom: 1px dotted #000;min-width:50px;padding-right:30px;' align='right'>\" . $executionCounts[$key] . \"</td>\"\n                             . \"<td style='border-bottom: 1px dotted #000;min-width:15px; '>*</td>\"\n                             . \"<td style='border-bottom: 1px dotted #000;min-width:100px;'>\" . round($val / $executionCounts[$key], 5) . \"s</td>\" . PHP_EOL;\n                } else {\n                    $log .= \" <td colspan=3 style='border-bottom: 1px dotted #000;min-width:100px;'> not stopped correctly! </td>\" . PHP_EOL;\n                }\n                $log .= '</tr>';\n            }\n            $log .= \"</table>\";\n        }\n\n        return $log;\n    }\n\n    /**\n     * general info (debug title)\n     *\n     * @return string\n     */\n    public function formatGeneralInfo()\n    {\n        $log = \"cl=\" . \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActiveView()->getClassKey();\n        if (($fnc = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActiveView()->getFncName())) {\n            $log .= \" fnc=$fnc\";\n        }\n\n        return $log;\n    }\n\n    /**\n     * Forms view name and timestamp to.\n     *\n     * @return string\n     */\n    public function formatTimeStamp()\n    {\n        $log = '';\n        $className = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActiveView()->getClassKey();\n        $log .= \"<div id='\" . $className . \"_executed'>Executed: \" . date('Y-m-d H:i:s') . \"</div>\";\n        $log .= \"<div id='\" . $className . \"_timestamp'>Timestamp: \" . microtime(true) . \"</div>\";\n\n        return $log;\n    }\n}\n"
  },
  {
    "path": "source/Core/Decryptor.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Class oxDecryptor\n */\nclass Decryptor\n{\n    /**\n     * Decrypts string with given key.\n     *\n     * @param string $string string\n     * @param string $key    key\n     *\n     * @return string\n     */\n    public function decrypt($string, $key)\n    {\n        $key = $this->formKey($key, $string);\n\n        $string = substr($string, 3);\n        $string = str_replace('!', '=', $string);\n        $string = base64_decode($string);\n        $string = $string ^ $key;\n\n        return substr($string, 2, -2);\n    }\n\n    /**\n     * Forms key for use in encoding.\n     *\n     * @param string $key\n     * @param string $string\n     *\n     * @return string\n     */\n    protected function formKey($key, $string)\n    {\n        $key = '_' . $key;\n        $keyLength = (int) (strlen($string) / strlen($key)) + 5;\n\n        return str_repeat($key, $keyLength);\n    }\n}\n"
  },
  {
    "path": "source/Core/Di/ContainerFacade.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Di;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\ContainerFactory;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\n/**\n * A Service Locator fallback to use in the application areas, not managed by the Dependency Injection Component (e.g. Application, Core).\n * Never use this class (or Container directly) in other namespaces (e.g. Internal)!\n */\nfinal class ContainerFacade\n{\n    public static function has(string $id): bool\n    {\n        return ContainerFactory::getInstance()\n            ->getContainer()\n            ->has($id);\n    }\n\n    /**\n     * @template T\n     * @param class-string<T> $id\n     * @return T\n     */\n    public static function get(string $id): object\n    {\n        return ContainerFactory::getInstance()\n            ->getContainer()\n            ->get($id);\n    }\n\n    public static function hasParameter(string $name): bool\n    {\n        return ContainerFactory::getInstance()\n            ->getContainer()\n            ->hasParameter($name);\n    }\n\n    public static function getParameter(string $name): mixed\n    {\n        return ContainerFactory::getInstance()\n            ->getContainer()\n            ->getParameter($name);\n    }\n\n    /**\n     * @template T of Event\n     * @param T $event\n     * @return T\n     */\n    public static function dispatch(Event $event): Event\n    {\n        return ContainerFactory::getInstance()\n            ->getContainer()\n            ->get(EventDispatcherInterface::class)\n            ->dispatch($event);\n    }\n}\n"
  },
  {
    "path": "source/Core/DisplayError.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * simple class to add a error message to display\n */\nclass DisplayError implements \\OxidEsales\\Eshop\\Core\\Contract\\IDisplayError\n{\n    /**\n     * Error message\n     *\n     * @var string $_sMessage\n     */\n    protected $_sMessage;\n\n    /** @var array */\n    private $_aFormatParameters = [];\n\n    /**\n     * Formats message using vsprintf if property _aFormatParameters was set and returns translated message.\n     *\n     * @return string stored message\n     */\n    public function getOxMessage()\n    {\n        $translatedMessage = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString($this->_sMessage);\n        if (!empty($this->_aFormatParameters)) {\n            $translatedMessage = vsprintf($translatedMessage, $this->_aFormatParameters);\n        }\n\n        return $translatedMessage;\n    }\n\n    /**\n     * Stored the message.\n     *\n     * @param string $message message\n     */\n    public function setMessage($message)\n    {\n        $this->_sMessage = $message;\n    }\n\n    /**\n     * Stes format parameters for message.\n     *\n     * @param array $formatParameters\n     */\n    public function setFormatParameters($formatParameters)\n    {\n        $this->_aFormatParameters = $formatParameters;\n    }\n\n    /**\n     * Returns errorrous class name (currently returns null)\n     *\n     * @return null\n     */\n    public function getErrorClassType()\n    {\n        return null;\n    }\n\n    /**\n     * Returns value (currently returns empty string)\n     *\n     * @param string $name value ignored\n     *\n     * @return string\n     */\n    public function getValue($name)\n    {\n        return '';\n    }\n}\n"
  },
  {
    "path": "source/Core/DynamicImageGenerator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace {\n\n    use OxidEsales\\Eshop\\Core\\DynamicImageGenerator;\n\n    /** Checks if instance name getter does not exist */\n    if (!function_exists(\"getGeneratorInstanceName\")) {\n        /**\n         * Returns image generator instance name\n         *\n         * @return string\n         */\n        function getGeneratorInstanceName()\n        {\n            return DynamicImageGenerator::class;\n        }\n    }\n\n    /** Checks if GD library version getter does not exist */\n    if (!function_exists(\"getGdVersion\")) {\n        /**\n         * Returns GD library version\n         *\n         * @return int\n         */\n        function getGdVersion()\n        {\n            static $version = null;\n\n            if ($version === null) {\n                $version = false;\n                if (function_exists(\"gd_info\")) {\n                    // extracting GD version from php\n                    $info = gd_info();\n                    if (isset($info[\"GD Version\"])) {\n                        $version = version_compare(preg_replace(\"/[^0-9\\.]/\", \"\", $info[\"GD Version\"]), 1, '>') ? 2 : 1;\n                    }\n                }\n            }\n\n            return $version;\n        }\n    }\n\n    /** Checks if image utils file loader does not exist */\n    if (!function_exists(\"includeImageUtils\")) {\n        /**\n         * Includes image utils\n         */\n        function includeImageUtils()\n        {\n            include_once __DIR__ . \"/utils/oxpicgenerator.php\";\n        }\n    }\n}\nnamespace OxidEsales\\EshopCommunity\\Core {\n\n    use OxidEsales\\Eshop\\Core\\Exception\\StandardException;\n    use OxidEsales\\Eshop\\Core\\Exception\\SystemComponentException;\n    use OxidEsales\\Eshop\\Core\\Registry;\n    use OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\n    use Symfony\\Component\\Filesystem\\Path;\n\n    /**\n     * Image generator class\n     */\n    class DynamicImageGenerator\n    {\n        /**\n         * Generator instance\n         *\n         * @var DynamicImageGenerator\n         */\n        protected static $_oInstance = null;\n\n        /**\n         * Custom headers\n         *\n         * @var array\n         */\n        protected $_aHeaders = [];\n\n        /**\n         * Allowed image types\n         *\n         * @var array\n         */\n        protected $_aAllowedImgTypes = [\"jpg\", \"jpeg\", \"png\", \"gif\", \"webp\"];\n\n        /**\n         * Image info like size and quality is defined in directory\n         * name e.g. 160_160_75, this means width_height_quality\n         *\n         * @var string\n         */\n        protected $_sImageInfoSep = \"_\";\n\n        /**\n         * Lockable file handle\n         *\n         * @var resource\n         */\n        protected $_hLockHandle = null;\n\n        /**\n         * Requested image uri\n         *\n         * @var string\n         */\n        protected $_sImageUri = null;\n\n        /**\n         * Map of config parameter to requested image path\n         *\n         * @var array\n         */\n        protected array $resolutionConfigParameters = [\n            \"sIconsize\",\n            \"sThumbnailsize\",\n            \"sZoomImageSize\",\n            \"sDetailImageSize\",\n            \"sManufacturerIconsize\",\n            \"sManufacturerPicturesize\",\n            \"sManufacturerThumbnailsize\",\n            \"sManufacturerPromotionsize\",\n            \"sCatThumbnailsize\",\n            \"sCatIconsize\",\n            \"sCatPromotionsize\"\n        ];\n\n        /**\n         * Creates and returns picture generator instance\n         *\n         * @return DynamicImageGenerator\n         */\n        public static function getInstance()\n        {\n            if (self::$_oInstance === null) {\n                $instanceName = getGeneratorInstanceName();\n                self::$_oInstance = new $instanceName();\n            }\n\n            return self::$_oInstance;\n        }\n\n        /**\n         * Only used for convenience in UNIT tests by doing so we avoid\n         * writing extended classes for testing protected or private methods\n         *\n         * @param string $method Methods name\n         * @param array  $arguments Argument array\n         * @return false|mixed\n         * @throws SystemComponentException\n         */\n        public function __call($method, $arguments)\n        {\n            if (method_exists($this, $method)) {\n                return call_user_func_array([& $this, $method], $arguments);\n            }\n            throw new SystemComponentException(\n                \"Function '$method' does not exist or is not accessible! (\" . get_class($this) . \")\" . PHP_EOL\n            );\n        }\n\n        /**\n         * Returns shops base path\n         *\n         * @return string\n         */\n        protected function getShopBasePath()\n        {\n            return ContainerFacade::getParameter('oxid_esales.shop_source_directory') . DIRECTORY_SEPARATOR;\n        }\n\n        /**\n         * Returns requested image uri\n         *\n         * @return string\n         */\n        protected function getImageUri()\n        {\n            if ($this->_sImageUri === null) {\n                $this->_sImageUri = \"\";\n                $reqPath = 'out/pictures/generated';\n\n                $reqImg = isset($_SERVER[\"REQUEST_URI\"]) ? urldecode($_SERVER[\"REQUEST_URI\"]) : \"\";\n                $reqImg = str_replace('//', '/', $reqImg);\n                if (($pos = strpos($reqImg, $reqPath)) !== false) {\n                    $this->_sImageUri = substr($reqImg, $pos);\n                }\n\n                $this->_sImageUri = trim($this->_sImageUri, \"/\");\n            }\n\n            return $this->_sImageUri;\n        }\n\n        /**\n         * Returns requested image name\n         *\n         * @return string\n         */\n        protected function getImageName()\n        {\n            return basename($this->getImageUri());\n        }\n\n        /**\n         * Returns path to possible master image\n         *\n         * @return string\n         */\n        protected function getImageMasterPath()\n        {\n            $uri = $this->getImageUri();\n            $path = false;\n\n            if ($uri && ($path = dirname(dirname($uri)))) {\n                $path = preg_replace(\"/\\/([^\\/]*)\\/([^\\/]*)\\/([^\\/]*)$/\", \"/master/\\\\2/\\\\3/\", $path);\n            }\n\n            return $path;\n        }\n\n        private function parseMediaPathFromUrl(): string\n        {\n            $uri = $this->getImageUri();\n\n            $originalImageDirectory = preg_replace('~(^|/)generated/~', '$1', dirname($uri, 2));\n\n            return Path::join(\n                $this->getShopBasePath(),\n                $originalImageDirectory,\n                $this->getImageName()\n            );\n        }\n\n        /**\n         * Returns image info array\n         *\n         * @return array\n         */\n        protected function getImageInfo()\n        {\n            $info = [0, 0, 0];\n            if (($uri = $this->getImageUri())) {\n                $info = explode($this->_sImageInfoSep, basename(dirname($uri)));\n            }\n\n            return $info;\n        }\n\n        /**\n         * Returns full requested image path on file system\n         *\n         * @return string\n         */\n        protected function getImageTarget()\n        {\n            return $this->getShopBasePath() . $this->getImageUri();\n        }\n\n        /**\n         * Nopic image path\n         *\n         * @return string\n         */\n        protected function getNopicImageTarget()\n        {\n            $path = $this->getShopBasePath() . $this->getImageUri();\n\n            return str_replace($this->getImageName(), $this->getNopicFilename(), $path);\n        }\n\n        private function getNopicFilename(): string\n        {\n            if (Registry::getConfig()->getConfigParam('blConvertImagesToWebP')) {\n                return 'nopic.webp';\n            }\n\n            return 'nopic.jpg';\n        }\n\n        /**\n         * Returns image type used for image generation and header setting\n         *\n         * @return string\n         */\n        protected function getImageType()\n        {\n            $fileExtension = strtolower(pathinfo($this->getImageName(), PATHINFO_EXTENSION));\n            if (!$this->validateImageFileExtension($fileExtension)) {\n                return false;\n            }\n\n            if ('jpg' == $fileExtension) {\n                $type = 'jpeg';\n            } else {\n                $type = $fileExtension;\n            }\n\n            return $type;\n        }\n\n        /**\n         * Generates PNG type image and returns its location on file system\n         *\n         * @param string $source image source\n         * @param string $target image target\n         * @param int    $width  image width\n         * @param int    $height image height\n         *\n         * @return string\n         */\n        protected function generatePng($source, $target, $width, $height)\n        {\n            return resizePng($source, $target, $width, $height, @getimagesize($source), getGdVersion(), null);\n        }\n\n        /**\n         * Generates JPG type image and returns its location on file system\n         *\n         * @param string $source  image source\n         * @param string $target  image target\n         * @param int    $width   image width\n         * @param int    $height  image height\n         * @param int    $quality new image quality\n         *\n         * @return string\n         */\n        protected function generateJpg($source, $target, $width, $height, $quality)\n        {\n            return resizeJpeg(\n                $source,\n                $target,\n                $width,\n                $height,\n                @getimagesize($source),\n                getGdVersion(),\n                null,\n                $quality\n            );\n        }\n\n        /**\n         * Generates GIF type image and returns its location on file system\n         *\n         * @param string $source image source\n         * @param string $target image target\n         * @param int    $width  image width\n         * @param int    $height image height\n         *\n         * @return string\n         */\n        protected function generateGif($source, $target, $width, $height)\n        {\n            $imageInfo = @getimagesize($source);\n\n            return resizeGif(\n                $source,\n                $target,\n                $width,\n                $height,\n                $imageInfo[0],\n                $imageInfo[1],\n                $this->validateGdVersion()\n            );\n        }\n\n        protected function generateWebp(string $source, string $target, int $width, int $height, int $quality): string\n        {\n            return resizeWebp($source, $target, $width, $height, $quality);\n        }\n\n        /**\n         * Checks if requested image path is valid. If path is valid\n         * but is not created - creates directory structure\n         *\n         * @param string $path image path name to check\n         *\n         * @return bool\n         */\n        protected function isTargetPathValid($path)\n        {\n            $valid = true;\n            $dir = dirname(trim($path));\n\n            // first time folder access?\n            if (!is_dir($dir) && ($valid = $this->isValidPath($dir))) {\n                // creating missing folders\n                $valid = $this->createFolders($dir);\n            }\n\n            return $valid;\n        }\n\n        /**\n         * Checks if valid and creates missing needed folders\n         *\n         * @param string $dir folder(s) to create\n         *\n         * @return bool\n         */\n        protected function createFolders($dir)\n        {\n            $config = Registry::getConfig();\n            $picFolderPath = dirname($config->getMasterPictureDir());\n\n            $done = false;\n            if ($picFolderPath && is_dir($picFolderPath)) {\n                // if its in main path..\n                if (Path::isBasePath($picFolderPath, $dir)) {\n                    // folder does not exist yet?\n                    if (!($done = file_exists($dir))) {\n                        clearstatcache();\n                        // in case creation did not succeed, maybe another process allready created folder?\n                        $mode = 0755;\n                        $done = mkdir($dir, $mode, true) || file_exists($dir);\n                    }\n                }\n            }\n\n            return $done;\n        }\n\n        /**\n         * Checks if main folder matches requested\n         *\n         * @param string $path image path name to check\n         *\n         * @return bool\n         */\n        protected function isValidPath($path)\n        {\n            list($width, $height, $quality) = $this->getImageInfo();\n            if ($width && $height && $quality) {\n                $checkSize = \"$width*$height\";\n\n                $config = Registry::getConfig();\n                $db = DatabaseProvider::getDb();\n\n                $names = [];\n                foreach ($this->resolutionConfigParameters as $paramName) {\n                    $names[] = $db->quote($paramName);\n                }\n                $names = implode(', ', $names);\n\n                // any name matching path?\n                if ($names) {\n                    // selecting shop which image quality matches user given\n                    $q = \"select oxshopid\n                            from oxconfig\n                            where oxvarname = 'sDefaultImageQuality' and\n                            oxvarvalue = :quality\";\n\n                    $shopIdsArray = $db->getAll($q, [\n                        'quality' => $quality\n                    ]);\n\n                    // building query:\n                    // shop id\n                    $shopIds = implode(', ', array_map(function ($shopId) use ($db) {\n                        // probably here we can resolve and check shop id to shorten check?\n                        return $db->quote($shopId['oxshopid']);\n                    }, $shopIdsArray));\n\n                    // any shop matching quality\n                    if ($shopIds) {\n                        // selecting config variables to check\n                        $q = \"select oxvartype, oxvarvalue from oxconfig\n                           where oxvarname in ( {$names} ) and oxshopid in ( {$shopIds} ) order by oxshopid\";\n\n                        $values = $db->getAll($q);\n                        foreach ($values as $value) {\n                            $confValues = (array) $config->decodeValue($value[\"oxvartype\"], $value[\"oxvarvalue\"]);\n                            foreach ($confValues as $confValue) {\n                                if (strcmp($checkSize, $confValue) == 0) {\n                                    return true;\n                                }\n                            }\n                        }\n                    }\n                }\n\n                return $this->isSizeAllowed($checkSize);\n            }\n\n            return false;\n        }\n\n        private function isSizeAllowed(string $checkSize): bool\n        {\n            return in_array(\n                $checkSize,\n                ContainerFacade::getParameter('oxid_esales.theme.media.allowed_image_sizes'),\n                true\n            );\n        }\n\n        /**\n         * Converts a given source image into a target image\n         *\n         * @param string $imageSource File path of the source image\n         * @param string $imageTarget File path of the image to be generated\n         *\n         * @throws StandardException If the path of imageTarget and generated image are not the same\n         *\n         * @return bool|string Return false on failure or file path of the generated image on success\n         */\n        protected function generateImage($imageSource, $imageTarget)\n        {\n            $generatedImagePath = false;\n            list($targetWidth, $targetHeight, $targetQuality) = $this->getImageInfo();\n\n            $fileExtensionSource = strtolower(pathinfo($imageSource, PATHINFO_EXTENSION));\n            $fileExtensionTarget = strtolower(pathinfo($imageTarget, PATHINFO_EXTENSION));\n\n            // Do some validation and return false on failure\n            if (\n                !$this->validateGdVersion()\n                || !$this->validateFileExist($imageSource)\n                || !$this->isTargetPathValid($imageTarget)\n                || !$this->validateImageFileExtension($fileExtensionSource)\n                || !$this->validateImageFileExtension($fileExtensionTarget)\n                || $fileExtensionSource !== $fileExtensionTarget\n            ) {\n                return false;\n            }\n\n            if ($this->validateFileExist($imageTarget)) {\n                list($currentWidth, $currentHeight) = $this->getImageDimensions($imageTarget);\n                if (($currentWidth == $targetWidth) && ($currentHeight == $targetHeight)) {\n                    return $imageTarget;\n                }\n            }\n\n            // including generator files\n            includeImageUtils();\n\n            /**\n             * There may be a different process trying to generate this image at the same moment.\n             * Get a lock in order not to write at the same file at the same time.\n             */\n            if ($this->lock($imageTarget)) {\n                // extracting image info - size/quality\n                switch ($fileExtensionSource) {\n                    case \"png\":\n                        $generatedImagePath = $this->generatePng(\n                            $imageSource,\n                            $imageTarget,\n                            $targetWidth,\n                            $targetHeight\n                        );\n                        break;\n                    case \"jpeg\":\n                    case \"jpg\":\n                        $generatedImagePath = $this->generateJpg(\n                            $imageSource,\n                            $imageTarget,\n                            $targetWidth,\n                            $targetHeight,\n                            $targetQuality\n                        );\n                        break;\n                    case \"gif\":\n                        $generatedImagePath = $this->generateGif(\n                            $imageSource,\n                            $imageTarget,\n                            $targetWidth,\n                            $targetHeight\n                        );\n                        break;\n                    case \"webp\":\n                        $generatedImagePath = $this->generateWebp(\n                            $imageSource,\n                            $imageTarget,\n                            $targetWidth,\n                            $targetHeight,\n                            $targetQuality\n                        );\n                        break;\n                }\n                // target must always be unlocked, no matter what the result of the former image generation was.\n                $this->unlock($imageTarget);\n            }\n            if ($generatedImagePath && $generatedImagePath != $imageTarget) {\n                throw new StandardException('imageTarget path and generatedImage path differ');\n            }\n\n            return $generatedImagePath;\n        }\n\n        /**\n         * Returns lock file name\n         *\n         * @param string $name original file name\n         *\n         * @return string\n         */\n        protected function getLockName($name)\n        {\n            return \"$name.lck\";\n        }\n\n        /**\n         * Locks file and returns locking state\n         *\n         * @param string $source source file which should be locked\n         *\n         * @return bool\n         */\n        protected function lock($source)\n        {\n            $locked = false;\n            $lockName = $this->getLockName($source);\n\n            // creating lock file\n            $this->_hLockHandle = @fopen($lockName, \"w\");\n            if (is_resource($this->_hLockHandle)) {\n                if (!($locked = flock($this->_hLockHandle, LOCK_EX))) {\n                    // on failure - closing\n                    fclose($this->_hLockHandle);\n                    $this->_hLockHandle = null;\n                }\n            }\n\n            // in case system does not support file lockings\n            if (!$locked) {\n                // start a blank file to inform other processes we are dealing with it.\n                if (!(file_exists($lockName) && abs(time() - filectime($lockName) < 40))) {\n                    if ($this->_hLockHandle = @fopen($lockName, \"w\")) {\n                        $locked = true;\n                    }\n                }\n            }\n\n            return $locked;\n        }\n\n        /**\n         * Deletes lock file\n         *\n         * @param string $source source file which should be locked\n         */\n        protected function unlock($source)\n        {\n            if (is_resource($this->_hLockHandle)) {\n                flock($this->_hLockHandle, LOCK_UN);\n                fclose($this->_hLockHandle);\n                $this->_hLockHandle = null;\n                unlink($this->getLockName($source));\n            }\n        }\n\n        /**\n         * Returns the file path of an image as requested by self::_getImageUri().\n         * If the requested image does not exist, if will be rendered from the master image.\n         * If the master image does not exist, a nopic image in the same directory as the requested image is shown.\n         * If the nopic image does not exist, it will be generated in with the same dimensions and quality as the requested\n         * image.\n         * If the nopic image does not exist, the method returns false.\n         *\n         * @param bool $absPath absolute requested image path (not url, but real path on file system)\n         *\n         * @return string|false\n         */\n        public function getImagePath($absPath = false)\n        {\n            if ($absPath) {\n                $this->_sImageUri = str_replace($this->getShopBasePath(), \"\", $absPath);\n            }\n\n            $imagePath = false;\n            $masterPath = $this->getImageMasterPath();\n\n            // building base path + extracting image name + extracting master image path\n            $masterImagePath = $this->getShopBasePath() . $masterPath . $this->getImageName();\n\n            if (!file_exists($masterImagePath)) {\n                $masterImagePath = $this->parseMediaPathFromUrl();\n            }\n\n            if (\n                Registry::getConfig()->getConfigParam('blConvertImagesToWebP') &&\n                !file_exists($masterImagePath)\n            ) {\n                $this->convertImageIfOriginalExists($masterImagePath);\n            }\n\n            if (file_exists($masterImagePath)) {\n                $genImagePath = $this->getImageTarget();\n            } else {\n                // nopic master path\n                $masterImagePath = $this->getShopBasePath() . dirname($masterPath, 2) . \"/\" . $this->getNopicFilename();\n                $genImagePath = $this->getNopicImageTarget();\n\n                // 404 header for nopic\n                $this->setHeader(\"HTTP/1.1 404 Not Found\");\n            }\n\n            // checking if master image is accessible\n            if (file_exists($genImagePath)) {\n                $imagePath = $genImagePath;\n            } elseif (file_exists($masterImagePath)) {\n                // generating image\n                $imagePath = $this->generateImage($masterImagePath, $genImagePath);\n            }\n\n            if ($this->validateFileExist($imagePath)) {\n                // image Content-Type\n                $contentType = mime_content_type($imagePath);\n                $this->setHeader(\"Content-Type: $contentType;\");\n            } else {\n                // unable to output any file\n                $this->setHeader(\"HTTP/1.1 404 Not Found\");\n            }\n\n            return $imagePath;\n        }\n\n        private function convertImageIfOriginalExists(string $desiredFilename): void\n        {\n            $pathParts = pathinfo($desiredFilename);\n            $originalFilename = $pathParts['dirname'] . '/' . $pathParts['filename'];\n\n            $sourceImage = false;\n            switch (pathinfo($originalFilename, PATHINFO_EXTENSION)) {\n                case 'png':\n                    $sourceImage = imagecreatefrompng($originalFilename);\n                    break;\n                case 'jpg':\n                case 'jpeg':\n                    $sourceImage = imagecreatefromjpeg($originalFilename);\n                    break;\n                case 'gif':\n                    $sourceImage = imagecreatefromgif($originalFilename);\n                    break;\n            }\n\n            if ($sourceImage) {\n                $quality = Registry::getConfig()->getConfigParam('sDefaultImageQuality');\n                imagewebp($sourceImage, $desiredFilename, $quality);\n            }\n        }\n\n        /**\n         * Creates and outputs requested image. If source file was not found -\n         * tries to render related \"nopic.jpg\". If \"nopic.jpg\" is not available -\n         * sends 404 header to browser\n         */\n        public function outputImage()\n        {\n            $buffer = true;\n\n            // starting output buffering\n            if ($buffer) {\n                ob_start();\n            }\n\n            //\n            $imgPath = $this->getImagePath();\n\n            // cleaning extra output\n            if ($buffer) {\n                ob_clean();\n            }\n\n            // outputting headers\n            $headers = $this->getHeaders();\n            foreach ($headers as $header) {\n                header($header);\n            }\n\n            // sending headers\n            if ($buffer) {\n                ob_end_flush();\n            }\n\n            // file is generated?\n            if ($imgPath) {\n                // outputting file\n                @readfile($imgPath);\n            }\n        }\n\n        /**\n         * @param string $fileExtension Extension to be validated. Validation is case insensitive.\n         *\n         * @return bool\n         */\n        protected function validateImageFileExtension($fileExtension)\n        {\n            return in_array(strtolower($fileExtension), $this->_aAllowedImgTypes);\n        }\n\n        /**\n         * Custom header setter\n         *\n         * @param string $header header\n         */\n        protected function setHeader($header)\n        {\n            $this->_aHeaders[] = $header;\n        }\n\n        /**\n         * Return headers array\n         *\n         * @return array\n         */\n        protected function getHeaders()\n        {\n            return $this->_aHeaders;\n        }\n\n        /**\n         * Return true, if the version of the gd library is correct\n         *\n         * @return bool\n         */\n        protected function validateGdVersion()\n        {\n            return getGdVersion() !== false;\n        }\n\n        /**\n         * Return true, if a given file path exists.\n         *\n         * @param string $filePath\n         *\n         * @return bool\n         */\n        protected function validateFileExist($filePath)\n        {\n            return file_exists($filePath);\n        }\n\n        /**\n         * Return an array with the dimensions (width x height) of an image file.\n         * returns array (0,0), if the dimensions could not be retrieved.\n         *\n         * @param string $imageFilePath\n         *\n         * @return array\n         */\n        protected function getImageDimensions($imageFilePath)\n        {\n            try {\n                list($width, $height) = getimagesize($imageFilePath);\n                $imageDimensions = [$width, $height];\n            } catch (\\Exception $exception) {\n                $imageDimensions = [0,0];\n            }\n\n            return $imageDimensions;\n        }\n    }\n}\n"
  },
  {
    "path": "source/Core/Email.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse Exception;\nuse OxidEsales\\Eshop\\Application\\Model\\OrderFileList;\nuse OxidEsales\\Eshop\\Core\\DynamicImageGenerator;\nuse OxidEsales\\Eshop\\Core\\Exception\\SystemComponentException;\nuse OxidEsales\\Eshop\\Core\\Field as OxidEsalesField;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Event\\AdminModeChangedEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Email\\EmailAdapterInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Email\\EmailValidatorServiceBridgeInterface;\nuse PHPMailer\\PHPMailer\\PHPMailer;\nuse Psr\\Log\\LoggerInterface;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\nuse Symfony\\Component\\Mailer\\MailerInterface;\nuse Throwable;\n\n/**\n * Mailing manager.\n * Collects mailing configuration, other parameters, performs mailing functions (ordering, registration emails, etc.).\n */\nclass Email extends PHPMailer\n{\n    /**\n     * Default Smtp server port\n     *\n     * @var int\n     */\n    public $smtpPort = 25;\n\n    /**\n     * Password reminder mail template\n     *\n     * @var string\n     */\n    protected $_sForgotPwdTemplate = \"email/html/forgotpwd\";\n\n    /**\n     * Password reminder plain mail template\n     *\n     * @var string\n     */\n    protected $_sForgotPwdTemplatePlain = \"email/plain/forgotpwd\";\n\n    /**\n     * Newsletter registration mail template\n     *\n     * @var string\n     */\n    protected $_sNewsletterOptInTemplate = \"email/html/newsletteroptin\";\n\n    /**\n     * Newsletter registration plain mail template\n     *\n     * @var string\n     */\n    protected $_sNewsletterOptInTemplatePlain = \"email/plain/newsletteroptin\";\n\n    /**\n     * Product suggest mail template\n     *\n     * @var string\n     */\n    protected $_sInviteTemplate = \"email/html/invite\";\n\n    /**\n     * Product suggest plain mail template\n     *\n     * @var string\n     */\n    protected $_sInviteTemplatePlain = \"email/plain/invite\";\n\n    /**\n     * Send order notification mail template\n     *\n     * @var string\n     */\n    protected $_sSenedNowTemplate = \"email/html/ordershipped\";\n\n    /**\n     * Send order notification plain mail template\n     *\n     * @var string\n     */\n    protected $_sSenedNowTemplatePlain = \"email/plain/ordershipped\";\n\n    /**\n     * Send ordered download links mail template\n     *\n     * @var string\n     */\n    protected $_sSendDownloadsTemplate = \"email/html/senddownloadlinks\";\n\n    /**\n     * Send ordered download links plain mail template\n     *\n     * @var string\n     */\n    protected $_sSendDownloadsTemplatePlain = \"email/plain/senddownloadlinks\";\n\n    /**\n     * Wishlist mail template\n     *\n     * @var string\n     */\n    protected $_sWishListTemplate = \"email/html/wishlist\";\n\n    /**\n     * Wishlist plain mail template\n     *\n     * @var string\n     */\n    protected $_sWishListTemplatePlain = \"email/plain/wishlist\";\n\n    /**\n     * Name of template used during registration\n     *\n     * @var string\n     */\n    protected $_sRegisterTemplate = \"email/html/register\";\n\n    /**\n     * Name of plain template used during registration\n     *\n     * @var string\n     */\n    protected $_sRegisterTemplatePlain = \"email/plain/register\";\n\n    /**\n     * Name of template used by reminder function (article).\n     *\n     * @var string\n     */\n    protected $_sReminderMailTemplate = \"email/html/owner_reminder\";\n\n    /**\n     * Order e-mail for customer HTML template\n     *\n     * @var string\n     */\n    protected $_sOrderUserTemplate = \"email/html/order_cust\";\n\n    /**\n     * Order e-mail for customer plain text template\n     *\n     * @var string\n     */\n    protected $_sOrderUserPlainTemplate = \"email/plain/order_cust\";\n\n    /**\n     * Order e-mail for shop owner HTML template\n     *\n     * @var string\n     */\n    protected $_sOrderOwnerTemplate = \"email/html/order_owner\";\n\n    /**\n     * Order e-mail for shop owner plain text template\n     *\n     * @var string\n     */\n    protected $_sOrderOwnerPlainTemplate = \"email/plain/order_owner\";\n\n    // #586A - additional templates for more customizable subjects\n\n    /**\n     * Order e-mail subject for customer template\n     *\n     * @var string\n     */\n    protected $_sOrderUserSubjectTemplate = \"email/html/order_cust_subj\";\n\n    /**\n     * Order e-mail subject for shop owner template\n     *\n     * @var string\n     */\n    protected $_sOrderOwnerSubjectTemplate = \"email/html/order_owner_subj\";\n\n    /**\n     * Price alarm e-mail for shop owner template\n     *\n     * @var string\n     */\n    protected $_sOwnerPricealarmTemplate = \"email/html/pricealarm_owner\";\n\n    /**\n     * Price alarm e-mail for shop owner template\n     *\n     * @var string\n     */\n    protected $_sPricealamrCustomerTemplate = \"email_pricealarm_customer\";\n\n    /**\n     * Language specific viewconfig object array containing view data, view config and shop object\n     *\n     * @var array\n     */\n    protected $_aShops = [];\n\n    /**\n     * Add inline images to mail\n     *\n     * @var bool\n     */\n    protected $_blInlineImgEmail = null;\n\n    /**\n     * Array of recipient email addresses\n     *\n     * @var array\n     */\n    protected $_aRecipients = [];\n\n    /**\n     * Array of reply addresses used\n     *\n     * @var array\n     */\n    protected $_aReplies = [];\n\n    /**\n     * Email view data\n     *\n     * @var array\n     */\n    protected $_aViewData = [];\n\n    /**\n     * Shop object\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Shop\n     */\n    protected $_oShop = null;\n\n    /**\n     * Email charset\n     *\n     * @var string\n     */\n    protected $_sCharSet = null;\n\n    public function __construct()\n    {\n        parent::__construct(true);\n\n        $myConfig = Registry::getConfig();\n\n        $this->setSmtp();\n        $this::$validator = static function ($email) {\n            return filter_var($email, FILTER_VALIDATE_EMAIL, FILTER_FLAG_EMAIL_UNICODE) !== false;\n        };\n\n        $this->setUseInlineImages($myConfig->getConfigParam('blInlineImgEmail'));\n        $this->setMailWordWrap(100);\n\n        $this->isHTML();\n\n        $this->setViewData('oEmailView', $this);\n        $this->setViewData('shopUrl', $myConfig->getShopUrl());\n        $this->setViewData('shopUrlWithLangAndSubshop', $myConfig->getShopUrl(null, false));\n\n        $this->Encoding = 'base64';\n    }\n\n    /**\n     * Only used for convenience in UNIT tests by doing so we avoid\n     * writing extended classes for testing protected or private methods\n     *\n     * @param string $method Methods name\n     * @param array  $arguments Argument array\n     * @return false|mixed\n     * @throws SystemComponentException\n     */\n    public function __call($method, $arguments)\n    {\n        if (method_exists($this, $method)) {\n            return call_user_func_array([&$this, $method], $arguments);\n        }\n        throw new SystemComponentException(\n            \"Function '$method' does not exist or is not accessible! (\" . get_class($this) . \")\" . PHP_EOL\n        );\n    }\n\n    protected function getRenderer()\n    {\n        return ContainerFacade::get(TemplateRendererBridgeInterface::class)\n            ->getTemplateRenderer();\n    }\n\n    /**\n     * Outputs email fields thought email output processor, includes images, and initiate email sending\n     * If fails to send mail via SMTP, tries to send via mail(). On failing to send, sends mail to\n     * shop administrator about failing mail sending\n     *\n     * @return bool\n     */\n    public function send()\n    {\n        if (count($this->getRecipient()) < 1) {\n            return false;\n        }\n\n        $myConfig = Registry::getConfig();\n        $this->setCharSet();\n\n        if ($this->getUseInlineImages()) {\n            $this->includeImages(\n                $myConfig->getImageUrl(),\n                $myConfig->getImageUrl(false, false),\n                $myConfig->getPictureUrl(null, false),\n                $myConfig->getImageDir(),\n                $myConfig->getPictureDir(false)\n            );\n        }\n\n        $this->makeOutputProcessing();\n\n        if ($this->isSymfonyMailerEnabled()) {\n            try {\n                $symfonyEmail = ContainerFacade::get(EmailAdapterInterface::class)->convertToSymfonyEmail($this);\n                ContainerFacade::get(MailerInterface::class)->send($symfonyEmail);\n\n                return true;\n            } catch (Throwable $e) {\n                ContainerFacade::get(LoggerInterface::class)\n                    ->error('Mailer failed, falling back to PHPMailer: ' . $e->getMessage(), [$e]);\n            }\n        }\n\n        if ($this->getMailer() == 'smtp') {\n            $ret = $this->sendMail();\n\n            if (!$ret) {\n                $this->sendMailErrorMsg();\n\n                $this->setMailer('mail');\n                $ret = $this->sendMail();\n            }\n        } else {\n            $this->setMailer('mail');\n            $ret = $this->sendMail();\n        }\n\n        if (!$ret) {\n            $this->sendMailErrorMsg();\n        }\n\n        return $ret;\n    }\n\n    /**\n     * Sets smtp parameters depending on the protocol used\n     * returns smtp url which should be used for fsockopen\n     *\n     * @param string $url initial smtp\n     *\n     * @return string\n     */\n    protected function setSmtpProtocol($url)\n    {\n        $protocol = '';\n        $smtpHost = $url;\n        $match = [];\n        if (Str::getStr()->preg_match('@^([0-9a-z]+://)?(.*)$@i', $url, $match)) {\n            if ($match[1]) {\n                if (($match[1] == 'ssl://') || ($match[1] == 'tls://')) {\n                    $this->set(\"SMTPSecure\", substr($match[1], 0, 3));\n                } else {\n                    $protocol = $match[1];\n                }\n            }\n            $smtpHost = $match[2];\n        }\n\n        return $protocol . $smtpHost;\n    }\n\n    /**\n     * Sets SMTP mailer parameters, such as user name, password, location.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Shop $shop Object, that keeps base shop info\n     *\n     * @return null\n     */\n    public function setSmtp($shop = null)\n    {\n        $shop = ($shop) ?: $this->getShop();\n\n        $smtpUrl = $this->setSmtpProtocol($shop->oxshops__oxsmtp->value);\n\n        if (!$this->isValidSmtpHost($smtpUrl)) {\n            $this->setMailer(\"mail\");\n\n            return;\n        }\n\n        $this->setHost($smtpUrl);\n        $this->setMailer(\"smtp\");\n\n        if ($shop->oxshops__oxsmtpuser->value) {\n            $this->setSmtpAuthInfo($shop->oxshops__oxsmtpuser->value, $shop->oxshops__oxsmtppwd->getRawValue());\n        }\n\n        if (ContainerFacade::getParameter('oxid_esales.smtp_debug_mode')) {\n            $this->setSmtpDebug(true);\n        }\n    }\n\n    /**\n     * Checks if smtp host is valid (tries to connect to it)\n     *\n     * @param string $smtpHost currently used smtp server host name\n     *\n     * @return bool\n     */\n    protected function isValidSmtpHost($smtpHost)\n    {\n        $isSmtp = false;\n        if ($smtpHost) {\n            $match = [];\n            $smtpPort = $this->smtpPort;\n            if (Str::getStr()->preg_match('@^(.*?)(:([0-9]+))?$@i', $smtpHost, $match)) {\n                $smtpHost = $match[1];\n                if (isset($match[3]) && (int) $match[3] !== 0) {\n                    $smtpPort = (int) $match[3];\n                }\n            }\n            if ($isSmtp = (bool) ($rHandle = @fsockopen($smtpHost, $smtpPort, $errNo, $errStr, 30))) {\n                fclose($rHandle);\n            }\n        }\n\n        return $isSmtp;\n    }\n\n    /**\n     * Sets mailer additional settings and sends ordering mail to user.\n     * Returns true on success.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Order $order   Order object\n     * @param string                                    $subject user defined subject [optional]\n     *\n     * @return bool\n     */\n    public function sendOrderEmailToUser($order, $subject = null)\n    {\n        if ($this->areOrderEmailsDisabled()) {\n            ContainerFacade::get(LoggerInterface::class)\n                ->notice('Order email not sent to user due to disabled configuration option');\n            return true;\n        }\n        $order = $this->addUserInfoOrderEMail($order);\n\n        $shop = $this->getShop();\n        $this->setMailParams($shop);\n\n        $user = $order->getOrderUser();\n        $this->setUser($user);\n\n        $renderer = $this->getRenderer();\n        $this->setViewData(\"order\", $order);\n\n        $this->setViewData(\"blShowReviewLink\", $this->shouldProductReviewLinksBeIncluded());\n\n        $this->processViewArray();\n\n        $this->setBody($renderer->renderTemplate($this->_sOrderUserTemplate, $this->getViewData()));\n        $this->setAltBody($renderer->renderTemplate($this->_sOrderUserPlainTemplate, $this->getViewData()));\n\n        // #586A\n        if ($subject === null) {\n            if ($renderer->exists($this->_sOrderUserSubjectTemplate)) {\n                $subject = $renderer->renderTemplate($this->_sOrderUserSubjectTemplate, $this->getViewData());\n            } else {\n                $subject = $shop->oxshops__oxordersubject->getRawValue()\n                    . \" (#\" . $order->oxorder__oxordernr->value . \")\";\n            }\n        }\n\n        $this->setSubject($subject);\n\n        $fullName = $user->oxuser__oxfname->getRawValue() . \" \" . $user->oxuser__oxlname->getRawValue();\n\n        $this->setRecipient($user->oxuser__oxusername->value, $fullName);\n        $this->setReplyTo($shop->oxshops__oxorderemail->value, $shop->oxshops__oxname->getRawValue());\n\n        return $this->send();\n    }\n\n    /**\n     * Sets mailer additional settings and sends ordering mail to shop owner.\n     * Returns true on success.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Order $order   Order object\n     * @param string                                    $subject user defined subject [optional]\n     *\n     * @return bool\n     */\n    public function sendOrderEmailToOwner($order, $subject = null)\n    {\n        if ($this->areOrderEmailsDisabled()) {\n            ContainerFacade::get(LoggerInterface::class)\n                ->notice('Order email not sent to owner due to disabled configuration option');\n            return true;\n        }\n        $config = Registry::getConfig();\n\n        $shop = $this->getShop();\n\n        $this->clearMailer();\n\n        $order = $this->addUserInfoOrderEMail($order);\n\n        $user = $order->getOrderUser();\n        $this->setUser($user);\n\n        $this->setFrom($shop->oxshops__oxowneremail->value);\n\n        $language = Registry::getLang();\n        $orderLanguage = $language->getObjectTplLanguage();\n\n        if ($shop->getLanguage() != $orderLanguage) {\n            $shop = $this->getShop($orderLanguage);\n        }\n\n        $this->setSmtp($shop);\n\n        $renderer = $this->getRenderer();\n        $this->setViewData(\"order\", $order);\n\n        $this->processViewArray();\n\n        $this->setBody($renderer->renderTemplate($this->_sOrderOwnerTemplate, $this->getViewData()));\n        $this->setAltBody($renderer->renderTemplate($this->_sOrderOwnerPlainTemplate, $this->getViewData()));\n\n        // #586A\n        if ($subject === null) {\n            if ($renderer->exists($this->_sOrderOwnerSubjectTemplate)) {\n                $subject = $renderer->renderTemplate($this->_sOrderOwnerSubjectTemplate, $this->getViewData());\n            } else {\n                $subject = $shop->oxshops__oxordersubject->getRawValue()\n                    . \" (#\" . $order->oxorder__oxordernr->value . \")\";\n            }\n        }\n\n        $this->setSubject($subject);\n        $this->setRecipient($shop->oxshops__oxowneremail->value, $language->translateString(\"order\"));\n\n        if ($user->oxuser__oxusername->value != \"admin\") {\n            $fullName = $user->oxuser__oxfname->getRawValue() . \" \" . $user->oxuser__oxlname->getRawValue();\n            $this->setReplyTo($user->oxuser__oxusername->value, $fullName);\n        }\n\n        $result = $this->send();\n\n        $this->onOrderEmailToOwnerSent($user, $order);\n\n        return $result;\n    }\n\n    /**\n     * Method is called when order email is sent to owner.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User  $user\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Order $order\n     */\n    protected function onOrderEmailToOwnerSent($user, $order)\n    {\n        $remark = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Remark::class);\n        $remark->oxremark__oxtext = new OxidEsalesField($this->getAltBody(), OxidEsalesField::T_RAW);\n        $remark->oxremark__oxparentid = new OxidEsalesField($user->getId(), OxidEsalesField::T_RAW);\n        $remark->oxremark__oxtype = new OxidEsalesField(\"o\", OxidEsalesField::T_RAW);\n        $remark->save();\n    }\n\n    /**\n     * Sets mailer additional settings and sends registration mail to user.\n     * Returns true on success.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $user    user object\n     * @param string                                   $subject user defined subject [optional]\n     *\n     * @return bool\n     */\n    public function sendRegisterConfirmEmail($user, $subject = null)\n    {\n        $this->setViewData(\"contentident\", \"oxregisteraltemail\");\n        $this->setViewData(\"contentplainident\", \"oxregisterplainaltemail\");\n\n        return $this->sendRegisterEmail($user, $subject);\n    }\n\n    /**\n     * Sets mailer additional settings and sends registration mail to user.\n     * Returns true on success.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $user    user object\n     * @param string                                   $subject user defined subject [optional]\n     *\n     * @return bool\n     */\n    public function sendRegisterEmail($user, $subject = null)\n    {\n        $user = $this->addUserRegisterEmail($user);\n\n        $shop = $this->getShop();\n\n        $this->setMailParams($shop);\n\n        $renderer = $this->getRenderer();\n        $this->setUser($user);\n\n        $this->processViewArray();\n\n        $this->setBody($renderer->renderTemplate($this->_sRegisterTemplate, $this->getViewData()));\n        $this->setAltBody($renderer->renderTemplate($this->_sRegisterTemplatePlain, $this->getViewData()));\n\n        $this->setSubject(($subject !== null) ? $subject : $shop->oxshops__oxregistersubject->getRawValue());\n\n        $fullName = $user->oxuser__oxfname->getRawValue() . \" \" . $user->oxuser__oxlname->getRawValue();\n\n        $this->setRecipient($user->oxuser__oxusername->value, $fullName);\n        $this->setReplyTo($shop->oxshops__oxorderemail->value, $shop->oxshops__oxname->getRawValue());\n\n        return $this->send();\n    }\n\n    /**\n     * Sets mailer additional settings and sends \"forgot password\" mail to user.\n     * Returns true on success.\n     *\n     * @param string $emailAddress user email address\n     * @param string $subject      user defined subject [optional]\n     *\n     * @return mixed true - success, false - user not found, -1 - could not send\n     */\n    public function sendForgotPwdEmail($emailAddress, $subject = null)\n    {\n        $result = false;\n        $shop = $this->addForgotPwdEmail($this->getShop());\n        $oxid = $this->getUserIdByUserName($emailAddress, $shop->getId());\n        $user = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n        if ($oxid && $user->load($oxid)) {\n            $renderer = $this->getRenderer();\n            $this->setUser($user);\n            $this->processViewArray();\n\n            $this->setMailParams($shop);\n            $this->setBody($renderer->renderTemplate($this->_sForgotPwdTemplate, $this->getViewData()));\n            $this->setAltBody($renderer->renderTemplate($this->_sForgotPwdTemplatePlain, $this->getViewData()));\n            $this->setSubject(($subject !== null) ? $subject : $shop->oxshops__oxforgotpwdsubject->getRawValue());\n\n            $fullName = $user->oxuser__oxfname->getRawValue() . \" \" . $user->oxuser__oxlname->getRawValue();\n            $recipientAddress = $user->oxuser__oxusername->getRawValue();\n\n            $this->setRecipient($recipientAddress, $fullName);\n            $this->setReplyTo($shop->oxshops__oxorderemail->value, $shop->oxshops__oxname->getRawValue());\n\n            if (!$this->send()) {\n                $result = -1;\n            } else {\n                $result = true;\n            }\n        }\n\n        return $result;\n    }\n\n    /**\n     * Sets mailer additional settings and sends contact info mail to user.\n     * Returns true on success.\n     *\n     * @param string $emailAddress Email address\n     * @param string $subject      Email subject\n     * @param string $message      Email message text\n     *\n     * @return bool\n     */\n    public function sendContactMail($emailAddress = null, $subject = null, $message = null)\n    {\n        $shop = $this->getShop();\n\n        $this->setMailParams($shop);\n\n        $this->setBody($message);\n        $this->setSubject($subject);\n\n        $this->setRecipient($shop->oxshops__oxinfoemail->value, \"\");\n        $this->setFrom($shop->oxshops__oxowneremail->value, $shop->oxshops__oxname->getRawValue());\n        $this->setReplyTo($emailAddress, \"\");\n\n        return $this->send();\n    }\n\n    /**\n     * Sets mailer additional settings and sends \"NewsletterDBOptInMail\" mail to user.\n     * Returns true on success.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $user    user object\n     * @param string                                   $subject user defined subject [optional]\n     *\n     * @return bool\n     */\n    public function sendNewsletterDbOptInMail($user, $subject = null)\n    {\n        $user = $this->addNewsletterDbOptInMail($user);\n\n        // shop info\n        $shop = $this->getShop();\n\n        $this->setMailParams($shop);\n\n        $renderer = $this->getRenderer();\n        $confirmCode = md5($user->oxuser__oxusername->value . $user->oxuser__oxpasssalt->value);\n        $this->setViewData(\"subscribeLink\", $this->getNewsSubsLink($user->oxuser__oxid->value, $confirmCode));\n        $this->setUser($user);\n\n        $this->processViewArray();\n\n        $this->setBody($renderer->renderTemplate($this->_sNewsletterOptInTemplate, $this->getViewData()));\n        $this->setAltBody($renderer->renderTemplate($this->_sNewsletterOptInTemplatePlain, $this->getViewData()));\n        $this->setSubject(\n            ($subject !== null)\n                ? $subject\n                : Registry::getLang()->translateString(\"NEWSLETTER\")\n                . ' '\n                . $shop->oxshops__oxname->getRawValue()\n        );\n\n        $fullName = $user->oxuser__oxfname->getRawValue() . \" \" . $user->oxuser__oxlname->getRawValue();\n\n        $this->setRecipient($user->oxuser__oxusername->value, $fullName);\n        $this->setFrom($shop->oxshops__oxinfoemail->value, $shop->oxshops__oxname->getRawValue());\n        $this->setReplyTo($shop->oxshops__oxinfoemail->value, $shop->oxshops__oxname->getRawValue());\n\n        return $this->send();\n    }\n\n    protected function getNewsSubsLink($id, $confirmCode = null)\n    {\n        $myConfig = Registry::getConfig();\n        $actShopLang = $myConfig->getActiveShop()->getLanguage();\n\n        $url = $myConfig->getShopHomeUrl() . 'cl=newsletter&amp;fnc=addme&amp;uid=' . $id;\n        $url .= '&amp;lang=' . $actShopLang;\n        $url .= ($confirmCode) ? '&amp;confirm=' . $confirmCode : \"\";\n\n        return $url;\n    }\n\n    /**\n     * Sets mailer additional settings and sends \"InviteMail\" mail to user.\n     * Returns true on success.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User $user Mailing parameters object\n     *\n     * @return bool\n     */\n    public function sendInviteMail($user)\n    {\n        $myConfig = Registry::getConfig();\n\n        $currLang = $myConfig->getActiveShop()->getLanguage();\n\n        $shop = $this->getShop($currLang);\n\n        $this->setFrom($user->send_email, $user->send_name);\n        $this->setSmtp();\n\n        /** @var TemplateRendererInterface $renderer */\n        $renderer = ContainerFacade::get(TemplateRendererBridgeInterface::class)\n            ->getTemplateRenderer();\n        $this->setUser($user);\n\n        $homeUrl = $this->getViewConfig()->getHomeLink();\n\n        if ($myConfig->getActiveView()->isActive('Invitations') && $activeUser = $shop->getUser()) {\n            $homeUrl = Registry::getUtilsUrl()->appendParamSeparator($homeUrl);\n            $homeUrl .= \"su=\" . $activeUser->getId();\n        }\n\n        if (is_array($user->rec_email) && count($user->rec_email) > 0) {\n            foreach ($user->rec_email as $email) {\n                if (!empty($email)) {\n                    $registerUrl = Registry::getUtilsUrl()->appendParamSeparator($homeUrl);\n                    $registerUrl .= \"re=\" . md5($email);\n                    $this->setViewData(\"sHomeUrl\", $registerUrl);\n\n                    // Process view data array through oxoutput processor\n                    $this->processViewArray();\n\n                    $this->setBody($renderer->renderTemplate($this->_sInviteTemplate, $this->getViewData()));\n\n                    $this->setAltBody($renderer->renderTemplate($this->_sInviteTemplatePlain, $this->getViewData()));\n                    $this->setSubject($user->send_subject);\n\n                    $this->setRecipient($email);\n                    $this->setReplyTo($user->send_email, $user->send_name);\n                    $this->send();\n                    $this->clearAllRecipients();\n                }\n            }\n\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Sets mailer additional settings and sends \"SendedNowMail\" mail to user.\n     * Returns true on success.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Order $order   order object\n     * @param string                                    $subject user defined subject [optional]\n     *\n     * @return bool\n     */\n    public function sendSendedNowMail($order, $subject = null)\n    {\n        $myConfig = Registry::getConfig();\n\n        $orderLang = (int) (isset($order->oxorder__oxlang->value) ? $order->oxorder__oxlang->value : 0);\n\n        $shop = $this->getShop($orderLang);\n\n        $this->setMailParams($shop);\n\n        $lang = Registry::getLang();\n        $renderer = $this->getRenderer();\n        $this->setViewData(\"order\", $order);\n        $this->setViewData(\"shopTemplateDir\", $myConfig->getTemplateDir(false));\n\n        if ($myConfig->getConfigParam('bl_perfLoadReviews', false)) {\n            $this->setViewData(\"blShowReviewLink\", true);\n            $user = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n            $this->setViewData(\"reviewuserhash\", $user->getReviewUserHash($order->oxorder__oxuserid->value));\n        } else {\n            $this->setViewData(\"blShowReviewLink\", false);\n        }\n\n        $this->processViewArray();\n\n        $oldTplLang = $lang->getTplLanguage();\n        $oldBaseLang = $lang->getBaseLanguage();\n        $lang->setTplLanguage($orderLang);\n        $lang->setBaseLanguage($orderLang);\n\n        $this->switchToShopMode();\n        $this->setBody($renderer->renderTemplate($this->_sSenedNowTemplate, $this->getViewData()));\n        $this->setAltBody($renderer->renderTemplate($this->_sSenedNowTemplatePlain, $this->getViewData()));\n        $this->switchToAdminMode();\n        $lang->setTplLanguage($oldTplLang);\n        $lang->setBaseLanguage($oldBaseLang);\n\n        $this->setSubject(($subject !== null) ? $subject : $shop->oxshops__oxsendednowsubject->getRawValue());\n\n        $fullName = $order->oxorder__oxbillfname->getRawValue() . \" \" . $order->oxorder__oxbilllname->getRawValue();\n\n        $this->setRecipient($order->oxorder__oxbillemail->value, $fullName);\n        $this->setReplyTo($shop->oxshops__oxorderemail->value, $shop->oxshops__oxname->getRawValue());\n\n        return $this->send();\n    }\n\n    /**\n     * Sets mailer additional settings and sends \"SendDownloadLinks\" mail to user.\n     * Returns true on success.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Order $order   order object\n     * @param string                                    $subject user defined subject [optional]\n     *\n     * @return bool\n     */\n    public function sendDownloadLinksMail($order, $subject = null)\n    {\n        $myConfig = Registry::getConfig();\n\n        $orderLang = (int) (isset($order->oxorder__oxlang->value) ? $order->oxorder__oxlang->value : 0);\n\n        $shop = $this->getShop($orderLang);\n\n        $this->setMailParams($shop);\n\n        $lang = Registry::getLang();\n        $renderer = $this->getRenderer();\n        $this->setViewData(\"order\", $order);\n        $this->setViewData(\"shopTemplateDir\", $myConfig->getTemplateDir(false));\n\n        $user = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n        $this->setViewData(\"reviewuserhash\", $user->getReviewUserHash($order->oxorder__oxuserid->value));\n\n        $this->processViewArray();\n\n        $oldTplLang = $lang->getTplLanguage();\n        $oldBaseLang = $lang->getTplLanguage();\n        $lang->setTplLanguage($orderLang);\n        $lang->setBaseLanguage($orderLang);\n\n        $this->switchToShopMode();\n        $this->setBody($renderer->renderTemplate($this->_sSendDownloadsTemplate, $this->getViewData()));\n        $this->setAltBody($renderer->renderTemplate($this->_sSendDownloadsTemplatePlain, $this->getViewData()));\n        $this->switchToAdminMode();\n        $lang->setTplLanguage($oldTplLang);\n        $lang->setBaseLanguage($oldBaseLang);\n\n        $this->setSubject(($subject !== null) ? $subject : $lang->translateString(\"DOWNLOAD_LINKS\", null, false));\n\n        $fullName = $order->oxorder__oxbillfname->getRawValue() . \" \" . $order->oxorder__oxbilllname->getRawValue();\n\n        $this->setRecipient($order->oxorder__oxbillemail->value, $fullName);\n        $this->setReplyTo($shop->oxshops__oxorderemail->value, $shop->oxshops__oxname->getRawValue());\n\n        return $this->send();\n    }\n\n    /**\n     * Basic wrapper for email message sending with default parameters from the oxBaseShop.\n     * Returns true on success.\n     *\n     * @param mixed  $to      Recipient or an array of the recipients\n     * @param string $subject Mail subject\n     * @param string $body    Mail body\n     *\n     * @return bool\n     */\n    public function sendEmail($to, $subject, $body)\n    {\n        $this->setMailParams();\n\n        if (is_array($to)) {\n            foreach ($to as $address) {\n                $this->setRecipient($address, \"\");\n                $this->setReplyTo($address, \"\");\n            }\n        } else {\n            $this->setRecipient($to, \"\");\n            $this->setReplyTo($to, \"\");\n        }\n\n        $this->isHTML(false);\n\n        $this->setSubject($subject);\n        $this->setBody($body);\n\n        return $this->send();\n    }\n\n    /**\n     * Sends reminder email to shop owner.\n     *\n     * @param array  $basketContents array of objects to pass to template\n     * @param string $subject        user defined subject [optional]\n     *\n     * @return bool\n     */\n    public function sendStockReminder($basketContents, $subject = null)\n    {\n        $send = false;\n\n        $articleList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class);\n        $articleList->loadStockRemindProducts($basketContents);\n\n        // nothing to remind?\n        if ($articleList->count()) {\n            $shop = $this->getShop();\n\n            $this->setMailParams($shop);\n            $lang = Registry::getLang();\n\n            $renderer = $this->getRenderer();\n            $this->setViewData(\"articles\", $articleList);\n\n            $this->processViewArray();\n\n            $this->setRecipient($shop->oxshops__oxowneremail->value, $shop->oxshops__oxname->getRawValue());\n            $this->setFrom($shop->oxshops__oxowneremail->value, $shop->oxshops__oxname->getRawValue());\n            $this->setBody($renderer->renderTemplate($this->_sReminderMailTemplate, $this->getViewData()));\n            $this->setAltBody(\"\");\n            $this->setSubject(($subject !== null) ? $subject : $lang->translateString('STOCK_LOW'));\n\n            $send = $this->send();\n        }\n\n        return $send;\n    }\n\n    /**\n     * Sets mailer additional settings and sends \"WishlistMail\" mail to user.\n     * Returns true on success.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\User|object $params Mailing parameters object\n     *\n     * @return bool\n     */\n    public function sendWishlistMail($params)\n    {\n        $this->clearMailer();\n\n        $this->setFrom($params->send_email, $params->send_name);\n        $this->setSmtp();\n\n        $renderer = $this->getRenderer();\n        $this->setUser($params);\n\n        $this->processViewArray();\n\n        $this->setBody($renderer->renderTemplate($this->_sWishListTemplate, $this->getViewData()));\n        $this->setAltBody($renderer->renderTemplate($this->_sWishListTemplatePlain, $this->getViewData()));\n        $this->setSubject($params->send_subject);\n\n        $this->setRecipient($params->rec_email, $params->rec_name);\n        $this->setReplyTo($params->send_email, $params->send_name);\n\n        return $this->send();\n    }\n\n    /**\n     * Sends a notification to the shop owner that price alarm was subscribed.\n     * Returns true on success.\n     *\n     * @param array                                          $params  Parameters array\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\PriceAlarm $alarm   oxPriceAlarm object\n     * @param string                                         $subject user defined subject [optional]\n     *\n     * @return bool\n     */\n    public function sendPriceAlarmNotification($params, $alarm, $subject = null)\n    {\n        $this->clearMailer();\n        $shop = $this->getShop();\n\n        $this->setMailParams($shop);\n\n        $alarmLang = $alarm->oxpricealarm__oxlang->value;\n\n        $article = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $article->loadInLang($alarmLang, $params['aid']);\n        $lang = Registry::getLang();\n\n        $renderer = $this->getRenderer();\n        $this->setViewData(\"product\", $article);\n        $this->setViewData(\"email\", $params['email']);\n        $this->setViewData(\"bidprice\", $lang->formatCurrency($alarm->oxpricealarm__oxprice->value));\n\n        $this->processViewArray();\n\n        $this->setRecipient($shop->oxshops__oxorderemail->value, $shop->oxshops__oxname->getRawValue());\n        $this->setSubject(\n            ($subject !== null)\n                ? $subject\n                : $lang->translateString('PRICE_ALERT_FOR_PRODUCT', $alarmLang)\n                . ' '\n                . $article->oxarticles__oxtitle->getRawValue()\n        );\n        $this->setBody($renderer->renderTemplate($this->_sOwnerPricealarmTemplate, $this->getViewData()));\n        $this->setFrom($params['email'], \"\");\n        $this->setReplyTo($params['email'], \"\");\n\n        return $this->send();\n    }\n\n    public function sendPricealarmToCustomer($recipient, $alarm, $body = null, $returnMailBody = null)\n    {\n        $this->clearMailer();\n\n        $shop = $this->getShop();\n\n        if ($shop->getId() != $alarm->oxpricealarm__oxshopid->value) {\n            $shop = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Shop::class);\n            $shop->load($alarm->oxpricealarm__oxshopid->value);\n            $this->setShop($shop);\n        }\n\n        $this->setMailParams($shop);\n\n        $renderer = $this->getRenderer();\n\n        $this->setViewData(\"product\", $alarm->getArticle());\n        $this->setViewData(\"oPriceAlarm\", $alarm);\n        $this->setViewData(\"bidprice\", $alarm->getFProposedPrice());\n        $this->setViewData(\"currency\", $alarm->getPriceAlarmCurrency());\n\n        $this->processViewArray();\n\n        $this->setRecipient($recipient, $recipient);\n        $this->setSubject($shop->oxshops__oxname->value);\n\n        if ($body === null) {\n            $body = $renderer->renderTemplate($this->_sPricealamrCustomerTemplate, $this->getViewData());\n        }\n        $this->setBody($body);\n\n        $this->addAddress($recipient, $recipient);\n        $this->setReplyTo($shop->oxshops__oxorderemail->value, $shop->oxshops__oxname->getRawValue());\n\n        if ($returnMailBody) {\n            return $this->getBody();\n        } else {\n            return $this->send();\n        }\n    }\n\n    protected function includeImages(\n        $imageDir = null,\n        $imageDirNoSSL = null,\n        $dynImageDir = null,\n        $absImageDir = null,\n        $absDynImageDir = null\n    ) {\n        $body = $this->getBody();\n        if (\n            preg_match_all(\n                '/<\\s*img\\s+[^>]*?src[\\s]*=[\\s]*[\\'\"]?([^[\\'\">]]+|.*?)?[\\'\">]/i',\n                $body,\n                $matches,\n                PREG_SET_ORDER\n            )\n        ) {\n            $fileUtils = Registry::getUtilsFile();\n            $reSetBody = false;\n\n            // preparing input\n            $dynImageDir = $fileUtils->normalizeDir($dynImageDir);\n            $imageDir = $fileUtils->normalizeDir($imageDir);\n            $imageDirNoSSL = $fileUtils->normalizeDir($imageDirNoSSL);\n\n            if (is_array($matches) && count($matches)) {\n                $imageCache = [];\n                $myUtils = Registry::getUtils();\n                $myUtilsObject = $this->getUtilsObjectInstance();\n                $imgGenerator = oxNew(DynamicImageGenerator::class);\n\n                foreach ($matches as $image) {\n                    $imageName = $image[1];\n                    $fileName = '';\n                    if (is_string($dynImageDir) && str_starts_with($imageName, $dynImageDir)) {\n                        $fileName = $fileUtils->normalizeDir($absDynImageDir)\n                            . str_replace($dynImageDir, '', $imageName);\n                    } elseif (str_starts_with($imageName, $imageDir)) {\n                        $fileName = $fileUtils->normalizeDir($absImageDir) . str_replace($imageDir, '', $imageName);\n                    } elseif (str_starts_with($imageName, $imageDirNoSSL)) {\n                        $fileName = $fileUtils->normalizeDir($absImageDir)\n                            . str_replace($imageDirNoSSL, '', $imageName);\n                    }\n\n                    if ($fileName && !@is_readable($fileName)) {\n                        $fileName = $imgGenerator->getImagePath($fileName);\n                    }\n\n                    if ($fileName) {\n                        if (isset($imageCache[$fileName]) && $imageCache[$fileName]) {\n                            $cId = $imageCache[$fileName];\n                        } else {\n                            $cId = $myUtilsObject->generateUId();\n                            $mIME = $myUtils->oxMimeContentType($fileName);\n                            if (in_array($mIME, ['image/jpeg', 'image/gif', 'image/png', 'image/webp'])) {\n                                if ($this->addEmbeddedImage($fileName, $cId, \"image\", \"base64\", $mIME)) {\n                                    $imageCache[$fileName] = $cId;\n                                } else {\n                                    $cId = '';\n                                }\n                            }\n                        }\n                        if ($cId && $cId == $imageCache[$fileName]) {\n                            if ($replTag = str_replace($imageName, 'cid:' . $cId, $image[0])) {\n                                $body = str_replace($image[0], $replTag, $body);\n                                $reSetBody = true;\n                            }\n                        }\n                    }\n                }\n            }\n\n            if ($reSetBody) {\n                $this->setBody($body);\n            }\n        }\n    }\n\n    public function setSubject($subject = null)\n    {\n        // A. HTML entities in subjects must be replaced\n        $subject = str_replace(['&amp;', '&quot;', '&#039;', '&lt;', '&gt;'], ['&', '\"', \"'\", '<', '>'], $subject);\n\n        $this->set(\"Subject\", $subject);\n    }\n\n    public function getSubject()\n    {\n        return $this->Subject;\n    }\n\n    public function setBody($body = null, $clearSid = true)\n    {\n        if ($clearSid) {\n            $body = $this->clearSidFromBody($body);\n        }\n\n        $this->set(\"Body\", $body);\n    }\n\n    public function getBody()\n    {\n        return $this->Body;\n    }\n\n    public function setAltBody($altBody = null, $clearSid = true)\n    {\n        if ($clearSid) {\n            $altBody = $this->clearSidFromBody($altBody);\n        }\n\n        // A. alt body is used for plain text emails so we should eliminate HTML entities\n        $altBody = str_replace(['&amp;', '&quot;', '&#039;', '&lt;', '&gt;'], ['&', '\"', \"'\", '<', '>'], $altBody);\n\n        $this->set(\"AltBody\", $altBody);\n    }\n\n    public function getAltBody()\n    {\n        return $this->AltBody;\n    }\n\n    public function setRecipient($address = null, $name = null)\n    {\n        try {\n            $address = $this->idnToAscii($address);\n\n            parent::addAddress($address, $name);\n\n            // copying values as original class does not allow to access recipients array\n            $this->_aRecipients[] = [$address, $name];\n        } catch (Exception $exception) {\n        }\n    }\n\n    public function getRecipient()\n    {\n        return $this->_aRecipients;\n    }\n\n    public function getCc(): array\n    {\n        return $this->cc;\n    }\n\n    public function getBcc(): array\n    {\n        return $this->bcc;\n    }\n\n    public function clearAllRecipients()\n    {\n        $this->_aRecipients = [];\n        parent::clearAllRecipients();\n    }\n\n    public function setReplyTo($email = null, $name = null)\n    {\n        $emailValidator = ContainerFacade::get(EmailValidatorServiceBridgeInterface::class);\n        if (!$emailValidator->isEmailValid($email)) {\n            $email = $this->getShop()->oxshops__oxorderemail->value;\n        }\n\n        $this->_aReplies[] = [$email, $name];\n\n        try {\n            parent::addReplyTo($email, $name);\n        } catch (Exception $ex) {\n        }\n    }\n\n    public function getReplyTo()\n    {\n        return $this->_aReplies;\n    }\n\n    public function clearReplyTos()\n    {\n        $this->_aReplies = [];\n        parent::clearReplyTos();\n    }\n\n    public function setFrom($address, $name = '', $auto = true)\n    {\n        $address = substr($address, 0, 150);\n        $name = substr($name, 0, 150);\n\n        $success = false;\n        try {\n            $success = parent::setFrom($address, $name, $auto);\n        } catch (Exception $exception) {\n        }\n\n        return $success;\n    }\n\n    public function getFrom()\n    {\n        return $this->From;\n    }\n\n    public function getFromName()\n    {\n        return $this->FromName;\n    }\n\n    public function setCharSet($charSet = null)\n    {\n        if ($charSet) {\n            $this->_sCharSet = $charSet;\n        } else {\n            $this->_sCharSet = Registry::getLang()->translateString(\"charset\");\n        }\n        $this->set(\"CharSet\", $this->_sCharSet);\n    }\n\n    public function setMailer($mailer = null)\n    {\n        $this->set(\"Mailer\", $mailer);\n    }\n\n    public function getMailer()\n    {\n        return $this->Mailer;\n    }\n\n    public function setHost($host = null)\n    {\n        $this->set(\"Host\", $host);\n    }\n\n    public function getErrorInfo()\n    {\n        return $this->ErrorInfo;\n    }\n\n    public function setMailWordWrap($wordWrap = null)\n    {\n        $this->set(\"WordWrap\", $wordWrap);\n    }\n\n    public function setUseInlineImages($useImages = null)\n    {\n        $this->_blInlineImgEmail = $useImages;\n    }\n\n    public function headerLine($name, $value)\n    {\n        if (stripos($name, 'X-') !== false) {\n            return null;\n        }\n\n        return parent::headerLine($name, $value);\n    }\n\n    protected function getUseInlineImages()\n    {\n        return $this->_blInlineImgEmail;\n    }\n\n    protected function sendMailErrorMsg()\n    {\n        $recipients = $this->getRecipient();\n\n        $ownerMessage = \"Error sending eMail(\" . $this->getSubject() . \") to: \\n\\n\";\n\n        foreach ($recipients as $eMail) {\n            $ownerMessage .= $eMail[0];\n            $ownerMessage .= (!empty($eMail[1])) ? ' (' . $eMail[1] . ')' : '';\n            $ownerMessage .= \" \\n \";\n        }\n        $ownerMessage .= \"\\n\\nError : \" . $this->getErrorInfo();\n\n        $shop = $this->getShop();\n\n        return @mail($shop->oxshops__oxorderemail->value, \"eMail problem in shop!\", $ownerMessage);\n    }\n\n    protected function addUserInfoOrderEMail($order)\n    {\n        return $order;\n    }\n\n    protected function addUserRegisterEmail($user)\n    {\n        return $user;\n    }\n\n    protected function addForgotPwdEmail($shop)\n    {\n        return $shop;\n    }\n\n    protected function addNewsletterDbOptInMail($user)\n    {\n        return $user;\n    }\n\n    protected function clearMailer()\n    {\n        $this->clearAllRecipients();\n        $this->clearReplyTos();\n        $this->clearAttachments();\n\n        $this->ErrorInfo = '';\n    }\n\n    protected function setMailParams($shop = null)\n    {\n        $this->clearMailer();\n\n        if (!$shop) {\n            $shop = $this->getShop();\n        }\n\n        $this->setFrom($shop->oxshops__oxorderemail->value, $shop->oxshops__oxname->getRawValue());\n        $this->setSmtp($shop);\n    }\n\n    public function getShop($langId = null, $shopId = null)\n    {\n        if ($langId === null && $shopId === null) {\n            if (isset($this->_oShop)) {\n                return $this->_oShop;\n            } else {\n                return $this->_oShop = Registry::getConfig()->getActiveShop();\n            }\n        }\n\n        $myConfig = Registry::getConfig();\n\n        $shop = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Shop::class);\n        if ($shopId !== null) {\n            $shop->setShopId($shopId);\n        }\n        if ($langId !== null) {\n            $shop->setLanguage($langId);\n        }\n        $shop->load($myConfig->getShopId());\n\n        return $shop;\n    }\n\n    protected function setSmtpAuthInfo($userName = null, $userPassword = null)\n    {\n        $this->set(\"SMTPAuth\", true);\n        $this->set(\"Username\", $userName);\n        $this->set(\"Password\", $userPassword);\n    }\n\n    protected function setSmtpDebug($debug = null)\n    {\n        $this->set(\"SMTPDebug\", $debug);\n    }\n\n    protected function makeOutputProcessing()\n    {\n        $output = oxNew(\\OxidEsales\\Eshop\\Core\\Output::class);\n        $this->setBody($output->process($this->getBody(), \"oxemail\"));\n        $this->setAltBody($output->process($this->getAltBody(), \"oxemail\"));\n        $output->processEmail($this);\n    }\n\n    protected function sendMail()\n    {\n        $result = false;\n        try {\n            $result = parent::send();\n        } catch (Exception $exception) {\n            $ex = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\StandardException::class);\n            $ex->setMessage($exception->getMessage());\n            if ($this->isDebugModeEnabled()) {\n                throw $ex;\n            } else {\n                Registry::getLogger()->error($ex->getMessage(), [$ex]);\n            }\n        }\n\n        return $result;\n    }\n\n    protected function processViewArray()\n    {\n        $outputProcessor = oxNew(\\OxidEsales\\Eshop\\Core\\Output::class);\n\n        // processing assigned template variables\n        $newArray = $outputProcessor->processViewArray($this->_aViewData, \"oxemail\");\n\n        $this->_aViewData = array_merge($this->_aViewData, $newArray);\n    }\n\n    public function getCharset()\n    {\n        if (!$this->_sCharSet) {\n            return Registry::getLang()->translateString(\"charset\");\n        } else {\n            return $this->CharSet;\n        }\n    }\n\n    public function setShop($shop)\n    {\n        $this->_oShop = $shop;\n    }\n\n    public function getViewConfig()\n    {\n        return Registry::getConfig()->getActiveView()->getViewConfig();\n    }\n\n    public function getView()\n    {\n        return Registry::getConfig()->getActiveView();\n    }\n\n    public function getCurrency()\n    {\n        $config = Registry::getConfig();\n\n        return $config->getActShopCurrencyObject();\n    }\n\n    public function setViewData($key, $value)\n    {\n        $this->_aViewData[$key] = $value;\n    }\n\n    public function getViewData()\n    {\n        return $this->_aViewData;\n    }\n\n    public function getViewDataItem($key)\n    {\n        if (isset($this->_aViewData[$key])) {\n            return $this->_aViewData;\n        }\n    }\n\n    public function setUser($user)\n    {\n        $this->_aViewData[\"oUser\"] = $user;\n    }\n\n    public function getUser()\n    {\n        return $this->_aViewData[\"oUser\"];\n    }\n\n    public function getOrderFileList($orderId)\n    {\n        $orderFileList = oxNew(OrderFileList::class);\n        $orderFileList->loadOrderFiles($orderId);\n\n        if (count($orderFileList) > 0) {\n            return $orderFileList;\n        }\n\n        return false;\n    }\n\n    private function clearSidFromBody($altBody)\n    {\n        return Str::getStr()->preg_replace(\n            '/(\\?|&(amp;)?)(force_)?(admin_)?sid=[A-Z0-9\\.]+/i',\n            '\\1shp=' . Registry::getConfig()->getShopId(),\n            $altBody\n        );\n    }\n\n    protected function getUtilsObjectInstance()\n    {\n        return Registry::getUtilsObject();\n    }\n\n    private function isDebugModeEnabled()\n    {\n        return ContainerFacade::getParameter('oxid_esales.debug_mode');\n    }\n\n    private function getUserIdByUserName($userName, $shopId)\n    {\n        $select = \"SELECT `OXID` \n          FROM `oxuser` \n          WHERE `OXACTIVE` = 1 \n          AND `OXUSERNAME` = :oxusername \n          AND `OXPASSWORD` != ''\";\n        if (Registry::getConfig()->getConfigParam('blMallUsers')) {\n            $select .= \" ORDER BY OXSHOPID = :oxshopid DESC\";\n        } else {\n            $select .= \" AND OXSHOPID = :oxshopid\";\n        }\n\n        $sOxId = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->getOne($select, [\n            'oxusername' => $userName,\n            'oxshopid'   => $shopId\n        ]);\n\n        return $sOxId;\n    }\n\n    private function shouldProductReviewLinksBeIncluded(): bool\n    {\n        $config = Registry::getConfig();\n\n        $reviewsEnabled = $config->getConfigParam('bl_perfLoadReviews', false);\n        $productReviewLinkInclusionEnabled = $config->getConfigParam('includeProductReviewLinksInEmail', false);\n\n        return $reviewsEnabled && $productReviewLinkInclusionEnabled;\n    }\n\n    private function idnToAscii($idn)\n    {\n        if (function_exists('idn_to_ascii')) {\n            $parts = explode('@', $idn);\n            return $parts[0] . '@' . idn_to_ascii($parts[1]);\n        }\n\n        return $idn;\n    }\n\n    private function switchToShopMode(): void\n    {\n        Registry::getConfig()->setAdminMode(false);\n        $this->dispatchAdminModeChangedEvent();\n    }\n\n    private function switchToAdminMode(): void\n    {\n        Registry::getConfig()->setAdminMode(true);\n        $this->dispatchAdminModeChangedEvent();\n    }\n\n    private function dispatchAdminModeChangedEvent(): void\n    {\n        ContainerFacade::get(EventDispatcherInterface::class)\n            ->dispatch(\n                new AdminModeChangedEvent()\n            );\n    }\n\n    private function areOrderEmailsDisabled(): bool\n    {\n        return ContainerFacade::hasParameter('oxid_esales.email.disable_order_emails')\n            && ContainerFacade::getParameter('oxid_esales.email.disable_order_emails');\n    }\n\n    private function isSymfonyMailerEnabled(): bool\n    {\n        return ContainerFacade::hasParameter('oxid_esales.mailing.use_symfony_mailer')\n            && ContainerFacade::getParameter('oxid_esales.mailing.use_symfony_mailer');\n    }\n}\n"
  },
  {
    "path": "source/Core/EmailBuilder.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\n\n/**\n * @internal Do not make a module extension for this class.\n *\n * Email builder class\n */\nabstract class EmailBuilder\n{\n    protected $buildParam = null;\n\n    /**\n     * Set configuration first, build and return the email after.\n     *\n     * @param mixed $buildParam\n     *\n     * @return Email\n     */\n    public function build($buildParam = null)\n    {\n        $this->buildParam = $buildParam;\n\n        return $this->buildEmail();\n    }\n\n    /**\n     * Builds and returns the email object\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Email\n     */\n    protected function buildEmail()\n    {\n        $email = $this->getEmailObject();\n\n        $email->setSubject($this->getSubject());\n        $email->setRecipient($this->getRecipient());\n        $email->setFrom($this->getSender());\n        $email->setBody($this->getBody());\n\n        return $email;\n    }\n\n    /**\n     * @return \\OxidEsales\\Eshop\\Core\\Email\n     */\n    protected function getEmailObject()\n    {\n        return oxNew(\\OxidEsales\\Eshop\\Core\\Email::class);\n    }\n\n    /**\n     * Prepare and get recipient address\n     *\n     * @return string\n     */\n    protected function getRecipient()\n    {\n        return $this->getShopInfoAddress();\n    }\n\n    /**\n     * Prepare and get sender address\n     *\n     * @return string\n     */\n    protected function getSender()\n    {\n        return $this->getShopInfoAddress();\n    }\n\n    /**\n     * Prepare and get subject\n     *\n     * @return string\n     */\n    protected function getSubject()\n    {\n        return '';\n    }\n\n    /**\n     * Prepare and get body\n     *\n     * @return string\n     */\n    protected function getBody()\n    {\n        return '';\n    }\n\n    /**\n     * Returns active shop info email address.\n     *\n     * @return string\n     */\n    protected function getShopInfoAddress()\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $activeShop = $config->getActiveShop();\n        return $activeShop->getFieldData('oxinfoemail');\n    }\n\n    /**\n     * Returns the message with email origin information.\n     *\n     * @return string\n     */\n    protected function getEmailOriginMessage()\n    {\n        $lang = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n        $shopUrl = ContainerFacade::getParameter('oxid_esales.shop_url');\n\n        return \"<br>\" . sprintf(\n            $lang->translateString(\n                'SHOP_EMAIL_ORIGIN_MESSAGE',\n                null,\n                true\n            ),\n            $shopUrl\n        );\n    }\n}\n"
  },
  {
    "path": "source/Core/Encryptor.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Class oxEncryptor\n */\nclass Encryptor\n{\n    /**\n     * Encrypts string with given key.\n     *\n     * @param string $string\n     * @param string $key\n     *\n     * @return string\n     */\n    public function encrypt($string, $key)\n    {\n        $string = \"ox{$string}id\";\n\n        $key = $this->formKey($key, $string);\n\n        $string = $string ^ $key;\n        $string = base64_encode($string);\n        $string = str_replace(\"=\", \"!\", $string);\n\n        return \"ox_$string\";\n    }\n\n    /**\n     * Forms key for use in encoding.\n     *\n     * @param string $key\n     * @param string $string\n     *\n     * @return string\n     */\n    protected function formKey($key, $string)\n    {\n        $key = '_' . $key;\n        $keyLength = (int) (strlen($string) / strlen($key)) + 5;\n\n        return str_repeat($key, $keyLength);\n    }\n}\n"
  },
  {
    "path": "source/Core/Exception/ArticleException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * Exception base class for an article\n */\nclass ArticleException extends \\OxidEsales\\Eshop\\Core\\Exception\\StandardException\n{\n    /**\n     * Exception type, currently old class name is used.\n     *\n     * @var string\n     */\n    protected $type = 'oxArticleException';\n\n    /**\n     * Article number who caused this exception\n     *\n     * @var string\n     */\n    protected $_sArticleNr = null;\n\n    /**\n     * Id of product which caused this exception\n     *\n     * @var string\n     */\n    protected $_sProductId = null;\n\n    /**\n     * Sets the article number of the article which caused the exception\n     *\n     * @param string $sArticleNr Article who causes the exception\n     */\n    public function setArticleNr($sArticleNr)\n    {\n        $this->_sArticleNr = $sArticleNr;\n    }\n\n    /**\n     * The article number of the faulty article\n     *\n     * @return string\n     */\n    public function getArticleNr()\n    {\n        return $this->_sArticleNr;\n    }\n\n    /**\n     * Sets the product id of the article which caused the exception\n     *\n     * @param string $sProductId id of product who causes the exception\n     */\n    public function setProductId($sProductId)\n    {\n        $this->_sProductId = $sProductId;\n    }\n\n    /**\n     * Faulty product id\n     *\n     * @return string\n     */\n    public function getProductId()\n    {\n        return $this->_sProductId;\n    }\n\n    /**\n     * Get string dump\n     * Overrides oxException::getString()\n     *\n     * @return string\n     */\n    public function getString()\n    {\n        return __CLASS__ . '-' . parent::getString() . \" Faulty Article --> \" . $this->_sArticleNr . \"\\n\";\n    }\n\n\n    /**\n     * Override of oxException::getValues()\n     *\n     * @return array\n     */\n    public function getValues()\n    {\n        $aRes = parent::getValues();\n        $aRes['articleNr'] = $this->getArticleNr();\n        $aRes['productId'] = $this->getProductId();\n\n        return $aRes;\n    }\n}\n"
  },
  {
    "path": "source/Core/Exception/ArticleInputException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * Article input exception..\n */\nclass ArticleInputException extends \\OxidEsales\\Eshop\\Core\\Exception\\ArticleException\n{\n    /**\n     * Exception type, currently old class name is used.\n     *\n     * @var string\n     */\n    protected $type = 'oxArticleInputException';\n\n    /**\n     * Get string dump\n     * Overrides oxException::getString()\n     *\n     * @return string\n     */\n    public function getString()\n    {\n        return __CLASS__ . '-' . parent::getString();\n    }\n}\n"
  },
  {
    "path": "source/Core/Exception/ConnectionException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * exception class for all kind of connection problems to external servers, e.g.:\n * - no connection, proxy problem, wrong configuration, etc.\n * - ipayment server\n * - online vat id check\n * - db server\n */\nclass ConnectionException extends \\OxidEsales\\Eshop\\Core\\Exception\\StandardException\n{\n    /**\n     * Exception type, currently old class name is used.\n     *\n     * @var string\n     */\n    protected $type = 'oxConnectionException';\n\n    /**\n     * Address value\n     *\n     * @var string\n     */\n    private $_sAddress;\n\n    /**\n     * connection error as given by connect method\n     *\n     * @var string\n     */\n    private $_sConnectionError;\n\n    /**\n     * Enter address of the external server which caused the exception\n     *\n     * @param string $sAdress Externalserver address\n     */\n    public function setAdress($sAdress)\n    {\n        $this->_sAddress = $sAdress;\n    }\n\n    /**\n     * Gives address of the external server which caused the exception\n     *\n     * @return string\n     */\n    public function getAdress()\n    {\n        return $this->_sAddress;\n    }\n\n    /**\n     * Sets the connection error returned by the connect function\n     *\n     * @param string $sConnError connection error\n     */\n    public function setConnectionError($sConnError)\n    {\n        $this->_sConnectionError = $sConnError;\n    }\n\n    /**\n     * Gives the connection error returned by the connect function\n     *\n     * @return string\n     */\n    public function getConnectionError()\n    {\n        return $this->_sConnectionError;\n    }\n\n    /**\n     * Get string dump\n     * Overrides oxException::getString()\n     *\n     * @return string\n     */\n    public function getString()\n    {\n        return __CLASS__ . '-' . parent::getString() . \" Connection Adress --> \" . $this->_sAddress . \"\\n\" . \"Connection Error --> \" . $this->_sConnectionError;\n    }\n\n    /**\n     * Override of oxException::getValues()\n     *\n     * @return array\n     */\n    public function getValues()\n    {\n        $aRes = parent::getValues();\n        $aRes['adress'] = $this->getAdress();\n        $aRes['connectionError'] = $this->getConnectionError();\n\n        return $aRes;\n    }\n}\n"
  },
  {
    "path": "source/Core/Exception/CookieException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * exception class for clients without cookies support\n */\nclass CookieException extends \\OxidEsales\\Eshop\\Core\\Exception\\StandardException\n{\n    /**\n     * Exception type, currently old class name is used.\n     *\n     * @var string\n     */\n    protected $type = 'oxCookieException';\n\n    /**\n     * Get string dump\n     * Overrides oxException::getString()\n     *\n     * @return string\n     */\n    public function getString()\n    {\n        return __CLASS__ . '-' . parent::getString();\n    }\n}\n"
  },
  {
    "path": "source/Core/Exception/DatabaseConnectionException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * Exception to be thrown on database connection errors\n */\nclass DatabaseConnectionException extends \\OxidEsales\\Eshop\\Core\\Exception\\DatabaseException\n{\n}\n"
  },
  {
    "path": "source/Core/Exception/DatabaseErrorException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * Exception to be thrown on database errors\n */\nclass DatabaseErrorException extends \\OxidEsales\\Eshop\\Core\\Exception\\DatabaseException\n{\n}\n"
  },
  {
    "path": "source/Core/Exception/DatabaseException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * Exception to be thrown on database errors\n */\nclass DatabaseException extends \\OxidEsales\\Eshop\\Core\\Exception\\StandardException\n{\n    /**\n     * DatabaseException constructor.\n     *\n     * Use this exception to catch and rethrow exceptions of the underlying DBAL.\n     * Provide the caught exception as the third parameter of the constructor to enable exception chaining.\n     *\n     * @param string     $message\n     * @param int        $code\n     * @param \\Exception $previous Previous exception thrown by the underlying DBAL\n     */\n    public function __construct($message, $code, \\Exception $previous)\n    {\n        parent::__construct($message, $code, $previous);\n    }\n}\n"
  },
  {
    "path": "source/Core/Exception/ExceptionHandler.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\ShopIdCalculator;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Logger\\LoggerServiceFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\Context;\nuse Throwable;\nuse function oxTriggerOfflinePageDisplay;\n\nclass ExceptionHandler\n{\n    public function __construct(private readonly bool $isDebugMode = false)\n    {\n    }\n\n    /**\n     * @throws Throwable\n     */\n    public function handleUncaughtException(Throwable $exception): void\n    {\n        try {\n            Registry::getLogger()->error($exception->getMessage(), [$exception]);\n        } catch (Throwable) {\n            (new LoggerServiceFactory(new Context(ShopIdCalculator::BASE_SHOP_ID)))\n                ->getLogger()\n                ->error($exception);\n        }\n        if ($this->isDebugMode || PHP_SAPI === 'cli') {\n            throw $exception;\n        }\n        oxTriggerOfflinePageDisplay();\n        exit(1);\n    }\n}\n"
  },
  {
    "path": "source/Core/Exception/ExceptionToDisplay.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * simplified Exception classes for simply displaying errors\n * saves resources when exception functionality is not needed\n */\nclass ExceptionToDisplay implements \\OxidEsales\\Eshop\\Core\\Contract\\IDisplayError\n{\n    /**\n     * Language const of a Message\n     *\n     * @var string\n     */\n    private $_sMessage;\n\n    /**\n     * Shop debug\n     *\n     * @var integer\n     */\n    protected $_blDebug = false;\n\n    /**\n     * Stack trace as a string\n     *\n     * @var string\n     */\n    private $_sStackTrace;\n\n    /**\n     * Additional values\n     *\n     * @var string\n     */\n    private $_aValues;\n\n    /**\n     * Typeof the exception (old class name)\n     *\n     * @var string\n     */\n    private $_sType;\n\n    /**\n     * Stack trace setter\n     *\n     * @param string $sStackTrace stack trace\n     */\n    public function setStackTrace($sStackTrace)\n    {\n        $this->_sStackTrace = $sStackTrace;\n    }\n\n    /**\n     * Returns stack trace\n     *\n     * @return string\n     */\n    public function getStackTrace()\n    {\n        return $this->_sStackTrace;\n    }\n\n    /**\n     * Sets \\OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay::_aValues value\n     *\n     * @param array $aValues exception values to store\n     */\n    public function setValues($aValues)\n    {\n        $this->_aValues = $aValues;\n    }\n\n    /**\n     * Stores into exception storage message or other value\n     *\n     * @param string $sName  storage name\n     * @param mixed  $sValue value to store\n     */\n    public function addValue($sName, $sValue)\n    {\n        $this->_aValues[$sName] = $sValue;\n    }\n\n    /**\n     * Exception type setter\n     *\n     * @param string $sType exception type\n     */\n    public function setExceptionType($sType)\n    {\n        $this->_sType = $sType;\n    }\n\n    /**\n     * Returns error class type\n     *\n     * @return string\n     */\n    public function getErrorClassType()\n    {\n        return $this->_sType;\n    }\n\n    /**\n     * Returns exception stored (by name) value\n     *\n     * @param string $sName storage name\n     *\n     * @return  mixed\n     */\n    public function getValue($sName)\n    {\n        return $this->_aValues[$sName];\n    }\n\n    /**\n     * Returns all exception stored values\n     *\n     * @return  array\n     */\n    public function getValues()\n    {\n        return $this->_aValues;\n    }\n\n    /**\n     * Exception debug mode setter\n     *\n     * @param bool $bl if TRUE debug mode on\n     */\n    public function setDebug($bl)\n    {\n        $this->_blDebug = $bl;\n    }\n\n    /**\n     * Exception message setter\n     *\n     * @param string $sMessage exception message\n     */\n    public function setMessage($sMessage)\n    {\n        $this->_sMessage = $sMessage;\n    }\n\n    /**\n     * Sets the exception message arguments used when\n     * outputing message using sprintf().\n     */\n    public function setMessageArgs()\n    {\n        $this->_aMessageArgs = func_get_args();\n    }\n\n    /**\n     * Returns translated exception message\n     *\n     * @return string\n     */\n    public function getOxMessage()\n    {\n        if ($this->_blDebug) {\n            return $this;\n        } else {\n            $sString = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString($this->_sMessage);\n\n            if (!empty($this->_aMessageArgs)) {\n                $sString = vsprintf($sString, $this->_aMessageArgs);\n            }\n\n            return $sString;\n        }\n    }\n\n    /**\n     * When exception is converted as string, this magic method return exception message\n     *\n     * @return string\n     */\n    public function __toString()\n    {\n        $sRes = $this->getErrorClassType() . \" (time: \" . date('Y-m-d H:i:s', \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime()) . \"): \" . $this->getOxMessage() . \" \\n Stack Trace: \" . $this->getStackTrace() . \"\\n\";\n        foreach ($this->_aValues as $key => $value) {\n            $sRes .= $key . \" => \" . $value . \"\\n\";\n        }\n\n        return $sRes;\n    }\n}\n"
  },
  {
    "path": "source/Core/Exception/FileException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * exception for invalid or non existin external files, e.g.:\n * - file does not exist\n * - file is not valid xml\n */\nclass FileException extends \\OxidEsales\\Eshop\\Core\\Exception\\StandardException\n{\n    /**\n     * Exception type, currently old class name is used.\n     *\n     * @var string\n     */\n    protected $type = 'oxFileException';\n\n    /**\n     * File connected to this exception.\n     *\n     * @var string\n     */\n    protected $_sErrFileName;\n\n    /**\n     * Error occured with the file, if provided\n     *\n     * @var string\n     */\n    protected $_sFileError;\n\n    /**\n     *  Sets the file name of the file related to the exception\n     *\n     * @param string $sFileName file name\n     */\n    public function setFileName($sFileName)\n    {\n        $this->_sErrFileName = $sFileName;\n    }\n\n    /**\n     * Gives file name related to the exception\n     *\n     * @return string\n     */\n    public function getFileName()\n    {\n        return $this->_sErrFileName;\n    }\n\n    /**\n     * sets the error returned by the file operation\n     *\n     * @param string $sFileError Error\n     */\n    public function setFileError($sFileError)\n    {\n        $this->_sFileError = $sFileError;\n    }\n\n    /**\n     * return the file error\n     *\n     * @return string\n     */\n    public function getFileError()\n    {\n        return $this->_sFileError;\n    }\n\n    /**\n     * Get string dump\n     * Overrides oxException::getString()\n     *\n     * @return string\n     */\n    public function getString()\n    {\n        return __CLASS__ . '-' . parent::getString() . \" Faulty File --> \" . $this->_sErrFileName . \"\\n\" . \"Error Code --> \" . $this->_sFileError;\n    }\n\n    /**\n     * Override of oxException::getValues()\n     *\n     * @return array\n     */\n    public function getValues()\n    {\n        $aRes = parent::getValues();\n        $aRes['fileName'] = $this->getFileName();\n\n        return $aRes;\n    }\n}\n"
  },
  {
    "path": "source/Core/Exception/InputException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * exception class for all kind of exceptions connected to an input done by the user e.g.:\n * - not valid email adress\n * - negative value\n */\nclass InputException extends \\OxidEsales\\Eshop\\Core\\Exception\\StandardException\n{\n    /**\n     * Exception type, currently old class name is used.\n     *\n     * @var string\n     */\n    protected $type = 'oxInputException';\n\n    /**\n     * Get string dump\n     * Overrides oxException::getString()\n     *\n     * @return string\n     */\n    public function getString()\n    {\n        return __CLASS__ . '-' . parent::getString();\n    }\n}\n"
  },
  {
    "path": "source/Core/Exception/LanguageNotFoundException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * exception class for missing languages\n */\nclass LanguageNotFoundException extends \\OxidEsales\\Eshop\\Core\\Exception\\StandardException\n{\n}\n"
  },
  {
    "path": "source/Core/Exception/ModuleValidationException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * Class ModuleValidationException\n *\n * This exception should be thrown, if a module validation fails in any point (activation, deactivation, module list, etc)\n */\nclass ModuleValidationException extends \\OxidEsales\\Eshop\\Core\\Exception\\StandardException\n{\n    /**\n     * Exception type\n     *\n     * @var string\n     */\n    protected $type = 'ModuleValidationException';\n}\n"
  },
  {
    "path": "source/Core/Exception/NoArticleException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * exception class for non existing articles\n */\nclass NoArticleException extends \\OxidEsales\\Eshop\\Core\\Exception\\ArticleException\n{\n    /**\n     * Exception type, currently old class name is used.\n     *\n     * @var string\n     */\n    protected $type = 'oxNoArticleException';\n\n    /**\n     * Get string dump\n     * Overrides oxException::getString()\n     *\n     * @return string\n     */\n    public function getString()\n    {\n        return __CLASS__ . '-' . parent::getString();\n    }\n}\n"
  },
  {
    "path": "source/Core/Exception/NoResultException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * exception class for non existing results found\n */\nclass NoResultException extends \\OxidEsales\\Eshop\\Core\\Exception\\StandardException\n{\n    /**\n     * Exception type.\n     *\n     * @var string\n     */\n    protected $type = 'NoResultException';\n}\n"
  },
  {
    "path": "source/Core/Exception/ObjectException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * e.g.:\n * - not existing object\n * - wrong type\n * - ID not set\n */\nclass ObjectException extends \\OxidEsales\\Eshop\\Core\\Exception\\StandardException\n{\n    /**\n     * Exception type, currently old class name is used.\n     *\n     * @var string\n     */\n    protected $type = 'oxObjectException';\n\n    /**\n     * Object causing exception.\n     *\n     * @var object\n     */\n    private $_oObject;\n\n    /**\n     * Sets the object which caused the exception.\n     *\n     * @param object $oObject exception object\n     */\n    public function setObject($oObject)\n    {\n        $this->_oObject = $oObject;\n    }\n\n    /**\n     * Get the object which caused the exception.\n     *\n     * @return object\n     */\n    public function getObject()\n    {\n        return $this->_oObject;\n    }\n\n    /**\n     * Get string dump\n     * Overrides oxException::getString()\n     *\n     * @return string\n     */\n    public function getString()\n    {\n        return __CLASS__ . '-' . parent::getString() . \" Faulty Object --> \" . get_class($this->_oObject) . \"\\n\";\n    }\n}\n"
  },
  {
    "path": "source/Core/Exception/OutOfStockException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * exception class for an article which is out of stock\n */\nclass OutOfStockException extends \\OxidEsales\\Eshop\\Core\\Exception\\ArticleException\n{\n    /**\n     * Exception type, currently old class name is used.\n     *\n     * @var string\n     */\n    protected $type = 'oxOutOfStockException';\n\n    /**\n     * Maximal possible amount (e.g. 2 if two items of the article are left).\n     *\n     * @var integer\n     */\n    private $_iRemainingAmount = 0;\n\n    /**\n     * Basket index value\n     *\n     * @var string\n     */\n    private $_sBasketIndex = null;\n\n    /**\n     * Sets the amount of the article remaining in stock.\n     *\n     * @param integer $iRemainingAmount Articles remaining in stock\n     */\n    public function setRemainingAmount($iRemainingAmount)\n    {\n        $this->_iRemainingAmount = (int) $iRemainingAmount;\n    }\n\n    /**\n     * Amount of articles left\n     *\n     * @return integer\n     */\n    public function getRemainingAmount()\n    {\n        return $this->_iRemainingAmount;\n    }\n\n    /**\n     * Sets the basket index for the article\n     *\n     * @param string $sBasketIndex Basket index for the faulty article\n     */\n    public function setBasketIndex($sBasketIndex)\n    {\n        $this->_sBasketIndex = $sBasketIndex;\n    }\n\n    /**\n     * The basketindex of the faulty article\n     *\n     * @return string\n     */\n    public function getBasketIndex()\n    {\n        return $this->_sBasketIndex;\n    }\n\n    /**\n     * Get string dump\n     * Overrides oxException::getString()\n     *\n     * @return string\n     */\n    public function getString()\n    {\n        return __CLASS__ . '-' . parent::getString() . \" Remaining Amount --> \" . $this->_iRemainingAmount;\n    }\n\n    /**\n     * Creates an array of field name => field value of the object.\n     * To make a easy conversion of exceptions to error messages possible.\n     * Should be extended when additional fields are used!\n     * Overrides oxException::getValues()\n     *\n     * @return array\n     */\n    public function getValues()\n    {\n        $aRes = parent::getValues();\n        $aRes['remainingAmount'] = $this->getRemainingAmount();\n        $aRes['basketIndex'] = $this->getBasketIndex();\n\n        return $aRes;\n    }\n\n    /**\n     * Defines a name of the view variable containing the messages.\n     * Currently it checks if destination value is set, and if\n     * not - overrides default error message with:\n     *\n     *    $this->getMessage(). $this->getRemainingAmount()\n     *\n     * It is necessary to display correct stock error message on\n     * any view (except basket).\n     *\n     * @param string $sDestination name of the view variable\n     */\n    public function setDestination($sDestination)\n    {\n        // in case destination not set, overriding default error message\n        if (!$sDestination) {\n            $this->message = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString($this->getMessage()) . \": \" . $this->getRemainingAmount();\n        } else {\n            $this->message = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString($this->getMessage()) . \": \";\n        }\n    }\n}\n"
  },
  {
    "path": "source/Core/Exception/RoutingException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * Cases:\n *\n * - Controller not found\n * - Controller action cannot be found/accessed\n */\nclass RoutingException extends \\OxidEsales\\Eshop\\Core\\Exception\\StandardException\n{\n}\n"
  },
  {
    "path": "source/Core/Exception/ShopException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * e.g.:\n * - shop is not active\n */\nclass ShopException extends \\OxidEsales\\Eshop\\Core\\Exception\\StandardException\n{\n    /**\n     * Exception type, currently old class name is used.\n     *\n     * @var string\n     */\n    protected $type = 'oxShopException';\n\n    /**\n     * Get string dump\n     * Overrides oxException::getString()\n     *\n     * @return string\n     */\n    public function getString()\n    {\n        return __CLASS__ . '-' . parent::getString();\n    }\n}\n"
  },
  {
    "path": "source/Core/Exception/StandardException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\nuse Exception;\n\n/**\n * Basic exception class\n */\nclass StandardException extends Exception\n{\n    /**\n     * Exception type, currently old class name is used.\n     *\n     * @var string\n     */\n    protected $type = 'oxException';\n\n    /**\n     * Not caught means the exception was not caught and occured in the rendering process,\n     * which is not allowed!\n     *\n     * @var bool\n     */\n    protected $_blRenderer = false;\n\n    /**\n     * Indicates that the Exception was caught in oxshopcontrol, which should be avoided!\n     *\n     * @var bool\n     */\n    protected $_blNotCaught = false;\n\n    /**\n     * Default constructor\n     *\n     * @param string          $sMessage exception message\n     * @param integer         $iCode    exception code\n     * @param Exception|null $previous previous exception\n     */\n    public function __construct($sMessage = 'not set', $iCode = 0, ?Exception $previous = null)\n    {\n        parent::__construct($sMessage, $iCode, $previous);\n    }\n\n    /**\n     * Sets the exception message\n     *\n     *  @deprecated since v6.0 (2017-02-27); This method will be removed. Set message in the constructor.\n     *\n     * @param string $sMessage exception message\n     */\n    public function setMessage($sMessage)\n    {\n        $this->message = $sMessage;\n    }\n\n    /**\n     * To define that the exception was caught in renderer\n     */\n    public function setRenderer()\n    {\n        $this->_blRenderer = true;\n    }\n\n    /**\n     * Is the exception caught in a renderer\n     *\n     * @return bool\n     */\n    public function isRenderer()\n    {\n        return $this->_blRenderer;\n    }\n\n    /**\n     * To define that the exception was not caught (only in oxexceptionhandler)\n     */\n    public function setNotCaught()\n    {\n        $this->_blNotCaught = true;\n    }\n\n    /**\n     * Is the exception \"not\" caught.\n     *\n     * @return bool\n     */\n    public function isNotCaught()\n    {\n        return $this->_blNotCaught;\n    }\n\n    /**\n     * Get complete string dump, should be overwritten by excptions extending this exceptions\n     * if they introduce new fields\n     *\n     * @return string\n     */\n    public function getString()\n    {\n        $sWarning = \"\";\n        if ($this->_blNotCaught) {\n            $sWarning .= \"--!--NOT CAUGHT--!--\";\n        }\n\n        if ($this->_blRenderer) {\n            $sWarning .= \"--!--RENDERER--!--\";\n        }\n\n        $currentTime = date('Y-m-d H:i:s', \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime());\n\n        return $sWarning . __CLASS__ . \" (time: \" . $currentTime . \"): [{$this->code}]: {$this->message} \\n Stack Trace: {$this->getTraceAsString()}\\n\\n\";\n    }\n\n    /**\n     * Creates an array of field name => field value of the object.\n     * To make a easy conversion of exceptions to error messages possible.\n     * Should be extended when additional fields are used!\n     *\n     * @return array\n     */\n    public function getValues()\n    {\n        return [];\n    }\n\n    /**\n     * Defines a name of the view variable containing the messages\n     *\n     * @param string $sDestination name of the view variable\n     */\n    public function setDestination($sDestination)\n    {\n    }\n\n    /**\n     * Get exception type.\n     * Currently old class name is used here for compatibility.\n     *\n     * @return string\n     */\n    public function getType()\n    {\n        return $this->type;\n    }\n}\n"
  },
  {
    "path": "source/Core/Exception/SystemComponentException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * exceptions for missing components e.g.:\n * - missing class\n * - missing function\n * - missing template\n * - missing field in object\n */\nclass SystemComponentException extends \\OxidEsales\\Eshop\\Core\\Exception\\StandardException\n{\n    /**\n     * Exception type, currently old class name is used.\n     *\n     * @var string\n     */\n    protected $type = 'oxSystemComponentException';\n\n    /**\n     * Component causing the exception.\n     *\n     * @var string\n     */\n    private $_sComponent;\n\n    /**\n     * Sets the component name which caused the exception as a string.\n     *\n     * @param string $sComponent name of component\n     */\n    public function setComponent($sComponent)\n    {\n        $this->_sComponent = $sComponent;\n    }\n\n    /**\n     * Name of the component that caused the exception\n     *\n     * @return string\n     */\n    public function getComponent()\n    {\n        return $this->_sComponent;\n    }\n\n    /**\n     * Get string dump\n     * Overrides oxException::getString()\n     *\n     * @return string\n     */\n    public function getString()\n    {\n        return __CLASS__ . '-' . parent::getString() . \" Faulty component --> \" . $this->_sComponent;\n    }\n\n    /**\n     * Creates an array of field name => field value of the object.\n     * To make a easy conversion of exceptions to error messages possible.\n     * Should be extended when additional fields are used!\n     * Overrides oxException::getValues().\n     *\n     * @return array\n     */\n    public function getValues()\n    {\n        $aRes = parent::getValues();\n        $aRes['component'] = $this->getComponent();\n\n        return $aRes;\n    }\n}\n"
  },
  {
    "path": "source/Core/Exception/UserException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * exception class for all kind of exceptions connected to a user e.g.:\n * - user doesn't exist\n * - wrong password\n */\nclass UserException extends \\OxidEsales\\Eshop\\Core\\Exception\\StandardException\n{\n    /**\n     * Exception type, currently old class name is used.\n     *\n     * @var string\n     */\n    protected $type = 'oxUserException';\n\n    /**\n     * Get string dump\n     * Overrides oxException::getString()\n     *\n     * @return string\n     */\n    public function getString()\n    {\n        return __CLASS__ . '-' . parent::getString();\n    }\n}\n"
  },
  {
    "path": "source/Core/Exception/VoucherException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Exception;\n\n/**\n * exception class covering voucher exceptions\n */\nclass VoucherException extends \\OxidEsales\\Eshop\\Core\\Exception\\StandardException\n{\n    /**\n     * Exception type, currently old class name is used.\n     *\n     * @var string\n     */\n    protected $type = 'oxVoucherException';\n\n    /**\n     * Voucher nr. involved in this exception\n     *\n     * @var string\n     */\n    private $_sVoucherNr;\n\n    /**\n     * Sets the voucher number as a string\n     *\n     * @param string $sVoucherNr voucher number\n     */\n    public function setVoucherNr($sVoucherNr)\n    {\n        $this->_sVoucherNr = (string) $sVoucherNr;\n    }\n\n    /**\n     * get voucher nr. involved\n     *\n     * @return string\n     */\n    public function getVoucherNr()\n    {\n        return $this->_sVoucherNr;\n    }\n\n    /**\n     * Get string dump\n     * Overrides oxException::getString()\n     *\n     * @return string\n     */\n    public function getString()\n    {\n        return __CLASS__ . '-' . parent::getString() . \" Faulty Voucher Nr --> \" . $this->_sVoucherNr;\n    }\n\n    /**\n     * Creates an array of field name => field value of the object.\n     * To make a easy conversion of exceptions to error messages possible.\n     * Should be extended when additional fields are used!\n     * Overrides oxException::getValues().\n     *\n     * @return array\n     */\n    public function getValues()\n    {\n        $aRes = parent::getValues();\n        $aRes['voucherNr'] = $this->getVoucherNr();\n\n        return $aRes;\n    }\n}\n"
  },
  {
    "path": "source/Core/Field.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse function is_string;\n\n#[\\AllowDynamicProperties]\nclass Field\n{\n    /**\n     * escaping functionality type: expected value is escaped text.\n     */\n    public const T_TEXT = 1;\n\n    /**\n     * escaping functionality type: expected value is not escaped (raw) text.\n     */\n    public const T_RAW = 2;\n\n    public function __construct($value = null, $type = self::T_TEXT)\n    {\n        $this->rawValue = $value;\n        if ((int)$type === self::T_RAW) {\n            $this->value = $value;\n        }\n    }\n\n    public function __isset($name): bool\n    {\n        return $this->{$name} !== null;\n    }\n\n    /**\n     * @param string $name\n     * @return mixed|string|null\n     */\n    public function __get(string $name)\n    {\n        if (!($name === 'value' || $name === 'rawValue')) {\n            return null;\n        }\n        if ($name === 'value') {\n            $this->value = $this->rawValue;\n            unset($this->rawValue);\n        }\n        return $this->value;\n    }\n\n    public function __toString(): string\n    {\n        return (string)$this->value;\n    }\n\n    /**\n     * @param $value\n     * @param $type\n     * @return void\n     */\n    public function setValue($value = null, $type = self::T_TEXT): void\n    {\n        unset($this->rawValue, $this->value);\n        $this->initValue($value, $type);\n    }\n\n    /**\n     * @return mixed\n     */\n    public function getRawValue(): mixed\n    {\n        return $this->rawValue ?? $this->value;\n    }\n\n    /**\n     * @param $value\n     * @param $type\n     * @return void\n     */\n    protected function initValue($value = null, $type = self::T_TEXT): void\n    {\n        if ((int)$type === self::T_TEXT) {\n            $this->rawValue = $value;\n        } else {\n            $this->value = $value;\n        }\n    }\n}\n"
  },
  {
    "path": "source/Core/FileSystem/FileSystem.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\FileSystem;\n\n/**\n * Wrapper for actions related to file system.\n *\n * @internal Do not make a module extension for this class.\n */\nclass FileSystem\n{\n    /**\n     * Connect all parameters with backslash to single path.\n     * Ensure that no double backslash appears if parameter already ends with backslash.\n     *\n     * @return string\n     */\n    public function combinePaths()\n    {\n        $pathElements = func_get_args();\n        foreach ($pathElements as $key => $pathElement) {\n            $pathElements[$key] = rtrim($pathElement, DIRECTORY_SEPARATOR);\n        }\n\n        return implode(DIRECTORY_SEPARATOR, $pathElements);\n    }\n\n    /**\n     * Check if file exists and is readable\n     *\n     * @param string $filePath\n     *\n     * @return bool\n     */\n    public function isReadable($filePath)\n    {\n        return (is_file($filePath) && is_readable($filePath));\n    }\n}\n"
  },
  {
    "path": "source/Core/Form/FormFields.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Form;\n\n/**\n * Unified way to define fields that are used in form fields.\n */\nclass FormFields\n{\n    /** @var array */\n    private $updatableFields;\n\n    /**\n     * @param array $updatableFields\n     */\n    public function __construct(array $updatableFields)\n    {\n        $this->updatableFields = $updatableFields;\n    }\n\n    /**\n     * @return \\ArrayIterator\n     */\n    public function getUpdatableFields()\n    {\n        return new \\ArrayIterator($this->updatableFields);\n    }\n}\n"
  },
  {
    "path": "source/Core/Form/FormFieldsCleaner.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Form;\n\n/**\n * Return those fields which could be changed by a customer.\n */\nclass FormFieldsCleaner\n{\n    /** @var FormFields */\n    private $updatableFields;\n\n    /**\n     * @param \\OxidEsales\\Eshop\\Core\\Form\\FormFields $updatableFields\n     */\n    public function __construct(\\OxidEsales\\Eshop\\Core\\Form\\FormFields $updatableFields)\n    {\n        $this->updatableFields = $updatableFields;\n    }\n\n    /**\n     * Return only those items which exist in both lists.\n     *\n     * @param array $listToClean All fields.\n     *\n     * @return array\n     */\n    public function filterByUpdatableFields(array $listToClean)\n    {\n        $allowedFields = $this->updatableFields->getUpdatableFields();\n\n        $cleanedList = $listToClean;\n        if ($allowedFields->count() > 0) {\n            $cleanedList = $this->filterFieldsByWhiteList($allowedFields, $listToClean);\n        }\n\n        return $cleanedList;\n    }\n\n    /**\n     * Return fields by performing a case-insensitive compare.\n     * Does not change original case-sensitivity of fields.\n     *\n     * @param \\ArrayIterator $allowedFields\n     * @param array          $listToClean\n     *\n     * @return array\n     */\n    private function filterFieldsByWhiteList(\\ArrayIterator $allowedFields, array $listToClean)\n    {\n        $allowedFieldsLowerCase = array_map('strtolower', (array)$allowedFields);\n        $cleanedList = array_filter($listToClean, function ($field) use ($allowedFieldsLowerCase) {\n            return in_array(strtolower($field), $allowedFieldsLowerCase);\n        }, ARRAY_FILTER_USE_KEY);\n\n        return $cleanedList;\n    }\n}\n"
  },
  {
    "path": "source/Core/Form/FormFieldsTrimmer.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Form;\n\nuse OxidEsales\\Eshop\\Core\\Form\\FormFieldsTrimmerInterface as EshopFormFieldsTrimmerInterface;\nuse OxidEsales\\Eshop\\Core\\Form\\FormFields as EshopFormFields;\n\n/**\n * Trim FormFields.\n */\nclass FormFieldsTrimmer implements EshopFormFieldsTrimmerInterface\n{\n    /**\n     * Returns trimmed fields.\n     *\n     * @param EshopFormFields $fields to trim.\n     *\n     * @return \\ArrayIterator\n     */\n    public function trim(EshopFormFields $fields)\n    {\n        $updatableFields = $fields->getUpdatableFields()->getArrayCopy();\n\n        array_walk_recursive($updatableFields, function (&$value) {\n            $value = $this->isTrimmableField($value) ? $this->trimField($value) : $value;\n        });\n\n        return new \\ArrayIterator($updatableFields);\n    }\n\n    /**\n     * @param mixed $value\n     *\n     * @return bool\n     */\n    private function isTrimmableField($value)\n    {\n        return is_string($value);\n    }\n\n    /**\n     * Returns trimmed field value.\n     *\n     * @param   string $field\n     *\n     * @return  string\n     */\n    private function trimField($field)\n    {\n        return trim($field);\n    }\n}\n"
  },
  {
    "path": "source/Core/Form/FormFieldsTrimmerInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Form;\n\nuse OxidEsales\\Eshop\\Core\\Form\\FormFields as EshopFormFields;\n\n/**\n * Trimm FormFields.\n */\ninterface FormFieldsTrimmerInterface\n{\n    /**\n     * Returns trimmed fields.\n     *\n     * @param EshopFormFields $formFields\n     */\n    public function trim(EshopFormFields $formFields);\n}\n"
  },
  {
    "path": "source/Core/Form/UpdatableFieldsConstructor.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Form;\n\nuse OxidEsales\\Eshop\\Core\\Model\\FieldNameHelper;\nuse OxidEsales\\Eshop\\Core\\Contract\\AbstractUpdatableFields;\n\n/**\n * Provides creators for cleaners of fields which could be updated by a customer.\n */\nclass UpdatableFieldsConstructor\n{\n    /**\n     * Get cleaner for field list which are allowed to be submitted in a form.\n     *\n     * @param AbstractUpdatableFields $updatableFields\n     *\n     * @return FormFieldsCleaner\n     */\n    public function getAllowedFieldsCleaner(AbstractUpdatableFields $updatableFields)\n    {\n        $helper = oxNew(FieldNameHelper::class);\n        $allowedFields = $helper->getFullFieldNames($updatableFields->getTableName(), $updatableFields->getUpdatableFields());\n\n        $updatableFields = oxNew(\\OxidEsales\\Eshop\\Core\\Form\\FormFields::class, $allowedFields);\n\n        return oxNew(\\OxidEsales\\Eshop\\Core\\Form\\FormFieldsCleaner::class, $updatableFields);\n    }\n}\n"
  },
  {
    "path": "source/Core/GenericImport/GenericImport.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\GenericImport;\n\nuse Exception;\nuse OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\ImportObject;\n\n/**\n * Class responsible for generic import functionality.\n */\nclass GenericImport\n{\n    const ERROR_USER_NO_RIGHTS = 'Not sufficient rights to perform operation!';\n    const ERROR_NO_INIT = 'Init not executed, Access denied!';\n\n    /** @var array Import objects types. */\n    protected $objects = [\n        'A' => 'Article',\n        'K' => 'Category',\n        'H' => 'Vendor',\n        'C' => 'CrossSelling',\n        'Z' => 'Accessories2Article',\n        'T' => 'Article2Category',\n        'I' => 'Article2Action',\n        'P' => 'ScalePrice',\n        'U' => 'User',\n        'O' => 'Order',\n        'R' => 'OrderArticle',\n        'N' => 'Country',\n        'Y' => 'ArticleExtends',\n    ];\n\n    /** @var string Imported data array. */\n    protected $importType = null;\n\n    /** @var array Imported id array */\n    protected $importedIds = [];\n\n    /** @var string Return message after import. */\n    protected $returnMessage;\n\n    /** @var string Csv file field terminator. */\n    protected $defaultStringTerminator = ';';\n\n    /** @var string Csv file field encloser. */\n    protected $defaultStringEncloser = '\"';\n\n    /** @var bool CSV file contains header or not. */\n    protected $csvContainsHeader = null;\n\n    /** @var string Import file location. */\n    protected $importFilePath;\n\n    /** @var bool */\n    protected $isInitialized = false;\n\n    /** @var int */\n    protected $userId = null;\n\n    /** @var array */\n    protected $statistics = [];\n\n    /** @var bool Whether import was retried. */\n    protected $retried = false;\n\n    /** @var array CSV file fields array. */\n    protected $csvFileFieldsOrder = [];\n\n    /** @var int Maximum length of imported line. */\n    protected $maxLineLength = 8192;\n\n    /**\n     * Init parameters needed for import.\n     * Creates Objects, checks Rights etc.\n     *\n     * @throws Exception\n     *\n     * @return boolean\n     */\n    public function init()\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $user = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n        $user->loadAdminUser();\n\n        $rights = $user->getFieldData('oxrights');\n        if ($rights === 'malladmin' || (int)$rights === (int)$config->getShopId()) {\n            $this->isInitialized = true;\n            $this->userId = $user->getId();\n        } else {\n            //user does not have sufficient rights for shop\n            throw new Exception(self::ERROR_USER_NO_RIGHTS);\n        }\n\n        return $this->isInitialized;\n    }\n\n    /**\n     * Get import object according import type.\n     *\n     * @param string $type Import object type.\n     *\n     * @return ImportObject\n     */\n    public function getImportObject($type)\n    {\n        $this->importType = $type;\n        $result = null;\n        try {\n            $importType = $this->getImportType();\n            $result = $this->createImportObject($importType);\n        } catch (Exception $e) {\n        }\n\n        return $result;\n    }\n\n    /**\n     * Set import object type prefix.\n     *\n     * @param string $type Import type prefix.\n     */\n    public function setImportType($type)\n    {\n        $this->importType = $type;\n    }\n\n    /**\n     * Set CSV file columns names.\n     *\n     * @param array $csvFields CSV fields.\n     */\n    public function setCsvFileFieldsOrder($csvFields)\n    {\n        $this->csvFileFieldsOrder = $csvFields;\n    }\n\n    /**\n     * Set if CSV file contains header row.\n     *\n     * @param bool $csvContainsHeader Whether imported file has a header row.\n     */\n    public function setCsvContainsHeader($csvContainsHeader)\n    {\n        $this->csvContainsHeader = $csvContainsHeader;\n    }\n    /**\n     * Main import method, whole import of all types via a given csv file is done here.\n     *\n     * @param string $importFilePath Full path of the CSV file.\n     *\n     * @return string\n     */\n    public function importFile($importFilePath = null)\n    {\n        $this->returnMessage = '';\n        $this->importFilePath = $importFilePath;\n\n        //init with given data\n        try {\n            $this->init();\n        } catch (Exception $ex) {\n            return $this->returnMessage = 'ERPGENIMPORT_ERROR_USER_NO_RIGHTS';\n        }\n\n        $file = fopen($this->importFilePath, 'r');\n\n        if (is_resource($file)) {\n            $data = [];\n            while (($row = fgetcsv($file, $this->maxLineLength, $this->getCsvFieldsTerminator(), $this->getCsvFieldsEncolser())) !== false) {\n                $data[] = $this->csvTextConvert($row, false);\n            }\n            fclose($file);\n\n            if ($this->csvContainsHeader) {\n                array_shift($data);\n            }\n\n            try {\n                $this->importData($data);\n            } catch (Exception $ex) {\n                echo $ex->getMessage();\n                $this->returnMessage = 'ERPGENIMPORT_ERROR_DURING_IMPORT';\n            }\n        } else {\n            $this->returnMessage = 'ERPGENIMPORT_ERROR_WRONG_FILE';\n        }\n\n        return $this->returnMessage;\n    }\n\n    /**\n     * Performs import action.\n     *\n     * @param array $data\n     */\n    public function importData($data)\n    {\n        foreach ($data as $key => $row) {\n            if ($row) {\n                try {\n                    $success = $this->importOne($row);\n                    $errorMessage = '';\n                } catch (Exception $e) {\n                    $success = false;\n                    $errorMessage = $e->getMessage();\n                }\n\n                $this->statistics[$key] = ['r' => $success, 'm' => $errorMessage];\n            }\n        }\n\n        $this->afterImport($data);\n    }\n\n    /**\n     * Returns statistics information about import.\n     *\n     * @return array\n     */\n    public function getStatistics()\n    {\n        return $this->statistics;\n    }\n\n    /**\n     * Returns count of imported rows, total, during import.\n     *\n     * @return int $_iImportedRowCount\n     */\n    public function getImportedRowCount()\n    {\n        return count($this->importedIds);\n    }\n\n    /**\n     * Returns allowed for import objects list.\n     *\n     * @return array\n     */\n    public function getImportObjectsList()\n    {\n        $importObjects = [];\n        foreach ($this->objects as $sKey => $importType) {\n            $type = $this->createImportObject($importType);\n            $importObjects[$sKey] = $type->getBaseTableName();\n        }\n\n        return $importObjects;\n    }\n\n    /**\n     * Main Import Handler, imports one row/call/object...\n     * returns true if there were any data processed, and\n     * master loop should run import again.\n     *\n     * after importing, fills $this->_aStatistics[$this->_iIdx] with array\n     * of r=>(bool)result, m=>(string)error message\n     *\n     * @param array $data\n     *\n     * @return bool\n     */\n    protected function importOne($data)\n    {\n        $type = $this->getImportType();\n        $importObject = $this->createImportObject($type);\n        $data = $this->mapFields($data);\n\n        $this->checkAccess($importObject, true);\n\n        $id = $importObject->import($data);\n        if ($id) {\n            $this->addImportedId($id);\n        }\n\n        return (bool) $id;\n    }\n\n    /**\n     * Performs after import actions.\n     * If any error occurred during import tries to run import again and marks retried as true.\n     * If after running import second time all of the records failed, stops.\n     *\n     * @param array $data\n     */\n    protected function afterImport($data)\n    {\n        $statistics = $this->getStatistics();\n\n        $dataForRetry = [];\n        foreach ($statistics as $key => $value) {\n            if ($value['r'] == false) {\n                $this->returnMessage .= \"File[\" . $this->importFilePath . \"] - dataset number: $key - Error: \" . $value['m'] . \" ---<br> \" . PHP_EOL;\n                $dataForRetry[$key] = $data[$key];\n            }\n        }\n\n        if (!empty($dataForRetry) && (!$this->retried || count($dataForRetry) != count($data))) {\n            $this->retried = true;\n            $this->returnMessage = '';\n            $this->importData($dataForRetry);\n        }\n    }\n\n    /**\n     * Gets import object type according type prefix.\n     *\n     * @throws Exception if no such import type prefix\n     *\n     * @return string\n     */\n    protected function getImportType()\n    {\n        $type = $this->importType;\n\n        if (strlen($type) != 1 || !array_key_exists($type, $this->objects)) {\n            throw new Exception('Error unknown command: ' . $type);\n        } else {\n            return $this->objects[$type];\n        }\n    }\n\n    /** Adds true to $_aImportedIds where key is given.\n     *\n     * @param mixed $id - given key\n     */\n    protected function addImportedId($id)\n    {\n        if (!array_key_exists($id, $this->importedIds)) {\n            $this->importedIds[$id] = true;\n        }\n    }\n\n    /**\n     * Maps numeric array to assoc. Array\n     *\n     * @param array $data numeric indices\n     *\n     * @return array assoc. indices\n     */\n    protected function mapFields($data)\n    {\n        $result = [];\n        $index = 0;\n\n        foreach ($this->csvFileFieldsOrder as $value) {\n            if (!empty($value)) {\n                if (strtolower($data[$index]) == 'null') {\n                    $result[$value] = null;\n                } else {\n                    $result[$value] = $data[$index];\n                }\n            }\n            $index++;\n        }\n\n        return $result;\n    }\n\n    /**\n     * Parses and replaces special chars.\n     *\n     * @param string $text input text\n     * @param bool   $mode true = Text2CSV, false = CSV2Text\n     *\n     * @return string\n     */\n    protected function csvTextConvert($text, $mode)\n    {\n        $search = [chr(13), chr(10), '\\'', '\"'];\n        $replace = ['&#13;', '&#10;', '&#39;', '&#34;'];\n\n        if ($mode) {\n            $text = str_replace($search, $replace, $text);\n        } else {\n            $text = str_replace($replace, $search, $text);\n        }\n\n        return $text;\n    }\n\n    /**\n     * Set csv field terminator symbol.\n     *\n     * @return string\n     */\n    protected function getCsvFieldsTerminator()\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $fieldTerminator = $config->getConfigParam('sGiCsvFieldTerminator');\n\n        if (!$fieldTerminator) {\n            $fieldTerminator = $config->getConfigParam('sCSVSign');\n        }\n        if (!$fieldTerminator) {\n            $fieldTerminator = $this->defaultStringTerminator;\n        }\n\n        return $fieldTerminator;\n    }\n\n    /**\n     * Get csv field encloser symbol.\n     *\n     * @return string\n     */\n    protected function getCsvFieldsEncolser()\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        if ($fieldEncloser = $config->getConfigParam('sGiCsvFieldEncloser')) {\n            return $fieldEncloser;\n        }\n\n        return $this->defaultStringEncloser;\n    }\n\n    /**\n     * Checks if user has sufficient rights.\n     *\n     * @param ImportObject $importObject  Data type object\n     * @param boolean      $isWriteAction Check for write permissions\n     *\n     * @throws Exception\n     */\n    protected function checkAccess($importObject, $isWriteAction)\n    {\n        if (!$this->isInitialized) {\n            throw new Exception(self::ERROR_NO_INIT);\n        }\n    }\n\n    /**\n     * Creates and returns import object.\n     *\n     * @param string $type Type name in objects dir.\n     *\n     * @return ImportObject\n     */\n    protected function createImportObject($type)\n    {\n        $className = __NAMESPACE__ . \"\\\\ImportObject\\\\\" . $type;\n\n        return oxNew($className);\n    }\n}\n"
  },
  {
    "path": "source/Core/GenericImport/ImportObject/Accessories2Article.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject;\n\n/**\n * Import object for assignment of accessories to articles.\n */\nclass Accessories2Article extends \\OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\ImportObject\n{\n    /** @var string Database table name. */\n    protected $tableName = 'oxaccessoire2article';\n}\n"
  },
  {
    "path": "source/Core/GenericImport/ImportObject/Article.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject;\n\n/**\n * Import object for Articles.\n */\nclass Article extends \\OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\ImportObject\n{\n    /** @var string Database table name. */\n    protected $tableName = 'oxarticles';\n\n    /** @var string Shop object name. */\n    protected $shopObjectName = 'oxArticle';\n\n    /**\n     * Imports article. Returns import status.\n     *\n     * @param array $data DB row array.\n     *\n     * @return string $oxid Id on success, bool FALSE on failure.\n     */\n    public function import($data)\n    {\n        if (isset($data['OXID'])) {\n            $this->checkIdField($data['OXID']);\n        }\n\n        return parent::import($data);\n    }\n\n    /**\n     * Issued before saving an object.\n     * Can modify $data array before saving.\n     * Set default value of OXSTOCKFLAG to 1 according to eShop admin functionality.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Model\\BaseModel $shopObject        shop object\n     * @param array                                  $data              data to prepare\n     * @param bool                                   $allowCustomShopId if allow custom shop id\n     *\n     * @return array\n     */\n    protected function preAssignObject($shopObject, $data, $allowCustomShopId)\n    {\n        if (!isset($data['OXSTOCKFLAG'])) {\n            if (!$data['OXID'] || !$shopObject->exists($data['OXID'])) {\n                $data['OXSTOCKFLAG'] = 1;\n            }\n        }\n\n        return parent::preAssignObject($shopObject, $data, $allowCustomShopId);\n    }\n\n    /**\n     * Post saving hook. can finish transactions if needed or ajust related data.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Article $shopObject Shop object.\n     * @param array                                       $data       Data to save.\n     *\n     * @return mixed data to return\n     */\n    protected function postSaveObject($shopObject, $data)\n    {\n        $articleId = $shopObject->getId();\n        $shopObject->onChange(null, $articleId, $articleId);\n\n        return $articleId;\n    }\n\n    /**\n     * Creates shop object.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n     */\n    protected function createShopObject()\n    {\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\Article $shopObject */\n        $shopObject = parent::createShopObject();\n        $shopObject->setNoVariantLoading(true);\n\n        return $shopObject;\n    }\n}\n"
  },
  {
    "path": "source/Core/GenericImport/ImportObject/Article2Action.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject;\n\n/**\n * Import object for Articles assignment to actions.\n */\nclass Article2Action extends \\OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\ImportObject\n{\n    /** @var string Database table name. */\n    protected $tableName = 'oxactions2article';\n}\n"
  },
  {
    "path": "source/Core/GenericImport/ImportObject/Article2Category.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject;\n\n/**\n * Import object for Articles assignment to categories.\n */\nclass Article2Category extends \\OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\ImportObject\n{\n    /** @var string Database table name. */\n    protected $tableName = 'oxobject2category';\n\n    /** @var array List of database key fields (i.e. oxid). */\n    protected $keyFieldList = [\n        'OXOBJECTID' => 'OXOBJECTID',\n        'OXCATNID'   => 'OXCATNID',\n    ];\n}\n"
  },
  {
    "path": "source/Core/GenericImport/ImportObject/ArticleExtends.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject;\n\nuse oxI18n;\n\n/**\n * Import object for Article Extends.\n */\nclass ArticleExtends extends \\OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\ImportObject\n{\n    /** @var string Database table name. */\n    protected $tableName = 'oxartextends';\n\n    /** @var string Shop object name. */\n    protected $shopObjectName = 'oxI18n';\n\n    /**\n     * Creates shop object.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel\n     */\n    protected function createShopObject()\n    {\n        $shopObject = parent::createShopObject();\n        $shopObject->init('oxartextends');\n\n        return $shopObject;\n    }\n}\n"
  },
  {
    "path": "source/Core/GenericImport/ImportObject/Category.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject;\n\n/**\n * Import object for Categories.\n */\nclass Category extends \\OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\ImportObject\n{\n    /** @var string Database table name. */\n    protected $tableName = 'oxcategories';\n\n    /** @var string Shop object name. */\n    protected $shopObjectName = 'oxcategory';\n\n    /**\n     * Issued before saving an object. can modify aData for saving.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Model\\BaseModel $shopObject        Shop object.\n     * @param array                                  $data              Data to prepare.\n     * @param bool                                   $allowCustomShopId If allow custom shop id.\n     *\n     * @return array\n     */\n    protected function preAssignObject($shopObject, $data, $allowCustomShopId)\n    {\n        $data = parent::preAssignObject($shopObject, $data, $allowCustomShopId);\n\n        if (!$data['OXPARENTID']) {\n            $data['OXPARENTID'] = 'oxrootid';\n        }\n\n        return $data;\n    }\n}\n"
  },
  {
    "path": "source/Core/GenericImport/ImportObject/Country.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject;\n\n/**\n * Import object for Countries.\n */\nclass Country extends \\OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\ImportObject\n{\n    /** @var string Database table name. */\n    protected $tableName = 'oxcountry';\n\n    /** @var string Shop object name. */\n    protected $shopObjectName = 'oxcountry';\n}\n"
  },
  {
    "path": "source/Core/GenericImport/ImportObject/CrossSelling.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject;\n\n/**\n * Import object for Cross-selling.\n */\nclass CrossSelling extends \\OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\ImportObject\n{\n    /** @var string Database table name. */\n    protected $tableName = 'oxobject2article';\n\n    /** @var array List of database key fields (i.e. oxid). */\n    protected $keyFieldList = [\n        'OXARTICLENID' => 'OXARTICLENID',\n        'OXOBJECTID'   => 'OXOBJECTID'\n    ];\n}\n"
  },
  {
    "path": "source/Core/GenericImport/ImportObject/ImportObject.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject;\n\nuse Exception;\nuse OxidEsales\\Eshop\\Core\\GenericImport\\GenericImport;\nuse OxidEsales\\Eshop\\Core\\Model\\BaseModel;\nuse OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\nabstract class ImportObject\n{\n    /** @var string Database table name. */\n    protected $tableName = null;\n\n    /** @var array List of database fields, to which data should be imported. */\n    protected $fieldList = null;\n\n    /** @var array List of database key fields (i.e. oxid). */\n    protected $keyFieldList = null;\n\n    /** @var string Shop object name. */\n    protected $shopObjectName = null;\n\n    /**\n     * Getter for _sTableName\n     *\n     * @return string\n     */\n    public function getBaseTableName()\n    {\n        return $this->tableName;\n    }\n\n    /**\n     * setter for field list\n     *\n     * @param array $aFieldList fields to set\n     */\n    public function setFieldList($aFieldList)\n    {\n        $this->fieldList = $aFieldList;\n    }\n\n    /**\n     * Basic access check for writing data, checks for same shopId, should be overridden if field oxshopid does not\n     * exist.\n     *\n     * @param BaseModel $shopObject Loaded shop object.\n     * @param array                                  $data       Fields to be written, null for default.\n     *\n     * @throws Exception on now access\n     */\n    public function checkWriteAccess($shopObject, $data = null)\n    {\n        if ($shopObject->isDerived()) {\n            throw new Exception(GenericImport::ERROR_USER_NO_RIGHTS);\n        }\n    }\n\n    /**\n     * Basic access check for creating new objects\n     *\n     * @param array $data fields to be written\n     *\n     * @throws Exception on now access\n     */\n    public function checkCreateAccess($data)\n    {\n    }\n\n    /**\n     * Insert or Update a Row into database.\n     *\n     * @param array $data Assoc. array with field names, values what should be stored in this table.\n     *\n     * @return string|false\n     */\n    public function import($data)\n    {\n        return $this->saveObject($data, false);\n    }\n\n    /**\n     * Used for the RR implementation.\n     *\n     * @return array\n     */\n    public function getRightFields()\n    {\n        $accessRightFields = [];\n        if (!$this->fieldList) {\n            $this->getFieldList();\n        }\n\n        foreach ($this->fieldList as $field) {\n            $accessRightFields[] = strtolower($this->tableName . '__' . $field);\n        }\n\n        return $accessRightFields;\n    }\n\n    /**\n     * Returns the predefined field list.\n     *\n     * @return array\n     */\n    public function getFieldList()\n    {\n        $shopObject = $this->createShopObject();\n        $viewName = $shopObject->getViewName();\n        $fields = str_ireplace(\n            \"`$viewName`.\",\n            '',\n            strtoupper($shopObject->getSelectFields())\n        );\n        $fields = str_ireplace(\n            [' ', '`'],\n            ['', ''],\n            $fields\n        );\n        $this->fieldList = explode(',', $fields);\n\n        return $this->fieldList;\n    }\n\n    /**\n     * Returns the keylist array.\n     *\n     * @return array\n     */\n    public function getKeyFields()\n    {\n        return $this->keyFieldList;\n    }\n\n    /**\n     * Getter for _sShopObjectName.\n     *\n     * @return string\n     */\n    protected function getShopObjectName()\n    {\n        return $this->shopObjectName;\n    }\n\n    /**\n     * Returns table or View name.\n     *\n     * @return string\n     */\n    protected function getTableName()\n    {\n        $shopId = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId();\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n\n        return $tableViewNameGenerator->getViewName($this->tableName, -1, $shopId);\n    }\n\n    /**\n     * Issued before saving an object. can modify aData for saving.\n     *\n     * @param BaseModel $shopObject        shop object\n     * @param array                                  $data              data to prepare\n     * @param bool                                   $allowCustomShopId if allow custom shop id\n     *\n     * @return array\n     */\n    protected function preAssignObject($shopObject, $data, $allowCustomShopId)\n    {\n        if (isset($data['OXSHOPID'])) {\n            $data['OXSHOPID'] = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId();\n        }\n\n        if (!isset($data['OXID'])) {\n            $data['OXID'] = $this->getOxidFromKeyFields($data);\n        }\n\n        // null values support\n        foreach ($data as $key => $val) {\n            if (!strlen((string) $val)) {\n                // oxBase will quote it as string if db does not support null for this field\n                $data[$key] = null;\n            }\n        }\n\n        return $data;\n    }\n\n    /**\n     * Prepares object for saving in shop.\n     * Returns true if save can proceed further.\n     *\n     * @param BaseModel $shopObject Shop object.\n     * @param array                                  $data       Data for importing.\n     *\n     * @return boolean\n     */\n    protected function preSaveObject($shopObject, $data)\n    {\n        return true;\n    }\n\n    /**\n     * Saves object data.\n     *\n     * @param array $data              data for saving\n     * @param bool  $allowCustomShopId allow custom shop id\n     *\n     * @return string|false\n     */\n    protected function saveObject($data, $allowCustomShopId)\n    {\n        $shopObject = $this->createShopObject();\n\n        foreach ($data as $key => $value) {\n            // change case to UPPER\n            $uppercaseKey = strtoupper($key);\n            if (!isset($data[$uppercaseKey])) {\n                unset($data[$key]);\n                $data[$uppercaseKey] = $value;\n            }\n        }\n\n        if (method_exists($shopObject, 'setForceCoreTableUsage')) {\n            $shopObject->setForceCoreTableUsage(true);\n        }\n\n        $isLoaded = false;\n        if ($data['OXID']) {\n            $isLoaded = $shopObject->load($data['OXID']);\n        }\n\n        $data = $this->preAssignObject($shopObject, $data, $allowCustomShopId);\n\n        if ($isLoaded) {\n            $this->checkWriteAccess($shopObject, $data);\n        } else {\n            $this->checkCreateAccess($data);\n        }\n\n        $shopObject->assign($data);\n\n        if ($allowCustomShopId) {\n            $shopObject->setIsDerived(false);\n        }\n\n        if ($this->preSaveObject($shopObject, $data)) {\n            // store\n            if ($shopObject->save()) {\n                return $this->postSaveObject($shopObject, $data);\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Post saving hook. can finish transactions if needed or adjust related data.\n     *\n     * @param BaseModel $shopObject Shop object.\n     * @param array                                  $data       Data to save.\n     *\n     * @return mixed data to return\n     */\n    protected function postSaveObject($shopObject, $data)\n    {\n        // returning ID on success\n        return $shopObject->getId();\n    }\n\n    /**\n     * Returns oxid of this data type from key fields.\n     *\n     * @param array $data Data for object.\n     *\n     * @return string\n     */\n    protected function getOxidFromKeyFields($data)\n    {\n        if (!is_array($this->getKeyFields())) {\n            return null;\n        }\n\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $queryWherePart = [];\n        $allKeysExists = true;\n        foreach ($this->getKeyFields() as $key) {\n            if (array_key_exists($key, $data)) {\n                $queryWherePart[] = $key . '=' . $database->quote($data[$key]);\n            } else {\n                $allKeysExists = false;\n            }\n        }\n\n        if ($allKeysExists) {\n            $query = 'SELECT OXID FROM ' . $this->getTableName() . ' WHERE ' . implode(' AND ', $queryWherePart);\n\n            return $database->getOne($query);\n        }\n\n        return null;\n    }\n\n    /**\n     * Checks if user is allowed to edit in this shop.\n     *\n     * @param int $shopId shop id\n     *\n     * @return bool\n     */\n    protected function isAllowedToEdit($shopId)\n    {\n        $user = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class);\n        $user->loadAdminUser();\n\n        if ($user->oxuser__oxrights->value == \"malladmin\" || $user->oxuser__oxrights->value == (int) $shopId) {\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Checks if id field is valid.\n     *\n     * @param string $id field check id\n     *\n     * @throws Exception\n     */\n    protected function checkIdField($id)\n    {\n        if (!isset($id) || !$id) {\n            throw new Exception(\"ERROR: Articlenumber/ID missing!\");\n        } elseif (strlen($id) > 32) {\n            throw new Exception(\"ERROR: Articlenumber/ID longer then allowed (32 chars max.)!\");\n        }\n    }\n\n    /**\n     * @return BaseModel\n     */\n    protected function createShopObject()\n    {\n        $objectName = $this->getShopObjectName();\n        if ($objectName) {\n            $shopObject = oxNew($objectName);\n            if ($shopObject instanceof MultiLanguageModel) {\n                $shopObject->setLanguage(0);\n                $shopObject->setEnableMultilang(false);\n            }\n        } else {\n            $shopObject = oxNew(BaseModel::class);\n            $shopObject->init($this->getBaseTableName());\n        }\n\n        return $shopObject;\n    }\n}\n"
  },
  {
    "path": "source/Core/GenericImport/ImportObject/Order.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject;\n\n/**\n * Import object for Orders.\n */\nclass Order extends \\OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\ImportObject\n{\n    /** @var string Database table name. */\n    protected $tableName = 'oxorder';\n\n    /** @var string Shop object name. */\n    protected $shopObjectName = 'oxorder';\n}\n"
  },
  {
    "path": "source/Core/GenericImport/ImportObject/OrderArticle.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject;\n\n/**\n * Import object for Order Articles.\n */\nclass OrderArticle extends \\OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\ImportObject\n{\n    /** @var string Database table name. */\n    protected $tableName = 'oxorderarticles';\n\n    /** @var string Shop object name. */\n    protected $shopObjectName = 'oxorderarticle';\n\n    /**\n     * issued before saving an object. can modify aData for saving\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Model\\BaseModel $shopObject        oxBase child for object\n     * @param array                                  $data              Data for object\n     * @param bool                                   $allowCustomShopId If true then AllowCustomShopId\n     *\n     * @return array\n     */\n    protected function preAssignObject($shopObject, $data, $allowCustomShopId)\n    {\n        $data = parent::preAssignObject($shopObject, $data, $allowCustomShopId);\n\n        // check if data is not serialized\n        $persParamValues = @unserialize($data['OXPERSPARAM']);\n        if (!is_array($persParamValues)) {\n            // data is a string with | separation, prepare for oxid\n            $persParamValues = explode(\"|\", $data['OXPERSPARAM']);\n            $data['OXPERSPARAM'] = serialize($persParamValues);\n        }\n        if (array_key_exists('OXORDERSHOPID', $data)) {\n            $data['OXORDERSHOPID'] = $this->getOrderShopId($data['OXORDERSHOPID']);\n        }\n\n        return $data;\n    }\n\n    /**\n     * Returns formed order shop id, which should be set to data array.\n     *\n     * @param string $currentShopId\n     *\n     * @return string\n     */\n    protected function getOrderShopId($currentShopId)\n    {\n        return 1;\n    }\n}\n"
  },
  {
    "path": "source/Core/GenericImport/ImportObject/ScalePrice.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject;\n\n/**\n * Import object for Scale Prices.\n */\nclass ScalePrice extends \\OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\ImportObject\n{\n    /** @var string Database table name. */\n    protected $tableName = 'oxprice2article';\n\n    /** @var array List of database key fields (i.e. oxid). */\n    protected $keyFieldList = [\n        'OXID' => 'OXID'\n    ];\n}\n"
  },
  {
    "path": "source/Core/GenericImport/ImportObject/User.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject;\n\nuse Exception;\n\n/**\n * Import object for Users.\n */\nclass User extends \\OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\ImportObject\n{\n    /** @var string Database table name. */\n    protected $tableName = 'oxuser';\n\n    /** @var string Shop object name. */\n    protected $shopObjectName = 'oxuser';\n\n    /**\n     * Imports user. Returns import status.\n     *\n     * @param array $data db row array\n     *\n     * @throws Exception If user exists with provided OXID, throw an exception.\n     *\n     * @return string $oxid on success, bool FALSE on failure\n     */\n    public function import($data)\n    {\n        if (isset($data['OXUSERNAME'])) {\n            $id = $data['OXID'];\n            $userName = $data['OXUSERNAME'];\n\n            $user = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\User::class, \"core\");\n            $user->oxuser__oxusername = new \\OxidEsales\\Eshop\\Core\\Field($userName, \\OxidEsales\\Eshop\\Core\\Field::T_RAW);\n\n            if ($user->exists($id) && $id != $user->getId()) {\n                throw new Exception(\"USER $userName already exists!\");\n            }\n        }\n\n        return parent::import($data);\n    }\n}\n"
  },
  {
    "path": "source/Core/GenericImport/ImportObject/Vendor.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject;\n\n/**\n * Import object for Vendors.\n */\nclass Vendor extends \\OxidEsales\\Eshop\\Core\\GenericImport\\ImportObject\\ImportObject\n{\n    /** @var string Database table name. */\n    protected $tableName = 'oxvendor';\n\n    /** @var string Shop object name. */\n    protected $shopObjectName = 'oxvendor';\n}\n"
  },
  {
    "path": "source/Core/Hasher.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Hasher abstract class\n *\n * @deprecated since v6.4.0 (2019-03-15); `\\OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\PasswordServiceBridgeInterface`\n *                                        was added as the new default for hashing passwords. Hashing passwords with\n *                                        MD5 and SHA512 is still supported in order support login with older\n *                                        password hashes. Therefor this class might not be\n *                                        compatible with the current passhword hash any more.\n */\nabstract class Hasher\n{\n    /**\n     * Hash string.\n     *\n     * @param string $string string for hashing.\n     *\n     * @return string\n     */\n    abstract public function hash($string);\n}\n"
  },
  {
    "path": "source/Core/Header.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * HTTP headers formator.\n * Collects HTTP headers and form HTTP header.\n */\nclass Header\n{\n    protected $_aHeader = [];\n\n    /**\n     * Sets header.\n     *\n     * @param string $header header value.\n     */\n    public function setHeader($header)\n    {\n        $header = str_replace([\"\\n\", \"\\r\"], '', $header);\n        $this->_aHeader[] = (string) $header . \"\\r\\n\";\n    }\n\n    /**\n     * Return header.\n     *\n     * @return array\n     */\n    public function getHeader()\n    {\n        return $this->_aHeader;\n    }\n\n    /**\n     * Outputs HTTP header.\n     */\n    public function sendHeader()\n    {\n        foreach ($this->_aHeader as $header) {\n            if (isset($header)) {\n                header($header);\n            }\n        }\n    }\n\n    /**\n     * Set to not cacheable.\n     *\n     * @todo check browser for different no-cache signs.\n     */\n    public function setNonCacheable()\n    {\n        $header = \"Cache-Control: no-cache;\";\n        $this->setHeader($header);\n    }\n}\n"
  },
  {
    "path": "source/Core/InputValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Address;\nuse OxidEsales\\Eshop\\Application\\Model\\User;\nuse OxidEsales\\Eshop\\Core\\Exception\\ArticleInputException;\nuse OxidEsales\\Eshop\\Core\\Exception\\InputException;\nuse OxidEsales\\Eshop\\Core\\Exception\\StandardException;\nuse OxidEsales\\Eshop\\Core\\Exception\\UserException;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Email\\EmailValidatorServiceBridgeInterface;\n\n/**\n * Class for validating input.\n */\nclass InputValidator extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Invalid account number error code for template.\n     */\n    const INVALID_ACCOUNT_NUMBER = -5;\n\n    /**\n     * Invalid bank number error code for template.\n     */\n    const INVALID_BANK_CODE = -4;\n\n    /**\n     * Input validation errors.\n     *\n     * @var array\n     */\n    protected $_aInputValidationErrors = [];\n\n\n    protected $_oCompanyVatInValidator = null;\n\n    /**\n     * Required fields for debit cards.\n     *\n     * @var array\n     */\n    protected $_aRequiredDCFields = [\n        'lsbankname',\n        'lsktonr',\n        'lsktoinhaber'\n    ];\n\n    /**\n     * Class constructor. The constructor is defined in order to be possible to call parent::__construct() in modules.\n     */\n    public function __construct()\n    {\n    }\n\n    /**\n     * Validates basket amount.\n     *\n     * @param float $amount Amount of article.\n     *\n     * @throws ArticleInputException If amount is not numeric or smaller 0.\n     *\n     * @return float\n     */\n    public function validateBasketAmount($amount)\n    {\n        $amount = str_replace(',', '.', $amount);\n\n        if (!is_numeric($amount) || $amount < 0) {\n            /**\n             * @var \\OxidEsales\\Eshop\\Core\\Exception\\ArticleInputException $exception\n             */\n            $exception = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ArticleInputException::class);\n            $exception->setMessage(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('ERROR_MESSAGE_INPUT_INVALIDAMOUNT'));\n            throw $exception;\n        }\n\n        if (!\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blAllowUnevenAmounts')) {\n            $amount = round((string) $amount);\n        }\n\n        //negative amounts are not allowed\n        //$dAmount = abs($dAmount);\n\n        return $amount;\n    }\n\n    /**\n     * Checks if user name does not break logic:\n     *  - if user wants to UPDATE his login name, performing check if\n     *    user entered correct password\n     *  - additionally checking for user name duplicates. This is usually\n     *    needed when creating new users.\n     * On any error exception is thrown.\n     *\n     * @param User   $user       Active user.\n     * @param string $login      User preferred login name.\n     * @param array  $invAddress User information.\n     *\n     * @return string login name\n     */\n    public function checkLogin($user, $login, $invAddress)\n    {\n        $login = (isset($invAddress['oxuser__oxusername']))\n            ? $invAddress['oxuser__oxusername']\n            : $login;\n\n        if (\n            isset($user->oxuser__oxpassword->value) &&\n            $user->oxuser__oxpassword->value &&\n            $login != $user->oxuser__oxusername->value\n        ) {\n            $newPassword = (isset($invAddress['oxuser__oxpassword']) && $invAddress['oxuser__oxpassword'])\n                ? $invAddress['oxuser__oxpassword']\n                : Registry::getRequest()->getRequestEscapedParameter('user_password');\n\n            if (!$newPassword) {\n                $message = Registry::getLang()->translateString('ERROR_MESSAGE_INPUT_NOTALLFIELDS');\n                $exception = oxNew(InputException::class, $message);\n\n                $this->addValidationError(\"oxuser__oxpassword\", $exception);\n\n                return $login;\n            } else {\n                if (!$user->isSamePassword($newPassword)) {\n                    $message = Registry::getLang()->translateString('ERROR_MESSAGE_PASSWORD_DO_NOT_MATCH');\n                    $exception = oxNew(UserException::class, $message);\n\n                    $this->addValidationError(\"oxuser__oxpassword\", $exception);\n\n                    return $login;\n                }\n            }\n        }\n\n        if ($user->checkIfEmailExists($login)) {\n            $message = Registry::getLang()->translateString('ERROR_MESSAGE_USER_USEREXISTS');\n            $exception = oxNew(UserException::class, $message);\n\n            $this->addValidationError(\"oxuser__oxusername\", $exception);\n\n            return $login;\n        }\n\n        return $login;\n    }\n\n    /**\n     * Checks if email (used as login) is not empty and is\n     * valid.\n     *\n     * @param User   $user  Active user.\n     * @param string $email User email/login.\n     *\n     * @return null\n     */\n    public function checkEmail($user, $email)\n    {\n        // missing email address (user login name) ?\n        if (empty($email)) {\n            $exception = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\InputException::class);\n            $exception->setMessage(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('ERROR_MESSAGE_INPUT_NOTALLFIELDS'));\n\n            return $this->addValidationError(\"oxuser__oxusername\", $exception);\n        }\n\n        $emailValidator = ContainerFacade::get(EmailValidatorServiceBridgeInterface::class);\n        if (!$emailValidator->isEmailValid($email)) {\n            $exception = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\InputException::class);\n            $exception->setMessage(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('ERROR_MESSAGE_INPUT_NOVALIDEMAIL'));\n\n            return $this->addValidationError(\"oxuser__oxusername\", $exception);\n        }\n    }\n\n    /**\n     * Checking if user password is fine. In case of error\n     * exception is thrown\n     *\n     * @param User   $user                      Active user.\n     * @param string $newPassword               New user password.\n     * @param string $confirmationPassword      Retyped user password.\n     * @param bool   $shouldCheckPasswordLength Option to check password length.\n     *\n     * @return Exception\\StandardException|null\n     */\n    public function checkPassword($user, $newPassword, $confirmationPassword, $shouldCheckPasswordLength = false)\n    {\n        //  no password at all\n        if ($shouldCheckPasswordLength && Str::getStr()->strlen($newPassword) == 0) {\n            $exception = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\InputException::class);\n            $exception->setMessage(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('ERROR_MESSAGE_INPUT_EMPTYPASS'));\n\n            return $this->addValidationError(\"oxuser__oxpassword\", $exception);\n        }\n\n        if ($shouldCheckPasswordLength && Str::getStr()->strlen($newPassword) < $this->getPasswordLength()) {\n            $exception = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\InputException::class);\n            $exception->setMessage(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('ERROR_MESSAGE_PASSWORD_TOO_SHORT'));\n\n            return $this->addValidationError(\"oxuser__oxpassword\", $exception);\n        }\n\n        //  passwords do not match ?\n        if ($newPassword != $confirmationPassword) {\n            $exception = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\UserException::class);\n            $exception->setMessage(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('ERROR_MESSAGE_PASSWORD_DO_NOT_MATCH'));\n\n            return $this->addValidationError(\"oxuser__oxpassword\", $exception);\n        }\n    }\n\n    /**\n     * Min length of password.\n     *\n     * @return int\n     */\n    public function getPasswordLength()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam(\"iPasswordLength\") ?: 6;\n    }\n\n    /**\n     * Checking if all required fields were filled. In case of error\n     * exception is thrown\n     *\n     * @param User  $user            Active user.\n     * @param array $billingAddress  Billing address.\n     * @param array $deliveryAddress Delivery address.\n     */\n    public function checkRequiredFields($user, $billingAddress, $deliveryAddress)\n    {\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\RequiredAddressFields $requiredAddressFields */\n        $requiredAddressFields = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\RequiredAddressFields::class);\n\n        /** @var \\OxidEsales\\Eshop\\Application\\Model\\RequiredFieldsValidator $fieldsValidator */\n        $fieldsValidator = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\RequiredFieldsValidator::class);\n\n        /** @var User $user */\n        $user = oxNew(User::class);\n        $billingAddress = $this->setFields($user, $billingAddress);\n        $fieldsValidator->setRequiredFields($requiredAddressFields->getBillingFields());\n        $fieldsValidator->validateFields($billingAddress);\n        $invalidFields = $fieldsValidator->getInvalidFields();\n\n        if (!empty($deliveryAddress)) {\n            /** @var \\OxidEsales\\Eshop\\Application\\Model\\Address $deliveryAddress */\n            $deliveryAddress = $this->setFields(oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Address::class), $deliveryAddress);\n            $fieldsValidator->setRequiredFields($requiredAddressFields->getDeliveryFields());\n            $fieldsValidator->validateFields($deliveryAddress);\n            $invalidFields = array_merge($invalidFields, $fieldsValidator->getInvalidFields());\n        }\n\n        foreach ($invalidFields as $sField) {\n            $exception = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\InputException::class);\n            $exception->setMessage(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('ERROR_MESSAGE_INPUT_NOTALLFIELDS'));\n\n            $this->addValidationError($sField, $exception);\n        }\n    }\n\n    /**\n     * Creates oxAddress object from given array.\n     *\n     * @param User|Address $object\n     * @param array        $fields\n     *\n     * @return User|Address\n     */\n    private function setFields($object, $fields)\n    {\n        $fields = is_array($fields) ? $fields : [];\n        foreach ($fields as $sKey => $sValue) {\n            $object->$sKey = oxNew('oxField', $sValue);\n        }\n\n        return $object;\n    }\n\n    /**\n     * Checks if user defined countries (billing and delivery) are active.\n     *\n     * @param User  $user            Active user.\n     * @param array $invAddress      Billing address info.\n     * @param array $deliveryAddress Delivery address info.\n     */\n    public function checkCountries($user, $invAddress, $deliveryAddress)\n    {\n        $billingCountry = isset($invAddress['oxuser__oxcountryid']) ? $invAddress['oxuser__oxcountryid'] : null;\n        $deliveryCountry = isset($deliveryAddress['oxaddress__oxcountryid']) ? $deliveryAddress['oxaddress__oxcountryid'] : null;\n\n        if ($billingCountry || $deliveryCountry) {\n            $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n            if (($billingCountry == $deliveryCountry) || (!$billingCountry && $deliveryCountry) || ($billingCountry && !$deliveryCountry)) {\n                $billingCountry = $billingCountry ? $billingCountry : $deliveryCountry;\n                $query = \"select oxactive from oxcountry where oxid = :oxbillingid\";\n                $params = [\n                    'oxbillingid' => $billingCountry\n                ];\n            } else {\n                $query = \"select ( select oxactive from oxcountry where oxid = :oxbillingid ) and\n                              ( select oxactive from oxcountry where oxid = :oxdeliveryid ) \";\n                $params = [\n                    'oxbillingid' => $billingCountry,\n                    'oxdeliveryid' => $deliveryCountry,\n                ];\n            }\n\n            if (!$database->getOne($query, $params)) {\n                $exception = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\UserException::class);\n                $exception->setMessage(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('ERROR_MESSAGE_INPUT_NOTALLFIELDS'));\n\n                $this->addValidationError(\"oxuser__oxcountryid\", $exception);\n            }\n        }\n    }\n\n    /**\n     * Checks if user passed VAT id is valid. Exception is thrown\n     * if id is not valid.\n     *\n     * @param User  $user       Active user.\n     * @param array $invAddress User input array.\n     *\n     * @return null\n     */\n    public function checkVatId($user, $invAddress)\n    {\n        if ($this->hasRequiredParametersForVatInCheck($invAddress)) {\n            $country = $this->getCountry($invAddress['oxuser__oxcountryid']);\n\n            if ($country && $country->isInEU()) {\n                $vatInValidator = $this->getCompanyVatInValidator($country);\n\n                /** @var \\OxidEsales\\Eshop\\Application\\Model\\CompanyVatIn $oVatIn */\n                $oVatIn = oxNew('oxCompanyVatIn', $invAddress['oxuser__oxustid']);\n\n                if (!$vatInValidator->validate($oVatIn)) {\n                    /** @var \\OxidEsales\\Eshop\\Core\\Exception\\InputException $exception */\n                    $exception = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\InputException::class);\n                    $exception->setMessage(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('VAT_MESSAGE_' . $vatInValidator->getError()));\n\n                    return $this->addValidationError(\"oxuser__oxustid\", $exception);\n                }\n            }\n        } elseif (isset($invAddress['oxuser__oxustid']) && $invAddress['oxuser__oxustid'] && !$invAddress['oxuser__oxcompany']) {\n            /** @var \\OxidEsales\\Eshop\\Core\\Exception\\InputException $exception */\n            $exception = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\InputException::class);\n            $exception->setMessage(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('VAT_MESSAGE_COMPANY_MISSING'));\n\n            return $this->addValidationError(\"oxuser__oxcompany\", $exception);\n        }\n    }\n\n\n    /**\n     * Load and return Country object.\n     *\n     * @param string $countryId\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Country\n     */\n    protected function getCountry($countryId)\n    {\n        $country = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Country::class);\n        $country->load($countryId);\n\n        return $country;\n    }\n\n    /**\n     * Returns error array if input validation for current field and rule reported an error\n     *\n     * @return array\n     */\n    public function getFieldValidationErrors()\n    {\n        return $this->_aInputValidationErrors;\n    }\n\n    /**\n     * Returns first user input validation error.\n     *\n     * @return StandardException\n     */\n    public function getFirstValidationError()\n    {\n        $aErr = reset($this->_aInputValidationErrors);\n        if (is_array($aErr)) {\n            return reset($aErr);\n        }\n    }\n\n    /**\n     * Validates payment input data debit note.\n     *\n     * @param string $paymentId    The payment id of current payment.\n     * @param array  $dynamicValue Values of payment.\n     *\n     * @return bool\n     */\n    public function validatePaymentInputData($paymentId, &$dynamicValue)\n    {\n        if ($paymentId === \"oxiddebitnote\") {\n            if ($this->isAllBankInformationSet($this->_aRequiredDCFields, $dynamicValue)) {\n                return $this->validateDebitNote($dynamicValue);\n            } else {\n                return false;\n            }\n        } else {\n            return true;\n        }\n    }\n\n    /**\n     * Used to collect user validation errors. This method is called from all of\n     * the input checking functionality to report found error.\n     *\n     * @param string            $fieldName\n     * @param StandardException $error\n     *\n     * @return StandardException\n     */\n    public function addValidationError($fieldName, $error)\n    {\n        return $this->_aInputValidationErrors[$fieldName][] = $error;\n    }\n\n    /**\n     * Validates debit note.\n     *\n     * @param array $debitInformation Debit information\n     *\n     * @return bool|int\n     */\n    protected function validateDebitNote($debitInformation)\n    {\n        $debitInformation = $this->cleanDebitInformation($debitInformation);\n        $bankCode = $debitInformation['lsblz'];\n        $accountNumber = $debitInformation['lsktonr'];\n        $sepaValidator = oxNew(SepaValidator::class);\n\n        if ($sepaValidator->isValidBIC($bankCode)) {\n            $validateResult = true;\n            if (!$sepaValidator->isValidIBAN($accountNumber)) {\n                $validateResult = self::INVALID_ACCOUNT_NUMBER;\n            }\n        } else {\n            $validateResult = self::INVALID_BANK_CODE;\n            if (!\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blSkipDebitOldBankInfo')) {\n                $validateResult = $this->validateOldDebitInfo($debitInformation);\n            }\n        }\n\n        return $validateResult;\n    }\n\n    /**\n     * Validates old debit info.\n     *\n     * @param array $debitInfo Debit info\n     *\n     * @return bool|int\n     */\n    protected function validateOldDebitInfo($debitInfo)\n    {\n        $stringHelper = Str::getStr();\n        $debitInfo = $this->fixAccountNumber($debitInfo);\n\n        $validationResult = true;\n\n        if (!$stringHelper->preg_match(\"/^\\d{5,8}$/\", $debitInfo['lsblz'])) {\n            // Bank code is invalid\n            $validationResult = self::INVALID_BANK_CODE;\n        }\n\n        if (true === $validationResult && !$stringHelper->preg_match(\"/^\\d{10,12}$/\", $debitInfo['lsktonr'])) {\n            // Account number is invalid\n            $validationResult = self::INVALID_ACCOUNT_NUMBER;\n        }\n\n\n        return $validationResult;\n    }\n\n    /**\n     * If account number is shorter than 10, add zeros in front of number.\n     *\n     * @param array $debitInfo Debit info.\n     *\n     * @return array\n     */\n    protected function fixAccountNumber($debitInfo)\n    {\n        $oStr = Str::getStr();\n\n        if ($oStr->strlen($debitInfo['lsktonr']) < 10) {\n            $sNewNum = str_repeat(\n                '0',\n                10 - $oStr->strlen($debitInfo['lsktonr'])\n            ) . $debitInfo['lsktonr'];\n            $debitInfo['lsktonr'] = $sNewNum;\n        }\n\n        return $debitInfo;\n    }\n\n    /**\n     * Checks if all bank information is set.\n     *\n     * @param array $requiredFields  fields must be set.\n     * @param array $bankInformation actual information.\n     *\n     * @return bool\n     */\n    protected function isAllBankInformationSet($requiredFields, $bankInformation)\n    {\n        $isSet = true;\n        foreach ($requiredFields as $fieldName) {\n            if (!isset($bankInformation[$fieldName]) || !trim($bankInformation[$fieldName])) {\n                $isSet = false;\n                break;\n            }\n        }\n\n        return $isSet;\n    }\n\n    /**\n     * Clean up spaces.\n     *\n     * @param array $debitInformation Debit information.\n     *\n     * @return mixed\n     */\n    protected function cleanDebitInformation($debitInformation)\n    {\n        $debitInformation['lsblz'] = str_replace(' ', '', $debitInformation['lsblz']);\n        $debitInformation['lsktonr'] = str_replace(' ', '', $debitInformation['lsktonr']);\n\n        return $debitInformation;\n    }\n\n    /**\n     * Check if all need parameters entered.\n     *\n     * @param array $invAddress Address.\n     *\n     * @return bool\n     */\n    protected function hasRequiredParametersForVatInCheck($invAddress)\n    {\n        return isset($invAddress['oxuser__oxustid']) && $invAddress['oxuser__oxustid']\n            && isset($invAddress['oxuser__oxcountryid']) && $invAddress['oxuser__oxcountryid']\n            && isset($invAddress['oxuser__oxcompany']) && $invAddress['oxuser__oxcompany'];\n    }\n\n    /**\n     * VAT IN validator setter.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\CompanyVatInValidator $companyVatInValidator validator\n     */\n    public function setCompanyVatInValidator($companyVatInValidator)\n    {\n        $this->_oCompanyVatInValidator = $companyVatInValidator;\n    }\n\n    /**\n     * Return VAT IN validator.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Country $country Country according which VAT id should be checked.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\CompanyVatInValidator\n     */\n    public function getCompanyVatInValidator($country)\n    {\n        if (is_null($this->_oCompanyVatInValidator)) {\n            /** @var \\OxidEsales\\Eshop\\Core\\CompanyVatInValidator $vatInValidator */\n            $vatInValidator = oxNew('oxCompanyVatInValidator', $country);\n\n            /** @var \\OxidEsales\\Eshop\\Core\\CompanyVatInCountryChecker $validator */\n            $validator = oxNew(\\OxidEsales\\Eshop\\Core\\CompanyVatInCountryChecker::class);\n\n            $vatInValidator->addChecker($validator);\n\n            /** @var \\OxidEsales\\Eshop\\Core\\OnlineVatIdCheck $onlineValidator */\n            if (!\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam(\"blVatIdCheckDisabled\")) {\n                $onlineValidator = oxNew(\\OxidEsales\\Eshop\\Core\\OnlineVatIdCheck::class);\n                $vatInValidator->addChecker($onlineValidator);\n            }\n\n            $this->setCompanyVatInValidator($vatInValidator);\n        }\n\n        return $this->_oCompanyVatInValidator;\n    }\n}\n"
  },
  {
    "path": "source/Core/Language.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Bridge\\AdminAreaModuleTranslationFileLocatorBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Bridge\\FrontendModuleTranslationFileLocatorBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Bridge\\AdminThemeBridgeInterface;\nuse OxidEsales\\Eshop\\Core\\Exception\\LanguageNotFoundException;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse stdClass;\nuse Symfony\\Contracts\\Cache\\ItemInterface;\nuse Symfony\\Contracts\\Cache\\TagAwareCacheInterface;\n\n/**\n * Language related utility class\n */\nclass Language extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Language parameter name\n     *\n     * @var string\n     */\n    protected $_sName = 'lang';\n\n    /**\n     * Current shop base language Id\n     *\n     * @var int\n     */\n    protected $_iBaseLanguageId = null;\n\n    /**\n     * Templates language Id\n     *\n     * @var int\n     */\n    protected $_iTplLanguageId = null;\n\n    /**\n     * Editing object language Id\n     *\n     * @var int\n     */\n    protected $_iEditLanguageId = null;\n\n    /**\n     * Language translations array cache\n     *\n     * @var array\n     */\n    protected $_aLangCache = [];\n\n    /**\n     * Array containing possible admin template translations\n     *\n     * @var array\n     */\n    protected $_aAdminTplLanguageArray = null;\n\n    /**\n     * Language abbreviation array\n     *\n     * @var array\n     */\n    protected $_aLangAbbr = [];\n\n    /**\n     * registered additional language filesets to load\n     *\n     * @var array\n     */\n    protected $_aAdditionalLangFiles = [];\n\n    /**\n     * registered additional language filesets to load\n     *\n     * @var array\n     */\n    protected $_aLangMap = [];\n\n    /**\n     * State is string translated or not\n     *\n     * @var bool\n     */\n    protected $_blIsTranslated = true;\n\n    /**\n     * Template language id.\n     *\n     * @var int\n     */\n    protected $_iObjectTplLanguageId = null;\n\n    /**\n     * Set translation state\n     *\n     * @param bool $blIsTranslated State is string translated or not. Default true.\n     */\n    public function setIsTranslated($blIsTranslated = true)\n    {\n        $this->_blIsTranslated = $blIsTranslated;\n    }\n\n    /**\n     * Set translation state\n     *\n     * @return bool\n     */\n    public function isTranslated()\n    {\n        return $this->_blIsTranslated;\n    }\n\n    /**\n     * resetBaseLanguage resets base language id cache\n     *\n     * @access public\n     */\n    public function resetBaseLanguage()\n    {\n        $this->_iBaseLanguageId = null;\n    }\n\n    /**\n     * Returns active shop language id\n     *\n     * @return string\n     */\n    public function getBaseLanguage()\n    {\n        if ($this->_iBaseLanguageId === null) {\n            $myConfig = Registry::getConfig();\n            $blAdmin = $this->isAdmin();\n\n            // languages and search engines\n            if ($blAdmin && (($iSeLang = Registry::getRequest()->getRequestEscapedParameter('changelang')) !== null)) {\n                $this->_iBaseLanguageId = $iSeLang;\n            }\n\n            if (is_null($this->_iBaseLanguageId)) {\n                $this->_iBaseLanguageId = Registry::getRequest()->getRequestEscapedParameter('lang');\n            }\n\n            //or determining by domain\n            $aLanguageUrls = $myConfig->getConfigParam('aLanguageURLs');\n\n            if (!$blAdmin && is_array($aLanguageUrls)) {\n                foreach ($aLanguageUrls as $iId => $sUrl) {\n                    if ($sUrl && $myConfig->isCurrentUrl($sUrl)) {\n                        $this->_iBaseLanguageId = $iId;\n                        break;\n                    }\n                }\n            }\n\n            if (is_null($this->_iBaseLanguageId)) {\n                $this->_iBaseLanguageId = Registry::getRequest()->getRequestEscapedParameter('language');\n                if (!isset($this->_iBaseLanguageId)) {\n                    $this->_iBaseLanguageId = Registry::getSession()->getVariable('language');\n                }\n            }\n\n            // if language still not set and not search engine browsing,\n            // getting language from browser\n            if (is_null($this->_iBaseLanguageId) && !$blAdmin && !Registry::getUtils()->isSearchEngine()) {\n                // getting from cookie\n                $this->_iBaseLanguageId = Registry::getUtilsServer()->getOxCookie('language');\n\n                // getting from browser\n                if (is_null($this->_iBaseLanguageId)) {\n                    $this->_iBaseLanguageId = $this->detectLanguageByBrowser();\n                }\n            }\n\n            if (is_null($this->_iBaseLanguageId)) {\n                $this->_iBaseLanguageId = $myConfig->getConfigParam('sDefaultLang');\n            }\n\n            $this->_iBaseLanguageId = (int) $this->_iBaseLanguageId;\n\n            // validating language\n            $this->_iBaseLanguageId = $this->validateLanguage($this->_iBaseLanguageId);\n\n            Registry::getUtilsServer()->setOxCookie('language', $this->_iBaseLanguageId);\n        }\n\n        return $this->_iBaseLanguageId;\n    }\n\n    /**\n     * Returns language id used to load objects according to current template language\n     *\n     * @return int\n     */\n    public function getObjectTplLanguage()\n    {\n        if ($this->_iObjectTplLanguageId === null) {\n            $this->_iObjectTplLanguageId = $this->getTplLanguage();\n\n            if ($this->isAdmin()) {\n                $aLanguages = $this->getAdminTplLanguageArray();\n                if (\n                    !isset($aLanguages[$this->_iObjectTplLanguageId]) ||\n                    $aLanguages[$this->_iObjectTplLanguageId]->active == 0\n                ) {\n                    $this->_iObjectTplLanguageId = key($aLanguages);\n                }\n            }\n        }\n\n        return $this->_iObjectTplLanguageId;\n    }\n\n    /**\n     * Returns active shop templates language id\n     * If it is not an admin area, template language id is same\n     * as base shop language id\n     *\n     * @return string\n     */\n    public function getTplLanguage()\n    {\n        if ($this->_iTplLanguageId === null) {\n            $iSessLang = Registry::getSession()->getVariable('tpllanguage');\n            $this->_iTplLanguageId = $this->isAdmin() ? $this->setTplLanguage($iSessLang) : $this->getBaseLanguage();\n        }\n\n        return $this->_iTplLanguageId;\n    }\n\n    /**\n     * Returns editing object working language id\n     *\n     * @return string\n     */\n    public function getEditLanguage()\n    {\n        if ($this->_iEditLanguageId === null) {\n            if (!$this->isAdmin()) {\n                $this->_iEditLanguageId = $this->getBaseLanguage();\n            } else {\n                $iLang = null;\n                // choosing language ident\n                // check if we really need to set the new language\n                if (\"saveinnlang\" == Registry::getConfig()->getActiveView()->getFncName()) {\n                    $iLang = Registry::getRequest()->getRequestEscapedParameter(\"new_lang\");\n                }\n                $iLang = ($iLang === null) ? Registry::getRequest()->getRequestEscapedParameter('editlanguage') : $iLang;\n                $iLang = ($iLang === null) ? Registry::getSession()->getVariable('editlanguage') : $iLang;\n                $iLang = ($iLang === null) ? $this->getBaseLanguage() : $iLang;\n\n                // validating language\n                $this->_iEditLanguageId = $this->validateLanguage($iLang);\n\n                // writing to session\n                Registry::getSession()->setVariable('editlanguage', $this->_iEditLanguageId);\n            }\n        }\n\n        return $this->_iEditLanguageId;\n    }\n\n    /**\n     * Returns array of available languages.\n     *\n     * @param integer $iLanguage    Number if current language (default null)\n     * @param bool    $blOnlyActive load only current language or all\n     * @param bool    $blSort       enable sorting or not\n     *\n     * @return array\n     */\n    public function getLanguageArray($iLanguage = null, $blOnlyActive = false, $blSort = false)\n    {\n        $myConfig = Registry::getConfig();\n\n        if (is_null($iLanguage)) {\n            $iLanguage = $this->_iBaseLanguageId;\n        }\n\n        $aLanguages = [];\n        $aConfLanguages = $myConfig->getConfigParam('aLanguages');\n        $aLangParams = $myConfig->getConfigParam('aLanguageParams');\n\n        if (is_array($aConfLanguages)) {\n            $i = 0;\n            reset($aConfLanguages);\n            foreach ($aConfLanguages as $key => $val) {\n                if ($blOnlyActive && is_array($aLangParams)) {\n                    //skipping non active languages\n                    if (!$aLangParams[$key]['active']) {\n                        $i++;\n                        continue;\n                    }\n                }\n\n                if ($val) {\n                    $oLang = new stdClass();\n                    $oLang->id = isset($aLangParams[$key]['baseId']) ? $aLangParams[$key]['baseId'] : $i;\n                    $oLang->oxid = $key;\n                    $oLang->abbr = $key;\n                    $oLang->name = $val;\n\n                    if (is_array($aLangParams)) {\n                        $oLang->active = $aLangParams[$key]['active'];\n                        $oLang->sort = $aLangParams[$key]['sort'];\n                    }\n\n                    $oLang->selected = (isset($iLanguage) && $oLang->id == $iLanguage) ? 1 : 0;\n                    $aLanguages[$oLang->id] = $oLang;\n                }\n                ++$i;\n            }\n        }\n\n        if ($blSort && is_array($aLangParams)) {\n            uasort($aLanguages, [$this, 'sortLanguagesCallback']);\n        }\n\n        return $aLanguages;\n    }\n\n    /**\n     * Returns languages array containing possible admin template translations\n     *\n     * @return array\n     */\n    public function getAdminTplLanguageArray()\n    {\n        if ($this->_aAdminTplLanguageArray === null) {\n            $config = Registry::getConfig();\n\n            $langArray = $this->getLanguageArray();\n            $this->_aAdminTplLanguageArray = [];\n\n            $adminThemeName = ContainerFacade::get(AdminThemeBridgeInterface::class)\n                ->getActiveTheme();\n            $sourceDirectory =\n                $config->getAppDir() .\n                'views' . DIRECTORY_SEPARATOR .\n                $adminThemeName . DIRECTORY_SEPARATOR;\n\n            foreach ($langArray as $langKey => $language) {\n                $filePath = $sourceDirectory . $language->abbr . DIRECTORY_SEPARATOR . 'lang.php';\n                if (file_exists($filePath) && is_readable($filePath)) {\n                    $this->_aAdminTplLanguageArray[$langKey] = $language;\n                }\n            }\n        }\n\n        // moving pointer to beginning\n        reset($this->_aAdminTplLanguageArray);\n\n        return $this->_aAdminTplLanguageArray;\n    }\n\n    /**\n     * Returns selected language abbreviation\n     *\n     * @param int $iLanguage language id [optional]\n     *\n     * @return string\n     */\n    public function getLanguageAbbr($language = null)\n    {\n        if ($this->_aLangAbbr === []) {\n            $this->_aLangAbbr = $this->getLanguageIds();\n        }\n\n        $language = isset($language) ? (int) $language : $this->getBaseLanguage();\n        if (isset($this->_aLangAbbr[$language])) {\n            return $this->_aLangAbbr[$language];\n        }\n\n        throw new LanguageNotFoundException(\n            'Could not find language abbreviation for language-id ' . $language . '! '\n            . (\n                count($this->_aLangAbbr) === 0\n                ? 'No languages available'\n                : 'Available languages: ' . implode(', ', $this->_aLangAbbr)\n            )\n        );\n    }\n\n    /**\n     * getLanguageNames returns array of language names e.g. array('Deutch', 'English')\n     *\n     * @access public\n     * @return array\n     */\n    public function getLanguageNames()\n    {\n        $aConfLanguages = Registry::getConfig()->getConfigParam('aLanguages');\n        $aLangIds = $this->getLanguageIds();\n        $aLanguages = [];\n        foreach ($aLangIds as $iId => $sValue) {\n            $aLanguages[$iId] = $aConfLanguages[$sValue];\n        }\n\n        return $aLanguages;\n    }\n\n    /**\n     * @param string $sStringToTranslate Initial string\n     * @param int $iLang optional language number\n     * @param bool|null $blAdminMode on special case you can force mode,\n     *  to load language constant from admin/shops language file\n     *\n     * @return string|array\n     * @deprecated use ShopAdapterInterface::translateString() instead\n     *\n     * Searches for translation string in file and on success returns translation,\n     * otherwise returns initial string.\n     */\n    public function translateString($sStringToTranslate, $iLang = null, $blAdminMode = null)\n    {\n        if ($sStringToTranslate !== null) {\n            $this->setIsTranslated();\n            $aLang = $this->getLangTranslationArray($iLang, $blAdminMode);\n            if (isset($aLang[$sStringToTranslate])) {\n                return $aLang[$sStringToTranslate];\n            }\n\n            $aMap = $this->getLanguageMap($iLang, $blAdminMode);\n            if (isset($aMap[$sStringToTranslate], $aLang[$aMap[$sStringToTranslate]])) {\n                return $aLang[$aMap[$sStringToTranslate]];\n            }\n\n            if (count($this->_aAdditionalLangFiles)) {\n                $aLang = $this->getLangTranslationArray($iLang, $blAdminMode, $this->_aAdditionalLangFiles);\n                if (isset($aLang[$sStringToTranslate])) {\n                    return $aLang[$sStringToTranslate];\n                }\n            }\n        }\n\n        $this->setIsTranslated(false);\n\n        if (!$this->isTranslated()) {\n            Registry::getLogger()->warning(\n                \"translation for $sStringToTranslate not found\",\n                compact('iLang', 'blAdminMode')\n            );\n        }\n\n        return $sStringToTranslate;\n    }\n\n    /**\n     * Iterates through given array ($aData) and collects data if array key is similar as\n     * searchable key ($sKey*). If you pass $aCollection, it will be appended with found items\n     *\n     * @param array  $aData       array to search in\n     * @param string $sKey        key to look for (looking for similar with strpos)\n     * @param array  $aCollection array to append found items [optional]\n     *\n     * @return array\n     */\n    protected function collectSimilar($aData, $sKey, $aCollection = [])\n    {\n        foreach ($aData as $sValKey => $sValue) {\n            if (strpos($sValKey, $sKey) === 0) {\n                $aCollection[$sValKey] = $sValue;\n            }\n        }\n\n        return $aCollection;\n    }\n\n    /**\n     * Returns array( \"MY_TRANSLATION_KEY\" => \"MY_TRANSLATION_VALUE\", ... ) by\n     * given filter \"MY_TRANSLATION_\" from language files\n     *\n     * @param string $sKey    key to look\n     * @param int    $iLang   language files to search [optional]\n     * @param bool   $blAdmin admin/non admin mode [optional]\n     *\n     * @return array\n     */\n    public function getSimilarByKey($sKey, $iLang = null, $blAdmin = null)\n    {\n        startProfile(\"getSimilarByKey\");\n\n        $iLang = isset($iLang) ? $iLang : $this->getTplLanguage();\n        $blAdmin = isset($blAdmin) ? $blAdmin : $this->isAdmin();\n\n        // checking if exists in cache\n        $aLang = $this->getLangTranslationArray($iLang, $blAdmin);\n        $aSimilarConst = $this->collectSimilar($aLang, $sKey);\n\n        // checking if in map exist\n        $aMap = $this->getLanguageMap($iLang, $blAdmin);\n        $aSimilarConst = $this->collectSimilar($aMap, $sKey, $aSimilarConst);\n\n        // checking if in theme options exist\n        if (count($this->_aAdditionalLangFiles)) {\n            $aLang = $this->getLangTranslationArray($iLang, $blAdmin, $this->_aAdditionalLangFiles);\n            $aSimilarConst = $this->collectSimilar($aLang, $sKey, $aSimilarConst);\n        }\n\n        stopProfile(\"getSimilarByKey\");\n\n        return $aSimilarConst;\n    }\n\n    /**\n     * Returns formatted number, according to active currency formatting standards.\n     *\n     * @param float  $dValue  Plain price\n     * @param object $oActCur Object of active currency\n     *\n     * @return string\n     */\n    public function formatCurrency($dValue, $oActCur = null)\n    {\n        if (!$oActCur) {\n            $oActCur = Registry::getConfig()->getActShopCurrencyObject();\n        }\n        $sValue = Registry::getUtils()->fRound($dValue, $oActCur);\n\n        return number_format($sValue, $oActCur->decimal, $oActCur->dec, $oActCur->thousand);\n    }\n\n    /**\n     * Returns formatted vat value, according to formatting standards.\n     *\n     * @param float  $dValue  Plain price\n     * @param object $oActCur Object of active currency\n     *\n     * @return string\n     */\n    public function formatVat($dValue, $oActCur = null)\n    {\n        $iDecPos = 0;\n        $sValue = (string) $dValue;\n        $oStr = Str::getStr();\n        if (($iDotPos = $oStr->strpos($sValue, '.')) !== false) {\n            $iDecPos = $oStr->strlen($oStr->substr($sValue, $iDotPos + 1));\n        }\n\n        $oActCur = $oActCur ? $oActCur : Registry::getConfig()->getActShopCurrencyObject();\n        $iDecPos = ($iDecPos < $oActCur->decimal) ? $iDecPos : $oActCur->decimal;\n\n        return number_format((float) $dValue, $iDecPos, $oActCur->dec, $oActCur->thousand);\n    }\n\n    /**\n     * According to user configuration forms and return language prefix.\n     *\n     * @param integer $iLanguage User selected language (default null)\n     *\n     * @return string\n     */\n    public function getLanguageTag($iLanguage = null)\n    {\n        if (!isset($iLanguage)) {\n            $iLanguage = $this->getBaseLanguage();\n        }\n\n        $iLanguage = (int) $iLanguage;\n\n        return ($iLanguage) ? \"_$iLanguage\" : \"\";\n    }\n\n    /**\n     * Validate language id. If not valid id, returns default value\n     *\n     * @param int $iLang Language id\n     *\n     * @return int\n     */\n    public function validateLanguage($iLang = null)\n    {\n        // checking if this language is valid\n        $aLanguages = $this->getLanguageArray(null, !$this->isAdmin());\n        if (!isset($aLanguages[$iLang]) && is_array($aLanguages)) {\n            $oLang = current($aLanguages);\n            if (isset($oLang->id)) {\n                $iLang = $oLang->id;\n            }\n        }\n\n        return (int) $iLang;\n    }\n\n    /**\n     * Set base shop language\n     *\n     * @param int $iLang Language id\n     */\n    public function setBaseLanguage($iLang = null)\n    {\n        if (is_null($iLang)) {\n            $iLang = $this->getBaseLanguage();\n        } else {\n            $this->_iBaseLanguageId = (int) $iLang;\n        }\n\n        Registry::getSession()->setVariable('language', $iLang);\n    }\n\n    /**\n     * Validates and sets templates language id\n     *\n     * @param int $iLang Language id\n     *\n     * @return null\n     */\n    public function setTplLanguage($iLang = null)\n    {\n        $this->_iTplLanguageId = isset($iLang) ? (int) $iLang : $this->getBaseLanguage();\n        if ($this->isAdmin()) {\n            $aLanguages = $this->getAdminTplLanguageArray();\n            if (!isset($aLanguages[$this->_iTplLanguageId])) {\n                $this->_iTplLanguageId = key($aLanguages);\n            }\n        }\n\n        Registry::getSession()->setVariable('tpllanguage', $this->_iTplLanguageId);\n\n        return $this->_iTplLanguageId;\n    }\n\n    /**\n     * Returns the encoding all translations will be converted to.\n     *\n     * @return string\n     */\n    protected function getTranslationsExpectedEncoding()\n    {\n        return 'UTF-8';\n    }\n\n    /**\n     * Returns array with paths where frontend language files are stored\n     *\n     * @param int $iLang active language\n     *\n     * @return array\n     */\n    protected function getLangFilesPathArray($iLang)\n    {\n        $oConfig = Registry::getConfig();\n        $aLangFiles = [];\n\n        $sAppDir = $oConfig->getAppDir();\n        $sLang = Registry::getLang()->getLanguageAbbr($iLang);\n        $sTheme = $oConfig->getConfigParam(\"sTheme\");\n\n        //get generic lang files\n        $sGenericPath = $sAppDir . 'translations/' . $sLang;\n        if ($sGenericPath) {\n            $aLangFiles = array_merge($aLangFiles, $this->getAbbreviationDirectoryLanguageFiles($sGenericPath));\n        }\n\n        //get theme lang files\n        if ($sTheme) {\n            $sThemePath = $sAppDir . 'views/' . $sTheme . '/';\n            $aLangFiles = array_merge($aLangFiles, $this->getThemeLanguageFiles($sThemePath, $sLang));\n        }\n\n        $aLangFiles = array_merge($aLangFiles, $this->getCustomThemeLanguageFiles($iLang));\n\n        // modules language files\n        $aLangFiles = $this->appendModuleLangFilesForFrontend($aLangFiles, $sLang);\n\n        // custom language files\n        $aLangFiles = $this->appendCustomLangFiles($aLangFiles, $sLang);\n\n        return count($aLangFiles) ? $aLangFiles : false;\n    }\n\n    /**\n     * @param string $themePath\n     * @param string $languageAbbreviation\n     * @return array<string>\n     */\n    protected function getThemeLanguageFiles(string $themePath, string $languageAbbreviation): array\n    {\n        $files = $this->getAbbreviationDirectoryLanguageFiles(\n            $themePath . 'translations/' . $languageAbbreviation\n        );\n\n        $files = array_merge($files, $this->getAbbreviationDirectoryLanguageFiles(\n            $themePath . $languageAbbreviation\n        ));\n\n        return $files;\n    }\n\n    /**\n     * @param string $directory\n     * @return array<string>\n     */\n    protected function getAbbreviationDirectoryLanguageFiles(string $directory): array\n    {\n        $files = [\n            $directory . '/lang.php'\n        ];\n\n        $files = $this->appendLangFile($files, $directory);\n\n        return $files;\n    }\n\n    /**\n     * Returns custom theme language files.\n     *\n     * @param int $language active language\n     *\n     * @return array\n     */\n    protected function getCustomThemeLanguageFiles($language)\n    {\n        $oConfig = Registry::getConfig();\n        $sCustomTheme = $oConfig->getConfigParam(\"sCustomTheme\");\n        $sAppDir = $oConfig->getAppDir();\n        $sLang = Registry::getLang()->getLanguageAbbr($language);\n        $aLangFiles = [];\n\n        if ($sCustomTheme) {\n            $customThemePath = $sAppDir . 'views/' . $sCustomTheme . '/';\n            $aLangFiles = array_merge($aLangFiles, $this->getThemeLanguageFiles($customThemePath, $sLang));\n        }\n\n        return $aLangFiles;\n    }\n\n    /**\n     * Returns array with paths where admin language files are stored\n     *\n     * @param int $activeLanguage The active language\n     *\n     * @return array\n     */\n    protected function getAdminLangFilesPathArray($activeLanguage)\n    {\n        $config = Registry::getConfig();\n        $langFiles = [];\n\n        $appDirectory = $config->getAppDir();\n        $language = Registry::getLang()->getLanguageAbbr($activeLanguage);\n\n        // admin lang files\n        $adminThemeName = ContainerFacade::get(AdminThemeBridgeInterface::class)\n            ->getActiveTheme();\n        $adminPath = $appDirectory .\n            'views' . DIRECTORY_SEPARATOR .\n            $adminThemeName . DIRECTORY_SEPARATOR .\n            $language;\n\n        $langFiles[] = $adminPath . DIRECTORY_SEPARATOR . 'lang.php';\n        $langFiles[] = $appDirectory .\n            'translations' . DIRECTORY_SEPARATOR .\n            $language . DIRECTORY_SEPARATOR .\n            'translit_lang.php';\n        $langFiles = $this->appendLangFile($langFiles, $adminPath);\n\n        // themes options lang files\n        $themePath = $appDirectory . 'views/*/' . $language;\n        $langFiles = $this->appendLangFile($langFiles, $themePath, \"options\");\n\n        $themePath = $appDirectory . 'views/*/translations/' . $language;\n        $langFiles = $this->appendLangFile($langFiles, $themePath, \"options\");\n\n        // module language files\n        $langFiles = $this->appendModuleLangFilesForAdminArea($langFiles, $language);\n\n\n        // custom language files\n        $langFiles = $this->appendCustomLangFiles($langFiles, $language, true);\n\n        return count($langFiles) ? $langFiles : false;\n    }\n\n    /**\n     * Appends lang or options files if exists, except custom lang files\n     *\n     * @param array  $aLangFiles   existing language files\n     * @param string $sFullPath    path to language files to append\n     * @param string $sFilePattern file pattern to search for, default is \"lang\"\n     *\n     * @return array\n     */\n    protected function appendLangFile($aLangFiles, $sFullPath, $sFilePattern = \"lang\")\n    {\n        $aFiles = glob($sFullPath . \"/*_{$sFilePattern}.php\");\n        if (is_array($aFiles) && count($aFiles)) {\n            foreach ($aFiles as $sFile) {\n                if (!strpos($sFile, 'cust_lang.php')) {\n                    $aLangFiles[] = $sFile;\n                }\n            }\n        }\n\n        return $aLangFiles;\n    }\n\n    /**\n     * Appends Custom language files cust_lang.php\n     *\n     * @param array  $languageFiles existing language files\n     * @param string $language      language abbreviation\n     * @param bool   $forAdmin      add files for admin\n     *\n     * @return array\n     */\n    protected function appendCustomLangFiles($languageFiles, $language, $forAdmin = false)\n    {\n        if ($forAdmin) {\n            $adminThemeName = ContainerFacade::get(AdminThemeBridgeInterface::class)\n                ->getActiveTheme();\n            $languageFiles[] = $this->getCustomFilePath($language, $adminThemeName);\n        } else {\n            $config = Registry::getConfig();\n            if ($config->getConfigParam(\"sTheme\")) {\n                $languageFiles[] = $this->getCustomFilePath($language, $config->getConfigParam(\"sTheme\"));\n            }\n            if ($config->getConfigParam(\"sCustomTheme\")) {\n                $languageFiles[] = $this->getCustomFilePath($language, $config->getConfigParam(\"sCustomTheme\"));\n            }\n        }\n\n        return $languageFiles;\n    }\n\n    /**\n     * @param int    $language  The language index\n     * @param string $themeName The name of the theme\n     *\n     * @return string\n     */\n    private function getCustomFilePath($language, $themeName)\n    {\n        $config = Registry::getConfig();\n        return $config->getAppDir() .\n            'views' . DIRECTORY_SEPARATOR .\n            $themeName . DIRECTORY_SEPARATOR  .\n            $language . DIRECTORY_SEPARATOR .\n            'cust_lang.php';\n    }\n\n    /**\n     * @param array  $langFiles\n     * @param string $lang\n     *\n     * @return array\n     */\n    private function appendModuleLangFilesForAdminArea(array $langFiles, string $lang): array\n    {\n        $moduleLangFiles = ContainerFacade::get(AdminAreaModuleTranslationFileLocatorBridgeInterface::class)\n            ->locate($lang);\n\n        return array_merge($langFiles, $moduleLangFiles);\n    }\n\n    /**\n     * @param array  $langFiles\n     * @param string $lang\n     *\n     * @return array\n     */\n    private function appendModuleLangFilesForFrontend(array $langFiles, string $lang): array\n    {\n        $moduleLangFiles = ContainerFacade::get(FrontendModuleTranslationFileLocatorBridgeInterface::class)\n            ->locate($lang);\n\n        return array_merge($langFiles, $moduleLangFiles);\n    }\n\n    /**\n     * Returns language cache file name\n     *\n     * @param bool  $blAdmin    admin or not\n     * @param int   $iLang      current language id\n     * @param array $aLangFiles language files to load [optional]\n     *\n     * @return string\n     */\n    protected function getLangFileCacheName($blAdmin, $iLang, $aLangFiles = null)\n    {\n        $myConfig = Registry::getConfig();\n        $sLangFilesIdent = '_default';\n        if (is_array($aLangFiles) && $aLangFiles) {\n            $sLangFilesIdent = '_' . md5(implode('+', $aLangFiles));\n        }\n\n        return \"langcache_\" . ((int) $blAdmin) . \"_{$iLang}_\" . $myConfig->getShopId() . \"_\" . $myConfig->getConfigParam('sTheme') . $sLangFilesIdent;\n    }\n\n    protected function getLanguageFileData($blAdmin = false, $iLang = 0, $aLangFiles = null)\n    {\n        $sCacheName = $this->getLangFileCacheName($blAdmin, $iLang, $aLangFiles);\n\n        $cache = ContainerFacade::get(TagAwareCacheInterface::class);\n        $aLangCache = $cache->get($sCacheName, function (ItemInterface $item) use ($aLangFiles, $blAdmin, $iLang): array {\n            $item->tag('oxid_esales.cache.language');\n            if ($aLangFiles === null) {\n                if ($blAdmin) {\n                    $aLangFiles = $this->getAdminLangFilesPathArray($iLang);\n                } else {\n                    $aLangFiles = $this->getLangFilesPathArray($iLang);\n                }\n            }\n            $aLangCache = [];\n            $sBaseCharset = $this->getTranslationsExpectedEncoding();\n            $aLang = [];\n            $aLangSeoReplaceChars = [];\n            foreach ($aLangFiles as $sLangFile) {\n                if (file_exists($sLangFile) && is_readable($sLangFile)) {\n                    //$aSeoReplaceChars null indicates that there is no setting made\n                    $aSeoReplaceChars = null;\n                    include $sLangFile;\n\n                    $aLang = array_merge(['charset' => 'UTF-8'], $aLang);\n\n                    if (isset($aSeoReplaceChars) && is_array($aSeoReplaceChars)) {\n                        $aLangSeoReplaceChars = array_merge($aLangSeoReplaceChars, $aSeoReplaceChars);\n                    }\n\n                    $aLangCache = array_merge($aLangCache, $aLang);\n                }\n            }\n\n            $aLangCache['charset'] = $sBaseCharset;\n\n            // special character replacement list\n            $aLangCache['_aSeoReplaceChars'] = $aLangSeoReplaceChars;\n\n            return $aLangCache;\n        });\n\n        return $aLangCache;\n    }\n\n    /**\n     * Returns language map array\n     *\n     * @param int  $language language index\n     * @param bool $isAdmin  admin mode [default NULL]\n     *\n     * @return array\n     */\n    protected function getLanguageMap($language, $isAdmin = null)\n    {\n        $isAdmin = isset($isAdmin) ? $isAdmin : $this->isAdmin();\n        $key = $language . ((int)$isAdmin);\n        if (!isset($this->_aLangMap[$key])) {\n            $this->_aLangMap[$key] = [];\n            $config = Registry::getConfig();\n\n            $mapFile = '';\n            $theme = $this->getRealThemeName($config->getConfigParam(\"sTheme\"), $isAdmin);\n            $customTheme = $this->getRealThemeName($config->getConfigParam(\"sCustomTheme\"), $isAdmin);\n\n            $languageAbbr = Registry::getLang()->getLanguageAbbr($language);\n            $possibleMapFileLocations = array_merge(\n                $this->getThemeLanguageFileMapLocations($customTheme, $languageAbbr),\n                $this->getThemeLanguageFileMapLocations($theme, $languageAbbr)\n            );\n\n            foreach ($possibleMapFileLocations as $tmpMapFileLocation) {\n                $possibleMapFile = $tmpMapFileLocation . DIRECTORY_SEPARATOR . 'map.php';\n                if (file_exists($possibleMapFile) && is_readable($possibleMapFile)) {\n                    $mapFile = $possibleMapFile;\n                    break;\n                }\n            }\n\n            if ($mapFile) {\n                $aMap = [];\n                include $mapFile;\n                $this->_aLangMap[$key] = $aMap;\n            }\n        }\n\n        return $this->_aLangMap[$key];\n    }\n\n    /**\n     * @param string $theme The name of the theme\n     * @param string $languageAbbreviation Language abbreviation\n     *\n     * @return string[]\n     */\n    private function getThemeLanguageFileMapLocations($theme, $languageAbbreviation)\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $themeDirectory = $config->getAppDir() . DIRECTORY_SEPARATOR\n            . 'views' . DIRECTORY_SEPARATOR\n            . $theme . DIRECTORY_SEPARATOR;\n\n        return [\n            $themeDirectory . $languageAbbreviation, // for backwards compatibility\n            $themeDirectory . 'translations' . DIRECTORY_SEPARATOR . $languageAbbreviation,\n            $themeDirectory . 'translations'\n        ];\n    }\n\n    /**\n     * @param bool   $isAdmin   The admin mode [default NULL]\n     * @param string $themeName The name of the theme\n     *\n     * @return string\n     */\n    private function getRealThemeName($themeName, $isAdmin = null)\n    {\n        return $isAdmin\n            ? ContainerFacade::get(AdminThemeBridgeInterface::class)\n                ->getActiveTheme()\n            : $themeName;\n    }\n\n    /**\n     * Returns current language cache language id\n     *\n     * @param bool $blAdmin admin mode\n     * @param int  $iLang   language id [optional]\n     *\n     * @return int\n     */\n    protected function getCacheLanguageId($blAdmin, $iLang = null)\n    {\n        $iLang = ($iLang === null && $blAdmin) ? $this->getTplLanguage() : $iLang;\n        if (!isset($iLang)) {\n            $iLang = $this->getBaseLanguage();\n            if (!isset($iLang)) {\n                $iLang = 0;\n            }\n        }\n\n        return (int) $iLang;\n    }\n\n    /**\n     * get language array from lang translation file\n     *\n     * @param int   $iLang      optional language\n     * @param bool  $blAdmin    admin mode switch\n     * @param array $aLangFiles language files to load [optional]\n     *\n     * @return array\n     */\n    protected function getLangTranslationArray($iLang = null, $blAdmin = null, $aLangFiles = null)\n    {\n        startProfile(\"_getLangTranslationArray\");\n\n        $blAdmin = isset($blAdmin) ? $blAdmin : $this->isAdmin();\n        $iLang = $this->getCacheLanguageId($blAdmin, $iLang);\n        $sCacheName = $this->getLangFileCacheName($blAdmin, $iLang, $aLangFiles);\n\n        if (!isset($this->_aLangCache[$sCacheName])) {\n            $this->_aLangCache[$sCacheName] = [];\n        }\n        if (!isset($this->_aLangCache[$sCacheName][$iLang])) {\n            // loading main lang files data\n            $this->_aLangCache[$sCacheName][$iLang] = $this->getLanguageFileData($blAdmin, $iLang, $aLangFiles);\n        }\n\n        stopProfile(\"_getLangTranslationArray\");\n\n        // if language array exists ..\n        return (isset($this->_aLangCache[$sCacheName][$iLang]) ? $this->_aLangCache[$sCacheName][$iLang] : []);\n    }\n\n    /**\n     * Language sorting callback function\n     *\n     * @param object $a1 first value to check\n     * @param object $a2 second value to check\n     *\n     * @return int\n     */\n    protected function sortLanguagesCallback($a1, $a2)\n    {\n        return $a1->sort <=> $a2->sort;\n    }\n\n    /**\n     * Returns language id param name\n     *\n     * @return string\n     */\n    public function getName()\n    {\n        return $this->_sName;\n    }\n\n    /**\n     * Returns form hidden language parameter\n     *\n     * @return string\n     */\n    public function getFormLang()\n    {\n        if (!$this->isAdmin()) {\n            return \"<input type=\\\"hidden\\\" name=\\\"\" . $this->getName() . \"\\\" value=\\\"\" . $this->getBaseLanguage() . \"\\\" />\";\n        }\n    }\n\n    /**\n     * Returns url language parameter\n     *\n     * @param int $iLang language id [optional]\n     *\n     * @return string\n     */\n    public function getUrlLang($iLang = null)\n    {\n        if (!$this->isAdmin()) {\n            $iLang = isset($iLang) ? $iLang : $this->getBaseLanguage();\n            return $this->getName() . \"=\" . $iLang;\n        }\n    }\n\n    /**\n     * Is needed appends url with language parameter\n     * Direct usage of this method to retrieve end url result is discouraged - instead\n     * see \\OxidEsales\\Eshop\\Core\\UtilsUrl::processUrl\n     *\n     * @param string $sUrl  url to process\n     * @param int    $iLang language id [optional]\n     *\n     * @see \\OxidEsales\\Eshop\\Core\\UtilsUrl::processUrl\n     *\n     * @return string\n     */\n    public function processUrl($sUrl, $iLang = null)\n    {\n        $iLang = isset($iLang) ? $iLang : $this->getBaseLanguage();\n        $iDefaultLang = (int) Registry::getConfig()->getConfigParam('sDefaultLang');\n        $iBrowserLanguage = (int)$this->detectLanguageByBrowser();\n        $oStr = Str::getStr();\n\n        if (!$this->isAdmin()) {\n            $sParam = $this->getUrlLang($iLang);\n            if (\n                !$oStr->preg_match('/(\\?|&(amp;)?)lang=[0-9]+/', $sUrl) &&\n                ($iLang != $iDefaultLang || $iDefaultLang != $iBrowserLanguage)\n            ) {\n                if ($sUrl) {\n                    if ($oStr->strpos($sUrl, '?') === false) {\n                        $sUrl .= \"?\";\n                    } elseif (!$oStr->preg_match('/(\\?|&(amp;)?)$/', $sUrl)) {\n                        $sUrl .= \"&amp;\";\n                    }\n                }\n                $sUrl .= $sParam . \"&amp;\";\n            } else {\n                $sUrl = $oStr->preg_replace('/(\\?|&(amp;)?)lang=[0-9]+/', '\\1' . $sParam, $sUrl);\n            }\n        }\n\n        return $sUrl;\n    }\n\n    /**\n     * Detect language by user browser settings. Returns language ID if\n     * detected, otherwise returns null.\n     *\n     * @return int\n     */\n    public function detectLanguageByBrowser()\n    {\n        $sBrowserLanguage = $this->getBrowserLanguage();\n\n        if (!is_null($sBrowserLanguage)) {\n            $aLanguages = $this->getLanguageArray(null, true);\n            foreach ($aLanguages as $oLang) {\n                if ($oLang->abbr == $sBrowserLanguage) {\n                    return $oLang->id;\n                }\n            }\n        }\n    }\n\n    /** @return array|string[] */\n    public function getMultiLangTables()\n    {\n        $tables = [\n            'oxactions',\n            'oxartextends',\n            'oxarticles',\n            'oxattribute',\n            'oxcategories',\n            'oxcontents',\n            'oxcountry',\n            'oxdelivery',\n            'oxdeliveryset',\n            'oxdiscount',\n            'oxgroups',\n            'oxlinks',\n            'oxmanufacturers',\n            'oxmediaurls',\n            'oxobject2attribute',\n            'oxpayments',\n            'oxselectlist',\n            'oxshops',\n            'oxstates',\n            'oxvendor',\n            'oxwrapping',\n        ];\n        $configTables = ContainerFacade::getParameter('oxid_esales.multilingual_tables');\n        if (\\is_array($configTables)) {\n            $tables = \\array_merge($tables, $configTables);\n        }\n        return $tables;\n    }\n\n    /**\n     * Get SEO spec. chars replacement list for current language\n     *\n     * @param int $iLang language ID\n     *\n     * @return null\n     */\n    public function getSeoReplaceChars($iLang)\n    {\n        // get language replace chars\n        $aSeoReplaceChars = $this->translateString('_aSeoReplaceChars', $iLang);\n        if (!is_array($aSeoReplaceChars)) {\n            $aSeoReplaceChars = [];\n        }\n\n        return $aSeoReplaceChars;\n    }\n\n    /**\n     * Gets browser language.\n     *\n     * @return string\n     */\n    protected function getBrowserLanguage()\n    {\n        if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) && $_SERVER['HTTP_ACCEPT_LANGUAGE']) {\n            return strtolower(substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2));\n        }\n    }\n\n    /**\n     * Returns available language IDs (abbreviations) for all sub shops\n     *\n     * @return array\n     */\n    public function getAllShopLanguageIds()\n    {\n        return $this->getLanguageIdsFromDatabase();\n    }\n\n    /**\n     * Get current Shop language ids.\n     *\n     * @param int $iShopId shop id\n     *\n     * @return array\n     */\n    public function getLanguageIds($iShopId = 0)\n    {\n        if (empty($iShopId) || $iShopId == Registry::getConfig()->getShopId()) {\n            $aLanguages = $this->getActiveShopLanguageIds();\n        } else {\n            $aLanguages = $this->getLanguageIdsFromDatabase($iShopId);\n        }\n\n        return $aLanguages;\n    }\n\n    /**\n     * Returns available language IDs (abbreviations)\n     *\n     * @return array\n     */\n    public function getActiveShopLanguageIds()\n    {\n        $oConfig = Registry::getConfig();\n\n        //if exists language parameters array, extract lang id's from there\n        $aLangParams = $oConfig->getConfigParam('aLanguageParams');\n        if (is_array($aLangParams)) {\n            $aIds = $this->getLanguageIdsFromLanguageParamsArray($aLangParams);\n        } else {\n            $languages = $oConfig->getConfigParam('aLanguages');\n            $aIds = $this->getLanguageIdsFromLanguagesArray(\n                is_array($languages) ? $languages : []\n            );\n        }\n\n        return $aIds;\n    }\n\n    /**\n     * Gets language Ids for given shopId or for all subshops\n     *\n     * @param null $shopId\n     *\n     * @return array\n     */\n    protected function getLanguageIdsFromDatabase($shopId = null)\n    {\n        return $this->getLanguageIds();\n    }\n\n    /**\n     * Returns list of all language codes taken from config values of given 'aLanguages' (for all subshops)\n     *\n     * @param string $sLanguageParameterName language config parameter name\n     * @param int    $iShopId                shop id\n     *\n     * @return array\n     */\n    protected function getConfigLanguageValues($sLanguageParameterName, $iShopId = null)\n    {\n        $aConfigDecodedValues = [];\n        $aConfigValues = $this->selectLanguageParamValues($sLanguageParameterName, $iShopId);\n\n        foreach ($aConfigValues as $sConfigValue) {\n            $aConfigLanguages = unserialize($sConfigValue['oxvarvalue']);\n\n            $aLanguages = [];\n            if ($sLanguageParameterName == 'aLanguageParams') {\n                $aLanguages = $this->getLanguageIdsFromLanguageParamsArray($aConfigLanguages);\n            } elseif ($sLanguageParameterName == 'aLanguages') {\n                $aLanguages = $this->getLanguageIdsFromLanguagesArray($aConfigLanguages);\n            }\n\n            $aConfigDecodedValues = array_unique(array_merge($aConfigDecodedValues, $aLanguages));\n        }\n\n        return $aConfigDecodedValues;\n    }\n\n    /**\n     * Returns array of all config values of given paramName\n     *\n     * @param string      $sParamName Parameter name\n     * @param string|null $sShopId    Shop id\n     *\n     * @return array\n     */\n    protected function selectLanguageParamValues($sParamName, $sShopId = null)\n    {\n        $oDb = DatabaseProvider::getDb();\n\n        $params = [\n            'oxvarname' => $sParamName\n        ];\n        $sQuery = \"\n            select oxvarvalue\n            from oxconfig\n            where oxvarname = :oxvarname\";\n\n        if (!empty($sShopId)) {\n            $sQuery .= \" and oxshopid = :oxshopid limit 1\";\n            $params['oxshopid'] = $sShopId;\n        }\n\n        return $oDb->getAll($sQuery, $params);\n    }\n\n    /**\n     * gets language code array from aLanguageParams array\n     *\n     * @param array $aLanguageParams Language parameters\n     *\n     * @return array\n     */\n    protected function getLanguageIdsFromLanguageParamsArray($aLanguageParams)\n    {\n        $aLanguages = [];\n        foreach ($aLanguageParams as $sAbbr => $aValue) {\n            $iBaseId = (int) $aValue['baseId'];\n            $aLanguages[$iBaseId] = $sAbbr;\n        }\n\n        return $aLanguages;\n    }\n\n    /**\n     * gets language code array from aLanguages array\n     *\n     * @param array $aLanguages Languages\n     *\n     * @return array\n     */\n    protected function getLanguageIdsFromLanguagesArray($aLanguages)\n    {\n        return is_array($aLanguages) ? array_keys($aLanguages) : [];\n    }\n}\n"
  },
  {
    "path": "source/Core/Model/BaseModel.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Model;\n\n/**\n * Defining triggered action type.\n */\nDEFINE('ACTION_NA', 0);\nDEFINE('ACTION_DELETE', 1);\nDEFINE('ACTION_INSERT', 2);\nDEFINE('ACTION_UPDATE', 3);\nDEFINE('ACTION_UPDATE_STOCK', 4);\n\nuse Exception;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Core\\Exception\\DatabaseException;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents\\AfterModelDeleteEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents\\AfterModelInsertEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents\\AfterModelUpdateEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents\\BeforeModelDeleteEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents\\BeforeModelUpdateEvent;\nuse oxObjectException;\n\n/**\n * Class BaseModel\n * @package OxidEsales\\EshopCommunity\\Core\\Model\n */\n#[\\AllowDynamicProperties]\nclass BaseModel extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Unique object ID. Normally representing record oxid field value\n     *\n     * @var string\n     */\n    protected $_sOXID = null;\n\n    /**\n     * ID of running shop session (default null).\n     *\n     * @var int\n     */\n    protected $_iShopId = null;\n\n    /**\n     * Whether instance\n     *\n     * @var bool\n     */\n    protected $_blIsSimplyClonable = true;\n\n    /**\n     * Name of current class.\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxbase';\n\n    /**\n     * Core database table name. $sCoreTable could be only original data table name and not view name.\n     *\n     * @var string\n     */\n    protected $_sCoreTable = null;\n\n    /**\n     * Current view name where object record is supposed to be SELECTED from.\n     *\n     * @var string\n     */\n    protected $_sViewTable = null;\n\n    /**\n     * Field name list\n     *\n     * @var array\n     */\n    protected $_aFieldNames = ['oxid' => 0];\n\n    /**\n     * Cache key. Assigned to object depending on active view. Is used for object caching identification in lazy loading mechanism.\n     *\n     * @var string\n     */\n    protected $_sCacheKey = null;\n\n    /**\n     * Set $_blUseLazyLoading to true if you want to load only actually used fields not full objet, depending on views.\n     *\n     * @var bool\n     */\n    protected $_blUseLazyLoading = false;\n\n    /**\n     * Field name array of ignored fields when doing record update() (eg. oxarticles__oxtime)\n     *\n     * @var array\n     */\n    protected $_aSkipSaveFields = ['oxtimestamp'];\n\n    /**\n     * Enable skip save fields usage\n     *\n     * @var bool\n     */\n    protected $_blUseSkipSaveFields = true;\n\n    /**\n     * SQL query string for searching in DB (??)\n     *\n     * @var string\n     */\n    protected $_sExistKey = 'oxid';\n\n    /**\n     * $_blIsDerived is set to true if object instance originally belongs to another shop (oxshopid is not curretn shop)\n     * Use $this->isDerived() to access the value of this variable;\n     *\n     * @var bool\n     */\n    protected $_blIsDerived = null;\n\n    /**\n     * Disables field cache when set to true.\n     * This variable is used in case when lazy loading is performed on one object\n     * in order to force other class instances to use lady loading as well.\n     * So far this functionality is used in lists, when first element is lazy loaded, we must lazy load others as well\n     * as otherwise we will get empty objects on the first load.\n     *\n     * @var bool\n     */\n    protected static $_blDisableFieldCaching = [];\n\n    /**\n     * Marks that current object is managed by SEO\n     *\n     * @var bool\n     */\n    protected $_blIsSeoObject = false;\n\n    /**\n     * Flag allowing seo update for certain objects\n     *\n     * @var bool\n     */\n    protected $_blUpdateSeo = true;\n\n    /**\n     * Read only for object\n     *\n     * @var bool\n     */\n    protected $_blReadOnly = false;\n\n    /**\n     * Indicates if the item is list element\n     *\n     * @var bool\n     */\n    protected $_blIsInList = false;\n\n    /**\n     * Marks if object was loaded from db or not yet.\n     *\n     * @var bool\n     */\n    protected $_isLoaded = false;\n\n    /**\n     * store objects atributes values\n     *\n     * @var array\n     */\n    protected $_aInnerLazyCache = null;\n\n    /**\n     * Marker that multilanguage is OFF\n     *\n     * @var bool\n     */\n    protected $_blEmployMultilanguage = false;\n\n    /**\n     * Getting use skip fields or not\n     *\n     * @return bool\n     */\n    public function getUseSkipSaveFields()\n    {\n        return $this->_blUseSkipSaveFields;\n    }\n\n    /**\n     * Setting use skip fields or not\n     *\n     * @param bool $useSkipSaveFields - true or false\n     */\n    public function setUseSkipSaveFields($useSkipSaveFields)\n    {\n        $this->_blUseSkipSaveFields = $useSkipSaveFields;\n    }\n\n    /**\n     * Class constructor, sets active shop.\n     */\n    public function __construct()\n    {\n        // set active shop\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $this->_sCacheKey = $this->getViewName();\n\n        $this->addSkippedSaveFieldsForMapping();\n        $this->disableLazyLoadingForCaching();\n\n        if ($this->_blUseLazyLoading) {\n            $this->_sCacheKey .= $myConfig->getActiveView()->getClassKey();\n        } else {\n            $this->_sCacheKey .= 'allviews';\n        }\n\n        //do not cache for admin?\n        if ($this->isAdmin()) {\n            $this->_sCacheKey = null;\n        }\n\n        $this->setShopId($myConfig->getShopId());\n    }\n\n    /**\n     * Magic setter. If using lazy loading, adds setted field to fields array\n     *\n     * @param string $fieldName  name value\n     * @param mixed  $fieldValue value\n     */\n    public function __set($fieldName, $fieldValue)\n    {\n        $this->$fieldName = $fieldValue;\n        if ($this->_blUseLazyLoading && strpos($fieldName, $this->_sCoreTable . '__') === 0) {\n            $preparedFieldName = str_replace($this->_sCoreTable . '__', '', $fieldName);\n            if (\n                $preparedFieldName !== 'oxnid'\n                && (!isset($this->_aFieldNames[$preparedFieldName]) || !$this->_aFieldNames[$preparedFieldName])\n            ) {\n                $allFieldsList = $this->getAllFields(true);\n                if (isset($allFieldsList[strtolower($preparedFieldName)])) {\n                    $fieldStatus = $this->getFieldStatus($preparedFieldName);\n                    $this->addField($preparedFieldName, $fieldStatus);\n                }\n            }\n        }\n    }\n\n    /**\n     * Magic getter for older versions and template variables\n     *\n     * @param string $variableName variable name\n     *\n     * @return mixed\n     */\n    public function __get($variableName)\n    {\n        switch ($variableName) {\n            case 'blIsDerived':\n                return $this->isDerived();\n            case 'sOXID':\n                return $this->getId();\n            case 'blReadOnly':\n                return $this->isReadOnly();\n        }\n\n        // implementing lazy loading fields\n        // This part of the code is slow and normally is called before field cache is built.\n        // Make sure it is not called after first page is loaded and cache data is fully built.\n        if ($this->_blUseLazyLoading && stripos($variableName, $this->_sCoreTable . \"__\") === 0) {\n            if ($this->getId()) {\n                //lazy load it\n                $fieldName = str_replace($this->_sCoreTable . '__', '', $variableName);\n                $cacheFieldName = strtoupper($fieldName);\n\n                $fieldStatus = $this->getFieldStatus($fieldName);\n\n                $viewName = $this->getGetterViewName();\n                $id = $this->getId();\n\n                try {\n                    if ($this->_aInnerLazyCache === null) {\n                        $database = DatabaseProvider::getDb();\n                        $query = 'SELECT * FROM ' . $viewName . ' WHERE `oxid` = :oxid';\n                        $queryResult = $database->select($query, [\n                            'oxid' => $id\n                        ]);\n                        if ($queryResult && $queryResult->count()) {\n                            $this->_aInnerLazyCache = array_change_key_case($queryResult->fields, CASE_UPPER);\n                            if (array_key_exists($cacheFieldName, $this->_aInnerLazyCache)) {\n                                $fieldValue = $this->_aInnerLazyCache[$cacheFieldName];\n                            } else {\n                                return null;\n                            }\n                        } else {\n                            return null;\n                        }\n                    } elseif (array_key_exists($cacheFieldName, $this->_aInnerLazyCache)) {\n                        $fieldValue = $this->_aInnerLazyCache[$cacheFieldName];\n                    } else {\n                        return null;\n                    }\n\n                    $this->addField($fieldName, $fieldStatus);\n                    $this->setFieldData($fieldName, $fieldValue);\n\n                    //save names to cache for next loading\n                    if ($this->_sCacheKey) {\n                        $myUtils = \\OxidEsales\\Eshop\\Core\\Registry::getUtils();\n                        $cacheKey = 'fieldnames_' . $this->_sCoreTable . '_' . $this->_sCacheKey;\n                        $fieldNames = $myUtils->fromFileCache($cacheKey);\n                        $fieldNames[$fieldName] = $fieldStatus;\n                        $myUtils->toFileCache($cacheKey, $fieldNames);\n                    }\n                } catch (Exception $e) {\n                    return null;\n                }\n\n                //do not use field cache for this page\n                //as if we use it for lists then objects are loaded empty instead of lazy loading.\n                self::$_blDisableFieldCaching[get_class($this)] = true;\n            }\n\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtilsObject()->resetInstanceCache(get_class($this));\n        }\n\n        //returns stdClass implementing __toString() method due to uknown scenario where this var should be used.\n        if (!$this->isPropertyLoaded($variableName)) {\n            $this->$variableName = null;\n        }\n\n        return $this->$variableName;\n    }\n\n    /**\n     * Get view name for magic getter\n     *\n     * @return string\n     */\n    protected function getGetterViewName()\n    {\n        return $this->getViewName();\n    }\n\n    /**\n     * Magic isset() handler. Workaround for detecting if protected properties are set.\n     *\n     * @param mixed $variableName Supplied class variable\n     *\n     * @return bool\n     */\n    public function __isset($variableName)\n    {\n        return $this->isPropertyLoaded($variableName) || $this->isPropertyField($variableName);\n    }\n\n    /**\n     * Magic function invoked on object cloning. Basically takes care about cloning properly DB fields.\n     */\n    public function __clone()\n    {\n        if (!$this->_blIsSimplyClonable) {\n            foreach ($this->_aFieldNames as $fieldName => $fieldValue) {\n                $fieldLongName = $this->getFieldLongName($fieldName);\n                if (is_object($this->$fieldLongName)) {\n                    $this->$fieldLongName = clone $this->$fieldLongName;\n                }\n            }\n        }\n    }\n\n    /**\n     * Clone this object - similar to Copy Constructor.\n     *\n     * @param object $object Object to copy\n     */\n    public function oxClone($object)\n    {\n        $classVariables = get_object_vars($object);\n        foreach ($classVariables as $name => $value) {\n            if (is_object($object->$name)) {\n                $this->$name = clone $object->$name;\n            } else {\n                $this->$name = $object->$name;\n            }\n        }\n    }\n\n    /**\n     * Returns update seo flag\n     *\n     * @return boolean\n     */\n    public function getUpdateSeo()\n    {\n        return $this->_blUpdateSeo;\n    }\n\n    /**\n     * Sets update seo flag\n     *\n     * @param boolean $updateSeo\n     */\n    public function setUpdateSeo($updateSeo)\n    {\n        $this->_blUpdateSeo = $updateSeo;\n    }\n\n    /**\n     * Checks whether certain field has changed, and sets update seo flag if needed.\n     * It can only set the value to false, so it allows for multiple calls to the method,\n     * and if atleast one requires seo update, other checks won't override that.\n     *\n     * @param string $fieldName Field name that will be checked\n     */\n    protected function setUpdateSeoOnFieldChange($fieldName)\n    {\n        if ($this->getId() && in_array($fieldName, $this->getFieldNames())) {\n            $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            $tableName = $this->getCoreTableName();\n            $title = $database->getOne(\"select `{$fieldName}` from `{$tableName}` where `oxid` = :oxid\", [\n                'oxid' => $this->getId()\n            ]);\n            $fieldValue = \"{$tableName}__{$fieldName}\";\n            $currentTime = $this->$fieldValue->value;\n\n            if ($title == $currentTime) {\n                $this->setUpdateSeo(false);\n            }\n        }\n    }\n\n    /**\n     * Sets the names to main and view tables, loads metadata of each table.\n     *\n     * @param string $tableName      Name of DB object table\n     * @param bool   $forceAllFields Forces initialisation of all fields overriding lazy loading functionality\n     */\n    public function init($tableName = null, $forceAllFields = false)\n    {\n        if ($tableName) {\n            $this->_sCoreTable = $tableName;\n        }\n\n        // reset view table\n        $this->_sViewTable = false;\n\n        if (count($this->_aFieldNames) <= 1) {\n            $this->initDataStructure($forceAllFields);\n        }\n    }\n\n    /**\n     * Assigns DB field values to object fields. Returns true on success.\n     *\n     * @param array $dbRecord Associative data values array\n     *\n     * @return null\n     */\n    public function assign($dbRecord)\n    {\n        if (!is_array($dbRecord)) {\n            return;\n        }\n\n        foreach ($dbRecord as $name => $value) {\n            $this->setFieldData($name, $value);\n        }\n\n        $oxidField = $this->getFieldLongName('oxid');\n        if ($this->$oxidField instanceof Field) {\n            $this->_sOXID = $this->$oxidField->value;\n        }\n    }\n\n    /**\n     * Returns object class name\n     *\n     * @return string\n     */\n    public function getClassName()\n    {\n        return $this->_sClassName;\n    }\n\n    /**\n     * Return object core table name\n     *\n     * @return string\n     */\n    public function getCoreTableName()\n    {\n        return $this->_sCoreTable;\n    }\n\n    /**\n     * Returns unique object id.\n     *\n     * @return string\n     */\n    public function getId()\n    {\n        return $this->_sOXID;\n    }\n\n    /**\n     * Sets unique object id\n     *\n     * @param string $oxid Record ID\n     *\n     * @return string\n     */\n    public function setId($oxid = null)\n    {\n        if ($oxid) {\n            $this->_sOXID = $oxid;\n        } else {\n            if ($this->getCoreTableName() == 'oxobject2category') {\n                $objectId = $this->oxobject2category__oxobjectid;\n                $categoryId = $this->oxobject2category__oxcatnid;\n                $shopId = $this->oxobject2category__oxshopid;\n                $this->_sOXID = md5($objectId . $categoryId . $shopId);\n            } else {\n                $this->_sOXID = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsObject()->generateUID();\n            }\n        }\n\n        $idFieldName = $this->getCoreTableName() . '__oxid';\n        $this->$idFieldName = new Field($this->_sOXID, Field::T_RAW);\n\n        return $this->_sOXID;\n    }\n\n    /**\n     * Sets original object shop ID\n     *\n     * @param int $shopId New shop ID\n     */\n    public function setShopId($shopId)\n    {\n        $this->_iShopId = $shopId;\n    }\n\n    /**\n     * Return original object shop id\n     *\n     * @return int\n     */\n    public function getShopId()\n    {\n        return $this->_iShopId;\n    }\n\n    /**\n     * Returns main table data is actually selected from (could be a view name as well)\n     *\n     * @param bool $forceCoreTableUsage (optional) use core views\n     *\n     * @return string\n     */\n    public function getViewName($forceCoreTableUsage = null)\n    {\n        if (!$this->_sViewTable || ($forceCoreTableUsage !== null)) {\n            if ($forceCoreTableUsage === true) {\n                return $this->getCoreTableName();\n            }\n\n            $forceCoreTableUsage = $this->checkIfCoreTableNeeded($forceCoreTableUsage);\n\n            if (($forceCoreTableUsage !== null) && $forceCoreTableUsage) {\n                $shopId = -1;\n            } else {\n                $shopId = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId();\n            }\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $viewName = $tableViewNameGenerator->getViewName(\n                $this->getCoreTableName(),\n                $this->_blEmployMultilanguage == false ? -1 : $this->getLanguage(),\n                $shopId\n            );\n            if ($forceCoreTableUsage !== null) {\n                return $viewName;\n            }\n            $this->_sViewTable = $viewName;\n        }\n\n        return $this->_sViewTable;\n    }\n\n    /**\n     * Additional check if core table name should be returned in getViewName\n     *\n     * @param mixed $forceCoreTableUsage\n     * @return mixed\n     */\n    protected function checkIfCoreTableNeeded($forceCoreTableUsage)\n    {\n        return $forceCoreTableUsage;\n    }\n\n    /**\n     * Lazy loading cache key modifier.\n     *\n     * @param string $cacheKey Cache  key\n     * @param bool   $override Marker to force override cache key\n     */\n    public function modifyCacheKey($cacheKey, $override = false)\n    {\n        if ($override) {\n            $this->_sCacheKey = $cacheKey;\n        } else {\n            $this->_sCacheKey .= $cacheKey;\n        }\n    }\n\n    /**\n     * Disables lazy loading mechanism and init object fully\n     */\n    public function disableLazyLoading()\n    {\n        $this->_blUseLazyLoading = false;\n        $this->initDataStructure(true);\n    }\n\n    /**\n     * Returns true in case the item represented by this object is derived from parent shop\n     *\n     * @return bool\n     */\n    public function isDerived()\n    {\n        return $this->_blIsDerived;\n    }\n\n    /**\n     * Returns true in case the item represented by this object is derived from parent shop\n     *\n     * @param bool $value if derived\n     */\n    public function setIsDerived($value)\n    {\n        $this->_blIsDerived = $value;\n    }\n\n    /**\n     * Returns true, if object has multi language fields (if object is derived from oxi18n class).\n     * In oxBase it is always returns false, as oxBase treats all fields as non multi language.\n     *\n     * @return bool\n     */\n    public function isMultiLang()\n    {\n        return false;\n    }\n\n    /**\n     * Loads object data from DB (object data ID is passed to method). Returns\n     * true on success.\n     * could throw oxObjectException F ?\n     *\n     * @param string $oxid Object ID\n     *\n     * @return bool\n     */\n    public function load($oxid)\n    {\n        //getting at least one field before lazy loading the object\n        $this->addField('oxid', 0);\n        $query = $this->buildSelectString([$this->getViewName() . '.oxid' => $oxid]);\n        $this->_isLoaded = $this->assignRecord($query);\n\n        return $this->_isLoaded;\n    }\n\n    /**\n     * Returns object \"loaded\" state\n     *\n     * @return bool\n     */\n    public function isLoaded()\n    {\n        return $this->_isLoaded;\n    }\n\n    /**\n     * Builds and returns SQL query string.\n     *\n     * @param null|array $whereCondition SQL select WHERE conditions array (default false)\n     *\n     * @return string\n     */\n    public function buildSelectString($whereCondition = null)\n    {\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $get = $this->getSelectFields();\n        $query = \"select $get from \" . $this->getViewName() . ' where 1 ';\n\n        if ($whereCondition) {\n            reset($whereCondition);\n            foreach ($whereCondition as $name => $value) {\n                $query .= ' and ' . $name . ' = ' . $database->quote($value ?? '');\n            }\n        }\n\n        return $query;\n    }\n\n    /**\n     * Performs SQL query, assigns record field values to object. Returns true on success.\n     *\n     * @param string $select SQL statement\n     *\n     * @return bool\n     * @deprecated method will be removed from the public API in v7.0 use QueryBuilderFactoryInterface and assign()\n     * @see BaseModel::assign()\n     * @see \\OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface\n     */\n    protected function assignRecord($select)\n    {\n        $record = $this->getRecordByQuery($select);\n\n        if ($record != false && $record->count() > 0) {\n            $this->assign($record->fields);\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Get record\n     *\n     * @param string $query\n     *\n     * @return mixed\n     * @deprecated method will be removed from the public API in v7.0 use QueryBuilderFactoryInterface\n     * @see \\OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface\n     */\n    protected function getRecordByQuery($query)\n    {\n        return DatabaseProvider::getDb()->select($query);\n    }\n\n    /**\n     * Gets field data\n     *\n     * @param string $fieldName name (eg. 'oxtitle') of a data field to get\n     *\n     * @return mixed value of a data field\n     */\n    public function getFieldData($fieldName)\n    {\n        return ($field = $this->getFieldIfAvailable($fieldName)) ? $field->value : null;\n    }\n\n    /**\n     * Gets raw field data\n     *\n     * @param string $fieldName name (eg. 'oxtitle') of a data field to get\n     *\n     * @return mixed value of a data field\n     *\n     */\n    public function getRawFieldData($fieldName)\n    {\n        return ($field = $this->getFieldIfAvailable($fieldName)) ? $field->rawValue : null;\n    }\n\n    private function getFieldIfAvailable($fieldName): ?Field\n    {\n        $longFieldName = $this->getFieldLongName($fieldName);\n\n        return ($this->$longFieldName instanceof Field) ? $this->$longFieldName : null;\n    }\n\n    /**\n     * Function builds the field list used in select.\n     *\n     * @param bool $forceCoreTableUsage (optional) use core views\n     *\n     * @return string\n     */\n    public function getSelectFields($forceCoreTableUsage = null)\n    {\n        $selectFields = [];\n\n        $viewName = $this->getViewName($forceCoreTableUsage);\n\n        foreach ($this->_aFieldNames as $key => $field) {\n            if ($viewName) {\n                $selectFields[] = \"`$viewName`.`$key`\";\n            } else {\n                $selectFields[] = \".`$key`\";\n            }\n        }\n\n        return implode(', ', $selectFields);\n    }\n\n    /**\n     * Delete this object from the database, returns true if entry was deleted.\n     *\n     * @param string $oxid Object ID(default null)\n     *\n     * @return bool\n     */\n    public function delete($oxid = null)\n    {\n        $oxid = $oxid ? : $this->getId();\n        if (!$oxid || !$this->allowDerivedDelete()) {\n            return false;\n        }\n\n        ContainerFacade::dispatch(new BeforeModelDeleteEvent($this));\n\n        $this->removeElement2ShopRelations($oxid);\n\n        $database = DatabaseProvider::getDb();\n        $coreTable = $this->getCoreTableName();\n        $deleteQuery = \"delete from {$coreTable} where oxid = :oxid\";\n        $affectedRows = $database->execute($deleteQuery, [\n            'oxid' => $oxid\n        ]);\n        if ($blDelete = (bool) $affectedRows) {\n            $this->onChange(ACTION_DELETE, $oxid);\n        }\n\n        return $blDelete;\n    }\n\n    /**\n     * Removes relevant mapping data for selected object if it is a multishop inheritable table\n     *\n     * @param string $oxid Object ID\n     */\n    protected function removeElement2ShopRelations($oxid)\n    {\n    }\n\n    /**\n     * Save this Object to database, insert or update as needed.\n     *\n     * @throws Exception\n     *\n     * @return string|bool\n     */\n    public function save()\n    {\n        if (!is_array($this->_aFieldNames)) {\n            return false;\n        }\n\n        // #739A - should be executed here because of date/time formatting feature\n        if (\n            $this->isAdmin()\n            && !\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blSkipFormatConversion')\n        ) {\n            foreach ($this->_aFieldNames as $name => $value) {\n                $longName = $this->getFieldLongName($name);\n                if (isset($this->$longName->fldtype) && $this->$longName->fldtype == 'datetime') {\n                    \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->convertDBDateTime($this->$longName, true);\n                } elseif (isset($this->$longName->fldtype) && $this->$longName->fldtype == 'timestamp') {\n                    \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->convertDBTimestamp($this->$longName, true);\n                } elseif (isset($this->$longName->fldtype) && $this->$longName->fldtype == 'date') {\n                    \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->convertDBDate($this->$longName, true);\n                }\n            }\n        }\n\n        $return = false;\n\n        $action = null;\n        $response = null;\n        /** We must check on the master database, if an entry exists, so we switch to master connection.*/\n        \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster();\n        if ($this->exists()) {\n            //only update if derived update is allowed\n            if ($this->allowDerivedUpdate()) {\n                $response = $this->update();\n                $action = ACTION_UPDATE;\n            }\n        } else {\n            $response = $this->insert();\n            $action = ACTION_INSERT;\n            ContainerFacade::dispatch(new AfterModelInsertEvent($this));\n        }\n\n        $this->onChange($action);\n\n        if ($response) {\n            $return = $this->getId();\n        }\n\n        return $return;\n    }\n\n    /**\n     * Checks if derived update is allowed (calls \\OxidEsales\\Eshop\\Core\\Model\\BaseModel::isDerived)\n     *\n     * @return bool\n     */\n    public function allowDerivedUpdate()\n    {\n        return !$this->isDerived();\n    }\n\n    /**\n     * Checks if derived delete is allowed (calls \\OxidEsales\\Eshop\\Core\\Model\\BaseModel::isDerived)\n     *\n     * @return bool\n     */\n    public function allowDerivedDelete()\n    {\n        return !$this->isDerived();\n    }\n\n    /**\n     * Checks if this object exists, returns true on success.\n     *\n     * @param string $oxid Object ID(default null)\n     *\n     * @return bool\n     */\n    public function exists($oxid = null)\n    {\n        if (!$oxid) {\n            $oxid = $this->getId();\n        }\n        if (!$oxid) {\n            return false;\n        }\n\n        $viewName = $this->getCoreTableName();\n        $database = DatabaseProvider::getDb();\n        $query = \"select {$this->_sExistKey} from {$viewName} where {$this->_sExistKey} = :oxid\";\n\n        return (bool) $database->getOne($query, [\n            'oxid' => $oxid\n        ]);\n    }\n\n    /**\n     * Returns SQL select string with checks if items are available\n     *\n     * @param bool $forceCoreTable forces core table usage (optional)\n     *\n     * @return string\n     */\n    public function getSqlActiveSnippet($forceCoreTable = null)\n    {\n        $query = '';\n        $tableName = $this->getViewName($forceCoreTable);\n\n        // has 'active' field ?\n        if (isset($this->_aFieldNames['oxactive'])) {\n            $query = \" $tableName.oxactive = 1 \";\n        }\n\n        // has 'activefrom'/'activeto' fields ?\n        if (isset($this->_aFieldNames['oxactivefrom']) && isset($this->_aFieldNames['oxactiveto'])) {\n            $query = $this->addSqlActiveRangeSnippet($query, $tableName);\n        }\n\n        return $query;\n    }\n\n    /**\n     * This function is triggered before the record is updated.\n     * If you make any update to the database record manually you should also call beforeUpdate() from your script.\n     *\n     * @param string $oxid Object ID(default null). Pass the ID in case object is not loaded.\n     */\n    public function beforeUpdate($oxid = null)\n    {\n        ContainerFacade::dispatch(new BeforeModelUpdateEvent($this));\n    }\n\n    /**\n     * This function is triggered whenever the object is saved or deleted.\n     * onChange() is triggered after saving the changes in Save() method, after deleting the instance from the database.\n     * If you make any change to the database record manually you should also call onChange() from your script.\n     *\n     * @param int    $action Action identifier.\n     * @param string $oxid   Object ID(default null). Pass the ID in case object is not loaded.\n     */\n    public function onChange($action = null, $oxid = null)\n    {\n        if (ACTION_DELETE == $action) {\n            ContainerFacade::dispatch(new AfterModelDeleteEvent($this));\n        } else {\n            ContainerFacade::dispatch(new AfterModelUpdateEvent($this));\n        }\n    }\n\n    /**\n     * Sets item as list element\n     */\n    public function setInList()\n    {\n        $this->_blIsInList = true;\n    }\n\n    /**\n     * Checks if this instance is one of oxList elements.\n     *\n     * @return bool\n     */\n    protected function isInList()\n    {\n        return $this->_blIsInList;\n    }\n\n    /**\n     * Returns actual object view or table name\n     *\n     * @param string $table  Original table name\n     * @param int    $shopId Shop ID\n     *\n     * @return string\n     */\n    protected function getObjectViewName($table, $shopId = null)\n    {\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        return $tableViewNameGenerator->getViewName($table, -1, $shopId);\n    }\n\n    /**\n     * Returns meta field or simple array of all object fields.\n     * This method is slow and normally is called before field cache is built.\n     * Make sure it is not called after first page is loaded and cache data is fully built (until tmp dir is cleaned).\n     *\n     * @param string $table             Table name\n     * @param bool $returnSimpleArray Set $returnSimple to true when you need simple array\n     *                                (meta data array is returned otherwise)\n     *\n     * @return array\n     */\n    protected function getTableFields($table, $returnSimpleArray = false)\n    {\n        $myUtils = \\OxidEsales\\Eshop\\Core\\Registry::getUtils();\n\n        $cacheKey = $table . '_allfields_' . $returnSimpleArray;\n        $metaFields = $myUtils->fromFileCache($cacheKey);\n\n        if ($metaFields) {\n            return $metaFields;\n        }\n\n        $metaFields = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getInstance()->getTableDescription($table);\n\n        if (!$returnSimpleArray) {\n            $myUtils->toFileCache($cacheKey, $metaFields);\n\n            return $metaFields;\n        }\n\n        //returning simple array\n        $result = [];\n        if (is_array($metaFields)) {\n            foreach ($metaFields as $valueObject) {\n                $result[strtolower($valueObject->name)] = 0;\n            }\n        }\n\n        $myUtils->toFileCache($cacheKey, $result);\n\n        return $result;\n    }\n\n    /**\n     * Returns meta field or simple array of all object fields.\n     * This method is slow and normally is called before field cache is built.\n     * Make sure it is not called after first page is loaded and cache data is fully built (until tmp dir is cleaned).\n     *\n     * @param bool $returnSimple Set $blReturnSimple to true when you need simple array\n     *                           (meta data array is returned otherwise)\n     *\n     * @return array\n     * @see \\OxidEsales\\Eshop\\Core\\Model\\BaseModel::_getTableFields()\n     *\n     */\n    protected function getAllFields($returnSimple = false)\n    {\n        if (!$this->getCoreTableName()) {\n            return [];\n        }\n\n        return $this->getTableFields($this->getCoreTableName(), $returnSimple);\n    }\n\n    /**\n     * Initializes object data structure.\n     * Either by trying to load from cache or by calling $this->_getNonCachedFieldNames\n     *\n     * @param bool $forceFullStructure Set to true if you want to load full structure in any case.\n     */\n    protected function initDataStructure($forceFullStructure = false)\n    {\n        $myUtils = \\OxidEsales\\Eshop\\Core\\Registry::getUtils();\n\n        //get field names from cache\n        $fieldNamesList = null;\n        $fullCacheKey = 'fieldnames_' . $this->getCoreTableName() . '_' . $this->_sCacheKey;\n        if ($this->_sCacheKey && !$this->isDisabledFieldCache()) {\n            $fieldNamesList = $myUtils->fromFileCache($fullCacheKey);\n        }\n\n        if (!$fieldNamesList) {\n            $fieldNamesList = $this->getNonCachedFieldNames($forceFullStructure);\n            if ($this->_sCacheKey && !$this->isDisabledFieldCache()) {\n                $myUtils->toFileCache($fullCacheKey, $fieldNamesList);\n            }\n        }\n\n        if ($fieldNamesList !== false) {\n            foreach ($fieldNamesList as $field => $status) {\n                $this->addField($field, $status);\n            }\n        }\n    }\n\n    /**\n     * Returns the list of fields. This function is slower and its result is normally cached.\n     * Basically we have 3 separate cases here:\n     *  1. We are in admin so we need extended info for all fields (name, field length and field type)\n     *  2. Object is not lazy loaded so we will return all data fields as simple array, as we need only names\n     *  3. Object is lazy loaded so we will return empty array as all fields are loaded on request (in __get()).\n     *\n     * @param bool $forceFullStructure Whether to force loading of full data structure\n     *\n     * @return array|bool\n     */\n    protected function getNonCachedFieldNames($forceFullStructure = false)\n    {\n        //T2008-02-22\n        //so if this method is executed on cached version we see it when profiling\n        startProfile('!__CACHABLE__!');\n\n        //case 1. (admin)\n        if ($this->isAdmin()) {\n            $metaFields = $this->getAllFields();\n            foreach ($metaFields as $oneField) {\n                if ($oneField->max_length == -1) {\n                    $oneField->max_length = 10; // double or float\n                }\n\n                if ($oneField->type == 'datetime') {\n                    $oneField->max_length = 20;\n                }\n\n                $this->addField(\n                    $oneField->name,\n                    $this->getFieldStatus($oneField->name),\n                    $oneField->type,\n                    $oneField->max_length\n                );\n            }\n            stopProfile('!__CACHABLE__!');\n\n            return false;\n        }\n\n        //case 2. (just get all fields)\n        if ($forceFullStructure || !$this->_blUseLazyLoading) {\n            $metaFields = $this->getAllFields(true);\n            stopProfile('!__CACHABLE__!');\n\n            return $metaFields;\n        }\n\n        //case 3. (get only oxid field, so we can fetch the rest of the fields over lazy loading mechanism)\n        stopProfile('!__CACHABLE__!');\n\n        return ['oxid' => 0];\n    }\n\n    /**\n     * Returns _aFieldName[] value. 0 means - non multi language, 1 - multi language field.\n     * But this is defined only in derived oxi18n class.\n     * In oxBase it is always 0, as oxBase treats all fields as non multi language.\n     *\n     * @param string $fieldName Field name\n     *\n     * @return int\n     */\n    protected function getFieldStatus($fieldName)\n    {\n        return 0;\n    }\n\n    /**\n     * Adds additional field to meta structure\n     *\n     * @param string $fieldName   Field name\n     * @param int    $fieldStatus Field name status. In derived classes it indicates multi language status.\n     * @param string $type        Field type\n     * @param string $length      Field Length\n     *\n     * @return null\n     */\n    protected function addField($fieldName, $fieldStatus, $type = null, $length = null)\n    {\n        //preparation\n        $fieldName = strtolower($fieldName);\n\n        //adding field names element\n        $this->_aFieldNames[$fieldName] = $fieldStatus;\n\n        //already set?\n        $fieldLongName = $this->getFieldLongName($fieldName);\n        if ($this->isPropertyLoaded($fieldLongName)) {\n            return;\n        }\n\n        //defining the field\n        $field = false;\n\n        if (isset($type)) {\n            $field = new Field();\n            $field->fldtype = $type;\n            //T2008-01-29\n            //can't clone as the fields are objects and are not fully cloned\n            $this->_blIsSimplyClonable = false;\n        }\n\n        if (isset($length)) {\n            if (!$field) {\n                $field = new Field();\n            }\n            $field->fldmax_length = $length;\n            $this->_blIsSimplyClonable = false;\n        }\n\n        $this->$fieldLongName = $field;\n    }\n\n    /**\n     * Returns long field name in \"<table>__<field_name>\" format.\n     *\n     * @param string $fieldName Short field name\n     *\n     * @return string\n     */\n    protected function getFieldLongName($fieldName)\n    {\n        //trying to avoid strpos call as often as possible\n        $coreTableName = $this->getCoreTableName();\n        if ($fieldName[2] == $coreTableName[2] && strpos($fieldName, $coreTableName . '__') === 0) {\n            return $fieldName;\n        }\n\n        return $coreTableName . '__' . strtolower($fieldName);\n    }\n\n    /**\n     * Sets data field value\n     *\n     * @param string $fieldName  Index OR name (eg. 'oxarticles__oxtitle') of a data field to set\n     * @param string $fieldValue Value of data field\n     * @param int    $dataType   Field type\n     */\n    protected function setFieldData($fieldName, $fieldValue, $dataType = Field::T_TEXT)\n    {\n        $longFieldName = $this->getFieldLongName($fieldName);\n        //$sLongFieldName = $this->_sCoreTable . \"__\" . strtolower($sFieldName);\n\n        // doing this because in lazy loaded lists on first load it would be harmful\n        // to have the fields initialised without setting values\n        // situation: only first article is loaded fully for \"select oxid from oxarticles\"\n        //if ($this->_blUseLazyLoading && !isset($this->$sLongFieldName))\n        //    return;\n\n        //in non lazy loading case we just add a field and do not care about it more\n        if (\n            !$this->_blUseLazyLoading\n            && !$this->isPropertyLoaded($longFieldName)\n        ) {\n            $fieldsList = $this->getAllFields(true);\n            if (isset($fieldsList[strtolower($fieldName)])) {\n                $this->addField($fieldName, $this->getFieldStatus($fieldName));\n            }\n        }\n        // if we have a double field we replace \",\" with \".\" in case somebody enters it in european format\n        $isPropertyLoaded = $this->isPropertyLoaded($longFieldName);\n        if (\n            $isPropertyLoaded\n            && isset($this->$longFieldName->fldtype)\n            && $this->$longFieldName->fldtype == 'double'\n        ) {\n            $fieldValue = str_replace(',', '.', $fieldValue);\n        }\n\n        // isset is REQUIRED here not to use getter\n        if (\n            $isPropertyLoaded\n            && is_object($this->$longFieldName)\n        ) {\n            $this->$longFieldName->setValue($fieldValue, $dataType);\n        } else {\n            $this->$longFieldName = new Field($fieldValue, $dataType);\n        }\n    }\n\n    /**\n     * check if db field can be null\n     *\n     * @param string $fieldName db field name\n     *\n     * @return bool\n     */\n    protected function canFieldBeNull($fieldName)\n    {\n        $metaData = $this->getAllFields();\n        foreach ($metaData as $metaInfo) {\n            if (strcasecmp($metaInfo->name, $fieldName) == 0) {\n                return !$metaInfo->not_null;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * returns default field value\n     *\n     * @param string $fieldName db field name\n     *\n     * @return mixed\n     */\n    protected function getFieldDefaultValue($fieldName)\n    {\n        $metaData = $this->getAllFields();\n        foreach ($metaData as $metaInfo) {\n            if (strcasecmp($metaInfo->name, $fieldName) == 0) {\n                return property_exists($metaInfo, 'default_value') ? $metaInfo->default_value : null;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * returns quoted field value for using in update statement\n     *\n     * @param string $fieldName name of field\n     * @param Field  $field     field object\n     *\n     * @return string\n     */\n    protected function getUpdateFieldValue($fieldName, $field)\n    {\n        $fieldValue = null;\n        if ($field instanceof Field) {\n            $fieldValue = $field->getRawValue();\n        } elseif (isset($field->value)) {\n            $fieldValue = $field->value;\n        }\n\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        //Check if this field value is null AND it can be null according if not returning default value\n        if ((null === $fieldValue)) {\n            if ($this->canFieldBeNull($fieldName)) {\n                return 'null';\n            } elseif ($fieldValue = $this->getFieldDefaultValue($fieldName)) {\n                return $database->quote($fieldValue);\n            }\n        }\n\n        return $database->quote($fieldValue ?? '');\n    }\n\n    /**\n     * Get object fields sql part used for updates or inserts:\n     * return e.g.  fldName1 = 'value1',fldName2 = 'value2'...\n     *\n     * @param bool $useSkipSaveFields forces usage of skip save fields array (default is true)\n     *\n     * @return string\n     */\n    protected function getUpdateFields($useSkipSaveFields = true)\n    {\n        $query = '';\n        $separator = '';\n\n        foreach (array_keys($this->_aFieldNames) as $oneFieldName) {\n            $longName = $this->getFieldLongName($oneFieldName);\n            $field = $this->$longName;\n\n            if (!$this->checkFieldCanBeUpdated($oneFieldName)) {\n                continue;\n            }\n\n            if (\n                !$useSkipSaveFields\n                || ($useSkipSaveFields && !in_array(strtolower($oneFieldName), $this->_aSkipSaveFields))\n            ) {\n                $query .= $separator . $oneFieldName . ' = ' . $this->getUpdateFieldValue($oneFieldName, $field);\n                $separator = ',';\n            }\n        }\n\n        return $query;\n    }\n\n    /**\n     * If needed, check if field can be updated\n     *\n     * @param string $fieldName\n     *\n     * @return bool\n     */\n    protected function checkFieldCanBeUpdated($fieldName)\n    {\n        return true;\n    }\n\n    /**\n     * Update this Object into the database, this function only works on\n     * the main table, it will not save any dependent tables, which might\n     * be loaded through oxList.\n     *\n     * @throws oxObjectException Throws on failure inserting\n     * @throws DatabaseException On database errors\n     *\n     * @return bool Will always return true. On failure an exception is thrown.\n     */\n    protected function update()\n    {\n        //do not allow derived item update\n        if (!$this->allowDerivedUpdate()) {\n            return false;\n        }\n\n        if (!$this->getId()) {\n            $exception = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ObjectException::class);\n            $exception->setMessage('EXCEPTION_OBJECT_OXIDNOTSET');\n            $exception->setObject($this);\n            throw $exception;\n        }\n        $coreTableName = $this->getCoreTableName();\n\n        $idKey = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->getArrFldName($coreTableName . '.oxid');\n        $this->$idKey = new Field($this->getId(), Field::T_RAW);\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $updateQuery = \"update {$coreTableName} set \" . $this->getUpdateFields() .\n                       \" where {$coreTableName}.oxid = \" . $database->quote($this->getId());\n\n        $this->beforeUpdate();\n        $this->executeDatabaseQuery($updateQuery);\n\n        return true;\n    }\n\n    /**\n     * Execute a query on the database.\n     *\n     * @param string $query The command to execute on the database.\n     * @param array  $params Parameters to fill the querry\n     *\n     * @return int The number of affected rows.\n     */\n    protected function executeDatabaseQuery($query, $params = [])\n    {\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        return $database->execute($query, $params);\n    }\n\n    /**\n     * Insert this Object into the database, this function only works\n     * on the main table, it will not save any dependent tables, which\n     * might be loaded through oxlist.\n     *\n     * @return bool\n     */\n    protected function insert()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $myUtils = \\OxidEsales\\Eshop\\Core\\Registry::getUtils();\n\n        // let's get a new ID\n        if (!$this->getId()) {\n            $this->setId();\n        }\n\n        $idKey = $myUtils->getArrFldName($this->getCoreTableName() . '.oxid');\n        $this->$idKey = new Field($this->getId(), Field::T_RAW);\n        $insertSql = \"Insert into {$this->getCoreTableName()} set \";\n\n        $shopIdField = $myUtils->getArrFldName($this->getCoreTableName() . '.oxshopid');\n\n        if (\n            $this->isPropertyLoaded($shopIdField)\n            && (!$this->isPropertyField($shopIdField) || !$this->$shopIdField->value)\n        ) {\n            $this->$shopIdField = new Field(\n                $myConfig->getShopId(),\n                Field::T_RAW\n            );\n        }\n\n        $insertSql .= $this->getUpdateFields($this->getUseSkipSaveFields());\n\n        return $result = (bool) $this->executeDatabaseQuery($insertSql);\n    }\n\n    /**\n     * Checks if current class disables field caching.\n     * This method is primary used in unit tests.\n     *\n     * @return bool\n     */\n    protected function isDisabledFieldCache()\n    {\n        $class = get_class($this);\n        if (isset(self::$_blDisableFieldCaching[$class]) && self::$_blDisableFieldCaching[$class]) {\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Add additional fields to skipped save fields\n     */\n    protected function addSkippedSaveFieldsForMapping()\n    {\n    }\n\n    /**\n     * Disable lazy loading if cache is enabled\n     */\n    protected function disableLazyLoadingForCaching()\n    {\n    }\n\n    /**\n     * Checks if object ID's first two chars are 'o' and 'x'. Returns true or false\n     *\n     * @return bool\n     */\n    public function isOx()\n    {\n        $oxid = $this->getId();\n        if (is_string($oxid) && $oxid[0] == 'o' && $oxid[1] == 'x') {\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Is object readonly\n     *\n     * @return bool\n     */\n    public function isReadOnly()\n    {\n        return $this->_blReadOnly;\n    }\n\n    /**\n     * Set object readonly\n     *\n     * @param bool $readOnly readonly flag\n     */\n    public function setReadOnly($readOnly)\n    {\n        $this->_blReadOnly = $readOnly;\n    }\n\n    /**\n     * Returns array with object field names\n     *\n     * @return array\n     */\n    public function getFieldNames()\n    {\n        $fieldNames = $this->_aFieldNames;\n\n        if (!$this->isAdmin() && $this->_blUseLazyLoading) {\n            $fieldNames = $this->getNonCachedFieldNames(true);\n        }\n\n        return array_keys($fieldNames);\n    }\n\n    /**\n     * Adds additional field name to meta structure\n     *\n     * @param string $name Field name\n     */\n    public function addFieldName($name)\n    {\n        //preparation\n        $name = strtolower($name);\n        $this->_aFieldNames[$name] = 0;\n    }\n\n    /**\n     * Returns -1, means object is not multi language\n     *\n     * @return int\n     */\n    public function getLanguage()\n    {\n        return -1;\n    }\n\n    /**\n     * Returns true if the property is loaded.\n     *\n     * @param  string $name\n     *\n     * @return bool\n     */\n    public function isPropertyLoaded($name)\n    {\n        return property_exists($this, $name) && isset($this->$name);\n    }\n\n    /**\n     * adds and activefrom/activeto to the query\n     *\n     * @param string $query\n     * @param string $tableName\n     *\n     * @return string\n     */\n    protected function addSqlActiveRangeSnippet($query, $tableName)\n    {\n        $dateObj = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate();\n        $secondsToRoundForQueryCache = $this->getSecondsToRoundForQueryCache();\n        $databaseFormattedDate = $dateObj->getRoundedRequestDateDBFormatted($secondsToRoundForQueryCache);\n        $query = $query ? \" $query or \" : '';\n\n        return \" ( $query ( $tableName.oxactivefrom < '$databaseFormattedDate' and $tableName.oxactiveto > '$databaseFormattedDate' ) ) \"; // phpcs:ignore\n    }\n\n    /**\n     *  Return a number of seconds used to define a interval for rounding timestamps\n     *  e.g. this method returns the value 60 then it means timestamps should be rounded to full minutes\n     *  so the query may get an cache hit because it can be stable for an interval of one minute\n     *\n     *  it is a own method to allow overriding in child classes\n     *  @return int the amount of seconds\n     */\n    protected function getSecondsToRoundForQueryCache()\n    {\n        //set default value cache time to 60 seconds\n        //because active from setting is based on minutes\n        return 60;\n    }\n\n    /**\n     * Returns true if the property is a Field.\n     *\n     * @param  string $name\n     *\n     * @return bool\n     */\n    private function isPropertyField($name)\n    {\n        return $this->$name instanceof Field;\n    }\n}\n"
  },
  {
    "path": "source/Core/Model/FieldNameHelper.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Model;\n\n/**\n * Helper to work with field names of a model.\n *\n * @internal Do not make a module extension for this class.\n */\nclass FieldNameHelper\n{\n    /**\n     * Return field names with and without table name as a prefix.\n     *\n     * @param string $tableName\n     * @param array  $fieldNames\n     *\n     * @return array\n     */\n    public function getFullFieldNames($tableName, $fieldNames)\n    {\n        $combinedFields = [];\n        $tablePrefix = strtolower($tableName) . '__';\n        foreach ($fieldNames as $fieldName) {\n            $fieldName = strtolower($fieldName);\n\n            $fieldNameWithoutTableName = str_replace($tablePrefix, '', $fieldName);\n            $combinedFields[] = $fieldNameWithoutTableName;\n\n            if (strpos($fieldName, $tablePrefix) !== 0) {\n                $fieldName = $tablePrefix . $fieldName;\n            }\n\n            $combinedFields[] = $fieldName;\n        }\n\n        return $combinedFields;\n    }\n}\n"
  },
  {
    "path": "source/Core/Model/ListModel.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Model;\n\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse ReturnTypeWillChange;\n\n/**\n * List manager.\n * Collects list data (eg. from DB), performs list changes updating (to DB), etc.\n */\nclass ListModel extends \\OxidEsales\\Eshop\\Core\\Base implements \\ArrayAccess, \\Iterator, \\Countable\n{\n    /**\n     * Array of objects (some object list).\n     *\n     * @var array $_aArray\n     */\n    protected $_aArray = [];\n\n    /**\n     * Save the state, that active element was unset\n     * needed for proper foreach iterator functionality\n     *\n     * @var bool $_blRemovedActive\n     */\n    protected $_blRemovedActive = false;\n\n    /**\n     * Template object used for some methods before the list is built.\n     *\n     * @var BaseModel\n     */\n    private $_oBaseObject = null;\n\n    /**\n     * Flag if array is ok or not\n     *\n     * @var boolean $_blValid\n     */\n    private $_blValid = true;\n\n    /**\n     * -----------------------------------------------------------------------------------------------------\n     *\n     * Implementation of SPL Array classes functions follows here\n     *\n     * -----------------------------------------------------------------------------------------------------\n     */\n\n    /**\n     * implementation of abstract classes for ArrayAccess follow\n     */\n    /**\n     * offsetExists for SPL\n     *\n     * @param mixed $offset SPL array offset\n     *\n     * @return boolean\n     */\n    #[ReturnTypeWillChange]\n    public function offsetExists($offset)\n    {\n        return isset($this->_aArray[$offset]);\n    }\n\n    /**\n     * offsetGet for SPL\n     *\n     * @param mixed $offset SPL array offset\n     *\n     * @return BaseModel\n     */\n    #[ReturnTypeWillChange]\n    public function offsetGet($offset)\n    {\n        if ($this->offsetExists($offset)) {\n            return $this->_aArray[$offset];\n        }\n\n        return false;\n    }\n\n    /**\n     * offsetSet for SPL\n     *\n     * @param mixed     $offset SPL array offset\n     * @param BaseModel $oBase  Array element\n     */\n    #[ReturnTypeWillChange]\n    public function offsetSet($offset, $oBase)\n    {\n        if (isset($offset)) {\n            $this->_aArray[$offset] = & $oBase;\n        } else {\n            $sLongFieldName = $this->getFieldLongName('oxid');\n            if (isset($oBase->$sLongFieldName->value)) {\n                $sOxid = $oBase->$sLongFieldName->value;\n                $this->_aArray[$sOxid] = & $oBase;\n            } else {\n                $this->_aArray[] = & $oBase;\n            }\n        }\n    }\n\n    /**\n     * offsetUnset for SPL\n     *\n     * @param mixed $offset SPL array offset\n     */\n    #[ReturnTypeWillChange]\n    public function offsetUnset($offset)\n    {\n        if ((string)$offset === (string)$this->key()) {\n            // #0002184: active element removed, next element will be prev / first\n            $this->_blRemovedActive = true;\n        }\n\n        unset($this->_aArray[$offset]);\n    }\n\n    /**\n     * Returns SPL array keys\n     *\n     * @return array\n     */\n    public function arrayKeys()\n    {\n        return array_keys($this->_aArray);\n    }\n\n    /**\n     * rewind for SPL\n     */\n    #[ReturnTypeWillChange]\n    public function rewind()\n    {\n        $this->_blRemovedActive = false;\n        $this->_blValid = (false !== reset($this->_aArray));\n    }\n\n    /**\n     * current for SPL\n     *\n     * @return null\n     */\n    #[ReturnTypeWillChange]\n    public function current()\n    {\n        return current($this->_aArray);\n    }\n\n    /**\n     * key for SPL\n     *\n     * @return mixed\n     */\n    #[ReturnTypeWillChange]\n    public function key()\n    {\n        return key($this->_aArray);\n    }\n\n    /**\n     * previous / first array element\n     *\n     * @return mixed\n     */\n    public function prev()\n    {\n        $oVar = prev($this->_aArray);\n        if ($oVar === false) {\n            // the first element, reset pointer\n            $oVar = reset($this->_aArray);\n        }\n        $this->_blRemovedActive = false;\n\n        return $oVar;\n    }\n\n    /**\n     * next for SPL\n     */\n    #[ReturnTypeWillChange]\n    public function next()\n    {\n        if ($this->_blRemovedActive === true && current($this->_aArray)) {\n            $oVar = $this->prev();\n        } else {\n            $oVar = next($this->_aArray);\n        }\n\n        $this->_blValid = (false !== $oVar);\n    }\n\n    /**\n     * valid for SPL\n     *\n     * @return boolean\n     */\n    #[ReturnTypeWillChange]\n    public function valid()\n    {\n        return $this->_blValid;\n    }\n\n    /**\n     * count for SPL\n     *\n     * @return integer\n     */\n    #[ReturnTypeWillChange]\n    public function count()\n    {\n        return count($this->_aArray);\n    }\n\n    /**\n     * clears/destroys list contents\n     */\n    public function clear()\n    {\n        /*\n        foreach ( $this->_aArray as $key => $sValue) {\n            unset( $this->_aArray[$key]);\n        }\n        reset( $this->_aArray);*/\n        $this->_aArray = [];\n    }\n\n    /**\n     * copies a given array over the objects internal array (something like old $myList->aList = $aArray)\n     *\n     * @param array $aArray array of list items\n     */\n    public function assign($aArray)\n    {\n        $this->_aArray = $aArray;\n    }\n\n    /**\n     * returns the array reversed, the internal array remains untouched\n     *\n     * @return array\n     */\n    public function reverse()\n    {\n        return array_reverse($this->_aArray);\n    }\n\n    /**\n     * -----------------------------------------------------------------------------------------------------\n     * SPL implementation end\n     * -----------------------------------------------------------------------------------------------------\n     */\n\n    /**\n     * List Object class name\n     *\n     * @var string\n     */\n    protected $_sObjectsInListName = 'oxBase';\n\n    /**\n     * Core table name\n     *\n     * @var string\n     */\n    protected $_sCoreTable = null;\n\n    /**\n     * @var string ShopID\n     */\n    protected $_sShopID = null;\n\n    /**\n     * @var array SQL Limit, 0 => Start, 1 => Records\n     */\n    protected $_aSqlLimit = [];\n\n    /**\n     * Class Constructor\n     *\n     * @param string $sObjectName Associated list item object type\n     */\n    public function __construct($sObjectName = null)\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $this->_aSqlLimit[0] = 0;\n        $this->_aSqlLimit[1] = 0;\n        $this->_sShopID = $myConfig->getShopId();\n\n        if ($sObjectName) {\n            $this->init($sObjectName);\n        }\n    }\n\n    /**\n     * Backward compatibility method\n     *\n     * @param string $sName Variable name\n     *\n     * @return mixed\n     */\n    public function __get($sName)\n    {\n        if ($sName == 'aList') {\n            return $this->_aArray;\n        }\n    }\n\n    /**\n     * Returns list items array\n     *\n     * @return array\n     */\n    public function getArray()\n    {\n        return $this->_aArray;\n    }\n\n    /**\n     * Inits list table name and object name.\n     *\n     * @param string $sObjectName List item object type\n     * @param string $sCoreTable  Db table name this list s selected from\n     */\n    public function init($sObjectName, $sCoreTable = null)\n    {\n        $this->_sObjectsInListName = $sObjectName;\n        if ($sCoreTable) {\n            $this->_sCoreTable = $sCoreTable;\n        }\n    }\n\n    /**\n     * Initializes or returns existing list template object.\n     *\n     * @return BaseModel\n     */\n    public function getBaseObject()\n    {\n        if (!$this->_oBaseObject) {\n            $this->_oBaseObject = oxNew($this->_sObjectsInListName);\n            $this->_oBaseObject->setInList();\n            $this->_oBaseObject->init($this->_sCoreTable);\n        }\n\n        return $this->_oBaseObject;\n    }\n\n    /**\n     * Sets base object for list.\n     *\n     * @param object $oObject Base object\n     */\n    public function setBaseObject($oObject)\n    {\n        $this->_oBaseObject = $oObject;\n    }\n\n    /**\n     * Convert results of the given select statement into objects and store them in _aArray.\n     *\n     * Developers are encouraged to use prepared statements like this:\n     * $sql = 'SELECT * FROM `mytable` WHERE oxid = ?';\n     * $parameters = ['MYOXID']\n     * selectString($sql, $parameters)\n     *\n     * @param string $sql        SQL select statement or prepared statement\n     * @param array  $parameters Parameters to be used in a prepared statement\n     */\n    public function selectString($sql, array $parameters = [])\n    {\n        $this->clear();\n\n        $database = DatabaseProvider::getDb();\n        if ($this->_aSqlLimit[0] || $this->_aSqlLimit[1]) {\n            $result = $database->selectLimit($sql, $this->_aSqlLimit[1], max(0, $this->_aSqlLimit[0]), $parameters);\n        } else {\n            $result = $database->select($sql, $parameters);\n        }\n\n        if ($result != false && $result->count() > 0) {\n            $oSaved = clone $this->getBaseObject();\n\n            while (!$result->EOF) {\n                $oListObject = clone $oSaved;\n\n                $this->assignElement($oListObject, $result->fields);\n\n                $this->add($oListObject);\n\n                $result->fetchRow();\n            }\n        }\n    }\n\n    /**\n     * Add an entry to object array.\n     *\n     * @param object $oObject Object to be added.\n     */\n    public function add($oObject)\n    {\n        if ($oObject->getId()) {\n            $this->_aArray[$oObject->getId()] = $oObject;\n        } else {\n            $this->_aArray[] = $oObject;\n        }\n    }\n\n    /**\n     * Assign data from array to list\n     *\n     * @param array $aData data for list\n     */\n    public function assignArray($aData)\n    {\n        $this->clear();\n        if (count($aData)) {\n            $oSaved = clone $this->getBaseObject();\n\n            foreach ($aData as $aItem) {\n                $oListObject = clone $oSaved;\n                $this->assignElement($oListObject, $aItem);\n                if ($oListObject->getId()) {\n                    $this->_aArray[$oListObject->getId()] = $oListObject;\n                } else {\n                    $this->_aArray[] = $oListObject;\n                }\n            }\n        }\n    }\n\n\n    /**\n     * Sets SQL Limit\n     *\n     * @param integer $iStart   Start e.g. limit Start,xxxx\n     * @param integer $iRecords Nr of Records e.g. limit xxx,Records\n     */\n    public function setSqlLimit($iStart, $iRecords)\n    {\n        $this->_aSqlLimit[0] = $iStart;\n        $this->_aSqlLimit[1] = $iRecords;\n    }\n\n    /**\n     * Function checks if there is at least one object in the list which has the given value in the given field\n     *\n     * @param mixed  $oVal       The searched value\n     * @param string $sFieldName The name of the field, give \"oxid\" will access the classname__oxid field\n     *\n     * @return boolean\n     */\n    public function containsFieldValue($oVal, $sFieldName)\n    {\n        $sFieldName = $this->getFieldLongName($sFieldName);\n        foreach ($this->_aArray as $obj) {\n            if ($obj->{$sFieldName}->value == $oVal) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Generic function for loading the list\n     *\n     * @return null\n     */\n    public function getList()\n    {\n        $oListObject = $this->getBaseObject();\n        $sFieldList = $oListObject->getSelectFields();\n        $sQ = \"select $sFieldList from \" . $oListObject->getViewName();\n        if ($sActiveSnippet = $oListObject->getSqlActiveSnippet()) {\n            $sQ .= \" where $sActiveSnippet \";\n        }\n        $this->selectString($sQ);\n\n        return $this;\n    }\n\n    /**\n     * Executes assign() method on list object. This method is called in loop in oxList::selectString().\n     * It is if you want to execute any functionality on every list ELEMENT after it is fully loaded (assigned).\n     *\n     * @param BaseModel $oListObject List object (the one derived from BaseModel)\n     * @param array     $aDbFields   An array holding db field values (normally the result of \\OxidEsales\\Eshop\\Core\\DatabaseProvider::Execute())\n     */\n    protected function assignElement($oListObject, $aDbFields)\n    {\n        $oListObject->assign($aDbFields);\n    }\n\n    /**\n     * Returns field long name\n     *\n     * @param string $sFieldName Field name\n     *\n     * @return string\n     */\n    protected function getFieldLongName($sFieldName)\n    {\n        if ($this->_sCoreTable) {\n            return $this->_sCoreTable . '__' . $sFieldName;\n        }\n\n        return $sFieldName;\n    }\n}\n"
  },
  {
    "path": "source/Core/Model/MultiLanguageModel.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Model;\n\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse oxObjectException;\n\n/**\n * Class handling multilanguage data fields\n */\nclass MultiLanguageModel extends \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n{\n    /**\n     * Name of class.\n     *\n     * @var string\n     */\n    protected $_sClassName = 'oxI18n';\n\n    /**\n     * Active object language.\n     *\n     * @var int\n     */\n    protected $_iLanguage = null;\n\n    /**\n     * Sometimes you need to deal with all fields not only with active\n     * language, then set to false (default is true).\n     *\n     * @var bool\n     */\n    protected $_blEmployMultilanguage = true;\n\n    /**\n     * Class constructor, initiates parent constructor (parent::oxBase()).\n     */\n    public function __construct()\n    {\n        parent::__construct();\n\n        //T2008-02-22\n        //lets try to differentiate cache keys for oxI18n and oxBase\n        //in order not to load cached structure for the instances of oxbase classe called on same table\n        if ($this->_sCacheKey) {\n            $this->_sCacheKey .= \"_i18n\";\n        }\n    }\n\n    /**\n     * Sets object language.\n     *\n     * @param string $lang string (default null)\n     */\n    public function setLanguage($lang = null)\n    {\n        $this->_iLanguage = (int) $lang;\n        // reset\n        $this->_sViewTable = false;\n    }\n\n    /**\n     * Returns object language\n     *\n     * @return int\n     */\n    public function getLanguage()\n    {\n        if ($this->_iLanguage === null) {\n            $this->_iLanguage = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage();\n        }\n\n        return $this->_iLanguage;\n    }\n\n    /**\n     * Object multilanguage mode setter (set true to enable multilang mode).\n     * This setter affects init() method so it should be called before init() is executed\n     *\n     * @param bool $employMultilanguage New $this->_blEmployMultilanguage value\n     */\n    public function setEnableMultilang($employMultilanguage)\n    {\n        if ($this->_blEmployMultilanguage != $employMultilanguage) {\n            $this->_blEmployMultilanguage = $employMultilanguage;\n            if (!$employMultilanguage) {\n                //#63T\n                $this->modifyCacheKey(\"_nonml\");\n            }\n            // reset\n            $this->_sViewTable = false;\n            if (count($this->_aFieldNames) > 1) {\n                $this->initDataStructure();\n            }\n        }\n    }\n\n    /**\n     * Checks if this field is multlingual\n     * (returns false if language = 0)\n     *\n     * @param string $fieldName Field name\n     *\n     * @return bool\n     */\n    public function isMultilingualField($fieldName)\n    {\n        $fieldName = strtolower($fieldName);\n        if (isset($this->_aFieldNames[$fieldName])) {\n            return (bool) $this->_aFieldNames[$fieldName];\n        }\n\n        //not inited field yet\n        //and note that this is should be called only in first call after tmp dir is empty\n        startProfile('!__CACHABLE2__!');\n        $isMultilang = (bool) $this->getFieldStatus($fieldName);\n        stopProfile('!__CACHABLE2__!');\n\n        return (bool) $isMultilang;\n    }\n\n    /**\n     * Returns true, if object has multilanguage fields.\n     * In oxi18n it is always returns true.\n     *\n     * @return bool\n     */\n    public function isMultilang()\n    {\n        return true;\n    }\n\n    /**\n     * Loads object data from DB in passed language, returns true on success.\n     *\n     * @param integer $language Load this language compatible data\n     * @param string  $oxid     object ID\n     *\n     * @return bool\n     */\n    public function loadInLang($language, $oxid)\n    {\n        // set new lang to this object\n        $this->setLanguage($language);\n        // reset\n        $this->_sViewTable = false;\n\n        return $this->load($oxid);\n    }\n\n    /**\n     * Lazy loading cache key modifier.\n     *\n     * @param string $cacheKey kache  key\n     * @param bool   $override marker to force override cache key\n     */\n    public function modifyCacheKey($cacheKey, $override = false)\n    {\n        if ($override) {\n            $this->_sCacheKey = $cacheKey . \"|i18n\";\n        } else {\n            $this->_sCacheKey .= $cacheKey;\n        }\n\n        if (!$cacheKey) {\n            $this->_sCacheKey = null;\n        }\n    }\n\n    /**\n     * Returns an array of languages in which object multilanguage\n     * fields are already setted\n     *\n     * @return array\n     */\n    public function getAvailableInLangs()\n    {\n        $languages = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getLanguageNames();\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $objFields = $this->getTableFields(\n            $tableViewNameGenerator->getViewName($this->_sCoreTable, -1, -1),\n            true\n        );\n        $multiLangFields = [];\n\n        //selecting all object multilang fields\n        foreach ($objFields as $key => $value) {\n            //skipping oxactive field\n            if (preg_match('/^oxactive(_(\\d{1,2}))?$/', $key)) {\n                continue;\n            }\n\n            $fieldLang = $this->getFieldLang($key);\n\n            //checking, if field is multilanguage\n            if ($this->isMultilingualField($key) || $fieldLang > 0) {\n                $newKey = preg_replace('/_(\\d{1,2})$/', '', $key);\n                $multiLangFields[$newKey][] = (int) $fieldLang;\n            }\n        }\n\n        // if no multilanguage fields, return default languages array\n        if (count($multiLangFields) < 1) {\n            return $languages;\n        }\n\n        // select from non-multilanguage core view (all ml tables joined to one)\n        $db = DatabaseProvider::getDb();\n        $query = \"select * from \" . $tableViewNameGenerator->getViewName($this->_sCoreTable, -1, -1) . \" where oxid = :oxid\";\n        $rs = $db->getAll($query, [\n            'oxid' => $this->getId()\n        ]);\n\n        $notInLang = $languages;\n\n        // checks if object field data is not empty in all available languages\n        // and formats not available in languages array\n        if (isset($rs[0]) && is_array($rs[0]) && count($rs[0])) {\n            $rs[0] = array_change_key_case($rs[0], CASE_UPPER);\n            foreach ($multiLangFields as $fieldId => $multiLangIds) {\n                foreach ($multiLangIds as $multiLangId) {\n                    $fieldName = ($multiLangId == 0) ? $fieldId : $fieldId . '_' . $multiLangId;\n                    if ($rs[0][strtoupper($fieldName)]) {\n                        unset($notInLang[$multiLangId]);\n                        continue;\n                    }\n                }\n            }\n        }\n\n        return array_diff($languages, $notInLang);\n    }\n\n    /**\n     * Returns _aFieldName[] value. 0 means - non multilanguage, 1 - multilanguage field.\n     * This method is slow, so we should make sure it is called only when tmp dir is cleaned (and then the results are cached).\n     *\n     * @param string $fieldName Field name\n     *\n     * @return int\n     */\n    protected function getFieldStatus($fieldName)\n    {\n        $allField = $this->getAllFields(true);\n        if (isset($allField[strtolower($fieldName) . \"_1\"])) {\n            return 1;\n        }\n\n        return 0;\n    }\n\n    /**\n     * Returns the list of fields. This function is slower and its result is normally cached.\n     * Basically we have 3 separate cases here:\n     *  1. We are in admin so we need extended info for all fields (name, field length and field type)\n     *  2. Object is not lazy loaded so we will return all data fields as simple array, as we need only names\n     *  3. Object is lazy loaded so we will return empty array as all fields are loaded on request (in __get()).\n     *\n     * @param bool $forceFullStructure Whether to force loading of full data structure\n     *\n     * @return array\n     */\n    protected function getNonCachedFieldNames($forceFullStructure = false)\n    {\n        //Tomas\n        //TODO: this place could be optimized. please check what we can do.\n        $fields = parent::getNonCachedFieldNames($forceFullStructure);\n\n        if (!$this->_blEmployMultilanguage) {\n            return $fields;\n        }\n\n        //lets do some pointer manipulation\n        if ($fields) {\n            //non admin fields\n            $workingFields = & $fields;\n        } else {\n            //most likely admin fields so we remove another language\n            $workingFields = & $this->_aFieldNames;\n        }\n\n        //we have an array of fields, lets remove multilanguage fields\n        foreach ($workingFields as $name => $val) {\n            if ($this->getFieldLang($name)) {\n                unset($workingFields[$name]);\n            } else {\n                $workingFields[$name] = $this->getFieldStatus($name);\n            }\n        }\n\n        return $workingFields;\n    }\n\n    /**\n     * Gets multilanguage field language. In case of oxtitle_2 it will return 2. 0 is returned if language ending is not defined.\n     *\n     * @param string $fieldName Field name\n     *\n     * @return bool\n     */\n    protected function getFieldLang($fieldName)\n    {\n        if (false === strpos($fieldName, '_')) {\n            return 0;\n        }\n        if (preg_match('/_(\\d{1,2})$/', $fieldName, $regs)) {\n            return $regs[1];\n        } else {\n            return 0;\n        }\n    }\n\n    /**\n     * Returns DB field name for update.\n     *\n     * @param string $field Field name\n     *\n     * @return string\n     */\n    public function getUpdateSqlFieldName($field)\n    {\n        $lang = $this->getLanguage();\n        if ($lang && $this->_blEmployMultilanguage && $this->isMultilingualField($field)) {\n            $field .= \"_\" . $lang;\n        }\n\n        return $field;\n    }\n\n    /**\n     * Checks whether certain field has changed, and sets update seo flag if needed.\n     * It can only set the value to false, so it allows for multiple calls to the method,\n     * and if atleast one requires seo update, other checks won't override that.\n     * Will try to get multilang table name for relevant field check.\n     *\n     * @param string $field Field name that will be checked\n     */\n    protected function setUpdateSeoOnFieldChange($field)\n    {\n        parent::setUpdateSeoOnFieldChange($this->getUpdateSqlFieldName($field));\n    }\n\n\n    /**\n     * return update fields SQL part\n     *\n     * @param string $table             table name to be updated\n     * @param bool   $useSkipSaveFields use skip save fields array?\n     *\n     * @return string\n     */\n    protected function getUpdateFieldsForTable($table, $useSkipSaveFields = true)\n    {\n        $coreTable = $this->getCoreTableName();\n\n        $skipMultilingual = false;\n        $skipCoreFields = false;\n\n        if ($table != $coreTable) {\n            $skipCoreFields = true;\n        }\n        if ($this->_blEmployMultilanguage) {\n            if ($table != getLangTableName($coreTable, $this->getLanguage())) {\n                $skipMultilingual = true;\n            }\n        }\n\n        $sql = '';\n        $sep = false;\n        foreach (array_keys($this->_aFieldNames) as $key) {\n            $keyLowercase = strtolower($key);\n            if ($keyLowercase != 'oxid') {\n                if ($this->_blEmployMultilanguage) {\n                    if ($skipMultilingual && $this->isMultilingualField($key)) {\n                        continue;\n                    }\n                    if ($skipCoreFields && !$this->isMultilingualField($key)) {\n                        continue;\n                    }\n                } else {\n                    // need to explicitly check field language\n                    $fieldLang = $this->getFieldLang($key);\n                    if ($fieldLang) {\n                        if ($table != getLangTableName($coreTable, $fieldLang)) {\n                            continue;\n                        }\n                    } elseif ($skipCoreFields) {\n                        continue;\n                    }\n                }\n            }\n\n            if (!$this->checkFieldCanBeUpdated($key)) {\n                continue;\n            }\n\n            $longName = $this->getFieldLongName($key);\n            $field = $this->$longName;\n\n            if (!$useSkipSaveFields || ($useSkipSaveFields && !in_array($keyLowercase, $this->_aSkipSaveFields))) {\n                $key = $this->getUpdateSqlFieldName($key);\n                $sql .= (($sep) ? ',' : '') . $key . \" = \" . $this->getUpdateFieldValue($key, $field);\n                $sep = true;\n            }\n        }\n\n        return $sql;\n    }\n\n    /**\n     * Get object fields sql part for base table\n     * used for updates or inserts:\n     * return e.g.  fldName1 = 'value1',fldName2 = 'value2'...\n     *\n     * @param bool $useSkipSaveFields forces usage of skip save fields array (default is true)\n     *\n     * @return string\n     */\n    protected function getUpdateFields($useSkipSaveFields = true)\n    {\n        return $this->getUpdateFieldsForTable($this->getCoreTableName(), $useSkipSaveFields);\n    }\n\n    /**\n     * Update this Object into the database, this function only works on\n     * the main table, it will not save any dependend tables, which might\n     * be loaded through oxlist (with exception of the active language set\n     * table, which will be updated).\n     *\n     * @throws oxObjectException Throws on failure inserting\n     *\n     * @return bool\n     */\n    protected function update()\n    {\n        $ret = parent::update();\n\n        if ($ret) {\n            //also update multilang table if it is separate\n            $updateTables = [];\n            if ($this->_blEmployMultilanguage) {\n                $coreTable = $this->getCoreTableName();\n                $langTable = getLangTableName($coreTable, $this->getLanguage());\n                if ($coreTable != $langTable) {\n                    $updateTables[] = $langTable;\n                }\n            } else {\n                $updateTables = $this->getLanguageSetTables();\n            }\n            foreach ($updateTables as $langTable) {\n                $insertSql = \"insert into $langTable set \" . $this->getUpdateFieldsForTable($langTable, $this->getUseSkipSaveFields()) .\n                             \" on duplicate key update \" . $this->getUpdateFieldsForTable($langTable);\n\n                $this->executeDatabaseQuery($insertSql);\n            }\n        }\n\n        // currently only multilanguage objects are SEO\n        // if current object is managed by SEO and SEO is ON\n        if ($ret && $this->_blIsSeoObject && $this->getUpdateSeo() && $this->isAdmin()) {\n            // marks all object db entries as expired\n            \\OxidEsales\\Eshop\\Core\\Registry::getSeoEncoder()->markAsExpired($this->getId(), null, 1, $this->getLanguage());\n        }\n\n        return $ret;\n    }\n\n    /**\n     * Return all DB tables for the language sets\n     *\n     * @param string $coreTableName core table name [optional]\n     *\n     * @return array\n     */\n    protected function getLanguageSetTables($coreTableName = null)\n    {\n        $coreTableName = $coreTableName ? $coreTableName : $this->getCoreTableName();\n\n        return oxNew(\\OxidEsales\\Eshop\\Core\\DbMetaDataHandler::class)->getAllMultiTables($coreTableName);\n    }\n\n    /**\n     * Insert this Object into the database, this function only works\n     * on the main table, it will not save any dependend tables, which\n     * might be loaded through oxlist.\n     *\n     * @return bool\n     */\n    protected function insert()\n    {\n        $result = parent::insert();\n\n        if ($result) {\n            //also insert to multilang tables if it is separate\n            foreach ($this->getLanguageSetTables() as $table) {\n                $sql = \"insert into $table set \" . $this->getUpdateFieldsForTable($table, $this->getUseSkipSaveFields());\n\n                $result = $result && (bool) $this->executeDatabaseQuery($sql);\n            }\n        }\n\n        return $result;\n    }\n\n    /**\n     * Returns actual object view or table name\n     *\n     * @param string $table  Original table name\n     * @param int    $shopId Shop ID\n     *\n     * @return string\n     */\n    protected function getObjectViewName($table, $shopId = null)\n    {\n        if (!$this->_blEmployMultilanguage) {\n            return parent::getObjectViewName($table, $shopId);\n        }\n\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        return $tableViewNameGenerator->getViewName($table, $this->getLanguage(), $shopId);\n    }\n\n    /**\n     * Returns meta field or simple array of all object fields.\n     * This method is slow and normally is called before field cache is built.\n     * Make sure it is not called after first page is loaded and cache data is fully built (until tmp dir is cleaned).\n     *\n     * @param bool $returnSimple Set $returnSimple to true when you need simple array (meta data array is returned otherwise)\n     *\n     * @see \\OxidEsales\\Eshop\\Core\\Model\\BaseModel::_getTableFields()\n     *\n     * @return array\n     */\n    protected function getAllFields($returnSimple = false)\n    {\n        if ($this->_blEmployMultilanguage) {\n            return parent::getAllFields($returnSimple);\n        } else {\n            $viewName = $this->getViewName();\n            if (!$viewName) {\n                return [];\n            }\n\n            return $this->getTableFields($viewName, $returnSimple);\n        }\n    }\n\n    /**\n     * Adds additional field to meta structure. Skips language fields\n     *\n     * @param string $name   Field name\n     * @param string $status Field status (0-non multilang field, 1-multilang field)\n     * @param string $type   Field type\n     * @param string $length Field Length\n     *\n     * @return null\n     */\n    protected function addField($name, $status, $type = null, $length = null)\n    {\n        if ($this->_blEmployMultilanguage && $this->getFieldLang($name)) {\n            return;\n        }\n\n        return parent::addField($name, $status, $type, $length);\n    }\n\n    /**\n     * check if db field can be null\n     * for multilingual fields it checks only the base fields as they may be\n     * coming from outer join views, so oxbase would return that they always\n     * support null (while in reality updates to their lang set table with null\n     * would fail)\n     *\n     * @param string $fieldName db field name\n     *\n     * @return bool\n     */\n    protected function canFieldBeNull($fieldName)\n    {\n        $fieldName = preg_replace('/_\\d{1,2}$/', '', $fieldName);\n\n        return parent::canFieldBeNull($fieldName);\n    }\n\n    /**\n     * Delete this object from the database, returns true on success.\n     *\n     * @param string $oxid Object ID(default null)\n     *\n     * @return bool\n     */\n    public function delete($oxid = null)\n    {\n        $deleted = parent::delete($oxid);\n        if ($deleted) {\n            $db = DatabaseProvider::getDb();\n\n            //delete the record\n            foreach ($this->getLanguageSetTables() as $setTbl) {\n                $db->execute(\"delete from {$setTbl} where oxid = :oxid\", [\n                    'oxid' => $oxid\n                ]);\n            }\n        }\n\n        return $deleted;\n    }\n}\n"
  },
  {
    "path": "source/Core/Module/Module.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Module;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ModuleConfigurationDaoBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ShopConfigurationDaoBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\ModuleConfigurationNotFoundException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataProvider;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridgeInterface;\n\n/**\n * Module class.\n *\n * @deprecated since v6.4.0 (2019-03-22); Use service 'OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ShopConfigurationDaoBridgeInterface'.\n * @internal Do not make a module extension for this class.\n */\nclass Module extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Metadata version as defined in metadata.php\n     */\n    protected $metaDataVersion;\n\n    /**\n     * @param mixed $metaDataVersion\n     */\n    public function setMetaDataVersion($metaDataVersion)\n    {\n        $this->metaDataVersion = $metaDataVersion;\n    }\n\n    /**\n     * Modules info array\n     *\n     * @var array\n     */\n    protected $_aModule = [];\n\n    /**\n     * Defines if module has metadata file or not\n     *\n     * @var bool\n     */\n    protected $_blMetadata = false;\n\n    /**\n     * Defines if module is registered in metadata or legacy storage\n     *\n     * @var bool\n     */\n    protected $_blRegistered = false;\n\n    /**\n     * Set passed module data\n     *\n     * @param array $aModule module data\n     */\n    public function setModuleData($aModule)\n    {\n        $this->_aModule = $aModule;\n    }\n\n    /**\n     * Get the modules metadata array\n     *\n     * @return  array Module meta data array\n     */\n    public function getModuleData()\n    {\n        return $this->_aModule;\n    }\n\n    /**\n     * Load module info\n     *\n     * @param string $moduleId\n     *\n     * @return bool\n     */\n    public function load($moduleId)\n    {\n        try {\n            $this->_aModule['id'] = $moduleId;\n            $moduleConfiguration = ContainerFacade::get(ModuleConfigurationDaoBridgeInterface::class)\n                ->get($moduleId);\n\n            $this->_aModule = $this->convertModuleConfigurationToArray($moduleConfiguration);\n            $this->_blRegistered = true;\n            $this->_blMetadata = true;\n            $this->_aModule['active'] = $this->isActive();\n\n            return true;\n        } catch (ModuleConfigurationNotFoundException $e) {\n            return false;\n        }\n\n        return false;\n    }\n\n    /**\n     * Get module description\n     *\n     * @return string\n     */\n    public function getDescription()\n    {\n        $iLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getTplLanguage();\n\n        return $this->getInfo(\"description\", $iLang);\n    }\n\n    /**\n     * Get module title\n     *\n     * @return string\n     */\n    public function getTitle()\n    {\n        $iLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getTplLanguage();\n\n        return $this->getInfo(\"title\", $iLang);\n    }\n\n    /**\n     * Get module ID\n     *\n     * @return string\n     */\n    public function getId()\n    {\n        return $this->_aModule['id'];\n    }\n\n    /**\n     * Returns array of module extensions.\n     *\n     * @return array\n     */\n    public function getExtensions()\n    {\n        $moduleConfiguration = ContainerFacade::get(ModuleConfigurationDaoBridgeInterface::class)\n            ->get(\n                $this->getId()\n            );\n\n        $extensions = [];\n\n        if ($moduleConfiguration->hasClassExtensions()) {\n            foreach ($moduleConfiguration->getClassExtensions() as $extension) {\n                $extensions[$extension->getShopClassName()] = $extension->getModuleExtensionClassName();\n            }\n        }\n\n        return $extensions;\n    }\n\n    /**\n     * Returns associative array of module controller ids and corresponding classes.\n     *\n     * @return array\n     */\n    public function getControllers()\n    {\n        if (isset($this->_aModule['controllers']) && ! is_array($this->_aModule['controllers'])) {\n            throw new \\InvalidArgumentException('Value for metadata key \"controllers\" must be an array');\n        }\n\n        return isset($this->_aModule['controllers']) ? array_change_key_case($this->_aModule['controllers']) : [];\n    }\n\n    /**\n     * Get module ID\n     *\n     * @param string $module extension full path\n     *\n     * @return string\n     */\n    public function getIdByPath($module)\n    {\n        $moduleId = null;\n        $moduleFile = $module;\n        $moduleId = $this->getModuleIdByClassName($module);\n        if (!$moduleId) {\n            $modulePaths = $this->getModulePaths();\n\n            if (is_array($modulePaths)) {\n                foreach ($modulePaths as $id => $path) {\n                    if (strpos($moduleFile, $path . \"/\") === 0) {\n                        $moduleId = $id;\n                    }\n                }\n            }\n        }\n        if (!$moduleId) {\n            $moduleId = substr($moduleFile, 0, strpos($moduleFile, \"/\"));\n        }\n        if (!$moduleId) {\n            $moduleId = $moduleFile;\n        }\n\n        return $moduleId;\n    }\n\n    /**\n     * Get the module id for a given class name. If there are duplicates, the first module id will be returned.\n     *\n     * @param string $className\n     *\n     * @return string\n     */\n    public function getModuleIdByClassName($className)\n    {\n        $moduleId = '';\n\n        foreach ($this->getShopConfiguration()->getModuleConfigurations() as $module) {\n            if ($module->hasClassExtension($className)) {\n                return $module->getId();\n            }\n        }\n\n        return $moduleId;\n    }\n\n    /**\n     * Get module info item. If second param is passed, will try\n     * to get value according selected language.\n     *\n     * @param string $sName name of info item to retrieve\n     * @param string $iLang language ID\n     *\n     * @return mixed\n     */\n    public function getInfo($sName, $iLang = null)\n    {\n        if (isset($this->_aModule[$sName])) {\n            if ($iLang !== null && is_array($this->_aModule[$sName])) {\n                $sValue = null;\n\n                $sLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getLanguageAbbr($iLang);\n\n                if (!empty($this->_aModule[$sName])) {\n                    if (!empty($this->_aModule[$sName][$sLang])) {\n                        $sValue = $this->_aModule[$sName][$sLang];\n                    } elseif (!empty($this->_aModule['lang'])) {\n                        // trying to get value according default language\n                        $sValue = $this->_aModule[$sName][$this->_aModule['lang']];\n                    } else {\n                        // returning first array value\n                        $sValue = reset($this->_aModule[$sName]);\n                    }\n\n                    return $sValue;\n                }\n            } else {\n                return $this->_aModule[$sName];\n            }\n        }\n    }\n\n    /**\n     * Check if extension is active\n     *\n     * @return bool\n     */\n    public function isActive()\n    {\n        if ($this->getId() === null) {\n            return false;\n        }\n\n        return ContainerFacade::get(ModuleActivationBridgeInterface::class)\n            ->isActive(\n                $this->getId(),\n                Registry::getConfig()->getShopId()\n            );\n    }\n\n    /**\n     * Checks if has extend class.\n     *\n     * @return bool\n     */\n    public function hasExtendClass()\n    {\n        return !empty($this->getExtensions());\n    }\n\n    /**\n     * Checks if module is registered in any way\n     *\n     * @return bool\n     */\n    public function isRegistered()\n    {\n        return $this->_blRegistered;\n    }\n\n    /**\n     * Checks if module has metadata\n     *\n     * @return bool\n     */\n    public function hasMetadata()\n    {\n        return $this->_blMetadata;\n    }\n\n    /**\n     * Get module id's with path\n     *\n     * @return array\n     */\n    public function getModulePaths()\n    {\n        $moduleConfigurations = $this->getInstalledModuleConfigurations();\n        $paths = [];\n        foreach ($moduleConfigurations as $moduleConfiguration) {\n            $paths[$moduleConfiguration->getId()] = $moduleConfiguration->getModuleSource();\n        }\n\n        return $paths;\n    }\n\n    /**\n     * Include data from metadata.php\n     *\n     * @param string $metadataPath Path to metadata.php\n     *\n     */\n    protected function includeModuleMetaData($metadataPath)\n    {\n        $sMetadataVersion = null;\n        include $metadataPath;\n\n        /**\n         * metadata.php should include a variable called $sMetadataVersion\n         */\n\n        if (isset($sMetadataVersion)) {\n            $this->setMetaDataVersion($sMetadataVersion);\n        }\n    }\n\n    /**\n     * @return array\n     */\n    private function getInstalledModuleConfigurations(): array\n    {\n        $shopConfiguration = $this->getShopConfiguration();\n\n        return $shopConfiguration->getModuleConfigurations();\n    }\n\n    /**\n     * @return ShopConfiguration\n     */\n    private function getShopConfiguration(): ShopConfiguration\n    {\n        return ContainerFacade::get(ShopConfigurationDaoBridgeInterface::class)\n            ->get();\n    }\n\n    /**\n     * Convert ModuleConfiguration to Array\n     *\n     * @param ModuleConfiguration $configuration\n     *\n     * @return array\n     */\n    private function convertModuleConfigurationToArray(ModuleConfiguration $configuration): array\n    {\n        $data = [\n            'id'          => $configuration->getId(),\n            'version'     => $configuration->getVersion(),\n            'title'       => $configuration->getTitle(),\n            'description' => $configuration->getDescription(),\n            'lang'        => $configuration->getLang(),\n            'thumbnail'   => $configuration->getThumbnail(),\n            'author'      => $configuration->getAuthor(),\n            'url'         => $configuration->getUrl(),\n            'email'       => $configuration->getEmail(),\n        ];\n\n        foreach ($this->convertModuleSettingsToArray($configuration) as $key => $value) {\n            $data[$key] = $value;\n        }\n\n        return $data;\n    }\n\n    /**\n     * @param ModuleConfiguration $moduleConfiguration\n     *\n     * @return array\n     */\n    private function convertModuleSettingsToArray(ModuleConfiguration $moduleConfiguration): array\n    {\n        $data = [];\n\n        $data[MetaDataProvider::METADATA_EXTEND] = $this->convertClassExtensionsToArray($moduleConfiguration);\n        $data[MetaDataProvider::METADATA_CONTROLLERS] = $this->convertControllersToArray($moduleConfiguration);\n        $data[MetaDataProvider::METADATA_EVENTS] = $this->convertEventsToArray($moduleConfiguration);\n        $data[MetaDataProvider::METADATA_SETTINGS] = $this->convertSettingsToArray($moduleConfiguration);\n\n        return $data;\n    }\n\n    /**\n     * @param ModuleConfiguration $moduleConfiguration\n     *\n     * @return array\n     */\n    private function convertClassExtensionsToArray(ModuleConfiguration $moduleConfiguration): array\n    {\n        $data = [];\n\n        foreach ($moduleConfiguration->getClassExtensions() as $extension) {\n            $data[$extension->getShopClassName()] = $extension->getModuleExtensionClassName();\n        }\n\n        return $data;\n    }\n\n    /**\n     * @param ModuleConfiguration $moduleConfiguration\n     *\n     * @return array\n     */\n    private function convertControllersToArray(ModuleConfiguration $moduleConfiguration): array\n    {\n        $data = [];\n\n        foreach ($moduleConfiguration->getControllers() as $controller) {\n            $data[$controller->getId()] = $controller->getControllerClassNameSpace();\n        }\n\n        return $data;\n    }\n\n    /**\n     * @param ModuleConfiguration $moduleConfiguration\n     *\n     * @return array\n     */\n    private function convertEventsToArray(ModuleConfiguration $moduleConfiguration): array\n    {\n        $data = [];\n\n        foreach ($moduleConfiguration->getEvents() as $event) {\n            $data[$event->getAction()] = $event->getMethod();\n        }\n\n        return $data;\n    }\n\n    /**\n     * @param ModuleConfiguration $moduleConfiguration\n     *\n     * @return array\n     */\n    private function convertSettingsToArray(ModuleConfiguration $moduleConfiguration): array\n    {\n        $data = [];\n\n        foreach ($moduleConfiguration->getModuleSettings() as $index => $setting) {\n            if ($setting->getGroupName()) {\n                $data[$index]['group'] = $setting->getGroupName();\n            }\n\n            if ($setting->getName()) {\n                $data[$index]['name'] = $setting->getName();\n            }\n\n            if ($setting->getType()) {\n                $data[$index]['type'] = $setting->getType();\n            }\n\n            $data[$index]['value'] = $setting->getValue();\n\n            if (!empty($setting->getConstraints())) {\n                $data[$index]['constraints'] = $setting->getConstraints();\n            }\n\n            if ($setting->getPositionInGroup() > 0) {\n                $data[$index]['position'] = $setting->getPositionInGroup();\n            }\n        }\n\n        return $data;\n    }\n}\n"
  },
  {
    "path": "source/Core/Module/ModuleChainsGenerator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Module;\n\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ActiveModulesDataProviderBridgeInterface;\n\n/**\n * Generates class chains for extended classes by modules.\n * IMPORTANT: Due to the way the shop is prepared for testing, you must not use Registry::getConfig() in this class.\n *            oxNew will enter in an endless loop, if you try to do that.\n *\n * @internal Do not make a module extension for this class.\n */\nclass ModuleChainsGenerator\n{\n    /**\n     * Creates given class chains.\n     *\n     * @param string $className  Class name.\n     * @param string $classAlias Class alias, used for searching module extensions. Class is used if no alias given.\n     *\n     * @return string\n     */\n    public function createClassChain($className, $classAlias = null)\n    {\n        if (!$classAlias) {\n            $classAlias = $className;\n        }\n        $activeChain = $this->getActiveChain($className, $classAlias);\n        if (!empty($activeChain)) {\n            $className = $this->createClassExtensions($activeChain, $classAlias);\n        }\n\n        return $className;\n    }\n\n    /**\n     * Assembles class chains.\n     *\n     * @param string $className  Class name.\n     * @param string $classAlias Class alias, used for searching module extensions. Class is used if no alias given.\n     *\n     * @return array\n     */\n    public function getActiveChain($className, $classAlias = null)\n    {\n        return $this->getFullChain($className, $classAlias);\n    }\n\n    /**\n     * Build full class chain.\n     *\n     * @param string $className\n     * @param string $classAlias\n     *\n     * @return array\n     */\n    public function getFullChain($className, $classAlias)\n    {\n        $chain = ContainerFacade::get(ActiveModulesDataProviderBridgeInterface::class)->getClassExtensions();\n        $classChain = $chain[$className] ?? [];\n\n        return $classAlias && $classAlias !== $className\n            ? array_merge($classChain, $this->getChainForBackwardsCompatibilityClassAlias($chain, $classAlias))\n            : $classChain;\n    }\n\n    /**\n     * Creates middle classes if needed.\n     *\n     * @param array  $classChain Module names\n     * @param string $baseClass  Oxid base class\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\SystemComponentException missing system component exception\n     *\n     * @return string\n     */\n    protected function createClassExtensions($classChain, $baseClass)\n    {\n        //security: just preventing string termination\n        $lastClass = str_replace(chr(0), '', $baseClass);\n        $parentClass = $lastClass;\n\n        foreach ($classChain as $extensionPath) {\n            $extensionPath = str_replace(chr(0), '', $extensionPath);\n\n            if ($this->createClassExtension($parentClass, $extensionPath)) {\n                if (\\OxidEsales\\Eshop\\Core\\NamespaceInformationProvider::isNamespacedClass($extensionPath)) {\n                    $parentClass = $extensionPath;\n                    $lastClass = $extensionPath;\n                } else {\n                    $parentClass = basename($extensionPath);\n                    $lastClass = basename($extensionPath);\n                }\n            }\n        }\n\n        //returning the last module from the chain\n        return $lastClass;\n    }\n\n    /**\n     * Checks, if a given class can be loaded and create an alias for _parent.\n     * If the class cannot be loaded, some error handling is done.\n     *\n     * @see self::onModuleExtensionCreationError\n     * @see self::handleSpecialCases\n     *\n     * e.g. class suboutput1_parent extends oxoutput {}\n     *      class suboutput2_parent extends suboutput1 {}\n     *\n     * @param string $parentClass\n     * @param string $moduleClass\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\SystemComponentException\n     *\n     * @return bool Return on error\n     */\n    protected function createClassExtension($parentClass, $moduleClass)\n    {\n        /**\n         * Test if the class file could be loaded\n         */\n        /** @var \\Composer\\Autoload\\ClassLoader $composerClassLoader */\n        $composerClassLoader = include VENDOR_PATH . 'autoload.php';\n        if (\n            !strpos($moduleClass, '_parent') &&\n            !$composerClassLoader->findFile($moduleClass)\n        ) {\n            $this->handleSpecialCases($parentClass);\n            $this->onModuleExtensionCreationError($moduleClass);\n\n            return false;\n        }\n\n        $moduleClassParentAlias = $moduleClass . \"_parent\";\n        if (!class_exists($moduleClassParentAlias, false)) {\n            class_alias($parentClass, $moduleClassParentAlias);\n        }\n\n        return true;\n    }\n\n    /**\n     * Special case is when oxconfig class is extended: we cant call \"_disableModule\" as it requires valid config object\n     * but we can't create it as module class extending it does not exist. So we will use original oxConfig object instead.\n     *\n     * @param string $requestedClass Class, for which extension chain was generated.\n     */\n    protected function handleSpecialCases($requestedClass)\n    {\n        // We do actually have to check the whole inheritance chain in case two OXID modules each have an extension\n        // on oxconfig. Checking for $requestedClass only would cover only one inheritance step.\n\n        $isConfigClass = false;\n        $currentClass = $requestedClass;\n        $safetyCount = 0;\n        do {\n            if (($currentClass == \"oxconfig\") || ($currentClass == \\OxidEsales\\Eshop\\Core\\Config::class)) {\n                $isConfigClass = true;\n                break;\n            }\n\n            if ($safetyCount++ === 200) {\n                throw new \\OxidEsales\\Eshop\\Core\\Exception\\SystemComponentException('Recursion limit reached while traversing class inheritance chain.');\n            }\n\n            // We can be sure that the parent class of the current class is actually defined due to the way\n            // the extension chain is traversed.\n        } while ($currentClass = get_parent_class($currentClass));\n\n        if ($isConfigClass) {\n            $config = new \\OxidEsales\\Eshop\\Core\\Config();\n            \\OxidEsales\\Eshop\\Core\\Registry::set(\\OxidEsales\\Eshop\\Core\\Config::class, $config);\n        }\n    }\n\n    /**\n     * Writes/logs an error on module extension creation problem\n     *\n     * @param string $moduleClass\n     */\n    protected function onModuleExtensionCreationError($moduleClass)\n    {\n        $moduleId = \"(module id not availible)\";\n        if (class_exists(\"\\OxidEsales\\Eshop\\Core\\Module\\Module\", false)) {\n            $module = new \\OxidEsales\\Eshop\\Core\\Module\\Module();\n            $moduleId = $module->getIdByPath($moduleClass);\n        }\n        $message = sprintf('Module class %s not found. Module ID %s', $moduleClass, $moduleId);\n        $exception = new \\OxidEsales\\Eshop\\Core\\Exception\\SystemComponentException($message);\n        \\OxidEsales\\Eshop\\Core\\Registry::getLogger()->error($exception->getMessage(), [$exception]);\n    }\n\n    private function getChainForBackwardsCompatibilityClassAlias(array $chain, string $classAlias): array\n    {\n        return array_change_key_case($chain)[strtolower($classAlias)] ?? [];\n    }\n}\n"
  },
  {
    "path": "source/Core/Module/ModuleList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Module;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ShopConfigurationDaoBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\State\\ModuleStateServiceInterface;\n\n/**\n * Modules list class.\n *\n * @deprecated since v6.4.0 (2019-03-22); Use service 'OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ShopConfigurationDaoBridgeInterface'.\n * @internal Do not make a module extension for this class.\n */\nclass ModuleList extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * All module extensions.\n     *\n     * @var array\n     */\n    protected $_aModuleExtensions = null;\n\n    /**\n     * @return array\n     */\n    public function getDisabledModuleClasses()\n    {\n        $disabledModules = $this->getDisabledModuleConfigurations();\n        $disabledModuleClasses = [];\n\n        foreach ($disabledModules as $module) {\n            if ($module->hasClassExtensions()) {\n                foreach ($module->getClassExtensions() as $extensionClass) {\n                    $disabledModuleClasses[] = $extensionClass->getModuleExtensionClassName();\n                }\n            }\n        }\n\n        return $disabledModuleClasses;\n    }\n\n    /**\n     * Removes extension metadata from shop.\n     */\n    public function cleanup()\n    {\n        $deletedModules = $this->getDeletedExtensions();\n\n        $deletedModuleIds = array_keys($deletedModules);\n\n        $moduleActivationBridge = ContainerFacade::get(ModuleActivationBridgeInterface::class);\n\n        foreach ($deletedModuleIds as $moduleId) {\n            if ($moduleActivationBridge->isActive($moduleId, Registry::getConfig()->getShopId())) {\n                $moduleActivationBridge->deactivate($moduleId, Registry::getConfig()->getShopId());\n            }\n        }\n    }\n\n    /**\n     * Checks module list - if there is extensions that are registered, but extension directory is missing\n     *\n     * @return array\n     */\n    public function getDeletedExtensions()\n    {\n        $aModulesIds = ContainerFacade::get(ShopConfigurationDaoBridgeInterface::class)\n            ->get()\n            ->getModuleIdsOfModuleConfigurations();\n\n        $oModule = $this->getModule();\n        $aDeletedExt = [];\n\n        foreach ($aModulesIds as $sModuleId) {\n            $oModule->setModuleData(['id' => $sModuleId]);\n            $aInvalidExtensions = $this->getInvalidExtensions($sModuleId);\n            if ($aInvalidExtensions) {\n                $aDeletedExt[$sModuleId]['extensions'] = $aInvalidExtensions;\n            }\n        }\n\n        return $aDeletedExt;\n    }\n\n    /**\n     * Returns oxModule object.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Module\\Module\n     */\n    public function getModule()\n    {\n        return oxNew(\\OxidEsales\\Eshop\\Core\\Module\\Module::class);\n    }\n\n    /**\n     * Parse array of module chains to nested array\n     *\n     * @param array $modules Module array (config format)\n     *\n     * @return array\n     */\n    public function parseModuleChains($modules)\n    {\n        $moduleArray = [];\n\n        if (is_array($modules)) {\n            foreach ($modules as $class => $moduleChain) {\n                if (strstr($moduleChain, '&')) {\n                    $moduleChain = explode('&', $moduleChain);\n                } else {\n                    $moduleChain = [$moduleChain];\n                }\n                $moduleArray[$class] = $moduleChain;\n            }\n        }\n\n        return $moduleArray;\n    }\n\n    /**\n     * Returns module extensions.\n     *\n     * @param string $sModuleId\n     *\n     * @return array\n     */\n    public function getModuleExtensions($sModuleId)\n    {\n        if (!isset($this->_aModuleExtensions)) {\n            $aModuleExtension = $this->getActivateModulesWithExtendedClass();\n            $oModule = $this->getModule();\n            $aExtension = [];\n            foreach ($aModuleExtension as $sOxClass => $aFiles) {\n                foreach ($aFiles as $sFilePath) {\n                    $sId = $oModule->getIdByPath($sFilePath);\n                    $aExtension[$sId][$sOxClass][] = $sFilePath;\n                }\n            }\n\n            $this->_aModuleExtensions = $aExtension;\n        }\n\n        return $this->_aModuleExtensions[$sModuleId] ?? [];\n    }\n\n    /**\n     * Returns shop classes and associated invalid module classes for a given module id\n     *\n     * @param string $moduleId Module id\n     *\n     * @return array\n     */\n    private function getInvalidExtensions($moduleId)\n    {\n        $extendedShopClasses = $this->getModuleExtensions($moduleId);\n        $invalidModuleClasses = [];\n\n        foreach ($extendedShopClasses as $extendedShopClass => $moduleClasses) {\n            foreach ($moduleClasses as $moduleClass) {\n                /** @var \\Composer\\Autoload\\ClassLoader $composerClassLoader */\n                $composerClassLoader = include VENDOR_PATH . 'autoload.php';\n                if (!$composerClassLoader->findFile($moduleClass)) {\n                    $invalidModuleClasses[$extendedShopClass][] = $moduleClass;\n                }\n            }\n        }\n\n        return $invalidModuleClasses;\n    }\n\n    /**\n     * @return ModuleConfiguration[]\n     */\n    private function getDisabledModuleConfigurations(): array\n    {\n        $moduleConfigurations = ContainerFacade::get(ShopConfigurationDaoBridgeInterface::class)\n            ->get()\n            ->getModuleConfigurations();\n\n        $disabledModuleConfigurations = [];\n\n        $moduleStateService = ContainerFacade::get(ModuleStateServiceInterface::class);\n\n        foreach ($moduleConfigurations as $moduleConfiguration) {\n            if (!$moduleStateService->isActive($moduleConfiguration->getId(), Registry::getConfig()->getShopId())) {\n                $disabledModuleConfigurations[] = $moduleConfiguration;\n            }\n        }\n\n        return $disabledModuleConfigurations;\n    }\n\n    /**\n     * Returns Active module ids which have extensions or files.\n     *\n     * @return ModuleConfiguration[]\n     */\n    private function getActiveModuleConfigurations(): array\n    {\n        $moduleConfigurations = ContainerFacade::get(ShopConfigurationDaoBridgeInterface::class)\n            ->get()\n            ->getModuleConfigurations();\n\n        $activeModules = [];\n\n        $moduleStateService = ContainerFacade::get(ModuleStateServiceInterface::class);\n\n        foreach ($moduleConfigurations as $moduleConfiguration) {\n            if ($moduleStateService->isActive($moduleConfiguration->getId(), Registry::getConfig()->getShopId())) {\n                $activeModules[] = $moduleConfiguration;\n            }\n        }\n\n        return $activeModules;\n    }\n\n    /**\n     * Get activate modules with Extended classes\n     *\n     * @return array\n     */\n    private function getActivateModulesWithExtendedClass()\n    {\n        $extendedClasses = [];\n\n        foreach ($this->getActiveModuleConfigurations() as $moduleConfiguration) {\n            if ($moduleConfiguration->hasClassExtensions()) {\n                foreach ($moduleConfiguration->getClassExtensions() as $extensions) {\n                    if (!isset($extendedClasses[$extensions->getShopClassName()])) {\n                        $extendedClasses[$extensions->getShopClassName()] = $extensions->getModuleExtensionClassName();\n                    } else {\n                        $extendedClasses[$extensions->getShopClassName()] .= '&' . $extensions->getModuleExtensionClassName();\n                    }\n                }\n            }\n        }\n\n        return $this->parseModuleChains($extendedClasses);\n    }\n}\n"
  },
  {
    "path": "source/Core/NamespaceInformationProvider.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Class NamespaceInformationProvider\n *\n * @package OxidEsales\\EshopCommunity\\Core\n *\n * @internal Do not make a module extension for this class.\n */\nclass NamespaceInformationProvider\n{\n    /**\n     * Array contains names of the official OXID eShop edition namespaces.\n     *\n     * @var array\n     */\n    protected static $shopEditionNamespaces = [\n        'CE' => 'OxidEsales\\\\EshopCommunity\\\\',\n        'PE' => 'OxidEsales\\\\EshopProfessional\\\\',\n        'EE' => 'OxidEsales\\\\EshopEnterprise\\\\'\n    ];\n\n    /**\n     * Array contains names of the official OXID eShop edition namespaces for tests.\n     *\n     * @var array\n     */\n    protected static $shopEditionTestNamespaces = [\n        'CE' => 'OxidEsales\\\\EshopCommunity\\\\Tests\\\\',\n        'PE' => 'OxidEsales\\\\EshopProfessional\\\\Tests\\\\',\n        'EE' => 'OxidEsales\\\\EshopEnterprise\\\\Tests\\\\'\n    ];\n\n    /**\n     * OXID eShop unified namespace.\n     *\n     * @var string\n     */\n    protected static $unifiedNamespace = 'OxidEsales\\\\Eshop\\\\';\n\n    /**\n     * Getter for array with official OXID eShop Edition namespaces.\n     *\n     * @return array\n     */\n    public static function getShopEditionNamespaces()\n    {\n        return static::$shopEditionNamespaces;\n    }\n\n    /**\n     * Getter for official OXID eShop Unified Namespace.\n     *\n     * @return string\n     */\n    public static function getUnifiedNamespace()\n    {\n        return static::$unifiedNamespace;\n    }\n\n\n    /**\n     * @param string $className\n     *\n     * @return bool\n     */\n    public static function isNamespacedClass($className)\n    {\n        return strpos($className, '\\\\') !== false;\n    }\n\n    /**\n     * Check if given class belongs to a shop edition namespace.\n     *\n     * @param string $className\n     *\n     * @return bool\n     */\n    public static function classBelongsToShopEditionNamespace($className)\n    {\n        return static::classBelongsToNamespace($className, static::getShopEditionNamespaces());\n    }\n\n    /**\n     * Check if given class belongs to a shop edition namespace.\n     *\n     * @param string $className\n     *\n     * @return bool\n     */\n    public static function classBelongsToShopUnifiedNamespace($className)\n    {\n        $lcClassName = strtolower(ltrim($className, '\\\\'));\n        $unifiedNamespace = static::getUnifiedNamespace();\n        $belongsToUnifiedNamespace = (false !== strpos($lcClassName, strtolower($unifiedNamespace)));\n\n        return $belongsToUnifiedNamespace;\n    }\n\n    /**\n     * Check if given class belongs to one of the supplied namespaces.\n     *\n     * @param string $className\n     * @param array  $namespaces\n     *\n     * @return bool\n     */\n    private static function classBelongsToNamespace($className, $namespaces)\n    {\n        $belongsToNamespace = false;\n        $check = array_values($namespaces);\n        $lcClassName = strtolower(ltrim($className, '\\\\'));\n\n        foreach ($check as $namespace) {\n            if (false !== strpos($lcClassName, strtolower($namespace))) {\n                $belongsToNamespace = true;\n                continue;\n            }\n        }\n        return $belongsToNamespace;\n    }\n}\n"
  },
  {
    "path": "source/Core/NoJsValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Validates if there are no Javascript.\n */\nclass NoJsValidator\n{\n    /**\n     * Checks if provided config value is not vulnerable.\n     *\n     * @param string $configValue\n     *\n     * @return bool\n     */\n    public function isValid($configValue)\n    {\n        return preg_match('/<script.*>/', $configValue) === 0;\n    }\n}\n"
  },
  {
    "path": "source/Core/OnlineCaller.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\EshopCommunity\\Core\\Exception\\StandardException;\nuse Exception;\n\n/**\n * Class oxOnlineCaller makes call to given URL which is taken from child classes and sends request parameter.\n *\n * @internal Do not make a module extension for this class.\n *\n * @ignore   This class will not be included in documentation.\n */\nabstract class OnlineCaller\n{\n    const ALLOWED_HTTP_FAILED_CALLS_COUNT = 4;\n\n    /** Amount of seconds for curl execution timeout. */\n    const CURL_EXECUTION_TIMEOUT = 5;\n\n    /** Amount of seconds for curl connect timeout. */\n    const CURL_CONNECT_TIMEOUT = 3;\n\n    /**\n     * @var \\OxidEsales\\Eshop\\Core\\Curl\n     */\n    private $_oCurl;\n\n    /**\n     * @var \\OxidEsales\\Eshop\\Core\\OnlineServerEmailBuilder\n     */\n    private $_oEmailBuilder;\n\n    /**\n     * @var \\OxidEsales\\Eshop\\Core\\SimpleXml\n     */\n    private $_oSimpleXml;\n\n    /**\n     * Gets XML document name.\n     *\n     * @return string XML document tag name.\n     */\n    abstract protected function getXMLDocumentName();\n    /**\n     * Gets service url.\n     *\n     * @return string Web service url.\n     */\n    abstract protected function getServiceUrl();\n\n    /**\n     * Sets dependencies.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Curl                     $oCurl         Sends request to OXID servers.\n     * @param \\OxidEsales\\Eshop\\Core\\OnlineServerEmailBuilder $oEmailBuilder Forms email when OXID servers are unreachable.\n     * @param \\OxidEsales\\Eshop\\Core\\SimpleXml                $oSimpleXml    Forms XML from Request for sending to OXID servers.\n     */\n    public function __construct(\\OxidEsales\\Eshop\\Core\\Curl $oCurl, \\OxidEsales\\Eshop\\Core\\OnlineServerEmailBuilder $oEmailBuilder, \\OxidEsales\\Eshop\\Core\\SimpleXml $oSimpleXml)\n    {\n        $this->_oCurl = $oCurl;\n        $this->_oEmailBuilder = $oEmailBuilder;\n        $this->_oSimpleXml = $oSimpleXml;\n    }\n\n    /**\n     * Makes curl call with given parameters to given url.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\OnlineRequest $oRequest Information set in Request object will be sent to OXID servers.\n     *\n     * @return null|string In XML format.\n     */\n    public function call(\\OxidEsales\\Eshop\\Core\\OnlineRequest $oRequest)\n    {\n        $sOutputXml = null;\n        $iFailedCallsCount = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getSystemConfigParameter('iFailedOnlineCallsCount');\n        try {\n            $sXml = $this->formXMLRequest($oRequest);\n            $sOutputXml = $this->executeCurlCall($this->getServiceUrl(), $sXml);\n            $statusCode = $this->getCurl()->getStatusCode();\n            if ($statusCode != 200) {\n                /** @var \\OxidEsales\\Eshop\\Core\\Exception\\StandardException $oException */\n                $oException = new StandardException('cUrl call to ' . $this->getCurl()->getUrl() . ' failed with HTTP status ' . $statusCode);\n                throw $oException;\n            }\n            $this->resetFailedCallsCount($iFailedCallsCount);\n        } catch (Exception $oEx) {\n            if ($iFailedCallsCount > self::ALLOWED_HTTP_FAILED_CALLS_COUNT) {\n                \\OxidEsales\\Eshop\\Core\\Registry::getLogger()->error($oEx->getMessage(), [$oEx]);\n\n                $sXml = $this->formEmail($oRequest);\n                $this->sendEmail($sXml);\n                $this->resetFailedCallsCount($iFailedCallsCount);\n            } else {\n                $this->increaseFailedCallsCount($iFailedCallsCount);\n            }\n        }\n\n        return $sOutputXml;\n    }\n\n    /**\n     * Forms email.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\OnlineRequest $oRequest Request object from which email should be formed.\n     *\n     * @return string\n     */\n    protected function formEmail($oRequest)\n    {\n        return $this->formXMLRequest($oRequest);\n    }\n\n    /**\n     * Forms XML request.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\OnlineRequest $oRequest Request object from which server request should be formed.\n     *\n     * @return string\n     */\n    protected function formXMLRequest($oRequest)\n    {\n        return $this->getSimpleXml()->objectToXml($oRequest, $this->getXMLDocumentName());\n    }\n\n    /**\n     * Gets simple XML.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\SimpleXml\n     */\n    protected function getSimpleXml()\n    {\n        return $this->_oSimpleXml;\n    }\n\n    /**\n     * Gets curl.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Curl\n     */\n    protected function getCurl()\n    {\n        return $this->_oCurl;\n    }\n\n    /**\n     * Gets email builder.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\OnlineServerEmailBuilder\n     */\n    protected function getEmailBuilder()\n    {\n        return $this->_oEmailBuilder;\n    }\n\n    /**\n     * Executes CURL call with given parameters.\n     *\n     * @param string $sUrl Server address to call to.\n     * @param string $sXml Data to send. Currently OXID servers only accept XML formatted data.\n     *\n     * @return string\n     */\n    private function executeCurlCall($sUrl, $sXml)\n    {\n        $oCurl = $this->getCurl();\n        $oCurl->setMethod('POST');\n        $oCurl->setUrl($sUrl);\n        $oCurl->setQuery(http_build_query(['xmlRequest' => $sXml]));\n        $oCurl->setOption(\n            \\OxidEsales\\Eshop\\Core\\Curl::EXECUTION_TIMEOUT_OPTION,\n            static::CURL_EXECUTION_TIMEOUT\n        );\n\n        return $oCurl->execute();\n    }\n\n    /**\n     * Sends an email with server information.\n     *\n     * @param string $sBody Mail content.\n     */\n    private function sendEmail($sBody)\n    {\n        $oEmail = $this->getEmailBuilder()->build($sBody);\n        $oEmail->send();\n    }\n\n    /**\n     * Resets config parameter iFailedOnlineCallsCount if it's bigger than 0.\n     *\n     * @param int $iFailedOnlineCallsCount Amount of calls which previously failed.\n     */\n    private function resetFailedCallsCount($iFailedOnlineCallsCount)\n    {\n        if ($iFailedOnlineCallsCount > 0) {\n            \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->saveSystemConfigParameter('int', 'iFailedOnlineCallsCount', 0);\n        }\n    }\n\n    /**\n     * increases failed calls count.\n     *\n     * @param int $iFailedOnlineCallsCount Amount of calls which previously failed.\n     */\n    private function increaseFailedCallsCount($iFailedOnlineCallsCount)\n    {\n        \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->saveSystemConfigParameter('int', 'iFailedOnlineCallsCount', ++$iFailedOnlineCallsCount);\n    }\n}\n"
  },
  {
    "path": "source/Core/OnlineLicenseCheck.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse stdClass;\nuse oxException;\n\n/**\n * Performs Online License Key check.\n *\n * @internal Do not make a module extension for this class.\n *\n * @ignore   This class will not be included in documentation.\n */\nclass OnlineLicenseCheck\n{\n    /**\n     * Variable name to be used in oxConfig table\n     */\n    const CONFIG_VAR_NAME = 'iOlcSuccess';\n\n    /**\n     * Expected valid response code.\n     *\n     * @var integer\n     */\n    protected $validResponseCode = 0;\n\n    /**\n     * Expected valid response message.\n     *\n     * @var string\n     */\n    protected $validResponseMessage = 'ACK';\n\n    /**\n     * List of serial keys to validate.\n     *\n     * @var array\n     */\n    protected $serialKeys = [];\n\n    /**\n     * Error message for the user.\n     *\n     * @var string\n     */\n    protected $errorMessage = '';\n\n    /**\n     * Indicates exception event\n     *\n     * @var bool\n     */\n    protected $isException = false;\n\n    /**\n     * @var \\OxidEsales\\Eshop\\Core\\OnlineLicenseCheckCaller\n     */\n    protected $caller = null;\n\n    /**\n     * @var \\OxidEsales\\Eshop\\Core\\UserCounter\n     */\n    protected $userCounter = null;\n\n    /**\n     * @var \\OxidEsales\\Eshop\\Core\\Service\\ApplicationServerExporterInterface\n     */\n    protected $appServerExporter = null;\n\n    /**\n     * Sets servers manager.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Service\\ApplicationServerExporterInterface $appServerExporter\n     */\n    public function setAppServerExporter($appServerExporter)\n    {\n        $this->appServerExporter = $appServerExporter;\n    }\n\n    /**\n     * Gets servers manager.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Service\\ApplicationServerExporterInterface\n     */\n    public function getAppServerExporter()\n    {\n        return $this->appServerExporter;\n    }\n\n    /**\n     * Sets user counter.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\UserCounter $userCounter\n     */\n    public function setUserCounter($userCounter)\n    {\n        $this->userCounter = $userCounter;\n    }\n\n    /**\n     * Gets user counter.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\UserCounter\n     */\n    protected function getUserCounter()\n    {\n        return $this->userCounter;\n    }\n\n    /**\n     * Sets dependencies.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\OnlineLicenseCheckCaller $caller\n     */\n    public function __construct($caller)\n    {\n        $this->caller = $caller;\n    }\n\n    /**\n     * Get error message.\n     *\n     * @return string\n     */\n    public function getErrorMessage()\n    {\n        return $this->errorMessage;\n    }\n\n    /**\n     * Indicates whether the exception was thrown\n     *\n     * @return bool\n     */\n    public function isException()\n    {\n        return $this->isException;\n    }\n\n    /**\n     * Takes active serial key and performs online license check in case it has never been performed before.\n     * In case of invalid license key, eShop is declared as unlicensed.\n     * In case of validation exception (eg. service can not be reached) the check is postponed until the next call.\n     */\n    public function validateShopSerials()\n    {\n        $aSerials = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam(\"aSerials\");\n        if (!$this->validate($aSerials) && !$this->isException()) {\n            $this->startGracePeriod();\n        }\n    }\n\n    /**\n     * The Online shop license check for the new serial is performed. Returns check result.\n     *\n     * @param string $serial Serial to check.\n     *\n     * @return bool\n     */\n    public function validateNewSerial($serial)\n    {\n        $serials = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam(\"aSerials\");\n        $serials[] = ['attributes' => ['state' => 'new'], 'value' => $serial];\n\n        return $this->validate($serials);\n    }\n\n    /**\n     * The Online shop license check is performed. Returns check result.\n     *\n     * @param array $serials Serial keys to be checked.\n     *\n     * @return bool\n     */\n    public function validate($serials)\n    {\n        $serials = (array)$serials;\n        $this->setIsException(false);\n\n        $result = false;\n        try {\n            $request = $this->formRequest($serials);\n\n            $caller = $this->getCaller();\n            $response = $caller->doRequest($request);\n\n            $result = $this->validateResponse($response);\n\n            if ($result) {\n                $this->logSuccess();\n            }\n        } catch (\\OxidEsales\\Eshop\\Core\\Exception\\StandardException $ex) {\n            $this->setErrorMessage($ex->getMessage());\n            $this->setIsException(true);\n        }\n\n        return $result;\n    }\n\n    /**\n     * Set error message.\n     *\n     * @param string $errorMessage Error message\n     */\n    protected function setErrorMessage($errorMessage)\n    {\n        $this->errorMessage = $errorMessage;\n    }\n\n    /**\n     * Gets caller.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\OnlineLicenseCheckCaller\n     */\n    protected function getCaller()\n    {\n        return $this->caller;\n    }\n\n    /**\n     * Performs a check of the response code and message.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\OnlineLicenseCheckResponse $response\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\StandardException\n     *\n     * @return bool\n     */\n    protected function validateResponse($response)\n    {\n        if (isset($response->code) && isset($response->message)) {\n            if (\n                $response->code == $this->validResponseCode &&\n                $response->message == $this->validResponseMessage\n            ) {\n                // serial keys are valid\n                $valid = true;\n            } else {\n                // serial keys are not valid\n                $this->setErrorMessage(\\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('OLC_ERROR_SERIAL_NOT_VALID'));\n                $valid = false;\n            }\n        } else {\n            // validation result is unknown\n            throw new \\OxidEsales\\Eshop\\Core\\Exception\\StandardException('OLC_ERROR_RESPONSE_NOT_VALID');\n        }\n\n        return $valid;\n    }\n\n    /**\n     * Builds request object with required parameters.\n     *\n     * @param array $serials Array of serials to add to request.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\OnlineLicenseCheckRequest\n     */\n    protected function formRequest($serials)\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        /** @var \\OxidEsales\\Eshop\\Core\\OnlineLicenseCheckRequest $request */\n        $request = oxNew(\\OxidEsales\\Eshop\\Core\\OnlineLicenseCheckRequest::class);\n\n        $request->keys = ['key' => $serials];\n\n        $request->productSpecificInformation = new stdClass();\n\n        if (!is_null($this->getAppServerExporter())) {\n            $servers = $this->getAppServerExporter()->exportAppServerList();\n            $request->productSpecificInformation->servers = ['server' => $servers];\n        }\n\n        $counters = $this->formCounters();\n        if (!empty($counters)) {\n            $request->productSpecificInformation->counters = ['counter' => $counters];\n        }\n\n        return $request;\n    }\n\n    /**\n     * Forms shop counters array for sending to OXID server.\n     *\n     * @return array\n     */\n    protected function formCounters()\n    {\n        $userCounter = $this->getUserCounter();\n\n        $counters = [];\n\n        if (!is_null($this->getUserCounter())) {\n            $counters[] = [\n                'name' => 'admin users',\n                'value' => $userCounter->getAdminCount(),\n            ];\n            $counters[] = [\n                'name' => 'active admin users',\n                'value' => $userCounter->getActiveAdminCount(),\n            ];\n        }\n\n        $counters[] = [\n            'name' => 'subShops',\n            'value' => \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getMandateCount(),\n        ];\n\n        return $counters;\n    }\n\n    /**\n     * Registers the latest Successful Online License check.\n     */\n    protected function logSuccess()\n    {\n        $time = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->getTime();\n        $baseShop = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getBaseShopId();\n        \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->saveShopConfVar(\n            \"str\",\n            \\OxidEsales\\Eshop\\Core\\OnlineLicenseCheck::CONFIG_VAR_NAME,\n            $time,\n            $baseShop\n        );\n    }\n\n    /**\n     * Sets exception flag.\n     *\n     * @param bool $isException Exception flag.\n     */\n    protected function setIsException($isException)\n    {\n        $this->isException = $isException;\n    }\n\n    /**\n     * Starts grace period.\n     * Sets to config options.\n     */\n    protected function startGracePeriod()\n    {\n    }\n}\n"
  },
  {
    "path": "source/Core/OnlineLicenseCheckCaller.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse oxException;\n\n/**\n * Class makes call to given URL address and sends request parameter.\n *\n * @internal Do not make a module extension for this class.\n *\n * @ignore   This class will not be included in documentation.\n */\nclass OnlineLicenseCheckCaller extends \\OxidEsales\\Eshop\\Core\\OnlineCaller\n{\n    /** Online License Key Check web service url. */\n    const WEB_SERVICE_URL = 'https://olc.oxid-esales.com/check.php';\n\n    /** XML document tag name. */\n    const XML_DOCUMENT_NAME = 'olcRequest';\n\n    /**\n     * Expected response element in the XML response message fom web service.\n     *\n     * @var string\n     */\n    private $_sResponseElement = 'olc';\n\n    /**\n     * Performs Web service request\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\OnlineLicenseCheckRequest $oRequest Object with request parameters\n     *\n     * @throws oxException\n     * @return \\OxidEsales\\Eshop\\Core\\OnlineLicenseCheckResponse\n     */\n    public function doRequest(\\OxidEsales\\Eshop\\Core\\OnlineLicenseCheckRequest $oRequest)\n    {\n        return $this->formResponse($this->call($oRequest));\n    }\n\n    /**\n     * Removes serial keys from request and forms email body.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\OnlineLicenseCheckRequest $oRequest\n     *\n     * @return string\n     */\n    protected function formEmail($oRequest)\n    {\n        $oRequest->keys = null;\n\n        return parent::formEmail($oRequest);\n    }\n\n    /**\n     * Parse response message received from Online License Key Check web service and save it to response object.\n     *\n     * @param string $sRawResponse UnResponse from server\n     *\n     * @throws oxException\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\OnlineLicenseCheckResponse\n     */\n    protected function formResponse($sRawResponse)\n    {\n        /** @var \\OxidEsales\\Eshop\\Core\\UtilsXml $oUtilsXml */\n        $oUtilsXml = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsXml();\n        if (empty($sRawResponse) || !($oDomDoc = $oUtilsXml->loadXml($sRawResponse))) {\n            throw new \\OxidEsales\\Eshop\\Core\\Exception\\StandardException('OLC_ERROR_RESPONSE_NOT_VALID');\n        }\n\n        if ($oDomDoc->documentElement->nodeName != $this->_sResponseElement) {\n            throw new \\OxidEsales\\Eshop\\Core\\Exception\\StandardException('OLC_ERROR_RESPONSE_UNEXPECTED');\n        }\n\n        $oResponseNode = $oDomDoc->firstChild;\n\n        if (!$oResponseNode->hasChildNodes()) {\n            throw new \\OxidEsales\\Eshop\\Core\\Exception\\StandardException('OLC_ERROR_RESPONSE_NOT_VALID');\n        }\n\n        $oNodes = $oResponseNode->childNodes;\n\n        /** @var \\OxidEsales\\Eshop\\Core\\OnlineLicenseCheckResponse $oResponse */\n        $oResponse = oxNew(\\OxidEsales\\Eshop\\Core\\OnlineLicenseCheckResponse::class);\n\n        // iterate through response node to get response parameters\n        for ($i = 0; $i < $oNodes->length; $i++) {\n            $sNodeName = $oNodes->item($i)->nodeName;\n            $sNodeValue = $oNodes->item($i)->nodeValue;\n            $oResponse->$sNodeName = $sNodeValue;\n        }\n\n        return $oResponse;\n    }\n\n    /**\n     * Gets XML document name.\n     *\n     * @return string XML document tag name.\n     */\n    protected function getXMLDocumentName()\n    {\n        return self::XML_DOCUMENT_NAME;\n    }\n\n    /**\n     * Gets service url.\n     *\n     * @return string Web service url.\n     */\n    protected function getServiceUrl()\n    {\n        return self::WEB_SERVICE_URL;\n    }\n}\n"
  },
  {
    "path": "source/Core/OnlineLicenseCheckRequest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Online license check request class used as entity.\n *\n * @internal Do not make a module extension for this class.\n *\n * @ignore   This class will not be included in documentation.\n */\nclass OnlineLicenseCheckRequest extends \\OxidEsales\\Eshop\\Core\\OnlineRequest\n{\n    /**\n     * Web service protocol version.\n     *\n     * @var string\n     */\n    public $pVersion = '1.1';\n\n    /**\n     * Serial keys.\n     *\n     * @var string\n     */\n    public $keys;\n\n    /**\n     * Product related specific information\n     * like amount of sub shops and amount of admin users.\n     *\n     * @var object\n     */\n    public $productSpecificInformation;\n}\n"
  },
  {
    "path": "source/Core/OnlineLicenseCheckResponse.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Online license check response class.\n *\n * @internal Do not make a module extension for this class.\n *\n * @ignore   This class will not be included in documentation.\n */\nclass OnlineLicenseCheckResponse\n{\n    /**\n     * Serial keys.\n     *\n     * @var string\n     */\n    public $code;\n\n    /**\n     * Build revision number.\n     *\n     * @var string\n     */\n    public $message;\n}\n"
  },
  {
    "path": "source/Core/OnlineModuleVersionNotifier.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\{\n    ModuleConfigurationDataMapperBridgeInterface, ShopConfigurationDaoBridgeInterface\n};\nuse stdClass;\n\n/**\n * Performs Online Module Version Notifier check.\n *\n * The Online Module Version Notification is used for checking if newer versions of modules are available.\n * Will be used by the upcoming online one click installer.\n * Is still under development\n * - still changes at the remote server are necessary\n * - therefore ignoring the results for now\n *\n * @internal Do not make a module extension for this class.\n *\n * @ignore   This class will not be included in documentation.\n */\nclass OnlineModuleVersionNotifier\n{\n    /** @var \\OxidEsales\\Eshop\\Core\\OnlineModuleVersionNotifierCaller */\n    private $_oCaller;\n\n    public function __construct(\\OxidEsales\\Eshop\\Core\\OnlineModuleVersionNotifierCaller $oCaller)\n    {\n        $this->_oCaller = $oCaller;\n    }\n\n    /**\n     * Perform Online Module version Notification. Returns result\n     *\n     * @return null\n     */\n    public function versionNotify()\n    {\n        if (true === \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('preventModuleVersionNotify')) {\n            return;\n        }\n\n        $oOMNCaller = $this->getOnlineModuleNotifierCaller();\n        $oOMNCaller->doRequest($this->formRequest());\n    }\n\n    protected function prepareModulesInformation()\n    {\n        $shopConfiguration = ContainerFacade::get(ShopConfigurationDaoBridgeInterface::class)->get();\n\n        $preparedModules = [];\n        foreach ($shopConfiguration->getModuleConfigurations() as $moduleConfiguration) {\n            $preparedModules[] = ContainerFacade::get(ModuleConfigurationDataMapperBridgeInterface::class)\n                ->toData($moduleConfiguration);\n        }\n\n        return $preparedModules;\n    }\n\n    /**\n     * Send request message to Online Module Version Notifier web service.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\OnlineModulesNotifierRequest\n     */\n    protected function formRequest()\n    {\n        $oRequestParams = new \\OxidEsales\\Eshop\\Core\\OnlineModulesNotifierRequest();\n\n        $oRequestParams->modules = new stdClass();\n        $oRequestParams->modules->module = $this->prepareModulesInformation();\n\n        return $oRequestParams;\n    }\n\n    /**\n     * Returns caller.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\OnlineModuleVersionNotifierCaller\n     */\n    protected function getOnlineModuleNotifierCaller()\n    {\n        return $this->_oCaller;\n    }\n}\n"
  },
  {
    "path": "source/Core/OnlineModuleVersionNotifierCaller.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Class makes call to given URL address and sends request parameter.\n *\n * The Online Module Version Notification is used for checking if newer versions of modules are available.\n * Will be used by the upcoming online one click installer.\n * Is still under development - still changes at the remote server are necessary - therefore ignoring the results for now\n *\n * @internal Do not make a module extension for this class.\n *\n * @ignore   This class will not be included in documentation.\n */\nclass OnlineModuleVersionNotifierCaller extends \\OxidEsales\\Eshop\\Core\\OnlineCaller\n{\n    /** Online Module Version Notifier web service url. */\n    const WEB_SERVICE_URL = 'https://omvn.oxid-esales.com/check.php';\n\n    /** XML document tag name. */\n    const XML_DOCUMENT_NAME = 'omvnRequest';\n\n    /**\n     * Performs Web service request\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\OnlineModulesNotifierRequest $oRequest Object with request parameters\n     */\n    public function doRequest(\\OxidEsales\\Eshop\\Core\\OnlineModulesNotifierRequest $oRequest)\n    {\n        $this->call($oRequest);\n    }\n\n    /**\n     * Gets XML document name.\n     *\n     * @return string XML document tag name.\n     */\n    protected function getXMLDocumentName()\n    {\n        return self::XML_DOCUMENT_NAME;\n    }\n\n    /**\n     * Gets service url.\n     *\n     * @return string Web service url.\n     */\n    protected function getServiceUrl()\n    {\n        return self::WEB_SERVICE_URL;\n    }\n}\n"
  },
  {
    "path": "source/Core/OnlineModulesNotifierRequest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Online module notifier request class and used as entity.\n *\n * @internal Do not make a module extension for this class.\n *\n * @ignore   This class will not be included in documentation.\n */\nclass OnlineModulesNotifierRequest extends \\OxidEsales\\Eshop\\Core\\OnlineRequest\n{\n    /**\n     * Web service protocol version.\n     *\n     * @var string\n     */\n    public $pVersion = '1.1';\n\n    /**\n     * Modules array.\n     *\n     * @var array\n     */\n    public $modules;\n}\n"
  },
  {
    "path": "source/Core/OnlineRequest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Online check base request class.\n *\n * @internal Do not make a module extension for this class.\n *\n * @ignore   This class will not be included in documentation.\n */\nclass OnlineRequest\n{\n    /**\n     * OXID eShop servers cluster id.\n     *\n     * @var string\n     */\n    public $clusterId;\n\n    /**\n     * OXID eShop edition.\n     *\n     * @var string\n     */\n    public $edition;\n\n    /**\n     * Shops version number.\n     *\n     * @var string\n     */\n    public $version;\n\n    /**\n     * @var string\n     */\n    public $shopUrl;\n\n    /**\n     * Web service protocol version.\n     *\n     * @var string\n     */\n    public $pVersion;\n\n    /**\n     * Product ID. Intended for possible partner modules in future.\n     *\n     * @var string\n     */\n    public $productId = 'eShop';\n\n    public function __construct()\n    {\n        $this->clusterId = $this->getClusterId();\n        $this->edition = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getEdition()->value;\n        $this->version = ShopVersion::getVersion();\n        $this->shopUrl = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopUrl();\n    }\n\n    /**\n     * Returns cluster id.\n     * Takes cluster id from configuration if set, otherwise generates it.\n     *\n     * @return string\n     */\n    private function getClusterId()\n    {\n        $oConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $sBaseShop = $oConfig->getBaseShopId();\n        $sClusterId = $oConfig->getShopConfVar('sClusterId', $sBaseShop);\n        if (!$sClusterId) {\n            $oUUIDGenerator = oxNew(\\OxidEsales\\Eshop\\Core\\UniversallyUniqueIdGenerator::class);\n            $sClusterId = $oUUIDGenerator->generate();\n            $oConfig->saveShopConfVar(\"str\", 'sClusterId', $sClusterId, $sBaseShop);\n        }\n\n        return $sClusterId;\n    }\n}\n"
  },
  {
    "path": "source/Core/OnlineServerEmailBuilder.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Class OnlineServerEmailBuilder is responsible for generation of email with specific message\n * when it's not possible to make OLIS call via CURL.\n *\n * @internal Do not make a module extension for this class.\n *\n * @ignore   This class will not be included in documentation.\n */\nclass OnlineServerEmailBuilder extends \\OxidEsales\\Eshop\\Core\\EmailBuilder\n{\n    const OLC_EMAIL = 'olc@oxid-esales.com';\n\n    /**\n     * @inheritdoc\n     *\n     * @return string\n     */\n    protected function getBody()\n    {\n        return $this->buildParam;\n    }\n\n    /**\n     * @inheritdoc\n     *\n     * @return string\n     */\n    protected function getSubject()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString(\n            'SUBJECT_UNABLE_TO_SEND_VIA_CURL',\n            null,\n            true\n        );\n    }\n\n    /**\n     * @inheritdoc\n     *\n     * @return string\n     */\n    protected function getRecipient()\n    {\n        return self::OLC_EMAIL;\n    }\n}\n"
  },
  {
    "path": "source/Core/OnlineVatIdCheck.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse stdClass;\nuse DOMDocument;\nuse Exception;\nuse SoapClient;\nuse SoapFault;\n\n/**\n * Online VAT id checker class.\n */\nclass OnlineVatIdCheck extends \\OxidEsales\\Eshop\\Core\\CompanyVatInChecker\n{\n    /**\n     * Keeps service check state\n     *\n     * @var bool\n     */\n    protected $_blServiceIsOn = null;\n\n    /**\n     * VAT check results cache\n     *\n     * @var array\n     */\n    protected static $_aVatCheckCache = [];\n\n    /**\n     * How many times to retry check if server is busy\n     */\n    const BUSY_RETRY_CNT = 1;\n\n    /**\n     * How much to wait between retries (in micro seconds)\n     */\n    const BUSY_RETRY_WAITUSEC = 500000;\n\n    /**\n     * Wsdl url\n     *\n     * @var string\n     */\n    protected $_sWsdl = 'https://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl';\n\n    /**\n     * Class constructor.\n     */\n    public function __construct()\n    {\n    }\n\n    /**\n     * Validates VAT.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\CompanyVatIn $oVatIn Company VAT identification number object.\n     *\n     * @return bool\n     */\n    public function validate(\\OxidEsales\\Eshop\\Application\\Model\\CompanyVatIn $oVatIn)\n    {\n        $oCheckVat = new stdClass();\n        $oCheckVat->countryCode = $oVatIn->getCountryCode();\n        $oCheckVat->vatNumber = $oVatIn->getNumbers();\n\n        $blResult = $this->checkOnline($oCheckVat);\n        if (!$blResult) {\n            $this->setError('ID_NOT_VALID');\n        }\n\n        return $blResult;\n    }\n\n    /**\n     * Catches soap warning which is usually thrown due to service problems.\n     * Return true and allows to continue process\n     *\n     * @param int    $iErrNo   error type number\n     * @param string $sErrStr  error message\n     * @param string $sErrFile error file\n     * @param int    $iErrLine error line\n     */\n    public function catchWarning($iErrNo, $sErrStr, $sErrFile, $iErrLine)\n    {\n        \\OxidEsales\\Eshop\\Core\\Registry::getLogger()->warning($sErrStr, [\n            'file' => $sErrFile,\n            'line' => $iErrLine,\n            'code' => $iErrNo\n        ]);\n    }\n\n    /**\n     * Checks if VAT check can be performed:\n     *  - if SoapClient class exists;\n     *  - if service returns any output;\n     *  - if output, returned by service, is valid.\n     *\n     * @return bool\n     */\n    protected function isServiceAvailable()\n    {\n        if ($this->_blServiceIsOn === null) {\n            $this->_blServiceIsOn = class_exists('SoapClient') ? true : false;\n            if ($this->_blServiceIsOn) {\n                $rFp = @fopen($this->getWsdlUrl(), 'r');\n                $this->_blServiceIsOn = $rFp !== false;\n                if ($this->_blServiceIsOn) {\n                    $sWsdl = '';\n                    while (!feof($rFp)) {\n                        $sWsdl .= fread($rFp, 8192);\n                    }\n                    fclose($rFp);\n\n                    // validating wsdl file\n                    try {\n                        $oDomDocument = new DOMDocument();\n                        $oDomDocument->loadXML($sWsdl);\n                    } catch (Exception $oExcp) {\n                        // invalid xml\n                        $this->_blServiceIsOn = false;\n                    }\n                }\n            }\n        }\n\n        return $this->_blServiceIsOn;\n    }\n\n    /**\n     * Checks online if USt.ID number is valid.\n     * Returns true on success. On error sets error value.\n     *\n     * @param object $oCheckVat vat object\n     *\n     * @return bool\n     */\n    protected function checkOnline($oCheckVat)\n    {\n        //Default D3 Source: https://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl\n        $aRetryErrors = [\n            'SERVER_BUSY',\n            'GLOBAL_MAX_CONCURRENT_REQ',\n            'MS_MAX_CONCURRENT_REQ',\n            'SERVICE_UNAVAILABLE',\n            'MS_UNAVAILABLE',\n            'TIMEOUT',\n        ];\n\n        if ($this->isServiceAvailable()) {\n            $iTryMoreCnt = self::BUSY_RETRY_CNT;\n\n            //T2009-07-02\n            //how long socket should wait for server RESPONSE\n            ini_set('default_socket_timeout', 5);\n\n            // setting local error handler to catch possible soap errors\n            set_error_handler([$this, 'catchWarning'], E_WARNING);\n\n            do {\n                try {\n                    //connection_timeout = how long we should wait to CONNECT to wsdl server\n                    $oSoapClient = new SoapClient($this->getWsdlUrl(), [\"connection_timeout\" => 5]);\n                    $this->setError('');\n                    $oRes = $oSoapClient->checkVat($oCheckVat);\n                    $iTryMoreCnt = 0;\n                } catch (SoapFault $e) {\n                    $this->setError($e->faultstring);\n                    if (in_array($this->getError(), $aRetryErrors)) {\n                        usleep(self::BUSY_RETRY_WAITUSEC);\n                    } else {\n                        $iTryMoreCnt = 0;\n                    }\n                }\n            } while (0 < $iTryMoreCnt--);\n\n            // restoring previous error handler\n            restore_error_handler();\n\n            return (bool) $oRes->valid;\n        } else {\n            $this->setError(\"SERVICE_UNREACHABLE\");\n\n            return false;\n        }\n    }\n\n    /**\n     * Returns wsdl url\n     *\n     * @return string\n     */\n    public function getWsdlUrl()\n    {\n        // overriding wsdl url\n        if (($sWsdl = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam(\"sVatIdCheckInterfaceWsdl\"))) {\n            $this->_sWsdl = $sWsdl;\n        }\n\n        return $this->_sWsdl;\n    }\n}\n"
  },
  {
    "path": "source/Core/OpenSSLFunctionalityChecker.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Class is responsible for openSSL functionality availability checking.\n */\nclass OpenSSLFunctionalityChecker\n{\n    /**\n     * Checks if openssl_random_pseudo_bytes function is available.\n     *\n     * @return bool\n     */\n    public function isOpenSslRandomBytesGeneratorAvailable()\n    {\n        return function_exists('openssl_random_pseudo_bytes');\n    }\n}\n"
  },
  {
    "path": "source/Core/Output.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Str;\n\n/**\n * class for output processing\n */\nclass Output extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    const OUTPUT_FORMAT_HTML = 'html';\n    const OUTPUT_FORMAT_JSON = 'json';\n\n    /**\n     * Keels search engine status\n     *\n     * @var bool\n     */\n    protected $_blSearchEngine = false;\n\n    /**\n     * page charset\n     *\n     * @var string\n     */\n    protected $_sCharset = null;\n\n    /**\n     * output format (html(default)/json)\n     *\n     * @var string\n     */\n    protected $_sOutputFormat = self::OUTPUT_FORMAT_HTML;\n\n    /**\n     * output buffer (e.g. for json)\n     *\n     * @var array\n     */\n    protected $_aBuffer = [];\n\n    /**\n     * Class constructor. Sets search engine mode according to client info\n     *\n     * @return null\n     */\n    public function __construct()\n    {\n        $this->setIsSearchEngine(\\OxidEsales\\Eshop\\Core\\Registry::getUtils()->isSearchEngine());\n    }\n\n    /**\n     * Search engine mode setter\n     *\n     * @param bool $blOn search engine mode\n     */\n    public function setIsSearchEngine($blOn)\n    {\n        $this->_blSearchEngine = $blOn;\n    }\n\n    /**\n     * function for front-end (normaly HTML) output processing\n     * This function is called from index.php\n     *\n     * @param string $sValue     value\n     * @param string $sClassName classname\n     *\n     * @return string\n     */\n    public function process($sValue, $sClassName)\n    {\n        return $sValue;\n    }\n\n    /**\n     * Add a version tag to a html page\n     *\n     * @param string $sOutput htmlheader\n     *\n     * @return string\n     */\n    final public function addVersionTags($sOutput)\n    {\n        // DISPLAY IT\n        $sEdition = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getFullEdition();\n        $sCurYear = date(\"Y\");\n\n        // Replacing only once per page\n        $sSearch = \"</head>\";\n        $sReplace = \"</head>\\n  <!-- OXID eShop {$sEdition}, Shopping Cart System (c) OXID eSales AG 2003 - {$sCurYear} - https://www.oxid-esales.com -->\";\n\n        $sOutput = ltrim($sOutput);\n        if (($pos = stripos($sOutput, $sSearch)) !== false) {\n            $sOutput = substr_replace($sOutput, $sReplace, $pos, strlen($sSearch));\n        }\n\n        return $sOutput;\n    }\n\n    /**\n     * Abstract function for template tag processing\n     * This function is called from index.php\n     *\n     * @param array  $aViewData  viewarray\n     * @param string $sClassName classname\n     *\n     * @return array\n     */\n    public function processViewArray($aViewData, $sClassName)\n    {\n        return $aViewData;\n    }\n\n    /**\n     * This function is called from index.php\n     *\n     * @param object $oEmail email object\n     */\n    public function processEmail(&$oEmail)\n    {\n        // #669 PHP5 claims that you cant pas full this but should instead pass reference what is anyway a much better idea\n        // removed \"return\" as by reference you dont need any return\n    }\n\n\n    /**\n     * set page charset\n     *\n     * @param string $sCharset charset to send with headers\n     */\n    public function setCharset($sCharset)\n    {\n        $this->_sCharset = $sCharset;\n    }\n\n    /**\n     * set page output format\n     *\n     * @param string $sFormat html or json\n     */\n    public function setOutputFormat($sFormat)\n    {\n        $this->_sOutputFormat = $sFormat;\n    }\n\n    /**\n     * output data\n     *\n     * @param string $sName  output name (used in json mode)\n     * @param string $output output text/data\n     */\n    public function output($sName, $output)\n    {\n        switch ($this->_sOutputFormat) {\n            case self::OUTPUT_FORMAT_JSON:\n                $this->_aBuffer[$sName] = $output;\n                break;\n            case self::OUTPUT_FORMAT_HTML:\n            default:\n                echo $output;\n                break;\n        }\n    }\n\n    /**\n     * flush pending output\n     */\n    public function flushOutput()\n    {\n        switch ($this->_sOutputFormat) {\n            case self::OUTPUT_FORMAT_JSON:\n                echo Str::getStr()->jsonEncode($this->_aBuffer);\n                break;\n            case self::OUTPUT_FORMAT_HTML:\n            default:\n                break;\n        }\n    }\n\n    /**\n     * send page headers (content type, charset)\n     */\n    public function sendHeaders()\n    {\n        switch ($this->_sOutputFormat) {\n            case self::OUTPUT_FORMAT_JSON:\n                \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->setHeader(\"Content-Type: application/json; charset=\" . $this->_sCharset);\n                break;\n            case self::OUTPUT_FORMAT_HTML:\n            default:\n                \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->setHeader(\"Content-Type: text/html; charset=\" . $this->_sCharset);\n                break;\n        }\n    }\n\n    /**\n     * Forms Shop mode name.\n     *\n     * @return string\n     */\n    protected function getShopMode()\n    {\n        return '';\n    }\n}\n"
  },
  {
    "path": "source/Core/Oxid.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Static class mostly containing static methods which are supposed to be called before the full framework initialization\n */\nclass Oxid\n{\n    /**\n     * Executes main shop controller\n     *\n     * @static\n     *\n     * @return void\n     */\n    public static function run()\n    {\n        /** @var ShopControl $shopControl */\n        $shopControl = oxNew(\\OxidEsales\\Eshop\\Core\\ShopControl::class);\n\n        return $shopControl->start();\n    }\n\n    /**\n     * Executes shop widget controller\n     *\n     * @static\n     *\n     * @return void\n     */\n    public static function runWidget()\n    {\n        /** @var WidgetControl $widgetControl */\n        $widgetControl = oxNew(\\OxidEsales\\Eshop\\Core\\WidgetControl::class);\n\n        return $widgetControl->start();\n    }\n}\n"
  },
  {
    "path": "source/Core/PasswordHasher.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Hash password together with salt, using set hash algorithm\n *\n * @deprecated since v6.4.0 (2019-03-15); `\\OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\PasswordServiceBridgeInterface`\n *                                        was added as the new default for hashing passwords. Hashing passwords with\n *                                        MD5 and SHA512 is still supported in order support login with older\n *                                        password hashes. Therefor this class might not be\n *                                        compatible with the current passhword hash any more.\n */\nclass PasswordHasher\n{\n    /**\n     * @var \\oxHasher\n     */\n    private $_ohasher = null;\n\n    /**\n     * Gets hasher.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Hasher\n     */\n    protected function getHasher()\n    {\n        return $this->_ohasher;\n    }\n\n    /**\n     * Sets dependencies.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Hasher $oHasher hasher.\n     */\n    public function __construct($oHasher)\n    {\n        $this->_ohasher = $oHasher;\n    }\n\n    /**\n     * Hash password with a salt.\n     *\n     * @param string $sPassword not hashed password.\n     * @param string $sSalt     salt string.\n     *\n     * @return string\n     */\n    public function hash($sPassword, $sSalt)\n    {\n        return $this->getHasher()->hash($sPassword . $sSalt);\n    }\n}\n"
  },
  {
    "path": "source/Core/PictureHandler.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Core\\Base;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse Symfony\\Component\\Filesystem\\Path;\n\n/**\n * class for pictures processing\n */\nclass PictureHandler extends Base\n{\n    /**\n     * Deletes master picture and all images generated from it.\n     * If third parameter is false, skips master image delete, only\n     * all generated images will be deleted.\n     *\n     * @param Article $oObject               article object\n     * @param int     $iIndex                master picture index\n     * @param bool    $blDeleteMasterPicture delete master picture, default is true\n     *\n     * @return void\n     */\n    public function deleteArticleMasterPicture($oObject, $iIndex, $blDeleteMasterPicture = true)\n    {\n        $myConfig = Registry::getConfig();\n        $myUtilsPic = Registry::getUtilsPic();\n        $oUtilsFile = Registry::getUtilsFile();\n        $blGeneratedImagesOnly = !$blDeleteMasterPicture;\n        $imageFieldName = \"oxpic$iIndex\";\n        $imageFieldValue = $oObject->getFieldData($imageFieldName);\n\n        $masterImageFilename = $imageFieldValue ? basename($imageFieldValue) : null;\n        if (!$masterImageFilename || $masterImageFilename === $this->getNopicFilename()) {\n            return;\n        }\n\n        $aPic = [\n            'sField' => $imageFieldName,\n            'sDir' => $oUtilsFile->getImageDirByType('M' . $iIndex, $blGeneratedImagesOnly),\n            'sFileName' => $masterImageFilename\n        ];\n\n        $sAbsDynImageDir = $myConfig->getPictureDir(false);\n        $blDeleted = $myUtilsPic->safePictureDelete(\n            $aPic['sFileName'],\n            $sAbsDynImageDir . $aPic['sDir'],\n            'oxarticles',\n            $aPic['sField']\n        );\n        if ($blDeleted) {\n            $this->deleteZoomPicture($oObject, $iIndex);\n\n            $aDelPics = [];\n            if ($iIndex == 1) {\n                // deleting generated main icon picture if custom main icon\n                // file name not equal with generated from master picture\n                if ($this->getMainIconName($masterImageFilename) != basename($oObject->oxarticles__oxicon->value)) {\n                    $aDelPics[] = [\n                        \"sField\"    => \"oxpic1\",\n                        \"sDir\"      => $oUtilsFile->getImageDirByType(\"ICO\", $blGeneratedImagesOnly),\n                        \"sFileName\" => $this->getMainIconName($masterImageFilename)\n                    ];\n                }\n\n                // deleting generated thumbnail picture if custom thumbnail\n                // file name not equal with generated from master picture\n                if ($this->getThumbName($masterImageFilename) != basename($oObject->oxarticles__oxthumb->value)) {\n                    $aDelPics[] = [\n                        \"sField\"    => \"oxpic1\",\n                        \"sDir\"      => $oUtilsFile->getImageDirByType(\"TH\", $blGeneratedImagesOnly),\n                        \"sFileName\" => $this->getThumbName($masterImageFilename)\n                    ];\n                }\n            }\n\n            foreach ($aDelPics as $aPic) {\n                $myUtilsPic->safePictureDelete($aPic[\"sFileName\"], $sAbsDynImageDir . $aPic[\"sDir\"], \"oxarticles\", $aPic[\"sField\"]);\n            }\n        }\n\n        //deleting custom zoom pic (compatibility mode)\n        if ($oObject->{\"oxarticles__oxzoom\" . $iIndex}->value) {\n            if (basename($oObject->{\"oxarticles__oxzoom\" . $iIndex}->value) !== $this->getNopicFilename()) {\n                // deleting old zoom picture\n                $this->deleteZoomPicture($oObject, $iIndex);\n            }\n        }\n    }\n\n    /**\n     * Deletes custom main icon, which name is specified in oxicon field.\n     *\n     * @param Article $oObject article object\n     */\n    public function deleteMainIcon($oObject)\n    {\n        if (($sMainIcon = $oObject->oxarticles__oxicon->value)) {\n            $sPath = Registry::getConfig()->getPictureDir(false) . Registry::getUtilsFile()->getImageDirByType(\"ICO\");\n            Registry::getUtilsPic()->safePictureDelete($sMainIcon, $sPath, \"oxarticles\", \"oxicon\");\n        }\n    }\n\n    /**\n     * Deletes custom thumbnail, which name is specified in oxthumb field.\n     *\n     * @param Article $oObject article object\n     */\n    public function deleteThumbnail($oObject)\n    {\n        if (($sThumb = $oObject->oxarticles__oxthumb->value)) {\n            // deleting article main icon and thumb picture\n            $sPath = Registry::getConfig()->getPictureDir(false) . Registry::getUtilsFile()->getImageDirByType(\"TH\");\n            Registry::getUtilsPic()->safePictureDelete($sThumb, $sPath, \"oxarticles\", \"oxthumb\");\n        }\n    }\n\n    /**\n     * Deletes custom zoom picture, which name is specified in oxzoom field.\n     *\n     * @param Article $oObject article object\n     * @param int     $iIndex  zoom picture index\n     *\n     * @return null\n     */\n    public function deleteZoomPicture($oObject, $iIndex)\n    {\n        // checking if oxzoom field exists\n        $oDbHandler = oxNew(\\OxidEsales\\Eshop\\Core\\DbMetaDataHandler::class);\n        $iZoomPicCount = (int) Registry::getConfig()->getConfigParam('iZoomPicCount');\n\n        if ($iIndex > $iZoomPicCount || !$oDbHandler->fieldExists(\"oxzoom\" . $iIndex, \"oxarticles\")) {\n            if ($sZoomPicName = $this->getZoomName($oObject->{\"oxarticles__oxpic\" . $iIndex}->value, $iIndex)) {\n                $sFieldToCheck = \"oxpic\" . $iIndex;\n            } else {\n                return;\n            }\n        } else {\n            $sZoomPicName = basename($oObject->{\"oxarticles__oxzoom\" . $iIndex}->value);\n            $sFieldToCheck = \"oxzoom\" . $iIndex;\n        }\n\n        if ($sZoomPicName && $sZoomPicName != $this->getNopicFilename()) {\n            // deleting zoom picture\n            $sPath = Registry::getConfig()->getPictureDir(false) . Registry::getUtilsFile()->getImageDirByType(\"Z\" . $iIndex);\n            Registry::getUtilsPic()->safePictureDelete($sZoomPicName, $sPath, \"oxarticles\", $sFieldToCheck);\n        }\n    }\n\n    /**\n     * Returns article picture icon name for selected article picture\n     *\n     * @param string $sFilename file name\n     *\n     * @return string\n     */\n    public function getIconName($sFilename)\n    {\n        return $sFilename;\n    }\n\n    /**\n     * Returns article main icon name generated from master picture\n     *\n     * @param string $sMasterImageFile master image file name\n     *\n     * @return string\n     */\n    public function getMainIconName($sMasterImageFile)\n    {\n        return $this->getBaseMasterImageFileName($sMasterImageFile);\n    }\n\n    /**\n     * Returns thumb image name generated from master picture\n     *\n     * @param string $sMasterImageFile master image file name\n     *\n     * @return string\n     */\n    public function getThumbName($sMasterImageFile)\n    {\n        return basename($sMasterImageFile);\n    }\n\n    /**\n     * Returns zoom image name generated from master picture\n     *\n     * @param string $sMasterImageFile master image file name\n     * @param string $iIndex           master image index\n     *\n     * @return string\n     */\n    public function getZoomName($sMasterImageFile, $iIndex)\n    {\n        return basename($sMasterImageFile);\n    }\n\n    /**\n     * Gets master image file name and removes suffics (e.g. _p1) from file end.\n     *\n     * @param string $sMasterImageFile master image file name\n     *\n     * @return null\n     */\n    protected function getBaseMasterImageFileName($sMasterImageFile)\n    {\n        return basename($sMasterImageFile);\n    }\n\n    /**\n     * Returns image sizes from provided config array\n     *\n     * @param mixed  $aImgSizes array or string of sizes in format x*y\n     * @param string $sIndex    index in array\n     *\n     * @return array\n     */\n    public function getImageSize($aImgSizes, $sIndex = null)\n    {\n        $aSize = [];\n        if (isset($sIndex) && is_array($aImgSizes) && isset($aImgSizes[$sIndex])) {\n            $aSize = explode('*', $aImgSizes[$sIndex]);\n        } elseif (is_string($aImgSizes)) {\n            $aSize = explode('*', $aImgSizes);\n        }\n        if (is_array($aSize) && 2 == count($aSize)) {\n            $x = (int) $aSize[0];\n            $y = (int) $aSize[1];\n            if ($x && $y) {\n                return $aSize;\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * Returns dir/url info for given image file\n     *\n     * @param string $sFilePath path to file\n     * @param string $sFile     filename in pictures dir\n     * @param bool   $blAdmin   is admin mode ?\n     * @param bool   $blSSL     is ssl ?\n     * @param int    $iLang     language id\n     * @param int    $iShopId   shop id\n     *\n     * @return array\n     */\n    protected function getPictureInfo($sFilePath, $sFile, $blAdmin = false, $blSSL = null, $iLang = null, $iShopId = null)\n    {\n        // custom server as image storage?\n        if ($sAltUrl = $this->getAltImageUrl($sFilePath, $sFile)) {\n            return ['path' => false, 'url' => $sAltUrl];\n        }\n\n        $oConfig = Registry::getConfig();\n        $sPath = $oConfig->getPicturePath($sFilePath . $sFile, $blAdmin, $iLang, $iShopId);\n        if (!$sPath) {\n            return ['path' => false, 'url' => false];\n        }\n\n        $sDirPrefix = $oConfig->getOutDir();\n        $sUrlPrefix = $oConfig->getOutUrl($blSSL, $blAdmin, $oConfig->getConfigParam('blNativeImages'));\n\n        return ['path' => $sPath, 'url' => str_replace($sDirPrefix, $sUrlPrefix, $sPath)];\n    }\n\n    public function getAltImageUrl($filePath, $file)\n    {\n        $altUrl = ContainerFacade::getParameter('oxid_esales.alternative_image_url') ?: null;\n\n        if ($altUrl && !is_null($file)) {\n            $altUrl = Path::join($altUrl, $filePath, $file);\n        }\n\n        return $altUrl;\n    }\n\n    /**\n     * Returns requested picture url. If image is not available - returns false\n     *\n     * @param string $sPath    path from pictures/master/\n     * @param string $sFile    picture file name\n     * @param string $sSize    picture sizes (x, y)\n     * @param string $sIndex   picture index [optional]\n     * @param string|false $sAltPath alternative picture path [optional]\n     * @param bool   $bSsl     Whether to force SSL\n     *\n     * @return string|bool\n     */\n    public function getPicUrl($sPath, $sFile, $sSize, $sIndex = null, $sAltPath = false, $bSsl = null)\n    {\n        $sUrl = null;\n        if ($sPath && $sFile && ($aSize = $this->getImageSize($sSize, $sIndex))) {\n            $aPicInfo = $this->getPictureInfo(\"master/\" . ($sAltPath ?: $sPath), $sFile, $this->isAdmin(), $bSsl);\n            if ($aPicInfo['url'] && $aSize[0] && $aSize[1]) {\n                $sDirName = \"{$aSize[0]}_{$aSize[1]}_\" . Registry::getConfig()->getConfigParam('sDefaultImageQuality');\n                $sUrl = str_replace(\"/master/\" . ($sAltPath ?: $sPath), \"/generated/{$sPath}{$sDirName}/\", $aPicInfo['url']);\n            }\n        }\n\n        // Add webp extension if automatic conversion is enabled\n        if (\n            $sUrl !== null &&\n            Registry::getConfig()->getConfigParam('blConvertImagesToWebP', false) &&\n            pathinfo($sUrl, PATHINFO_EXTENSION) != 'webp'\n        ) {\n            $sUrl .= '.webp';\n        }\n\n        return $sUrl;\n    }\n\n    /**\n     * Returns requested product picture url. If image is not available - returns url to nopic.jpg\n     *\n     * @param string $sPath  path from pictures/master/\n     * @param string $sFile  picture file name\n     * @param string $sSize  picture sizes (x, y)\n     * @param string $sIndex picture index [optional]\n     * @param bool   $bSsl   Whether to force SSL\n     *\n     * @return string|bool\n     */\n    public function getProductPicUrl($sPath, $sFile, $sSize, $sIndex = null, $bSsl = null)\n    {\n        $sUrl = null;\n        if (!$sFile || !($sUrl = $this->getPicUrl($sPath, $sFile, $sSize, $sIndex, false, $bSsl))) {\n            $sUrl = $this->getPicUrl($sPath, $this->getNopicFilename(), $sSize, $sIndex, \"/\", $bSsl);\n        }\n\n        return $sUrl;\n    }\n\n    private function getNopicFilename(): string\n    {\n        if (Registry::getConfig()->getConfigParam('blConvertImagesToWebP')) {\n            return 'nopic.webp';\n        }\n\n        return 'nopic.jpg';\n    }\n}\n"
  },
  {
    "path": "source/Core/Price.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Price calculation class. Responsible for simple price calculations. Basically contains Brutto, Netto prices and VAT values.\n */\nclass Price\n{\n    /**\n     * Brutto price\n     *\n     * @var double\n     */\n    protected $_dBrutto = 0.0;\n\n    /**\n     * Netto price\n     *\n     * @var double\n     */\n    protected $_dNetto = 0.0;\n\n    /**\n     * VAT percent\n     *\n     * @var double\n     */\n    protected $_dVat = 0.0;\n\n\n    /**\n     * Assigned discount array\n     *\n     * @var array\n     */\n    protected $_aDiscounts = null;\n\n\n    /**\n     * Price entering mode\n     * Reference to myConfig->blEnterNetPrice\n     * Then true  - setPrice sets netto price and calculates brutto price\n     * Then false - setPrice sets brutto price and calculates netto price\n     *\n     * @var boolean\n     */\n    protected $_blNetPriceMode;\n\n    /**\n     * Class constructor. Gets price entering mode.\n     *\n     * @param double $dPrice given price\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Price\n     */\n    public function __construct($dPrice = null)\n    {\n        $this->setNettoMode(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('blEnterNetPrice'));\n\n        if (!is_null($dPrice)) {\n            $this->setPrice($dPrice);\n        }\n    }\n\n    /**\n     * Netto price mode setter\n     *\n     * @param bool $blNetto State to set price to net mode (default true).\n     */\n    public function setNettoMode($blNetto = true)\n    {\n        $this->_blNetPriceMode = $blNetto;\n    }\n\n    /**\n     * return true if mode is netto\n     *\n     * @return bool\n     */\n    public function isNettoMode()\n    {\n        return $this->_blNetPriceMode;\n    }\n\n    /**\n     * Netto price mode setter\n     */\n    public function setNettoPriceMode()\n    {\n        $this->setNettoMode();\n    }\n\n    /**\n     * Brutto price mode setter\n     */\n    public function setBruttoPriceMode()\n    {\n        $this->setNettoMode(false);\n    }\n\n    /**\n     * Sets new VAT percent, and recalculates price.\n     *\n     * @param float $dVat vat percent\n     */\n    public function setVat($dVat)\n    {\n        $this->_dVat = (float) $dVat;\n    }\n\n    /**\n     * Sets new base VAT percent, recalculates brutto, and then netto price (in brutto mode).\n     * if bruttoMode then BruttoPrice =(BruttoPrice - oldVAT% ) + newVat;\n     * oldVAT = newVat;\n     * finally recalculate;\n     * USE ONLY TO CHANGE BASE VAT (in case when local VAT differs from user VAT),\n     * USE setVat() in usual case !!!\n     *\n     * @param float $newVat vat percent\n     */\n    public function setUserVat($newVat)\n    {\n        if (!$this->isNettoMode() && $newVat != $this->_dVat) {\n            $this->_dBrutto = self::Netto2Brutto(self::Brutto2Netto($this->_dBrutto, $this->_dVat), (float) $newVat);\n        }\n        $this->_dVat = (float) $newVat;\n    }\n\n    /**\n     * Returns VAT percent\n     *\n     * @return double\n     */\n    public function getVat()\n    {\n        return $this->_dVat;\n    }\n\n    /**\n     * Sets new price and VAT percent(optional). Recalculates price by\n     * price entering mode\n     *\n     * @param double $dPrice new price\n     * @param double $dVat   VAT\n     */\n    public function setPrice($dPrice, $dVat = null)\n    {\n        if (!is_null($dVat)) {\n            $this->setVat($dVat);\n        }\n\n        if ($this->isNettoMode()) {\n            $this->_dNetto = $dPrice;\n        } else {\n            $this->_dBrutto = $dPrice;\n        }\n    }\n\n    /**\n     * Returns price depending on mode brutto or netto\n     *\n     * @return double\n     */\n    public function getPrice()\n    {\n        if ($this->isNettoMode()) {\n            return $this->getNettoPrice();\n        } else {\n            return $this->getBruttoPrice();\n        }\n    }\n\n    /**\n     * Returns brutto price\n     *\n     * @return double\n     */\n    public function getBruttoPrice()\n    {\n        if ($this->isNettoMode()) {\n            return $this->getNettoPrice() + $this->getVatValue();\n        } else {\n            return \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->fRound($this->_dBrutto);\n        }\n    }\n\n    /**\n     * Returns netto price\n     *\n     * @return double\n     */\n    public function getNettoPrice()\n    {\n        if ($this->isNettoMode()) {\n            return \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->fRound($this->_dNetto);\n        } else {\n            return $this->getBruttoPrice() - $this->getVatValue();\n        }\n    }\n\n    /**\n     * Returns absolute VAT value\n     *\n     * @return double\n     */\n    public function getVatValue()\n    {\n        if ($this->isNettoMode()) {\n            $dVatValue = $this->getNettoPrice() * $this->getVat() / 100;\n        } else {\n            $dVatValue = $this->getBruttoPrice() * $this->getVat() / (100 + $this->getVat());\n        }\n\n        return \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->fRound($dVatValue);\n    }\n\n    /**\n     * Subtracts given percent from price depending  on price entering mode,\n     * and recalculates price\n     *\n     * @param double $dValue percent to subtract from price\n     */\n    public function subtractPercent($dValue)\n    {\n        $dPrice = $this->getPrice();\n        $this->setPrice($dPrice - self::percent($dPrice, $dValue));\n    }\n\n    /**\n     * Adds given percent to price depending  on price entering mode,\n     * and recalculates price\n     *\n     * @param double $dValue percent to add to price\n     */\n    public function addPercent($dValue)\n    {\n        $this->subtractPercent(-$dValue);\n    }\n\n    /**\n     * Adds another oxPrice object and recalculates current method.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Price $oPrice object\n     */\n    public function addPrice(\\OxidEsales\\Eshop\\Core\\Price $oPrice)\n    {\n        if ($this->isNettoMode()) {\n            $this->add($oPrice->getNettoPrice());\n        } else {\n            $this->add($oPrice->getBruttoPrice());\n        }\n    }\n\n    /**\n     * Adds given value to price depending  on price entering mode,\n     * and recalculates price\n     *\n     * @param double $dValue value to add to price\n     */\n    public function add($dValue)\n    {\n        $dPrice = $this->getPrice();\n        $this->setPrice($dPrice + $dValue);\n    }\n\n    /**\n     * Subtracts given value from price depending  on price entering mode,\n     * and recalculates price\n     *\n     * @param double $dValue value to subtracts from price\n     */\n    public function subtract($dValue)\n    {\n        $this->add(-$dValue);\n    }\n\n    /**\n     * Multiplies price by given value depending on price entering mode,\n     * and recalculates price\n     *\n     * @param double $dValue value for multiplying price\n     */\n    public function multiply($dValue)\n    {\n        $dPrice = $this->getPrice();\n        $this->setPrice($dPrice * $dValue);\n    }\n\n    /**\n     * Divides price by given value depending on price entering mode,\n     * and recalculates price\n     *\n     * @param double $dValue value for dividing price\n     */\n    public function divide($dValue)\n    {\n        $dPrice = $this->getPrice();\n        $this->setPrice($dPrice / $dValue);\n    }\n\n    /**\n     * Compares this object to another oxPrice objects. Comparison is performed on brutto price.\n     * Result is equal to:\n     *   0 - when prices are equal.\n     *   1 - when this price is larger than $oPrice.\n     *  -1 - when this price is smaller than $oPrice.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Price $oPrice price object\n     *\n     * @return null\n     */\n    public function compare(\\OxidEsales\\Eshop\\Core\\Price $oPrice)\n    {\n        $dBruttoPrice1 = $this->getBruttoPrice();\n        $dBruttoPrice2 = $oPrice->getBruttoPrice();\n\n        if ($dBruttoPrice1 == $dBruttoPrice2) {\n            $iRes = 0;\n        } elseif ($dBruttoPrice1 > $dBruttoPrice2) {\n            $iRes = 1;\n        } else {\n            $iRes = -1;\n        }\n\n        return $iRes;\n    }\n\n    /**\n     * Private function for percent value calculations\n     *\n     * @param double $dValue   value\n     * @param double $dPercent percent\n     *\n     * @return float\n     */\n    public static function percent($dValue, $dPercent)\n    {\n        return ((float) $dValue * (float) $dPercent) / 100.0;\n    }\n\n    /**\n     * Converts Brutto price to Netto using formula:\n     * X + $dVat% = $dBrutto\n     * X/100 = $dBrutto/(100+$dVAT)\n     * X= ($dBrutto/(100+$dVAT))/100\n     * returns X\n     *\n     * @param double $dBrutto brutto price\n     * @param double $dVat    vat\n     *\n     * @return double\n     */\n    public static function brutto2Netto($dBrutto, $dVat)\n    {\n        // if VAT = -100% Return 0 because we subtract all what we have.\n        // made to avoid division by zero in formula.\n        if ($dVat == -100) {\n            return 0;\n        }\n\n        return (float) $dBrutto * 100.0 / (100.0 + (float) $dVat);\n    }\n\n    /**\n     * Converts Netto price to Brutto using formula:\n     * X = $dNetto + $dVat%\n     * returns X\n     *\n     * @param float $dNetto netto price\n     * @param float $dVat   vat\n     *\n     * @return float\n     */\n    public static function netto2Brutto($dNetto, $dVat)\n    {\n        return (float) $dNetto + self::percent($dNetto, $dVat);\n    }\n\n    /**\n     * Returns price multiplied by current currency\n     *\n     * @param string $dPrice price value\n     *\n     * @return float\n     */\n    public static function getPriceInActCurrency($dPrice)\n    {\n        $oCur = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getActShopCurrencyObject();\n\n        return ((float) $dPrice) * $oCur->rate;\n    }\n\n\n    /**\n     * Sets discount to price\n     *\n     * @param double $dValue discount value\n     * @param string $sType  discount type: abs or %\n     */\n    public function setDiscount($dValue, $sType)\n    {\n        $this->_aDiscounts[] = ['value' => $dValue, 'type' => $sType];\n    }\n\n    /**\n     * Returns assigned discounts\n     *\n     * @return array\n     */\n    public function getDiscounts()\n    {\n        return $this->_aDiscounts;\n    }\n\n    /**\n     * Flush assigned discounts\n     */\n    protected function flushDiscounts()\n    {\n        $this->_aDiscounts = null;\n    }\n\n    /**\n     * Calculates price: affects discounts\n     */\n    public function calculateDiscount()\n    {\n        $dPrice = $this->getPrice();\n        $aDiscounts = $this->getDiscounts();\n\n        if ($aDiscounts) {\n            foreach ($aDiscounts as $aDiscount) {\n                if ($aDiscount['type'] == 'abs') {\n                    $dPrice = $dPrice - $aDiscount['value'];\n                } else {\n                    $dPrice = $dPrice * (100 - $aDiscount['value']) / 100;\n                }\n            }\n            if ($dPrice < 0) {\n                $this->setPrice(0);\n            } else {\n                $this->setPrice($dPrice);\n            }\n\n            $this->flushDiscounts();\n        }\n    }\n}\n"
  },
  {
    "path": "source/Core/PriceList.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Price list class. Deals with a list of oxPrice object.\n * The main reason why we can't just sum oxPrice objects is that they have different VAT percents.\n */\nclass PriceList\n{\n    /**\n     * Array containing oxPrice objects\n     *\n     * @var array\n     */\n    protected $_aList = [];\n\n    /**\n     * Class constructor. The constructor is defined in order to be possible to call parent::__construct() in modules.\n     *\n     * @return null\n     */\n    public function __construct()\n    {\n    }\n\n    /**\n     * Returns Brutto price sum\n     *\n     * @return double\n     */\n    public function getBruttoSum()\n    {\n        $dSum = 0;\n        foreach ($this->_aList as $oPrice) {\n            $dSum += $oPrice->getBruttoPrice();\n        }\n\n        return $dSum;\n    }\n\n    /**\n     * Returns the sum of list Netto prices\n     *\n     * @return double\n     */\n    public function getNettoSum()\n    {\n        $dSum = 0;\n        foreach ($this->_aList as $oPrice) {\n            $dSum += $oPrice->getNettoPrice();\n        }\n\n        return $dSum;\n    }\n\n    /**\n     * Returns the sum of list Netto prices\n     *\n     * @param bool $isNettoMode mode in which calculate sum, default netto\n     *\n     * @return double\n     */\n    public function getSum($isNettoMode = true)\n    {\n        if ($isNettoMode) {\n            return $this->getNettoSum();\n        } else {\n            return $this->getBruttoSum();\n        }\n    }\n\n    /**\n     * Returns VAT values sum separated to different array elements depending on VAT\n     *\n     * @param bool $isNettoMode mode in which calculate sum, default netto\n     *\n     * @return array\n     */\n    public function getVatInfo($isNettoMode = true)\n    {\n        $aVatValues = [];\n        $aPrices = [];\n        foreach ($this->_aList as $oPrice) {\n            $sKey = (string) $oPrice->getVat();\n            if (!isset($aPrices[$sKey])) {\n                $aPrices[$sKey]['sum'] = 0;\n                $aPrices[$sKey]['vat'] = $oPrice->getVat();\n            }\n            $aPrices[$sKey]['sum'] += $oPrice->getPrice();\n        }\n\n        foreach ($aPrices as $sKey => $aPrice) {\n            if ($isNettoMode) {\n                $dPrice = $aPrice['sum'] * $aPrice['vat'] / 100;\n            } else {\n                $dPrice = $aPrice['sum'] * $aPrice['vat'] / (100 + $aPrice['vat']);\n            }\n            $aVatValues[$sKey] = $dPrice;\n        }\n\n        return $aVatValues;\n    }\n\n\n    /**\n     * Return prices separated to different array elements depending on VAT\n     *\n     * @return array\n     */\n    public function getPriceInfo()\n    {\n        $aPrices = [];\n        foreach ($this->_aList as $oPrice) {\n            $sVat = (string) $oPrice->getVat();\n            if (!isset($aPrices[$sVat])) {\n                $aPrices[$sVat] = 0;\n            }\n            $aPrices[$sVat] += $oPrice->getBruttoPrice();\n        }\n\n        return $aPrices;\n    }\n\n    /**\n     * Iterates through applied VATs and fetches VAT for delivery.\n     * If not VAT was applied - default VAT (myConfig->dDefaultVAT) will be used\n     *\n     * @return double\n     */\n    public function getMostUsedVatPercent()\n    {\n        $aPrices = $this->getPriceInfo();\n        if (count($aPrices) == 0) {\n            return;\n        }\n\n        return max(array_keys($aPrices, max($aPrices)));\n    }\n\n    /**\n     * Iterates through applied VATs and calculates proportional VAT\n     *\n     * @return double\n     */\n    public function getProportionalVatPercent()\n    {\n        $dTotalSum = 0;\n\n        foreach ($this->_aList as $oPrice) {\n            $dTotalSum += $oPrice->getNettoPrice();\n        }\n\n        $dProportionalVat = 0;\n\n        foreach ($this->_aList as $oPrice) {\n            if ($dTotalSum > 0) {\n                $dProportionalVat += $oPrice->getNettoPrice() / $dTotalSum * $oPrice->getVat();\n            }\n        }\n\n        return $dProportionalVat;\n    }\n\n\n    /**\n     * Add an oxPrice object to prices array\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Price $oPrice oxprice object\n     */\n    public function addToPriceList($oPrice)\n    {\n        $this->_aList[] = $oPrice;\n    }\n\n    /**\n     * Recalculate price list to one price: sum total value of prices, and calculate VAT\n     *\n     * @return null\n     */\n    public function calculateToPrice()\n    {\n        if (count($this->_aList) == 0) {\n            return;\n        }\n\n        $dNetoTotal = 0;\n        $dVatTotal = 0;\n\n        foreach ($this->_aList as $oPrice) {\n            $dNetoTotal += $oPrice->getNettoPrice();\n            $dVatTotal += $oPrice->getVatValue();\n        }\n\n        $oPrice = oxNew(\\OxidEsales\\Eshop\\Core\\Price::class);\n\n        if ($dNetoTotal) {\n            $dVat = $dVatTotal * 100 / $dNetoTotal;\n\n            $oPrice->setNettoPriceMode();\n            $oPrice->setPrice($dNetoTotal);\n            $oPrice->setVat($dVat);\n        }\n\n        return $oPrice;\n    }\n\n    /**\n     * Return count of added oxPrices\n     *\n     * @return int\n     */\n    public function getCount()\n    {\n        return count($this->_aList);\n    }\n}\n"
  },
  {
    "path": "source/Core/Registry.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\EshopCommunity\\Core\\Autoload\\BackwardsCompatibilityClassMapProvider;\n\n/**\n * Object registry design pattern implementation. Stores the instances of objects\n */\nclass Registry\n{\n    /**\n     * Instance array\n     *\n     * @var array\n     */\n    protected static $instances = [];\n\n    /**\n     * Hold BC class to Unified Namespace class map\n     *\n     * @var null|array\n     */\n    protected static $backwardsCompatibilityClassMap = null;\n\n    /**\n     * Instance getter. Return an existing or new instance for a given class name.\n     * Consider using the getter methods over the generic Registry::get() method.\n     * In order to avoid issues with different shop editions, the given class name must be from the Unified Namespace.\n     *\n     * For reasons of backwards compatibility old class names like 'oxconfig' are still supported and equivalent\n     * to the corresponding class name from the Unified Namespace, as they store and retrieve the same instances.\n     * But be aware, that support for old class names will be dropped in the future.\n     *\n     * @template T\n     * @param class-string<T> $className The class name from the Unified Namespace.\n     * param mixed  ...$args   constructor arguments\n     *\n     * @static\n     *\n     * @return T\n     */\n    public static function get($className)\n    {\n        $key = self::getStorageKey($className);\n\n        return self::getObject($key);\n    }\n\n    /**\n     * Instance setter\n     *\n     * @param string       $className Class name\n     * @param null|object  $instance  Object instance\n     *\n     * @static\n     *\n     * @return null\n     */\n    public static function set($className, $instance)\n    {\n        $key = self::getStorageKey($className);\n\n        if (is_null($instance)) {\n            unset(self::$instances[$key]);\n\n            return;\n        }\n\n        self::$instances[$key] = $instance;\n\n        return;\n    }\n\n    /**\n     * Return an instance of \\OxidEsales\\Eshop\\Core\\Config\n     *\n     * @static\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Config\n     */\n    public static function getConfig()\n    {\n        return self::getObject(\\OxidEsales\\Eshop\\Core\\Config::class);\n    }\n\n    /**\n     * Returns an instance of \\OxidEsales\\Eshop\\Core\\Session\n     *\n     * @static\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Session\n     */\n    public static function getSession()\n    {\n        return self::getObject(\\OxidEsales\\Eshop\\Core\\Session::class);\n    }\n\n    /**\n     * Returns an instance of \\OxidEsales\\Eshop\\Core\\Language\n     *\n     * @static\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Language\n     */\n    public static function getLang()\n    {\n        return self::getObject(\\OxidEsales\\Eshop\\Core\\Language::class);\n    }\n\n    /**\n     * Returns an instance of \\OxidEsales\\Eshop\\Core\\Utils\n     *\n     * @static\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Utils\n     */\n    public static function getUtils()\n    {\n        return self::getObject(\\OxidEsales\\Eshop\\Core\\Utils::class);\n    }\n\n    /**\n     * Returns an instance of OxidEsales\\Eshop\\Core\\UtilsObject\n     *\n     * @static\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\UtilsObject\n     */\n    public static function getUtilsObject()\n    {\n        return \\OxidEsales\\Eshop\\Core\\UtilsObject::getInstance();\n    }\n\n    /**\n     * Return an instance of \\OxidEsales\\Eshop\\Core\\InputValidator\n     *\n     * @static\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\InputValidator\n     */\n    public static function getInputValidator()\n    {\n        return self::getObject(\\OxidEsales\\Eshop\\Core\\InputValidator::class);\n    }\n\n    /**\n     * Return an instance of \\OxidEsales\\Eshop\\Core\\PictureHandler\n     *\n     * @static\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\PictureHandler\n     */\n    public static function getPictureHandler()\n    {\n        return self::getObject(\\OxidEsales\\Eshop\\Core\\PictureHandler::class);\n    }\n\n    /**\n     * Return an instance of \\OxidEsales\\Eshop\\Core\\Request\n     *\n     * @static\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Request\n     */\n    public static function getRequest()\n    {\n        return self::getObject(\\OxidEsales\\Eshop\\Core\\Request::class);\n    }\n\n    /**\n     * Return an instance of \\OxidEsales\\Eshop\\Core\\SeoEncoder\n     *\n     * @static\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\SeoEncoder\n     */\n    public static function getSeoEncoder()\n    {\n        return self::getObject(\\OxidEsales\\Eshop\\Core\\SeoEncoder::class);\n    }\n\n    /**\n     * Return an instance of \\OxidEsales\\Eshop\\Core\\SeoDecoder\n     *\n     * @static\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\SeoDecoder\n     */\n    public static function getSeoDecoder()\n    {\n        return self::getObject(\\OxidEsales\\Eshop\\Core\\SeoDecoder::class);\n    }\n\n    /**\n     * Return an instance of \\OxidEsales\\Eshop\\Core\\UtilsCount\n     *\n     * @static\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\UtilsCount\n     */\n    public static function getUtilsCount()\n    {\n        return self::getObject(\\OxidEsales\\Eshop\\Core\\UtilsCount::class);\n    }\n\n    /**\n     * Return an instance of \\OxidEsales\\Eshop\\Core\\UtilsDate\n     *\n     * @static\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\UtilsDate\n     */\n    public static function getUtilsDate()\n    {\n        return self::getObject(\\OxidEsales\\Eshop\\Core\\UtilsDate::class);\n    }\n\n    /**\n     * Return an instance of \\OxidEsales\\Eshop\\Core\\UtilsFile\n     *\n     * @static\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\UtilsFile\n     */\n    public static function getUtilsFile()\n    {\n        return self::getObject(\\OxidEsales\\Eshop\\Core\\UtilsFile::class);\n    }\n\n    /**\n     * Return an instance of \\OxidEsales\\Eshop\\Core\\UtilsPic\n     *\n     * @static\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\UtilsPic\n     */\n    public static function getUtilsPic()\n    {\n        return self::getObject(\\OxidEsales\\Eshop\\Core\\UtilsPic::class);\n    }\n\n    /**\n     * Return an instance of \\OxidEsales\\Eshop\\Core\\UtilsServer\n     *\n     * @static\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\UtilsServer\n     */\n    public static function getUtilsServer()\n    {\n        return self::getObject(\\OxidEsales\\Eshop\\Core\\UtilsServer::class);\n    }\n\n    /**\n     * Return an instance of \\OxidEsales\\Eshop\\Core\\UtilsString\n     *\n     * @static\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\UtilsString\n     */\n    public static function getUtilsString()\n    {\n        return self::getObject(\\OxidEsales\\Eshop\\Core\\UtilsString::class);\n    }\n\n    /**\n     * Return an instance of \\OxidEsales\\Eshop\\Core\\UtilsUrl\n     *\n     * @static\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\UtilsUrl\n     */\n    public static function getUtilsUrl()\n    {\n        return self::getObject(\\OxidEsales\\Eshop\\Core\\UtilsUrl::class);\n    }\n\n    /**\n     * Return an instance of \\OxidEsales\\Eshop\\Core\\UtilsXml\n     *\n     * @static\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\UtilsXml\n     */\n    public static function getUtilsXml()\n    {\n        return self::getObject(\\OxidEsales\\Eshop\\Core\\UtilsXml::class);\n    }\n\n    /**\n     * Return an instance of \\OxidEsales\\Eshop\\Core\\UtilsView\n     *\n     * @static\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\UtilsView\n     */\n    public static function getUtilsView()\n    {\n        return self::getObject(\\OxidEsales\\Eshop\\Core\\UtilsView::class);\n    }\n\n    /**\n     * Return an instance of \\OxidEsales\\Eshop\\Core\\Routing\\ControllerClassNameResolver\n     *\n     * @static\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Routing\\ControllerClassNameResolver\n     */\n    public static function getControllerClassNameResolver()\n    {\n        return self::getObject(\\OxidEsales\\Eshop\\Core\\Routing\\ControllerClassNameResolver::class);\n    }\n\n    /**\n     * Returns Logger\n     *\n     * @static\n     * @return \\Psr\\Log\\LoggerInterface\n     */\n    public static function getLogger()\n    {\n        if (!self::instanceExists('logger')) {\n            self::set('logger', getLogger());\n        }\n        return self::get('logger');\n    }\n\n    /**\n     * Return all class instances, which are currently set in the registry\n     *\n     * @return array\n     */\n    public static function getKeys()\n    {\n        return array_keys(self::$instances);\n    }\n\n    /**\n     * Check if an instance of a given class is set in the registry\n     *\n     * @param string $className\n     *\n     * @return bool\n     */\n    public static function instanceExists($className)\n    {\n        $key = self::getStorageKey($className);\n\n        return isset(self::$instances[$key]);\n    }\n\n    /**\n     * Get backwardsCompatibilityClassMap\n     *\n     * @return array\n     */\n    public static function getBackwardsCompatibilityClassMap()\n    {\n        if (is_null(self::$backwardsCompatibilityClassMap)) {\n            $classMap = (new BackwardsCompatibilityClassMapProvider())->getMap();\n            self::$backwardsCompatibilityClassMap = $classMap;\n        }\n\n        return self::$backwardsCompatibilityClassMap;\n    }\n\n    /**\n     * Translate a given old class name like 'oxconfig' into a storage key as known by the Registry.\n     * If a new class name is used, the method just returns it as it is.\n     *\n     * @param string $className Class name to be converted.\n     *\n     * @return string\n     */\n    public static function getStorageKey($className)\n    {\n        $key = $className;\n\n        if (!\\OxidEsales\\Eshop\\Core\\NamespaceInformationProvider::isNamespacedClass($className)) {\n            $bcMap = self::getBackwardsCompatibilityClassMap();\n            $key = isset($bcMap[strtolower($key)]) ? $bcMap[strtolower($key)] : strtolower($key);\n        }\n\n        return $key;\n    }\n\n    /**\n     * Special case handling: The recommended way to get an instance of UtilsObject is to use Registry::getUtilsObject\n     * IMPORTANT: UtilsObject is not delivered from Registry::instances this way, so Registry::set\n     *            will have no effect on which UtilsObject is delivered.\n     *            Also Registry::instanceExists will always return false for UtilsObject.\n     * This does only affect BC class name and unified namespace class names, not the edition own classes atm.\n     *\n     * @param string $className Class name.\n     *\n     * @return object\n     */\n    protected static function createObject($className)\n    {\n        if (('oxutilsobject' === strtolower($className)) || \\OxidEsales\\Eshop\\Core\\UtilsObject::class === $className) {\n            $object = \\OxidEsales\\Eshop\\Core\\UtilsObject::getInstance();\n        } else {\n            $object = \\oxNew($className);\n        }\n\n        return $object;\n    }\n\n    /**\n     * Return a well known object from the registry\n     *\n     * @template T\n     * @param class-string<T> $className A unified namespace class name\n     * param mixed  ...$args   constructor arguments\n     *\n     * @static\n     *\n     * @return T\n     */\n    protected static function getObject($className)\n    {\n        if (!isset(self::$instances[$className])) {\n            self::$instances[$className] = self::createObject($className);\n        }\n\n        return self::$instances[$className];\n    }\n}\n"
  },
  {
    "path": "source/Core/Request.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Str;\n\n/**\n * Request represents an HTTP request.\n */\nclass Request\n{\n    /**\n     * @deprecated use RequestInterface::get() instead\n     *\n     * Returns raw value of parameter stored in POST,GET.\n     *\n     * @param string $name         Name of parameter.\n     * @param string $defaultValue Default value if no value provided.\n     *\n     * @return mixed\n     */\n    public function getRequestParameter($name, $defaultValue = null)\n    {\n        if (isset($_POST[$name])) {\n            $value = $_POST[$name];\n        } elseif (isset($_GET[$name])) {\n            $value = $_GET[$name];\n        } else {\n            $value = $defaultValue;\n        }\n\n        return $value;\n    }\n\n    /**\n     * Returns escaped value of parameter stored in POST,GET.\n     *\n     * @param string $name         Name of parameter.\n     * @param string $defaultValue Default value if no value provided.\n     *\n     * @return mixed\n     */\n    public function getRequestEscapedParameter($name, $defaultValue = null)\n    {\n        $value = $this->getRequestParameter($name, $defaultValue);\n\n        // TODO: remove this after special chars concept implementation\n        $isAdmin = Registry::getConfig()->isAdmin() && Registry::getSession()->getVariable(\"blIsAdmin\");\n        if ($value !== null && !$isAdmin) {\n            $this->checkParamSpecialChars($value);\n        }\n\n        return $value;\n    }\n\n    /**\n     * Returns request url, which was executed to render current page view\n     *\n     * @param string $sParams     Parameters to object\n     * @param bool   $blReturnUrl If return url\n     *\n     * @return string\n     */\n    public function getRequestUrl($sParams = '', $blReturnUrl = false)\n    {\n        $requestUrl = '';\n        if (!isset($_SERVER[\"REQUEST_METHOD\"]) || $_SERVER[\"REQUEST_METHOD\"] != \"POST\") {\n            if (isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI']) {\n                $rawRequestUrl = $_SERVER['REQUEST_URI'];\n            } else {\n                $rawRequestUrl = $_SERVER['SCRIPT_URI'] ?? '';\n            }\n\n            // trying to resolve controller file name\n            if ($rawRequestUrl && ($iPos = stripos($rawRequestUrl, '?')) !== false) {\n                $string = Str::getStr();\n                // formatting request url\n                $requestUrl = 'index.php' . $string->substr($rawRequestUrl, $iPos);\n\n                // removing possible session id\n                $requestUrl = $string->preg_replace('/(&|\\?)(force_)?(admin_)?sid=[^&]*&?/', '$1', $requestUrl);\n                $requestUrl = $string->preg_replace('/(&|\\?)stoken=[^&]*&?/', '$1', $requestUrl);\n                $requestUrl = $string->preg_replace('/&$/', '', $requestUrl);\n                $requestUrl = str_replace('&', '&amp;', $requestUrl);\n            }\n        }\n\n        return $requestUrl;\n    }\n\n    /**\n     * Checks if passed parameter has special chars and replaces them.\n     * Returns checked value.\n     *\n     * @param mixed $sValue value to process escaping\n     * @param array $aRaw   keys of unescaped values\n     *\n     * @return mixed\n     */\n    public function checkParamSpecialChars(&$sValue, $aRaw = null)\n    {\n        if (is_object($sValue)) {\n            return $sValue;\n        }\n\n        if (is_array($sValue)) {\n            $newValue = [];\n            foreach ($sValue as $sKey => $sVal) {\n                $sValidKey = $sKey;\n                if (!$aRaw || !in_array($sKey, $aRaw)) {\n                    $this->checkParamSpecialChars($sValidKey);\n                    $this->checkParamSpecialChars($sVal);\n                    if ($sValidKey != $sKey) {\n                        unset($sValue[$sKey]);\n                    }\n                }\n                $newValue[$sValidKey] = $sVal;\n            }\n            $sValue = $newValue;\n        } elseif (is_string($sValue)) {\n            $sValue = str_replace(\n                ['&', '<', '>', '\"', \"'\", chr(0), '\\\\', \"\\n\", \"\\r\"],\n                ['&amp;', '&lt;', '&gt;', '&quot;', '&#039;', '', '&#092;', '&#10;', '&#13;'],\n                $sValue\n            );\n        }\n\n        return $sValue;\n    }\n}\n"
  },
  {
    "path": "source/Core/Routing/ControllerClassNameResolver.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Routing;\n\nuse OxidEsales\\Eshop\\Core\\Contract\\ClassNameResolverInterface;\nuse OxidEsales\\Eshop\\Core\\Contract\\ControllerMapProviderInterface;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ActiveModulesDataProviderBridgeInterface;\n\n/**\n * This class maps controller ID to controller class name and vice versa.\n *\n * @internal Do not make a module extension for this class.\n */\nclass ControllerClassNameResolver implements ClassNameResolverInterface\n{\n    public function __construct(\n        private ?ControllerMapProviderInterface $shopControllerMapProvider = null,\n        private ?ActiveModulesDataProviderBridgeInterface $activeModulesDataProvider = null\n    ) {\n    }\n\n    /**\n     * Map argument classId to related className.\n     *\n     * @param string $classId\n     *\n     * @return string|null\n     */\n    public function getClassNameById($classId)\n    {\n        return $this->getClassNameFromContainerParametersMap($classId)\n            ?: $this->getClassNameFromShopMap($classId)\n            ?: $this->getClassNameFromModuleMap($classId);\n    }\n\n    /**\n     * Map argument className to related classId.\n     *\n     * @param string $className\n     *\n     * @return string|null\n     */\n    public function getIdByClassName($className)\n    {\n        return $this->getClassIdFromContainerParametersMap($className)\n            ?: $this->getClassIdFromShopMap($className)\n            ?: $this->getClassIdFromModuleMap($className);\n    }\n\n    /**\n     * Get class name from shop controller provider.\n     *\n     * @param string $classId\n     *\n     * @return string|null\n     */\n    protected function getClassNameFromShopMap($classId)\n    {\n        $idToNameMap = $this->getShopControllerMapProvider()->getControllerMap();\n\n        return $this->arrayLookup($classId, $idToNameMap);\n    }\n\n    /**\n     * Get class name from module controller provider.\n     *\n     * @param string $classId\n     *\n     * @return string|null\n     */\n    protected function getClassNameFromModuleMap($classId)\n    {\n        $controllersArray = [];\n        foreach ($this->getActiveModulesDataProvider()->getControllers() as $controller) {\n            $controllersArray[$controller->getId()] = $controller->getControllerClassNameSpace();\n        }\n\n        return $this->arrayLookup($classId, $controllersArray);\n    }\n\n    /**\n     * Get class id from shop controller provider.\n     *\n     * @param string $className\n     *\n     * @return string|null\n     */\n    protected function getClassIdFromShopMap($className)\n    {\n        $idToNameMap = $this->getShopControllerMapProvider()->getControllerMap();\n\n        return $this->arrayLookup($className, array_flip($idToNameMap));\n    }\n\n    /**\n     * Get class id from module controller provider.\n     *\n     * @param string $className\n     *\n     * @return string|null\n     */\n    protected function getClassIdFromModuleMap($className)\n    {\n        $controllersArray = [];\n        foreach ($this->getActiveModulesDataProvider()->getControllers() as $controller) {\n            $controllersArray[$controller->getId()] = $controller->getControllerClassNameSpace();\n        }\n\n        return $this->arrayLookup($className, array_flip($controllersArray));\n    }\n\n    /**\n     * @param string $key\n     * @param array  $keys2Values\n     *\n     * @return string|null\n     */\n    protected function arrayLookup($key, $keys2Values)\n    {\n        $keys2Values = array_change_key_case($keys2Values);\n        $key = strtolower($key);\n\n        return $keys2Values[$key] ?? null;\n    }\n\n    /**\n     * Getter for ShopControllerMapProvider object\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Routing\\ShopControllerMapProvider\n     */\n    protected function getShopControllerMapProvider()\n    {\n        if ($this->shopControllerMapProvider === null) {\n            $this->shopControllerMapProvider = oxNew(\\OxidEsales\\Eshop\\Core\\Routing\\ShopControllerMapProvider::class);\n        }\n\n        return $this->shopControllerMapProvider;\n    }\n\n    private function getActiveModulesDataProvider(): ActiveModulesDataProviderBridgeInterface\n    {\n        if ($this->activeModulesDataProvider === null) {\n            $this->activeModulesDataProvider = ContainerFacade::get(ActiveModulesDataProviderBridgeInterface::class);\n        }\n\n        return $this->activeModulesDataProvider;\n    }\n\n    private function getClassNameFromContainerParametersMap(string $classId): ?string\n    {\n        return ContainerFacade::getParameter('oxid.view_controllers_map')[$classId] ?? null;\n    }\n\n    private function getClassIdFromContainerParametersMap(string $className): false|string\n    {\n        return array_search(\n            $className,\n            ContainerFacade::getParameter('oxid.view_controllers_map'),\n            true\n        );\n    }\n}\n"
  },
  {
    "path": "source/Core/Routing/ShopControllerMapProvider.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Routing;\n\nuse OxidEsales\\Eshop\\Application\\Component\\Widget as Widget;\nuse OxidEsales\\Eshop\\Application\\Controller as Controller;\nuse OxidEsales\\Eshop\\Core\\Contract\\ControllerMapProviderInterface;\nuse OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticlePicturesAjax;\n\n/**\n * This class determines the controllers which should be allowed to be called directly via\n * HTTP GET/POST Parameters, inside form actions or with oxid_include_widget.\n * Those controllers are specified e.g. inside a form action with a controller key which is mapped to its class.\n *\n * @internal Do not make a module extension for this class.\n */\nclass ShopControllerMapProvider implements ControllerMapProviderInterface\n{\n    private $controllerMap = [\n        'language' => Controller\\Admin\\LanguageController::class,\n        'module' => Controller\\Admin\\ModuleController::class,\n        'theme' => Controller\\Admin\\ThemeController::class,\n        'manufacturerlist' => Controller\\ManufacturerListController::class,\n        'review' => Controller\\ReviewController::class,\n        'vendorlist' => Controller\\VendorListController::class,\n        'actions' => Controller\\Admin\\ActionsController::class,\n        'account_user' => Controller\\AccountUserController::class,\n        'account_wishlist' => Controller\\AccountWishlistController::class,\n        'vendor_main' => Controller\\Admin\\VendorMain::class,\n        'vendor_main_ajax' => Controller\\Admin\\VendorMainAjax::class,\n        'vendor_seo' => Controller\\Admin\\VendorSeo::class,\n        'voucherserie' => Controller\\Admin\\VoucherSerieController::class,\n        'voucherserie_export' => Controller\\Admin\\VoucherSerieExport::class,\n        'voucherserie_generate' => Controller\\Admin\\VoucherSerieGenerate::class,\n        'voucherserie_groups' => Controller\\Admin\\VoucherSerieGroups::class,\n        'voucherserie_groups_ajax' => Controller\\Admin\\VoucherSerieGroupsAjax::class,\n        'voucherserie_list' => Controller\\Admin\\VoucherSerieList::class,\n        'voucherserie_main' => Controller\\Admin\\VoucherSerieMain::class,\n        'wrapping_list' => Controller\\Admin\\WrappingList::class,\n        'wrapping_main' => Controller\\Admin\\WrappingMain::class,\n        'genexport' => Controller\\Admin\\GenericExport::class,\n        'genexport_do' => Controller\\Admin\\GenericExportDo::class,\n        'genexport_main' => Controller\\Admin\\GenericExportMain::class,\n        'genimport' => Controller\\Admin\\GenericImport::class,\n        'language_list' => Controller\\Admin\\LanguageList::class,\n        'language_main' => Controller\\Admin\\LanguageMain::class,\n        'list_review' => Controller\\Admin\\ListReview::class,\n        'list_user' => Controller\\Admin\\ListUser::class,\n        'login' => Controller\\Admin\\LoginController::class,\n        'manufacturer' => Controller\\Admin\\ManufacturerController::class,\n        'manufacturer_list' => Controller\\Admin\\ManufacturerList::class,\n        'manufacturer_main' => Controller\\Admin\\ManufacturerMain::class,\n        'manufacturer_main_ajax' => Controller\\Admin\\ManufacturerMainAjax::class,\n        'manufacturer_picture' => Controller\\Admin\\ManufacturerPicture::class,\n        'manufacturer_seo' => Controller\\Admin\\ManufacturerSeo::class,\n        'module_config' => Controller\\Admin\\ModuleConfiguration::class,\n        'module_list' => Controller\\Admin\\ModuleList::class,\n        'module_main' => Controller\\Admin\\ModuleMain::class,\n        'module_sortlist' => Controller\\Admin\\ModuleSortList::class,\n        'navigation' => Controller\\Admin\\NavigationController::class,\n        'order_address' => Controller\\Admin\\OrderAddress::class,\n        'order_article' => Controller\\Admin\\OrderArticle::class,\n        'order_downloads' => Controller\\Admin\\OrderDownloads::class,\n        'order_main' => Controller\\Admin\\OrderMain::class,\n        'order_overview' => Controller\\Admin\\OrderOverview::class,\n        'order_remark' => Controller\\Admin\\OrderRemark::class,\n        'oxnavigationtree' => Controller\\Admin\\NavigationTree::class,\n        'payment_country' => Controller\\Admin\\PaymentCountry::class,\n        'payment_country_ajax' => Controller\\Admin\\PaymentCountryAjax::class,\n        'payment_list' => Controller\\Admin\\PaymentList::class,\n        'payment_main' => Controller\\Admin\\PaymentMain::class,\n        'payment_main_ajax' => Controller\\Admin\\PaymentMainAjax::class,\n        'payment_rdfa' => Controller\\Admin\\PaymentRdfa::class,\n        'pricealarm_list' => Controller\\Admin\\PriceAlarmList::class,\n        'pricealarm_mail' => Controller\\Admin\\PriceAlarmMail::class,\n        'pricealarm_main' => Controller\\Admin\\PriceAlarmMain::class,\n        'pricealarm_send' => Controller\\Admin\\PriceAlarmSend::class,\n        'selectlist' => Controller\\Admin\\SelectListController::class,\n        'selectlist_list' => Controller\\Admin\\SelectListList::class,\n        'selectlist_main' => Controller\\Admin\\SelectListMain::class,\n        'selectlist_main_ajax' => Controller\\Admin\\SelectListMainAjax::class,\n        'selectlist_order_ajax' => Controller\\Admin\\SelectListOrderAjax::class,\n        'shop' => Controller\\Admin\\ShopController::class,\n        'shop_default_category_ajax' => Controller\\Admin\\ShopDefaultCategoryAjax::class,\n        'shop_license' => Controller\\Admin\\ShopLicense::class,\n        'shop_list' => Controller\\Admin\\ShopList::class,\n        'shop_main' => Controller\\Admin\\ShopMain::class,\n        'shop_performance' => Controller\\Admin\\ShopPerformance::class,\n        'shop_rdfa' => Controller\\Admin\\ShopRdfa::class,\n        'shop_seo' => Controller\\Admin\\ShopSeo::class,\n        'shop_system' => Controller\\Admin\\ShopSystem::class,\n        'sysreq' => Controller\\Admin\\SystemRequirements::class,\n        'sysreq_list' => Controller\\Admin\\SystemRequirementsList::class,\n        'sysreq_main' => Controller\\Admin\\SystemRequirementsMain::class,\n        'systeminfo' => Controller\\Admin\\SystemInfoController::class,\n        'theme_config' => Controller\\Admin\\ThemeConfiguration::class,\n        'theme_list' => Controller\\Admin\\ThemeList::class,\n        'theme_main' => Controller\\Admin\\ThemeMain::class,\n        'tools' => Controller\\Admin\\ToolsController::class,\n        'tools_list' => Controller\\Admin\\ToolsList::class,\n        'tools_main' => Controller\\Admin\\ToolsMain::class,\n        'user_address' => Controller\\Admin\\UserAddress::class,\n        'user_article' => Controller\\Admin\\UserArticle::class,\n        'user_extend' => Controller\\Admin\\UserExtend::class,\n        'user_main' => Controller\\Admin\\UserMain::class,\n        'user_main_ajax' => Controller\\Admin\\UserMainAjax::class,\n        'user_overview' => Controller\\Admin\\UserOverview::class,\n        'user_payment' => Controller\\Admin\\UserPayment::class,\n        'user_remark' => Controller\\Admin\\UserRemark::class,\n        'usergroup' => Controller\\Admin\\UserGroupController::class,\n        'usergroup_list' => Controller\\Admin\\UserGroupList::class,\n        'usergroup_main' => Controller\\Admin\\UserGroupMain::class,\n        'usergroup_main_ajax' => Controller\\Admin\\UserGroupMainAjax::class,\n        'vendor' => Controller\\Admin\\VendorController::class,\n        'vendor_list' => Controller\\Admin\\VendorList::class,\n        'actions_article_ajax' => Controller\\Admin\\ActionsArticleAjax::class,\n        'actions_groups_ajax' => Controller\\Admin\\ActionsGroupsAjax::class,\n        'actions_list' => Controller\\Admin\\ActionsList::class,\n        'actions_main' => Controller\\Admin\\ActionsMain::class,\n        'actions_main_ajax' => Controller\\Admin\\ActionsMainAjax::class,\n        'actions_order_ajax' => Controller\\Admin\\ActionsOrderAjax::class,\n        'admin_content' => Controller\\Admin\\AdminContent::class,\n        'admin_links' => Controller\\Admin\\AdminLinks::class,\n        'admin_newsletter' => Controller\\Admin\\AdminNewsletter::class,\n        'admin_order' => Controller\\Admin\\AdminOrder::class,\n        'admin_payment' => Controller\\Admin\\AdminPayment::class,\n        'admin_pricealarm' => Controller\\Admin\\AdminPriceAlarm::class,\n        'admin_start' => Controller\\Admin\\AdminStart::class,\n        'admin_user' => Controller\\Admin\\AdminUser::class,\n        'admin_wrapping' => Controller\\Admin\\AdminWrapping::class,\n        'adminlinks_list' => Controller\\Admin\\AdminlinksList::class,\n        'adminlinks_main' => Controller\\Admin\\AdminlinksMain::class,\n        'article' => Controller\\Admin\\ArticleController::class,\n        'article_accessories_ajax' => Controller\\Admin\\ArticleAccessoriesAjax::class,\n        'article_attribute' => Controller\\Admin\\ArticleAttribute::class,\n        'article_attribute_ajax' => Controller\\Admin\\ArticleAttributeAjax::class,\n        'article_bundle_ajax' => Controller\\Admin\\ArticleBundleAjax::class,\n        'article_crossselling' => Controller\\Admin\\ArticleCrossselling::class,\n        'article_crossselling_ajax' => Controller\\Admin\\ArticleCrosssellingAjax::class,\n        'article_extend' => Controller\\Admin\\ArticleExtend::class,\n        'article_extend_ajax' => Controller\\Admin\\ArticleExtendAjax::class,\n        'article_files' => Controller\\Admin\\ArticleFiles::class,\n        'article_main' => Controller\\Admin\\ArticleMain::class,\n        'article_overview' => Controller\\Admin\\ArticleOverview::class,\n        'article_pictures' => Controller\\Admin\\ArticlePictures::class,\n        'article_pictures_ajax' => ArticlePicturesAjax::class,\n        'article_review' => Controller\\Admin\\ArticleReview::class,\n        'article_selection_ajax' => Controller\\Admin\\ArticleSelectionAjax::class,\n        'article_seo' => Controller\\Admin\\ArticleSeo::class,\n        'article_stock' => Controller\\Admin\\ArticleStock::class,\n        'article_userdef' => Controller\\Admin\\ArticleUserdef::class,\n        'article_variant' => Controller\\Admin\\ArticleVariant::class,\n        'attribute' => Controller\\Admin\\AttributeController::class,\n        'attribute_category' => Controller\\Admin\\AttributeCategory::class,\n        'attribute_category_ajax' => Controller\\Admin\\AttributeCategoryAjax::class,\n        'attribute_list' => Controller\\Admin\\AttributeList::class,\n        'attribute_main' => Controller\\Admin\\AttributeMain::class,\n        'attribute_main_ajax' => Controller\\Admin\\AttributeMainAjax::class,\n        'attribute_order_ajax' => Controller\\Admin\\AttributeOrderAjax::class,\n        'category' => Controller\\Admin\\CategoryController::class,\n        'category_list' => Controller\\Admin\\CategoryList::class,\n        'category_main' => Controller\\Admin\\CategoryMain::class,\n        'category_main_ajax' => Controller\\Admin\\CategoryMainAjax::class,\n        'category_order' => Controller\\Admin\\CategoryOrder::class,\n        'category_order_ajax' => Controller\\Admin\\CategoryOrderAjax::class,\n        'category_pictures' => Controller\\Admin\\CategoryPictures::class,\n        'category_seo' => Controller\\Admin\\CategorySeo::class,\n        'category_text' => Controller\\Admin\\CategoryText::class,\n        'category_update' => Controller\\Admin\\CategoryUpdate::class,\n        'content_list' => Controller\\Admin\\ContentList::class,\n        'content_main' => Controller\\Admin\\ContentMain::class,\n        'content_seo' => Controller\\Admin\\ContentSeo::class,\n        'country' => Controller\\Admin\\CountryController::class,\n        'country_list' => Controller\\Admin\\CountryList::class,\n        'country_main' => Controller\\Admin\\CountryMain::class,\n        'delivery' => Controller\\Admin\\DeliveryController::class,\n        'delivery_articles' => Controller\\Admin\\DeliveryArticles::class,\n        'delivery_articles_ajax' => Controller\\Admin\\DeliveryArticlesAjax::class,\n        'delivery_categories_ajax' => Controller\\Admin\\DeliveryCategoriesAjax::class,\n        'delivery_groups_ajax' => Controller\\Admin\\DeliveryGroupsAjax::class,\n        'delivery_list' => Controller\\Admin\\DeliveryList::class,\n        'delivery_main' => Controller\\Admin\\DeliveryMain::class,\n        'delivery_main_ajax' => Controller\\Admin\\DeliveryMainAjax::class,\n        'delivery_users' => Controller\\Admin\\DeliveryUsers::class,\n        'delivery_users_ajax' => Controller\\Admin\\DeliveryUsersAjax::class,\n        'deliveryset' => Controller\\Admin\\DeliverySetController::class,\n        'deliveryset_country_ajax' => Controller\\Admin\\DeliverySetCountryAjax::class,\n        'deliveryset_groups_ajax' => Controller\\Admin\\DeliverySetGroupsAjax::class,\n        'deliveryset_list' => Controller\\Admin\\DeliverySetList::class,\n        'deliveryset_main' => Controller\\Admin\\DeliverySetMain::class,\n        'deliveryset_main_ajax' => Controller\\Admin\\DeliverySetMainAjax::class,\n        'deliveryset_payment' => Controller\\Admin\\DeliverySetPayment::class,\n        'deliveryset_payment_ajax' => Controller\\Admin\\DeliverySetPaymentAjax::class,\n        'deliveryset_rdfa' => Controller\\Admin\\DeliverySetRdfa::class,\n        'deliveryset_users' => Controller\\Admin\\DeliverySetUsers::class,\n        'deliveryset_users_ajax' => Controller\\Admin\\DeliverySetUsersAjax::class,\n        'diagnostics' => Controller\\Admin\\DiagnosticsController::class,\n        'diagnostics_list' => Controller\\Admin\\DiagnosticsList::class,\n        'diagnostics_main' => Controller\\Admin\\DiagnosticsMain::class,\n        'discount' => Controller\\Admin\\DiscountController::class,\n        'discount_articles' => Controller\\Admin\\DiscountArticles::class,\n        'discount_articles_ajax' => Controller\\Admin\\DiscountArticlesAjax::class,\n        'discount_categories_ajax' => Controller\\Admin\\DiscountCategoriesAjax::class,\n        'discount_groups_ajax' => Controller\\Admin\\DiscountGroupsAjax::class,\n        'discount_item_ajax' => Controller\\Admin\\DiscountItemAjax::class,\n        'discount_list' => Controller\\Admin\\DiscountList::class,\n        'discount_main' => Controller\\Admin\\DiscountMain::class,\n        'discount_main_ajax' => Controller\\Admin\\DiscountMainAjax::class,\n        'discount_users' => Controller\\Admin\\DiscountUsers::class,\n        'discount_users_ajax' => Controller\\Admin\\DiscountUsersAjax::class,\n        'content' => Controller\\ContentController::class,\n        'links' => Controller\\LinksController::class,\n        'newsletter' => Controller\\NewsletterController::class,\n        'pricealarm' => Controller\\PriceAlarmController::class,\n        'account_noticelist' => Controller\\AccountNoticeListController::class,\n        'oxadminlist' => Controller\\Admin\\AdminListController::class,\n        'order_list' => Controller\\Admin\\OrderList::class,\n        'user_list' => Controller\\Admin\\UserList::class,\n        'details' => Controller\\ArticleDetailsController::class,\n        'oxubase' => Controller\\FrontendController::class,\n        'alist' => Controller\\ArticleListController::class,\n        'user' => Controller\\UserController::class,\n        'account' => Controller\\AccountController::class,\n        'basket' => Controller\\BasketController::class,\n        'compare' => Controller\\CompareController::class,\n        'order' => Controller\\OrderController::class,\n        'payment' => Controller\\PaymentController::class,\n        'recommlist' => Controller\\RecommListController::class,\n        'search' => Controller\\SearchController::class,\n        'start' => Controller\\StartController::class,\n        'thankyou' => Controller\\ThankYouController::class,\n        'wishlist' => Controller\\WishListController::class,\n        'wrapping' => Controller\\WrappingController::class,\n        'oxstart' => Controller\\OxidStartController::class,\n        'dynexportbase' => Controller\\Admin\\DynamicExportBaseController::class,\n        'genimport_main' => Controller\\Admin\\GenericImportMain::class,\n        'object_seo' => Controller\\Admin\\ObjectSeo::class,\n        'shop_config' => Controller\\Admin\\ShopConfiguration::class,\n        'clearcookies' => Controller\\ClearCookiesController::class,\n        'account_order' => Controller\\AccountOrderController::class,\n        'account_downloads' => Controller\\AccountDownloadsController::class,\n        'account_newsletter' => Controller\\AccountNewsletterController::class,\n        'account_password' => Controller\\AccountPasswordController::class,\n        'account_recommlist' => Controller\\AccountRecommlistController::class,\n        'account_reviewlist' => Controller\\AccountReviewController::class,\n        'contact' => Controller\\ContactController::class,\n        'credits' => Controller\\CreditsController::class,\n        'download' => Controller\\DownloadController::class,\n        'exceptionerror' => Controller\\ExceptionErrorController::class,\n        'forgotpwd' => Controller\\ForgotPasswordController::class,\n        'invite' => Controller\\InviteController::class,\n        'moredetails' => Controller\\MoreDetailsController::class,\n        'recommadd' => Controller\\RecommendationAddController::class,\n        'register' => Controller\\RegisterController::class,\n        'tpl' => Controller\\TemplateController::class,\n        'oxwactions' => Widget\\Actions::class,\n        'oxwcategorytree' => Widget\\CategoryTree::class,\n        'oxwinformation' => Widget\\Information::class,\n        'oxwrating' => Widget\\Rating::class,\n        'oxwservicemenu' => Widget\\ServiceMenu::class,\n        'oxwarticlebox' => Widget\\ArticleBox::class,\n        'oxwcookienote' => Widget\\CookieNote::class,\n        'oxwlanguagelist' => Widget\\LanguageList::class,\n        'oxwrecommendation' => Widget\\Recommendation::class,\n        'oxwarticledetails' => Widget\\ArticleDetails::class,\n        'oxwcurrencylist' => Widget\\CurrencyList::class,\n        'oxwmanufacturerlist' => Widget\\ManufacturerList::class,\n        'oxwreview' => Widget\\Review::class,\n        'oxwvendorlist' => Widget\\VendorList::class,\n        'oxwidget' => Widget\\WidgetController::class,\n        'oxwminibasket' => Widget\\MiniBasket::class,\n        'oxwservicelist' => Widget\\ServiceList::class,\n        'oxadminview' => Controller\\Admin\\AdminController::class,\n        'oxadmindetails' => Controller\\Admin\\AdminDetailsController::class,\n        'article_list' => Controller\\Admin\\ArticleList::class,\n        'ajaxlistcomponent' => Controller\\Admin\\ListComponentAjax::class,\n    ];\n\n    /**\n     * All available controller maps will be merged together like this: CE <- PE <- EE\n     *\n     * @return array Edition specific mapping of controller keys to classes\n     */\n    public function getControllerMap()\n    {\n        return $this->controllerMap;\n    }\n}\n"
  },
  {
    "path": "source/Core/SeoDecoder.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Seo encoder base\n */\nclass SeoDecoder extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * _parseStdUrl parses given url into array of params\n     *\n     * @param string $sUrl given url\n     *\n     * @access protected\n     * @return array\n     */\n    public function parseStdUrl($sUrl)\n    {\n        $oStr = Str::getStr();\n        $aRet = [];\n        $sUrl = $oStr->html_entity_decode($sUrl);\n\n        if (($iPos = strpos($sUrl, '?')) !== false) {\n            parse_str($oStr->substr($sUrl, $iPos + 1), $aRet);\n        }\n\n        return $aRet;\n    }\n\n    /**\n     * Returns ident (md5 of seo url) to fetch seo data from DB\n     *\n     * @param string $sSeoUrl  seo url to calculate ident\n     * @param bool   $blIgnore if FALSE - blocks from direct access when default language seo url with language ident executed\n     *\n     * @return string\n     */\n    protected function getIdent($sSeoUrl, $blIgnore = false)\n    {\n        return md5(strtolower($sSeoUrl));\n    }\n\n    /**\n     * decodeUrl decodes given url into oxid eShop required parameters which are returned as array\n     *\n     * @param string $seoUrl SEO url\n     *\n     * @access        public\n     * @return array || false\n     */\n    public function decodeUrl($seoUrl)\n    {\n        $stringObject = Str::getStr();\n        $baseUrl = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopURL();\n        if ($stringObject->strpos($seoUrl, $baseUrl) === 0) {\n            $seoUrl = $stringObject->substr($seoUrl, $stringObject->strlen($baseUrl));\n        }\n        $seoUrl = rawurldecode($seoUrl);\n\n        //extract page number from seo url\n        list($seoUrl, $pageNumber) = $this->extractPageNumberFromSeoUrl($seoUrl);\n        $shopId = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId();\n\n        $key = $this->getIdent($seoUrl);\n        $urlParameters = false;\n\n        $database = DatabaseProvider::getDb();\n        $resultSet = $database->select(\"select oxstdurl, oxlang from oxseo where oxident = :oxident and oxshopid = :oxshopid limit 1\", [\n            'oxident' => $key,\n            'oxshopid' => $shopId\n        ]);\n        if (!$resultSet->EOF) {\n            // primary seo language changed ?\n            $urlParameters = $this->parseStdUrl($resultSet->fields['oxstdurl']);\n            $urlParameters['lang'] = $resultSet->fields['oxlang'];\n        }\n        if (is_array($urlParameters) && !is_null($pageNumber) && ($pageNumber > 0)) {\n            $urlParameters['pgNr'] = $pageNumber;\n        }\n\n        return $urlParameters;\n    }\n\n    /**\n     * Checks if url is stored in history table and if it was found - tries\n     * to fetch new url from seo table\n     *\n     * @param string $seoUrl SEO url\n     *\n     * @access         public\n     * @return string || false\n     */\n    protected function decodeOldUrl($seoUrl)\n    {\n        $stringObject = Str::getStr();\n        $database =DatabaseProvider::getDb();\n        $baseUrl = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopURL();\n        if ($stringObject->strpos($seoUrl, $baseUrl) === 0) {\n            $seoUrl = $stringObject->substr($seoUrl, $stringObject->strlen($baseUrl));\n        }\n        $shopId = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId();\n        $seoUrl = rawurldecode($seoUrl);\n\n        //extract page number from seo url\n        list($seoUrl, $pageNumber) = $this->extractPageNumberFromSeoUrl($seoUrl);\n\n        $key = $this->getIdent($seoUrl, true);\n\n        $url = false;\n        $resultSet = $database->select(\"select oxobjectid, oxlang from oxseohistory where oxident = :oxident and oxshopid = :oxshopid limit 1\", [\n            'oxident' => $key,\n            'oxshopid' => $shopId\n        ]);\n        if (!$resultSet->EOF) {\n            // updating hit info (oxtimestamp field will be updated automatically)\n            $database->execute(\n                \"update oxseohistory set oxhits = oxhits + 1 where oxident = :oxident and oxshopid = :oxshopid limit 1\",\n                [\n                    'oxident' => $key,\n                    'oxshopid' => $shopId\n                ]\n            );\n\n            // fetching new url\n            $url = $this->getSeoUrl($resultSet->fields['oxobjectid'], $resultSet->fields['oxlang'], $shopId);\n\n            // appending with $_SERVER[\"QUERY_STRING\"]\n            $url = $this->addQueryString($url);\n        }\n        if ($url && !is_null($pageNumber)) {\n            $url = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsUrl()->appendUrl($url, ['pgNr' => $pageNumber]);\n        }\n\n        return $url;\n    }\n\n    /**\n     * Appends and returns given url with $_SERVER[\"QUERY_STRING\"] value\n     *\n     * @param string $sUrl url to append\n     *\n     * @return string\n     */\n    protected function addQueryString($sUrl)\n    {\n        if (isset($_SERVER[\"QUERY_STRING\"]) && $_SERVER[\"QUERY_STRING\"]) {\n            $sUrl = rtrim($sUrl, \"&?\");\n            $sQ = ltrim($_SERVER[\"QUERY_STRING\"], \"&?\");\n\n            $sUrl .= (strpos($sUrl, '?') === false) ? \"?\" : \"&\";\n            $sUrl .= $sQ;\n        }\n\n        return $sUrl;\n    }\n\n    /**\n     * retrieve SEO url by its object id\n     * normally used for getting the redirect url from seo history\n     *\n     * @param string $sObjectId object id\n     * @param int    $iLang     language to fetch\n     * @param int    $iShopId   shop id\n     *\n     * @return ?string\n     */\n    protected function getSeoUrl($sObjectId, $iLang, $iShopId)\n    {\n        $oDb = DatabaseProvider::getDb();\n        $aInfo = $oDb->getRow(\"select oxseourl, oxtype from oxseo where oxobjectid = :oxobjectid and oxlang = :oxlang and oxshopid = :oxshopid order by oxparams limit 1\", [\n            'oxobjectid' => $sObjectId,\n            'oxlang' => $iLang,\n            'oxshopid' => $iShopId,\n        ]);\n\n        if (isset($aInfo['oxtype']) &&'oxarticle' == $aInfo['oxtype']) {\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $sMainCatId = $oDb->getOne(\"select oxcatnid from \" . $tableViewNameGenerator->getViewName(\"oxobject2category\") . \" where oxobjectid = :oxobjectid order by oxtime\", [\n                'oxobjectid' => $sObjectId\n            ]);\n            if ($sMainCatId) {\n                $sUrl = $oDb->getOne(\"select oxseourl from oxseo where oxobjectid = :oxobjectid and oxlang = :oxlang and oxshopid = :oxshopid  and oxparams = :oxparams order by oxexpired\", [\n                    'oxobjectid' => $sObjectId,\n                    'oxlang' => $iLang,\n                    'oxshopid' => $iShopId,\n                    'oxparams' => $sMainCatId,\n                ]);\n                if ($sUrl) {\n                    return $sUrl;\n                }\n            }\n        }\n\n        return $aInfo['oxseourl'] ?? null;\n    }\n\n    /**\n     * processSeoCall handles Server information and passes it to decoder\n     *\n     * @param string $sRequest request\n     * @param string $sPath    path\n     *\n     * @access public\n     */\n    public function processSeoCall($sRequest = null, $sPath = null)\n    {\n        // first - collect needed parameters\n        if (!$sRequest) {\n            if (isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI']) {\n                $sRequest = $_SERVER['REQUEST_URI'];\n            } else {\n                // try something else\n                $sRequest = $_SERVER['SCRIPT_URI'] ?? null;\n            }\n        }\n\n        $sPath = $sPath ? $sPath : str_replace('oxseo.php', '', $_SERVER['SCRIPT_NAME']);\n        if (($sParams = $this->getParams($sRequest, $sPath))) {\n            // in case SEO url is actual\n            if (is_array($aGet = $this->decodeUrl($sParams))) {\n                $_GET = array_merge($aGet, $_GET);\n                \\OxidEsales\\Eshop\\Core\\Registry::getLang()->resetBaseLanguage();\n            } elseif (($sRedirectUrl = $this->decodeOldUrl($sParams))) {\n                // in case SEO url was changed - redirecting to new location\n                \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->redirect(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopURL() . $sRedirectUrl, false, 301);\n            } elseif (($sRedirectUrl = $this->decodeSimpleUrl($sParams))) {\n                // old type II seo urls\n                \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->redirect(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopURL() . $sRedirectUrl, false, 301);\n            } else {\n                \\OxidEsales\\Eshop\\Core\\Registry::getSession()->start();\n                // unrecognized url\n                error_404_handler($sParams);\n            }\n        }\n    }\n\n    /**\n     * Tries to fetch SEO url according to type II seo url data. If no\n     * specified data is found NULL will be returned\n     *\n     * @param string $sParams request params (url chunk)\n     *\n     * @return string\n     */\n    protected function decodeSimpleUrl($sParams)\n    {\n        $sLastParam = trim($sParams, '/');\n\n        // active object id\n        $sUrl = null;\n\n        if ($sLastParam) {\n            $iLanguage = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage();\n\n            // article ?\n            if (strpos($sLastParam, '.htm') !== false) {\n                $sUrl = $this->getObjectUrl($sLastParam, 'oxarticles', $iLanguage, 'oxarticle');\n            } else {\n                // category ?\n                if (!($sUrl = $this->getObjectUrl($sLastParam, 'oxcategories', $iLanguage, 'oxcategory'))) {\n                    // maybe manufacturer ?\n                    if (!($sUrl = $this->getObjectUrl($sLastParam, 'oxmanufacturers', $iLanguage, 'oxmanufacturer'))) {\n                        // then maybe vendor ?\n                        $sUrl = $this->getObjectUrl($sLastParam, 'oxvendor', $iLanguage, 'oxvendor');\n                    }\n                }\n            }\n        }\n\n        return $sUrl;\n    }\n\n    /**\n     * Searches and returns (if available) current objects seo url\n     *\n     * @param string $sSeoId    ident (or last chunk of url)\n     * @param string $sTable    name of table to look for data\n     * @param int    $iLanguage current language identifier\n     * @param string $sType     type of object to search in seo table\n     *\n     * @return string\n     */\n    protected function getObjectUrl($sSeoId, $sTable, $iLanguage, $sType)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sTable = $tableViewNameGenerator->getViewName($sTable, $iLanguage);\n\n        // first checking of field exists at all\n        if ($oDb->getOne(\"show columns from {$sTable} where field = 'oxseoid'\")) {\n            // if field exists - searching for object id\n            if (\n                $sObjectId = $oDb->getOne(\"select oxid from {$sTable} where oxseoid = :oxseoid\", [\n                'oxseoid' => $sSeoId\n                ])\n            ) {\n                return $oDb->getOne(\"select oxseourl from oxseo where oxtype = :oxtype and oxobjectid = :oxobjectid and oxlang = :oxlang\", [\n                    'oxtype' => $sType,\n                    'oxobjectid' => $sObjectId,\n                    'oxlang' => $iLanguage,\n                ]);\n            }\n        }\n    }\n\n    /**\n     * Extracts SEO paramteters and returns as array\n     *\n     * @param string $sRequest request\n     * @param string $sPath    path\n     *\n     * @return array $aParams extracted params\n     */\n    protected function getParams($sRequest, $sPath)\n    {\n        $oStr = Str::getStr();\n\n        $sParams = $oStr->preg_replace('/\\?.*/', '', $sRequest);\n        $sPath = preg_quote($sPath, '/');\n        $sParams = $oStr->preg_replace(\"/^$sPath/\", '', $sParams);\n\n        // this should not happen on most cases, because this redirect is handled by .htaccess\n        if ($sParams && !$oStr->preg_match('/\\.html$/', $sParams) && !$oStr->preg_match('/\\/$/', $sParams)) {\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->redirect(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopURL() . $sParams . '/', false, 301);\n        }\n\n        return $sParams;\n    }\n\n    /**\n     * Splits seo url into:\n     *     - seo url without page number\n     *     - page number\n     *\n     * @param string $seoUrl\n     *\n     * @return array\n     */\n    private function extractPageNumberFromSeoUrl($seoUrl)\n    {\n        $pageNumber = null;\n        if (1 === preg_match('/(.*?)\\/(\\d+)\\/(.*)/', $seoUrl, $matches)) {\n            $seoUrl = $matches[1] . '/' . $matches[3];\n            $pageNumber = $this->convertSeoPageStringToActualPageNumber($matches[2]);\n        }\n        return [$seoUrl, $pageNumber];\n    }\n\n    /**\n     * Converts seo url pagination number to actual page number.\n     *\n     * @param int $seoPageNumber\n     *\n     * @return int\n     */\n    private function convertSeoPageStringToActualPageNumber($seoPageNumber)\n    {\n        if (!is_null($seoPageNumber)) {\n            $seoPageNumber = max(0, (int) $seoPageNumber - 1);\n        }\n        return $seoPageNumber;\n    }\n}\n"
  },
  {
    "path": "source/Core/SeoEncoder.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse Exception;\n\n/**\n * Seo encoder base\n */\n#[\\AllowDynamicProperties]\nclass SeoEncoder extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Strings that cannot be used in SEO URLs as this may cause\n     * compatability/access problems\n     *\n     * @var array\n     */\n    protected static $_aReservedWords = ['admin'];\n\n    /**\n     * cache for reserved path root node keys\n     *\n     * @var array\n     */\n    protected static $_aReservedEntryKeys = null;\n\n    /**\n     * SEO separator.\n     *\n     * @var string\n     */\n    protected static $_sSeparator = null;\n\n    /**\n     * SEO id length.\n     *\n     * @var integer\n     */\n    protected $_iIdLength = 255;\n\n    /**\n     * SEO prefix.\n     *\n     * @var string\n     */\n    protected static $_sPrefix = null;\n\n    /**\n     * Added parameters.\n     *\n     * @var string\n     */\n    protected $_sAddParams = null;\n\n    /**\n     * Url fixed state cache\n     *\n     * @return array\n     */\n    protected static $_aFixedCache = [];\n\n    /**\n     * SEO Cache key for active view\n     *\n     * @var string\n     */\n    protected static $_sCacheKey = null;\n\n    /**\n     * SEO cache array\n     *\n     * @var array\n     */\n    protected static $_aCache = [];\n\n    /**\n     * Maximum seo/dynamic url length\n     *\n     * @var int\n     */\n    protected $_iMaxUrlLength = null;\n\n    /**\n     * Returns part of url defining active language\n     *\n     * @param string $sSeoUrl seo url\n     * @param int    $iLang   language id\n     *\n     * @return string\n     */\n    public function addLanguageParam($sSeoUrl, $iLang)\n    {\n        $iLang = (int) $iLang;\n        $iDefLang = (int) Registry::getConfig()->getConfigParam('iDefSeoLang');\n        $aLangIds = Registry::getLang()->getLanguageIds();\n\n        if (\n            $iLang != $iDefLang &&\n            isset($aLangIds[$iLang]) &&\n            // #0006407 bugfix, we should not search for the string saved in the db but for the escaped string\n            Str::getStr()->strpos($sSeoUrl, $this->replaceSpecialChars($aLangIds[$iLang]) . '/') !== 0\n        ) {\n            $sSeoUrl = $aLangIds[$iLang] . '/' . $sSeoUrl;\n        }\n\n        return $sSeoUrl;\n    }\n\n    /**\n     * Processes seo url before saving to db:\n     *  - \\OxidEsales\\Eshop\\Core\\SeoEncoder::addLanguageParam();\n     *  - \\OxidEsales\\Eshop\\Core\\SeoEncoder::_getUniqueSeoUrl().\n     *\n     * @param string $sSeoUrl   seo url to process\n     * @param string $sObjectId seo object id [optional]\n     * @param int    $iLang     active language id [optional]\n     * @param bool   $blExclude exclude language prefix while building seo url\n     *\n     * @return string\n     */\n    protected function processSeoUrl($sSeoUrl, $sObjectId = null, $iLang = null, $blExclude = false)\n    {\n        if (!$blExclude) {\n            $sSeoUrl = $this->addLanguageParam($sSeoUrl, $iLang);\n        }\n\n        return $this->getUniqueSeoUrl($sSeoUrl, $sObjectId, $iLang);\n    }\n\n    /**\n     * SEO encoder constructor\n     */\n    public function __construct()\n    {\n        $myConfig = Registry::getConfig();\n        if (!self::$_sSeparator) {\n            $this->setSeparator($myConfig->getConfigParam('sSEOSeparator'));\n        }\n        if (!self::$_sPrefix) {\n            $this->setPrefix($myConfig->getConfigParam('sSEOuprefix'));\n        }\n        $this->setReservedWords($myConfig->getConfigParam('aSEOReservedWords'));\n    }\n\n    /**\n     * Moves current seo record to seo history table\n     *\n     * @param string $sId     object id\n     * @param int    $iShopId active shop id\n     * @param int    $iLang   object language\n     * @param string $sType   object type (if you pass real object - type is not necessary)\n     * @param string $sNewId  new object id, mostly used for static url updates (optional)\n     */\n    protected function copyToHistory($sId, $iShopId, $iLang, $sType = null, $sNewId = null)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sObjectid = $sNewId ? $oDb->quote($sNewId) : 'oxobjectid';\n        $sType = $sType ? \"oxtype =\" . $oDb->quote($sType) . \" and\" : '';\n        $iLang = (int) $iLang;\n\n        // moving\n        $sSub = \"select $sObjectid, MD5( LOWER( oxseourl ) ), oxshopid, oxlang, now() from oxseo\n                 where {$sType} oxobjectid = \" . $oDb->quote($sId) . \" and oxshopid = \" . $oDb->quote($iShopId) . \" and\n                 oxlang = {$iLang} and oxexpired = '1'\";\n        $sQ = \"replace oxseohistory ( oxobjectid, oxident, oxshopid, oxlang, oxinsert ) {$sSub}\";\n        $oDb->execute($sQ);\n    }\n\n    /**\n     * Generates dynamic url object id (calls \\OxidEsales\\Eshop\\Core\\SeoEncoder::_getStaticObjectId)\n     *\n     * @param int    $iShopId shop id\n     * @param string $sStdUrl standard (dynamic) url\n     *\n     * @return string\n     */\n    public function getDynamicObjectId($iShopId, $sStdUrl)\n    {\n        return $this->getStaticObjectId($iShopId, $sStdUrl);\n    }\n\n    /**\n     * Returns dynamic object SEO URI\n     *\n     * @param string $sStdUrl standard url\n     * @param string $sSeoUrl seo uri\n     * @param int    $iLang   active language\n     *\n     * @return string\n     */\n    protected function getDynamicUri($sStdUrl, $sSeoUrl, $iLang)\n    {\n        $iShopId = Registry::getConfig()->getShopId();\n\n        $sStdUrl = $this->trimUrl($sStdUrl);\n        $sObjectId = $this->getDynamicObjectId($iShopId, $sStdUrl);\n        $sSeoUrl = $this->prepareUri($this->addLanguageParam($sSeoUrl, $iLang), $iLang);\n\n        //load details link from DB\n        $sOldSeoUrl = $this->loadFromDb('dynamic', $sObjectId, $iLang);\n        if ($sOldSeoUrl === $sSeoUrl) {\n            $sSeoUrl = $sOldSeoUrl;\n        } else {\n            if ($sOldSeoUrl) {\n                // old must be transferred to history\n                $this->copyToHistory($sObjectId, $iShopId, $iLang, 'dynamic');\n            }\n\n            // creating unique\n            $sSeoUrl = $this->processSeoUrl($sSeoUrl, $sObjectId, $iLang);\n\n            // inserting\n            $this->saveToDb('dynamic', $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId);\n        }\n\n        return $sSeoUrl;\n    }\n\n    /**\n     * Returns SEO url with shop's path + additional params ( \\OxidEsales\\Eshop\\Core\\SeoEncoder:: _getAddParams)\n     */\n    protected function getFullUrl($seoUrl, $lang = null)\n    {\n        if ($seoUrl) {\n            $fullUrl = Registry::getConfig()->getShopUrl($lang) . $seoUrl;\n\n            return Registry::getUtilsUrl()->processSeoUrl($fullUrl);\n        }\n\n        return false;\n    }\n\n    /**\n     * _getSeoIdent returns seo ident for db search\n     *\n     * @param string $sSeoUrl seo url\n     *\n     * @access protected\n     *\n     * @return string\n     */\n    protected function getSeoIdent($sSeoUrl)\n    {\n        return md5(strtolower($sSeoUrl));\n    }\n\n    /**\n     * Returns SEO static uri\n     *\n     * @param string $sStdUrl standard page url\n     * @param int    $iShopId active shop id\n     * @param int    $iLang   active language\n     *\n     * @return string\n     */\n    protected function getStaticUri($sStdUrl, $iShopId, $iLang)\n    {\n        $sStdUrl = $this->trimUrl($sStdUrl, $iLang);\n\n        return $this->loadFromDb('static', $this->getStaticObjectId($iShopId, $sStdUrl), $iLang, $iShopId);\n    }\n\n    /**\n     * Returns target \"extension\"\n     *\n     * @return null\n     */\n    protected function getUrlExtension()\n    {\n        return;\n    }\n\n    /**\n     * _getUniqueSeoUrl returns possibly modified url\n     * for not to be same as already existing in db\n     *\n     * @param string $sSeoUrl     seo url\n     * @param string $sObjectId   current object id, used to skip self in query\n     * @param int    $iObjectLang object language id\n     *\n     * @access protected\n     *\n     * @return string\n     */\n    protected function getUniqueSeoUrl($sSeoUrl, $sObjectId = null, $iObjectLang = null)\n    {\n        $sSeoUrl = $this->prepareUri($sSeoUrl, $iObjectLang);\n        $oStr = Str::getStr();\n        $sExt = '';\n        if ($oStr->preg_match('/(\\.html?|\\/)$/i', $sSeoUrl, $aMatched)) {\n            $sExt = $aMatched[0];\n        }\n        $sBaseSeoUrl = $sSeoUrl;\n        if ($sExt && $oStr->substr($sSeoUrl, 0 - $oStr->strlen($sExt)) == $sExt) {\n            $sBaseSeoUrl = $oStr->substr($sSeoUrl, 0, $oStr->strlen($sSeoUrl) - $oStr->strlen($sExt));\n        }\n\n        $iShopId = Registry::getConfig()->getShopId();\n        $iCnt = 0;\n        $sCheckSeoUrl = $this->trimUrl($sSeoUrl);\n        $sQ = \"select 1 from oxseo where oxshopid = '{$iShopId}'\";\n\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        // skipping self\n        if ($sObjectId && isset($iObjectLang)) {\n            $iObjectLang = (int) $iObjectLang;\n            $sQ .= \" and not (oxobjectid = \" . $oDb->quote($sObjectId) . \" and oxlang = $iObjectLang)\";\n        }\n\n        while ($oDb->getOne($sQ . \" and oxident= \" . $oDb->quote($this->getSeoIdent($sCheckSeoUrl)))) {\n            $sAdd = '';\n            if (self::$_sPrefix) {\n                $sAdd = self::$_sSeparator . self::$_sPrefix;\n            }\n            if ($iCnt) {\n                $sAdd .= self::$_sSeparator . $iCnt;\n            }\n            ++$iCnt;\n\n            $sSeoUrl = $sBaseSeoUrl . $sAdd . $sExt;\n            $sCheckSeoUrl = $this->trimUrl($sSeoUrl);\n        }\n\n        return $sSeoUrl;\n    }\n\n    /**\n     * check if seo url exist and is fixed\n     *\n     * @param string $sType               object type\n     * @param string $sId                 object identifier\n     * @param int    $iLang               active language id\n     * @param mixed  $iShopId             active shop id\n     * @param string $sParams             additional seo params. optional (mostly used for db indexing)\n     * @param bool   $blStrictParamsCheck strict parameters check\n     *\n     * @access protected\n     *\n     * @return bool\n     */\n    protected function isFixed($sType, $sId, $iLang, $iShopId = null, $sParams = null, $blStrictParamsCheck = true)\n    {\n        if ($iShopId === null) {\n            $iShopId = Registry::getConfig()->getShopId();\n        }\n        $iLang = (int) $iLang;\n\n        if (!isset(self::$_aFixedCache[$sType][$iShopId][$sId][$iLang])) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n            $sQ = \"SELECT `oxfixed`\n                FROM `oxseo`\n                WHERE `oxtype` = \" . $oDb->quote($sType) . \"\n                   AND `oxobjectid` = \" . $oDb->quote($sId) . \"\n                   AND `oxshopid` = \" . $oDb->quote($iShopId) . \"\n                   AND `oxlang` = '{$iLang}'\";\n\n            $sParams = $sParams ? $oDb->quote($sParams) : \"''\";\n            if ($sParams && $blStrictParamsCheck) {\n                $sQ .= \" AND `oxparams` = {$sParams}\";\n            } else {\n                $sQ .= \" ORDER BY `oxparams` ASC\";\n            }\n            $sQ .= \" LIMIT 1\";\n\n            self::$_aFixedCache[$sType][$iShopId][$sId][$iLang] = (bool) $oDb->getOne($sQ);\n        }\n\n        return self::$_aFixedCache[$sType][$iShopId][$sId][$iLang];\n    }\n\n    /**\n     * Returns cache key (in non admin mode)\n     *\n     * @param string $sType   object type\n     * @param int    $iLang   active language id\n     * @param mixed  $iShopId active shop id\n     * @param string $sParams additional seo params. optional (mostly used for db indexing)\n     *\n     * @return string\n     */\n    protected function getCacheKey($sType, $iLang = null, $iShopId = null, $sParams = null)\n    {\n        $blAdmin = $this->isAdmin();\n        if (!$blAdmin && $sType !== \"oxarticle\") {\n            return $sType . ((int) $iLang) . ((int) $iShopId) . \"seo\";\n        }\n\n        // use cache in non admin mode\n        if (self::$_sCacheKey === null) {\n            self::$_sCacheKey = false;\n            if (!$blAdmin && ($oView = Registry::getConfig()->getActiveView())) {\n                self::$_sCacheKey = md5($oView->getViewId()) . \"seo\";\n            }\n        }\n\n        return self::$_sCacheKey;\n    }\n\n    /**\n     * Loads seo data from cache for active view (in non admin mode)\n     *\n     * @param string $sCacheIdent cache identifier\n     * @param string $sType       object type\n     * @param int    $iLang       active language id\n     * @param mixed  $iShopId     active shop id\n     * @param string $sParams     additional seo params. optional (mostly used for db indexing)\n     *\n     * @return string\n     */\n    protected function loadFromCache($sCacheIdent, $sType, $iLang = null, $iShopId = null, $sParams = null)\n    {\n        if (!Registry::getConfig()->getConfigParam('blEnableSeoCache')) {\n            return false;\n        }\n\n        startProfile(\"seoencoder_loadFromCache\");\n\n        $sCacheKey = $this->getCacheKey($sType, $iLang, $iShopId, $sParams);\n        $sCache = false;\n\n        if ($sCacheKey && !isset(self::$_aCache[$sCacheKey])) {\n            self::$_aCache[$sCacheKey] = Registry::getUtils()->fromFileCache($sCacheKey);\n        }\n\n        if (isset(self::$_aCache[$sCacheKey]) && isset(self::$_aCache[$sCacheKey][$sCacheIdent])) {\n            $sCache = self::$_aCache[$sCacheKey][$sCacheIdent];\n        }\n\n        stopProfile(\"seoencoder_loadFromCache\");\n\n        return $sCache;\n    }\n\n    /**\n     * Saves seo cache data for active view (in non admin mode)\n     *\n     * @param string $sCacheIdent cache identifier\n     * @param string $sCache      cacheable data\n     * @param string $sType       object type\n     * @param int    $iLang       active language id\n     * @param mixed  $iShopId     active shop id\n     * @param string $sParams     additional seo params. optional (mostly used for db indexing)\n     *\n     * @return bool\n     */\n    protected function saveInCache($sCacheIdent, $sCache, $sType, $iLang = null, $iShopId = null, $sParams = null)\n    {\n        if (!Registry::getConfig()->getConfigParam('blEnableSeoCache')) {\n            return false;\n        }\n\n        startProfile(\"seoencoder_saveInCache\");\n\n        $blSaved = false;\n        if ($sCache && ($sCacheKey = $this->getCacheKey($sType, $iLang, $iShopId, $sParams)) !== false) {\n            self::$_aCache[$sCacheKey][$sCacheIdent] = $sCache;\n            $blSaved = Registry::getUtils()->toFileCache($sCacheKey, self::$_aCache[$sCacheKey]);\n        }\n\n        stopProfile(\"seoencoder_saveInCache\");\n\n        return $blSaved;\n    }\n\n    /**\n     * _loadFromDb loads data from oxseo table if exists\n     * returns oxseo url\n     *\n     * @param string $sType               object type\n     * @param string $sId                 object identifier\n     * @param int    $iLang               active language id\n     * @param mixed  $iShopId             active shop id\n     * @param string $sParams             additional seo params. optional (mostly used for db indexing)\n     * @param bool   $blStrictParamsCheck strict parameters check\n     *\n     * @access         protected\n     *\n     * @return string || false\n     */\n    protected function loadFromDb($sType, $sId, $iLang, $iShopId = null, $sParams = null, $blStrictParamsCheck = true)\n    {\n        if ($iShopId === null) {\n            $iShopId = Registry::getConfig()->getShopId();\n        }\n\n        $iLang = (int) $iLang;\n\n        $params = [\n            'oxtype' => $sType,\n            'oxobjectid' => $sId,\n            'oxshopid' => $iShopId,\n            'oxlang' => $iLang\n        ];\n\n        $sQ = \"\n            SELECT\n                `oxfixed`,\n                `oxseourl`,\n                `oxexpired`,\n                `oxtype`\n            FROM `oxseo`\n            WHERE `oxtype` = :oxtype\n               AND `oxobjectid` = :oxobjectid\n               AND `oxshopid` = :oxshopid\n               AND `oxlang` = :oxlang\";\n\n        $sParams = $sParams ? $sParams : '';\n        if ($sParams && $blStrictParamsCheck) {\n            $sQ .= \" AND `oxparams` = :oxparams\";\n            $params['oxparams'] = $sParams;\n        } else {\n            $sQ .= \" ORDER BY `oxparams` ASC\";\n        }\n\n        $sQ .= \" LIMIT 1\";\n\n        // caching to avoid same queries..\n        $sIdent = md5(\"_loadFromDb\" . serialize($params));\n\n        // looking in cache\n        if (($sSeoUrl = $this->loadFromCache($sIdent, $sType, $iLang, $iShopId, $sParams)) === false) {\n            $oDb = DatabaseProvider::getDb();\n            $oRs = $oDb->select($sQ, $params);\n\n            if ($oRs && $oRs->count() > 0 && !$oRs->EOF) {\n                // moving expired static urls to history ..\n                if ($oRs->fields['oxexpired'] && ($oRs->fields['oxtype'] == 'static' || $oRs->fields['oxtype'] == 'dynamic')) {\n                    // if expired - copying to history, marking as not expired\n                    $this->copyToHistory($sId, $iShopId, $iLang);\n                    $oDb->execute(\"update oxseo set oxexpired = 0 where oxobjectid = :oxobjectid and oxlang = :oxlang and oxshopid = :oxshopid\", [\n                        'oxobjectid' => $sId,\n                        'oxlang' => $iLang,\n                        'oxshopid' => $iShopId\n                    ]);\n                    $sSeoUrl = $oRs->fields['oxseourl'];\n                } elseif (!$oRs->fields['oxexpired'] || $oRs->fields['oxfixed']) {\n                    // if seo url is available and is valid\n                    $sSeoUrl = $oRs->fields['oxseourl'];\n                }\n\n                // storing in cache\n                $this->saveInCache($sIdent, $sSeoUrl, $sType, $iLang, $iShopId, $sParams);\n            }\n        }\n\n        return $sSeoUrl;\n    }\n\n    /**\n     * cached getter: check root directory php file names for them not to be in 1st part of seo url\n     * because then apache will execute that php file instead of url parser\n     *\n     * @return array\n     */\n    protected function getReservedEntryKeys()\n    {\n        if (!isset(self::$_aReservedEntryKeys) || !is_array(self::$_aReservedEntryKeys)) {\n            $sDir = getShopBasePath();\n            self::$_aReservedEntryKeys = array_map('preg_quote', self::$_aReservedWords, ['#']);\n            $oStr = Str::getStr();\n            foreach (glob(\"$sDir/*\") as $sFile) {\n                if ($oStr->preg_match('/^(.+)\\.php[0-9]*$/i', basename($sFile), $aMatches)) {\n                    self::$_aReservedEntryKeys[] = preg_quote($aMatches[0], '#');\n                    self::$_aReservedEntryKeys[] = preg_quote($aMatches[1], '#');\n                } elseif (is_dir($sFile)) {\n                    self::$_aReservedEntryKeys[] = preg_quote(basename($sFile), '#');\n                }\n            }\n            self::$_aReservedEntryKeys = array_unique(self::$_aReservedEntryKeys);\n        }\n\n        return self::$_aReservedEntryKeys;\n    }\n\n    /**\n     * Makes safe seo uri - removes unsupported/reserved characters\n     *\n     * @param string $sUri  seo uri\n     * @param int|bool $iLang language ID, for which URI should be prepared\n     *\n     * @return string\n     */\n    protected function prepareUri($sUri, $iLang = false)\n    {\n        // decoding entities\n        $sUri = $this->encodeString($sUri, true, $iLang);\n\n        // basic string preparation\n        $oStr = Str::getStr();\n        $sUri = $oStr->strip_tags($sUri);\n\n        // if found \".html\" or \"/\" at the end - removing it temporary\n        $sExt = $this->getUrlExtension();\n        if ($sExt === null) {\n            $aMatched = [];\n            if ($oStr->preg_match('/(\\.html?|\\/)$/i', $sUri, $aMatched)) {\n                $sExt = $aMatched[0];\n            } else {\n                $sExt = '/';\n            }\n        }\n        if ($sExt && $oStr->substr($sUri, 0 - $oStr->strlen($sExt)) == $sExt) {\n            $sUri = $oStr->substr($sUri, 0, $oStr->strlen($sUri) - $oStr->strlen($sExt));\n        }\n\n        $sUri = $this->replaceSpecialChars($sUri);\n\n        // SEO id is empty ?\n        if (!$sUri && self::$_sPrefix) {\n            $sUri = $this->prepareUri(self::$_sPrefix, $iLang);\n        }\n\n        $sAdd = '_' . self::$_sPrefix;\n        if ('/' != self::$_sSeparator) {\n            $sAdd = self::$_sSeparator . self::$_sPrefix;\n            $sUri = trim($sUri, self::$_sSeparator);\n        }\n\n        // binding the ending back\n        $sUri .= $sExt;\n\n        // lowercase uri if option is set\n        if (Registry::getConfig()->getConfigParam('blSEOLowerCaseUrls')) {\n            $strUtility = Str::getStr();\n            $sUri = $strUtility->strtolower($sUri);\n        }\n\n        // fix for not having url, which executes through /other/ script then seo decoder\n        $sUri = $oStr->preg_replace(\"#^(/*)(\" . implode('|', $this->getReservedEntryKeys()) . \")(/|$)#i\", \"\\$1\\$2$sAdd\\$3\", $sUri);\n\n        // cleaning\n        $sQuotedSeparator = preg_quote(self::$_sSeparator, '/');\n\n        return $oStr->preg_replace(\n            ['|//+|', '/' . $sQuotedSeparator . $sQuotedSeparator . '+/'],\n            ['/', self::$_sSeparator],\n            $sUri\n        );\n    }\n\n\n    /**\n     * Prepares and returns formatted object SEO id\n     *\n     * @param string   $sTitle         Original object title\n     * @param bool     $blSkipTruncate Truncate title into defined lenght or not\n     * @param int|bool $iLang          language ID, for which to prepare the title\n     *\n     * @return string\n     */\n    protected function prepareTitle($sTitle, $blSkipTruncate = false, $iLang = false)\n    {\n        $sTitle = $this->encodeString($sTitle, true, $iLang);\n        $sSep = self::$_sSeparator;\n        if (!$sSep || ('/' == $sSep)) {\n            $sSep = '_';\n        }\n\n        $sRegExp = '/[^A-Za-z0-9\\/' . preg_quote(self::$_sPrefix, '/') . preg_quote($sSep, '/') . ']+/';\n        $sTitle = preg_replace([\"#/+#\", $sRegExp, \"# +#\", \"#(\" . preg_quote($sSep, '/') . \")+#\"], $sSep, $sTitle);\n\n        $oStr = Str::getStr();\n        // smart truncate\n        if (!$blSkipTruncate && $oStr->strlen($sTitle) > $this->_iIdLength) {\n            $iFirstSpace = $oStr->strpos($sTitle, $sSep, $this->_iIdLength);\n            if ($iFirstSpace !== false) {\n                $sTitle = $oStr->substr($sTitle, 0, $iFirstSpace);\n            }\n        }\n\n        $sTitle = trim($sTitle, $sSep);\n\n        if (!$sTitle) {\n            return self::$_sPrefix;\n        }\n\n        // cleaning\n        return $sTitle;\n    }\n\n\n    /**\n     * _saveToDb saves values to seo table\n     *\n     * @param string $sType     url type (static, dynamic, oxarticle etc)\n     * @param string $sObjectId object identifier\n     * @param string $sStdUrl   standard url\n     * @param string $sSeoUrl   seo url\n     * @param int    $iLang     active object language\n     * @param mixed  $iShopId   active object shop id\n     * @param bool   $blFixed   seo entry marker. if true, entry should not be automatically changed\n     * @param string $sParams   additional seo params. optional (mostly used for db indexing)\n     *\n     * @access protected\n     *\n     * @return mixed\n     */\n    protected function saveToDb($sType, $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId = null, $blFixed = null, $sParams = null)\n    {\n        if ($iShopId === null) {\n            $iShopId = Registry::getConfig()->getShopId();\n        }\n\n        $iLang = (int) $iLang;\n\n        $sStdUrl = $this->trimUrl($sStdUrl);\n        $sSeoUrl = $this->trimUrl($sSeoUrl);\n        $sIdent = $this->getSeoIdent($sSeoUrl);\n\n        // transferring old url, thus current url will be regenerated\n\n        $params = [\n            'oxstdurl' => $sStdUrl,\n            'oxseourl' => $sSeoUrl,\n            'oxtype' => $sType,\n            'oxobjectid' => $sObjectId,\n            'oxshopid' => $iShopId,\n            'oxlang' => $iLang\n        ];\n\n        $sQ = \"select oxfixed, oxexpired, ( oxstdurl like :oxstdurl ) as samestdurl, oxseourl like :oxseourl as sameseourl\n                from oxseo\n                where oxtype = :oxtype and\n                oxobjectid = :oxobjectid and\n                oxshopid = :oxshopid and\n                oxlang = :oxlang\";\n\n        if ($sParams) {\n            $sQ .= \" and oxparams = :oxparams \";\n            $params['oxparams'] = $sParams;\n        }\n\n        $sQ .= \" limit 1\";\n\n        $oDb = DatabaseProvider::getDb();\n        $oRs = $oDb->select($sQ, $params);\n        if ($oRs && $oRs->count() > 0 && !$oRs->EOF) {\n            if ($oRs->fields['samestdurl'] && $oRs->fields['sameseourl'] && $oRs->fields['oxexpired']) {\n                // fixed state change\n                $sFixed = isset($blFixed) ? \", oxfixed = \" . ((int) $blFixed) . \" \" : '';\n                // nothing was changed - setting expired status back to 0\n                $sSql = \"update oxseo set oxexpired = 0 {$sFixed} where oxtype = :oxtype and\n                          oxobjectid = :oxobjectid and oxshopid = :oxshopid and oxlang = :oxlang \";\n                $sSql .= $sParams ? \" and oxparams = :oxparams \" : '';\n                $sSql .= \" limit 1\";\n\n                return $this->executeQuery($sSql, [\n                    'oxtype' => $sType,\n                    'oxobjectid' => $sObjectId,\n                    'oxshopid' => $iShopId,\n                    'oxlang' => $iLang,\n                    'oxparams' => $sParams\n                ]);\n            } elseif ($oRs->fields['oxexpired']) {\n                // copy to history\n                $this->copyToHistory($sObjectId, $iShopId, $iLang, $sType);\n            }\n        }\n\n        // inserting new or updating\n        $sQ = \"insert into oxseo\n                    (oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype, oxfixed, oxexpired, oxparams)\n                values\n                    (:oxobjectid, :oxident, :oxshopid, :oxlang, :oxstdurl, :oxseourl, :oxtype, :oxfixed, '0', :oxparams)\n                on duplicate key update\n                    oxobjectid = :oxobjectid, oxident = :oxident, oxstdurl = :oxstdurl, oxseourl = :oxseourl, oxfixed = :oxfixed, oxexpired = '0'\";\n\n        return $this->executeQuery($sQ, [\n            'oxobjectid' => $sObjectId ?? '',\n            'oxident' => $sIdent,\n            'oxshopid' => $iShopId,\n            'oxlang' => $iLang,\n            'oxstdurl' => $sStdUrl,\n            'oxseourl' => $sSeoUrl,\n            'oxtype' => $sType,\n            'oxfixed' => (int) $blFixed,\n            'oxparams' => $sParams ?: ''\n        ]);\n    }\n\n    /**\n     * Runs query.\n     * Returns false when the query fail, otherwise return true\n     *\n     * @param string $query Query to execute.\n     * @param array  $params\n     *\n     * @return bool\n     */\n    protected function executeQuery($query, $params = [])\n    {\n        $dataBase = DatabaseProvider::getDb();\n        $success = true;\n        try {\n            $dataBase->execute($query, $params);\n        } catch (\\OxidEsales\\Eshop\\Core\\Exception\\StandardException $exception) {\n            Registry::getLogger()->error($exception->getMessage(), [$exception]);\n            $success = false;\n        }\n\n        return $success;\n    }\n\n    /**\n     * Removes shop path part and session id from given url\n     *\n     * @param string $sUrl  url to clean bad chars\n     * @param int|null $iLang active language\n     *\n     * @access protected\n     *\n     * @return string\n     */\n    protected function trimUrl($sUrl, $iLang = null)\n    {\n        $myConfig = Registry::getConfig();\n        $oStr = Str::getStr();\n        $sUrl = str_replace($myConfig->getShopUrl($iLang, false), '', $sUrl);\n        $sUrl = $oStr->preg_replace('/(\\?|&(amp;)?)(force_)?(admin_)?sid=[a-z0-9\\.]+&?(amp;)?/i', '\\1', $sUrl);\n        $sUrl = $oStr->preg_replace('/(\\?|&(amp;)?)shp=[0-9]+&?(amp;)?/i', '\\1', $sUrl);\n        $sUrl = $oStr->preg_replace('/(\\?|&(amp;)?)lang=[0-9]+&?(amp;)?/i', '\\1', $sUrl);\n        $sUrl = $oStr->preg_replace('/(\\?|&(amp;)?)cur=[0-9]+&?(amp;)?/i', '\\1', $sUrl);\n        $sUrl = $oStr->preg_replace('/(\\?|&(amp;)?)stoken=[a-z0-9]+&?(amp;)?/i', '\\1', $sUrl);\n        $sUrl = $oStr->preg_replace('/(\\?|&(amp;)?)&(amp;)?/i', '\\1', $sUrl);\n        $sUrl = $oStr->preg_replace('/(\\?|&(amp;)?)+$/i', '', $sUrl);\n        $sUrl = trim($sUrl);\n\n        // max length <= $this->_iMaxUrlLength\n        $iLength = $this->getMaxUrlLength();\n        if ($oStr->strlen($sUrl) > $iLength) {\n            $sUrl = $oStr->substr($sUrl, 0, $iLength);\n        }\n\n        return $sUrl;\n    }\n\n    /**\n     * Returns maximum seo/dynamic url length\n     *\n     * @return int\n     */\n    protected function getMaxUrlLength()\n    {\n        if ($this->_iMaxUrlLength === null) {\n            // max length <= 2048 / custom\n            $this->_iMaxUrlLength = Registry::getConfig()->getConfigParam(\"iMaxSeoUrlLength\");\n            if (!$this->_iMaxUrlLength) {\n                $this->_iMaxUrlLength = 2048;\n            }\n        }\n\n        return $this->_iMaxUrlLength;\n    }\n\n    /**\n     * Replaces special chars in text\n     *\n     * @param string    $sString        string to encode\n     * @param bool      $blReplaceChars is true, replaces user defined (\\OxidEsales\\Eshop\\Core\\Language::getSeoReplaceChars) characters into alternative\n     * @param int|false $iLang          language, for which to encode the string\n     *\n     * @return string\n     */\n    public function encodeString($sString, $blReplaceChars = true, $iLang = false)\n    {\n        // decoding entities\n        $sString = Str::getStr()->html_entity_decode($sString);\n\n        if ($blReplaceChars) {\n            if ($iLang === false || !is_numeric($iLang)) {\n                $iLang = Registry::getLang()->getEditLanguage();\n            }\n\n            if ($aReplaceChars = Registry::getLang()->getSeoReplaceChars($iLang)) {\n                $sString = str_replace(array_keys($aReplaceChars), array_values($aReplaceChars), $sString);\n            }\n        }\n\n        return str_replace(['&amp;', '&quot;', '&#039;', '&lt;', '&gt;'], '', $sString);\n    }\n\n    /**\n     * Sets SEO separator\n     *\n     * @param string $sSeparator SEO seperator\n     */\n    public function setSeparator($sSeparator = null)\n    {\n        self::$_sSeparator = $sSeparator;\n        if (!self::$_sSeparator) {\n            self::$_sSeparator = '-';\n        }\n    }\n\n    /**\n     * Sets SEO prefix\n     *\n     * @param string $sPrefix SEO prefix\n     */\n    public function setPrefix($sPrefix)\n    {\n        if ($sPrefix) {\n            self::$_sPrefix = $sPrefix;\n        } else {\n            self::$_sPrefix = 'oxid';\n        }\n    }\n\n    /**\n     * sets seo id length\n     *\n     * @param string $iIdlength id length\n     */\n    public function setIdLength($iIdlength = null)\n    {\n        if (isset($iIdlength)) {\n            $this->_iIdLength = $iIdlength;\n        }\n    }\n\n    /**\n     * Sets array of words which must be checked before building seo url\n     * These words are appended by seo prefix if they are the initial uri segment\n     *\n     * @param array $aReservedWords reserved words\n     */\n    public function setReservedWords($aReservedWords)\n    {\n        self::$_aReservedWords = array_merge(self::$_aReservedWords, $aReservedWords);\n    }\n\n\n    /**\n     * Marks object seo records as expired\n     *\n     * @param string $sId      changed object id. If null is passed, object dependency is not checked\n     * @param int    $iShopId  active shop id. Shop id must be passed uf you want to do shop level update (default null)\n     * @param int    $iExpStat expiration status: 1 - standard expiration\n     * @param int    $iLang    active language (optiona;)\n     * @param string $sParams  additional params\n     */\n    public function markAsExpired($sId, $iShopId = null, $iExpStat = 1, $iLang = null, $sParams = null)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sWhere = $sId ? \"where oxobjectid =  \" . $oDb->quote($sId) : '';\n        $sWhere .= isset($iShopId) ? ($sWhere ? \" and oxshopid = \" . $oDb->quote($iShopId) : \"where oxshopid = \" . $oDb->quote($iShopId)) : '';\n        $sWhere .= !is_null($iLang) ? ($sWhere ? \" and oxlang = '{$iLang}'\" : \"where oxlang = '{$iLang}'\") : '';\n        $sWhere .= $sParams ? ($sWhere ? \" and {$sParams}\" : \"where {$sParams}\") : '';\n\n        $sQ = \"update oxseo set oxexpired = :oxexpired $sWhere\";\n        $oDb->execute($sQ, ['oxexpired' => $iExpStat]);\n    }\n\n    /**\n     * Loads if exists or prepares and saves new seo url for passed object\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Model\\BaseModel $oObject object to prepare seo data\n     * @param string                                 $sType   type of object (oxvendor/oxcategory)\n     * @param string                                 $sStdUrl stanradr url\n     * @param string                                 $sSeoUrl seo uri\n     * @param string                                 $sParams additional params, liek page number etc. mostly used by mysql for indexes\n     * @param int                                    $iLang   language\n     * @param bool                                   $blFixed fixed url marker (default is false)\n     *\n     * @return string\n     */\n    protected function getPageUri($oObject, $sType, $sStdUrl, $sSeoUrl, $sParams, $iLang = null, $blFixed = false)\n    {\n        if (!isset($iLang)) {\n            $iLang = $oObject->getLanguage();\n        }\n        $iShopId = Registry::getConfig()->getShopId();\n\n        //load page link from DB\n        $sOldSeoUrl = $this->loadFromDb($sType, $oObject->getId(), $iLang, $iShopId, $sParams);\n        if (!$sOldSeoUrl) {\n            // generating new..\n            $sSeoUrl = $this->processSeoUrl($sSeoUrl, $oObject->getId(), $iLang);\n            $this->saveToDb($sType, $oObject->getId(), $sStdUrl, $sSeoUrl, $iLang, $iShopId, (int) $blFixed, $sParams);\n        } else {\n            // using old\n            $sSeoUrl = $sOldSeoUrl;\n        }\n\n        return $sSeoUrl;\n    }\n\n    /**\n     * Generates static url object id\n     *\n     * @param int    $iShopId shop id\n     * @param string $sStdUrl standard (dynamic) url\n     *\n     * @return string\n     */\n    protected function getStaticObjectId($iShopId, $sStdUrl)\n    {\n        return md5(strtolower($iShopId . $this->trimUrl($sStdUrl)));\n    }\n\n    /**\n     * Static url encoder\n     *\n     * @param array $aStaticUrl static url info (contains standard URL and urls for each language)\n     * @param int   $iShopId    active shop id\n     * @param int   $iLang      active language\n     *\n     * @throws Exception\n     *\n     * @return null\n     */\n    public function encodeStaticUrls($aStaticUrl, $iShopId, $iLang)\n    {\n        $db = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $sValues = '';\n        $sOldObjectId = null;\n\n        // standard url\n        $sStdUrl = $this->trimUrl(trim($aStaticUrl['oxseo__oxstdurl']));\n        $sObjectId = $aStaticUrl['oxseo__oxobjectid'];\n\n        if (!$sObjectId || $sObjectId == '-1') {\n            $sObjectId = $this->getStaticObjectId($iShopId, $sStdUrl);\n        } else {\n            // marking entry as needs to move to history\n            $sOldObjectId = $sObjectId;\n\n            // if std url does not match old\n            if ($this->getStaticObjectId($iShopId, $sStdUrl) != $sObjectId) {\n                $sObjectId = $this->getStaticObjectId($iShopId, $sStdUrl);\n            }\n        }\n\n        foreach ($aStaticUrl['oxseo__oxseourl'] as $iLang => $sSeoUrl) {\n            $iLang = (int) $iLang;\n\n            // generating seo url\n            $sSeoUrl = $this->trimUrl($sSeoUrl);\n            if ($sSeoUrl) {\n                $sSeoUrl = $this->processSeoUrl($sSeoUrl, $sObjectId, $iLang);\n            }\n\n            if ($sOldObjectId) {\n                // Transaction picks master automatically (see ESDEV-3804 and ESDEV-3822).\n                $db->startTransaction();\n                try {\n                    // move changed records to history\n                    $result = $db->getOne(\"select (:oxseourl like oxseourl) & (:oxstdurl like oxstdurl) from oxseo where oxobjectid = :oxobjectid and oxshopid = :oxshopid and oxlang = :oxlang\", [\n                        'oxseourl' => $sSeoUrl,\n                        'oxstdurl' => $sStdUrl,\n                        'oxobjectid' => $sOldObjectId,\n                        'oxshopid' => $iShopId,\n                        'oxlang' => $iLang\n                    ]);\n                    if (!$result) {\n                        $this->copyToHistory($sOldObjectId, $iShopId, $iLang, 'static', $sObjectId);\n                    }\n\n                    $db->commitTransaction();\n                } catch (Exception $exception) {\n                    $db->rollbackTransaction();\n\n                    throw $exception;\n                }\n            }\n\n            if (!$sSeoUrl || !$sStdUrl) {\n                continue;\n            }\n\n            $sIdent = $this->getSeoIdent($sSeoUrl);\n\n            if ($sValues) {\n                $sValues .= ', ';\n            }\n\n            $sValues .= \"( \" . $db->quote($sObjectId) . \", \" . $db->quote($sIdent) . \", \" . $db->quote($iShopId) . \", '{$iLang}', \" . $db->quote($sStdUrl) . \", \" . $db->quote($sSeoUrl) . \", 'static' )\";\n        }\n\n        // must delete old before insert/update\n        if ($sOldObjectId) {\n            $this->executeDatabaseQuery(\"delete from oxseo where oxobjectid in ( \" . $db->quote($sOldObjectId) . \", \" . $db->quote($sObjectId) . \" )\");\n        }\n\n        // (re)inserting\n        if ($sValues) {\n            $sql = \"insert into oxseo ( oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype ) values {$sValues} \";\n            $this->executeDatabaseQuery($sql);\n        }\n\n        return $sObjectId;\n    }\n\n    /**\n     * Method copies static urls from base shop to newly created\n     *\n     * @param int $iShopId new created shop id\n     */\n    public function copyStaticUrls($iShopId)\n    {\n        $iBaseShopId = Registry::getConfig()->getBaseShopId();\n        if ($iShopId != $iBaseShopId) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n            foreach (array_keys(Registry::getLang()->getLanguageIds()) as $iLang) {\n                $sQ = \"insert into oxseo ( oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype )\n                       select MD5( LOWER( CONCAT( :shopId, oxstdurl ) ) ), MD5( LOWER( oxseourl ) ),\n                       :shopId, oxlang, oxstdurl, oxseourl, oxtype from oxseo where oxshopid = :baseShopId and oxtype = 'static' and oxlang = :lang\";\n                $oDb->execute($sQ, [\n                    'shopId' => $iShopId,\n                    'baseShopId' => $iBaseShopId,\n                    'lang' => $iLang\n                ]);\n            }\n        }\n    }\n\n    /**\n     * Returns static url for passed standard link (if available)\n     *\n     * @param string $sStdUrl standard Url\n     * @param int    $iLang   active language (optional). default null\n     * @param int    $iShopId active shop id (optional). default null\n     *\n     * @return string\n     */\n    public function getStaticUrl($sStdUrl, $iLang = null, $iShopId = null)\n    {\n        if (!isset($iShopId)) {\n            $iShopId = Registry::getConfig()->getShopId();\n        }\n        if (!isset($iLang)) {\n            $iLang = Registry::getLang()->getEditLanguage();\n        }\n\n        if (isset($this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId])) {\n            return $this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId];\n        }\n\n        $sFullUrl = '';\n        if (($sSeoUrl = $this->getStaticUri($sStdUrl, $iShopId, $iLang))) {\n            $sFullUrl = $this->getFullUrl($sSeoUrl, $iLang, strpos($sStdUrl, \"https:\") === 0);\n        }\n\n\n        $this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId] = $sFullUrl;\n\n        return $sFullUrl;\n    }\n\n    /**\n     * Adds new seo entry to db\n     *\n     * @param string $sObjectId    objects id\n     * @param int    $iShopId      shop id\n     * @param int    $iLang        objects language\n     * @param string $sStdUrl      default url\n     * @param string $sSeoUrl      seo url\n     * @param string $sType        object type\n     * @param bool   $blFixed      marker to keep seo config unchangeable\n     * @param string $sKeywords    seo keywords\n     * @param string $sDescription seo description\n     * @param string $sParams      additional seo params. optional (mostly used for db indexing)\n     * @param bool   $blExclude    exclude language prefix while building seo url\n     * @param string $sAltObjectId alternative object id used while saving meta info (used to override object id when saving tags related info)\n     */\n    public function addSeoEntry($sObjectId, $iShopId, $iLang, $sStdUrl, $sSeoUrl, $sType, $blFixed = 1, $sKeywords = '', $sDescription = '', $sParams = '', $blExclude = false, $sAltObjectId = null)\n    {\n        $sSeoUrl = $this->processSeoUrl($this->trimUrl($sSeoUrl ? $sSeoUrl : $this->getAltUri($sAltObjectId ? $sAltObjectId : $sObjectId, $iLang)), $sObjectId, $iLang, $blExclude);\n        if ($this->saveToDb($sType, $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId, $blFixed, $sParams)) {\n            $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n            $oStr = Str::getStr();\n            if ($sKeywords !== false) {\n                $sKeywords = $oStr->htmlspecialchars($this->encodeString($oStr->strip_tags($sKeywords), false, $iLang));\n            }\n\n            if ($sDescription !== false) {\n                $sDescription = $oStr->htmlspecialchars($oStr->strip_tags($sDescription));\n            }\n\n            $sQ = \"insert into oxobject2seodata\n                       (oxobjectid, oxshopid, oxlang, oxkeywords, oxdescription)\n                   values\n                       (:oxobjectid, :oxshopid, :oxlang, :insertKeywords, :insertDescription)\n                   on duplicate key update\n                       oxkeywords = :updateKeywords, oxdescription = :updateDescription\";\n\n            $objectId = $sAltObjectId ?: $sObjectId;\n            $insertKeywords = $sKeywords ?: '';\n            $insertDescription = $sDescription ?: '';\n            $updateKeywords = ($sKeywords || $sKeywords == '') ? $sKeywords : 'oxkeywords';\n            $updateDescription = ($sDescription || $sDescription == '') ? $sDescription : 'oxdescription';\n\n            $oDb->execute($sQ, [\n                'oxobjectid' => $objectId,\n                'oxshopid' => $iShopId,\n                'oxlang' => $iLang,\n                'insertKeywords' => $insertKeywords,\n                'insertDescription' => $insertDescription,\n                'updateKeywords' => $updateKeywords,\n                'updateDescription' => $updateDescription,\n            ]);\n        }\n    }\n\n    /**\n     * Returns alternative uri used while updating seo\n     *\n     * @param string $sObjectId object id\n     * @param int    $iLang     language id\n     */\n    protected function getAltUri($sObjectId, $iLang)\n    {\n    }\n\n    /**\n     * Remove a SEO entry from the database.\n     *\n     * @param string $objectId The id of the object to delete.\n     * @param int    $shopId   The shop id of the object to delete.\n     * @param int    $language The language of the object to delete.\n     * @param string $type     The type of the object to delete.\n     */\n    public function deleteSeoEntry($objectId, $shopId, $language, $type)\n    {\n        $query = \"delete from oxseo where oxobjectid = :oxobjectid and oxshopid = :oxshopid and oxlang = :oxlang and oxtype = :oxtype\";\n\n        $this->executeDatabaseQuery($query, [\n            'oxobjectid' => $objectId,\n            'oxshopid' => $shopId,\n            'oxlang' => $language,\n            'oxtype' => $type,\n        ]);\n    }\n\n    /**\n     * Execute a query on the database.\n     *\n     * @param string $query  The command to execute on the database.\n     * @param array  $params Parameters used in prepare statement.\n     */\n    protected function executeDatabaseQuery($query, $params = [])\n    {\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $database->execute($query, $params);\n    }\n\n    /**\n     * Returns meta information for preferred object\n     *\n     * @param string $sObjectId information object id\n     * @param string $sMetaType metadata type - \"oxkeywords\", \"oxdescription\"\n     * @param int    $iShopId   active shop id [optional]\n     * @param int    $iLang     active language [optional]\n     *\n     * @return string\n     */\n    public function getMetaData($sObjectId, $sMetaType, $iShopId = null, $iLang = null)\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $iShopId = (!isset($iShopId)) ? Registry::getConfig()->getShopId() : $iShopId;\n        $iLang = (!isset($iLang)) ? Registry::getLang()->getObjectTplLanguage() : ((int) $iLang);\n\n        return $oDb->getOne(\"SELECT {$sMetaType} FROM oxobject2seodata WHERE oxobjectid = :oxobjectid AND oxshopid = :oxshopid AND oxlang = :oxlang\", [\n            'oxobjectid' => $sObjectId,\n            'oxshopid' => $iShopId,\n            'oxlang' => $iLang\n        ]);\n    }\n\n    /**\n     * getDynamicUrl acts similar to static urls,\n     * except, that dynamic url are not shown in admin\n     * and they can be re-encoded by providing new seo url\n     *\n     * @param string $sStdUrl standard url\n     * @param string $sSeoUrl part of URL query which will be attached to standard shop url\n     * @param int    $iLang   active language\n     *\n     * @access public\n     *\n     * @return string\n     */\n    public function getDynamicUrl($sStdUrl, $sSeoUrl, $iLang)\n    {\n        startProfile(\"getDynamicUrl\");\n        $sDynUrl = $this->getFullUrl($this->getDynamicUri($sStdUrl, $sSeoUrl, $iLang), $iLang, strpos($sStdUrl, \"https:\") === 0);\n        stopProfile(\"getDynamicUrl\");\n\n        return $sDynUrl;\n    }\n\n    /**\n     * Searches for seo url in seo table. If not found - FALSE is returned\n     *\n     * @param string $standardUrl\n     * @param int    $languageId\n     *\n     * @return string|false\n     */\n    public function fetchSeoUrl($standardUrl, $languageId = null)\n    {\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n        $languageId = isset($languageId) ? ((int) $languageId) : Registry::getLang()->getBaseLanguage();\n\n        $shopId = Registry::getConfig()->getShopId();\n\n        $utilsUrl = Registry::getUtilsUrl();\n        $urlParameters = $utilsUrl->stringToParamsArray($standardUrl);\n        $noPageNrStandardUrl = $utilsUrl->cleanUrlParams($utilsUrl->cleanUrl($standardUrl, ['pgNr']));\n        $postfix = isset($urlParameters['pgNr']) ? 'pgNr=' . $urlParameters['pgNr'] : '';\n\n        $query = \"SELECT `oxseourl` FROM `oxseo` WHERE `oxstdurl` = :oxstdurl AND `oxlang` = :oxlang AND `oxshopid` = :oxshopid LIMIT 1\";\n        $result = $database->getOne($query, [\n            'oxstdurl' => $noPageNrStandardUrl,\n            'oxlang' => $languageId,\n            'oxshopid' => $shopId\n        ]);\n        $result = ((false !== $result) && !empty($postfix)) ? $utilsUrl->appendParamSeparator($result) . $postfix : $result;\n\n        return $result;\n    }\n\n    /**\n     * Searches for special characters in a string and replaces them with the configured strings.\n     *\n     * @param string $stringWithSpecialChars\n     *\n     * @return string\n     */\n    protected function replaceSpecialChars($stringWithSpecialChars)\n    {\n        if (!is_string($stringWithSpecialChars)) {\n            return \"\";\n        }\n        $oStr = Str::getStr();\n        $sQuotedPrefix = preg_quote(self::$_sSeparator . self::$_sPrefix, '/');\n        $sRegExp = '/[^A-Za-z0-9' . $sQuotedPrefix . '\\/]+/';\n        $sanitized = $oStr->preg_replace(\n            [\"/\\W*\\/\\W*/\", $sRegExp],\n            [\"/\", self::$_sSeparator],\n            $stringWithSpecialChars\n        );\n\n        return $sanitized;\n    }\n\n    /**\n     * Assemble full paginated url.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Model\\BaseModel $object     Object, atm category, vendor, manufacturer, recommendationList.\n     * @param string                               $type       Seo identifier, see oxseo.oxtype.\n     * @param string                               $stdUrl     Standard url\n     * @param string                               $seoUrl     Seo url\n     * @param integer                              $pageNumber Number of the page which should be prepared.\n     * @param string                               $parameters Additional parameters, mostly used by mysql for indices.\n     * @param int                                  $languageId Language id.\n     * @param bool                                 $isFixed    Fixed url marker (default is null).\n     *\n     * @return string\n     */\n    protected function assembleFullPageUrl($object, $type, $stdUrl, $seoUrl, $pageNumber, $parameters, $languageId, $isFixed)\n    {\n        $postfix = (int) $pageNumber > 0 ? 'pgNr=' . (int) $pageNumber : '';\n        $urlPart = $this->getPageUri($object, $type, $stdUrl, $seoUrl, $parameters, $languageId, $isFixed);\n        $fullUrl = $this->getFullUrl($urlPart, $languageId);\n        $fullUrl = (!empty($postfix)) ? Registry::getUtilsUrl()->appendParamSeparator($fullUrl) . $postfix : $fullUrl;\n\n        return $fullUrl;\n    }\n}\n"
  },
  {
    "path": "source/Core/SepaBICValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Str;\n\n/**\n * SEPA (Single Euro Payments Area) BIC validation class\n */\nclass SepaBICValidator\n{\n    /**\n     * Business identifier code validation\n     *\n     * Structure\n     *  - 4 letters: Institution Code or bank code.\n     *  - 2 letters: ISO 3166-1 alpha-2 country code\n     *  - 2 letters or digits: location code\n     *  - 3 letters or digits: branch code, optional\n     *\n     * @param string $sBIC code to check\n     *\n     * @return bool\n     */\n    public function isValid($sBIC)\n    {\n        $sBIC = strtoupper(trim($sBIC));\n\n        return (bool) Str::getStr()->preg_match(\"(^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$)\", $sBIC);\n    }\n}\n"
  },
  {
    "path": "source/Core/SepaIBANValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Str;\n\n/**\n * SEPA (Single Euro Payments Area) validation class\n */\nclass SepaIBANValidator\n{\n    const IBAN_ALGORITHM_MOD_VALUE = 97;\n\n    protected $_aCodeLengths = [];\n\n    /**\n     * International bank account number validation\n     *\n     * An IBAN is validated by converting it into an integer and performing a basic mod-97 operation (as described in ISO 7064) on it.\n     * If the IBAN is valid, the remainder equals 1.\n     *\n     * @param string $sIBAN code to check\n     *\n     * @return bool\n     */\n    public function isValid($sIBAN)\n    {\n        $blValid = false;\n        $sIBAN = strtoupper(trim($sIBAN));\n\n        if ($this->isLengthValid($sIBAN)) {\n            $blValid = $this->isAlgorithmValid($sIBAN);\n        }\n\n        return $blValid;\n    }\n\n    /**\n     * Validation of IBAN registry\n     *\n     * @param array $aCodeLengths\n     *\n     * @return bool\n     */\n    public function isValidCodeLengths($aCodeLengths)\n    {\n        $blValid = false;\n        if ($this->isNotEmptyArray($aCodeLengths)) {\n            $blValid = $this->isEachCodeLengthValid($aCodeLengths);\n        }\n\n        return $blValid;\n    }\n\n    /**\n     * Set IBAN Registry\n     *\n     * @param array $aCodeLengths\n     *\n     * @return bool\n     */\n    public function setCodeLengths($aCodeLengths)\n    {\n        if ($this->isValidCodeLengths($aCodeLengths)) {\n            $this->_aCodeLengths = $aCodeLengths;\n\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    /**\n     * Get IBAN length by country data\n     *\n     * @return array\n     */\n    public function getCodeLengths()\n    {\n        return $this->_aCodeLengths;\n    }\n\n\n    /**\n     * Check if the total IBAN length is correct as per country. If not, the IBAN is invalid.\n     *\n     * @param string $sIBAN IBAN\n     *\n     * @return bool\n     */\n    protected function isLengthValid($sIBAN)\n    {\n        $iActualLength = Str::getStr()->strlen($sIBAN);\n\n        $iCorrectLength = $this->getLengthForCountry($sIBAN);\n\n        return !is_null($iCorrectLength) && $iActualLength === $iCorrectLength;\n    }\n\n\n    /**\n     * Gets length for country.\n     *\n     * @param string $sIBAN IBAN\n     *\n     * @return null\n     */\n    protected function getLengthForCountry($sIBAN)\n    {\n        $aIBANRegistry = $this->getCodeLengths();\n\n        $sCountryCode = Str::getStr()->substr($sIBAN, 0, 2);\n\n        $iCorrectLength = (isset($aIBANRegistry[$sCountryCode])) ? $aIBANRegistry[$sCountryCode] : null;\n\n        return $iCorrectLength;\n    }\n\n    /**\n     * Checks if IBAN is valid according to checksum algorithm\n     *\n     * @param string $sIBAN IBAN\n     *\n     * @return bool\n     */\n    protected function isAlgorithmValid($sIBAN)\n    {\n        $sIBAN = $this->moveInitialCharactersToEnd($sIBAN);\n\n        $sIBAN = $this->replaceLettersToNumbers($sIBAN);\n\n        return $this->isIBANChecksumValid($sIBAN);\n    }\n\n    /**\n     * Move the four initial characters to the end of the string.\n     *\n     * @param string $sIBAN IBAN\n     *\n     * @return string\n     */\n    protected function moveInitialCharactersToEnd($sIBAN)\n    {\n        $oStr = Str::getStr();\n\n        $sInitialChars = $oStr->substr($sIBAN, 0, 4);\n        $sIBAN = $oStr->substr($sIBAN, 4);\n\n        return $sIBAN . $sInitialChars;\n    }\n\n    /**\n     * Replace each letter in the string with two digits, thereby expanding the string, where A = 10, B = 11, ..., Z = 35.\n     *\n     * @param string $sIBAN IBAN\n     *\n     * @return string\n     */\n    protected function replaceLettersToNumbers($sIBAN)\n    {\n        $aReplaceArray = [\n            'A' => 10,\n            'B' => 11,\n            'C' => 12,\n            'D' => 13,\n            'E' => 14,\n            'F' => 15,\n            'G' => 16,\n            'H' => 17,\n            'I' => 18,\n            'J' => 19,\n            'K' => 20,\n            'L' => 21,\n            'M' => 22,\n            'N' => 23,\n            'O' => 24,\n            'P' => 25,\n            'Q' => 26,\n            'R' => 27,\n            'S' => 28,\n            'T' => 29,\n            'U' => 30,\n            'V' => 31,\n            'W' => 32,\n            'X' => 33,\n            'Y' => 34,\n            'Z' => 35\n        ];\n\n        return str_replace(\n            array_keys($aReplaceArray),\n            $aReplaceArray,\n            $sIBAN\n        );\n    }\n\n    /**\n     * Interpret the string as a decimal integer and compute the remainder of that number on division by 97.\n     *\n     * @param string $sIBAN IBAN\n     *\n     * @return bool\n     */\n    protected function isIBANChecksumValid($sIBAN)\n    {\n        return (int) bcmod($sIBAN, self::IBAN_ALGORITHM_MOD_VALUE) === 1;\n    }\n\n    /**\n     * Checks if Code length is non empty array\n     *\n     * @param array $aCodeLengths Code lengths\n     *\n     * @return bool\n     */\n    protected function isNotEmptyArray($aCodeLengths)\n    {\n        return is_array($aCodeLengths) && !empty($aCodeLengths);\n    }\n\n    /**\n     * Checks if each code length is valid.\n     *\n     * @param array $aCodeLengths Code lengths\n     *\n     * @return bool\n     */\n    protected function isEachCodeLengthValid($aCodeLengths)\n    {\n        $blValid = true;\n\n        foreach ($aCodeLengths as $sCountryAbbr => $iLength) {\n            if (\n                !$this->isCodeLengthKeyValid($sCountryAbbr) ||\n                !$this->isCodeLengthValueValid($iLength)\n            ) {\n                $blValid = false;\n                break;\n            }\n        }\n\n        return $blValid;\n    }\n\n    /**\n     * Checks if country code is valid\n     *\n     * @param string $sCountryAbbr Country abbreviation\n     *\n     * @return bool\n     */\n    protected function isCodeLengthKeyValid($sCountryAbbr)\n    {\n        return (int) preg_match(\"/^[A-Z]{2}$/\", $sCountryAbbr) !== 0;\n    }\n\n    /**\n     * Checks if value is numeric and does not contain whitespaces\n     *\n     * @param integer $iLength Length\n     *\n     * @return bool\n     */\n    protected function isCodeLengthValueValid($iLength)\n    {\n        return is_numeric($iLength) && (int) preg_match(\"/\\./\", $iLength) !== 1;\n    }\n}\n"
  },
  {
    "path": "source/Core/SepaValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * SEPA (Single Euro Payments Area) validation class\n */\nclass SepaValidator\n{\n    /**\n     * @var array IBAN Code Length array\n     */\n    protected $_aIBANCodeLengths = [\n        'AL' => 28,\n        'AD' => 24,\n        'AT' => 20,\n        'AZ' => 28,\n        'BH' => 22,\n        'BE' => 16,\n        'BA' => 20,\n        'BR' => 29,\n        'BG' => 22,\n        'CR' => 21,\n        'HR' => 21,\n        'CY' => 28,\n        'CZ' => 24,\n        'DK' => 18, // Same DENMARK\n        'FO' => 18, // Same DENMARK\n        'GL' => 18, // Same DENMARK\n        'DO' => 28,\n        'EE' => 20,\n        'FI' => 18,\n        'FR' => 27,\n        'GE' => 22,\n        'DE' => 22,\n        'GI' => 23,\n        'GR' => 27,\n        'GT' => 28,\n        'HU' => 28,\n        'IS' => 26,\n        'IE' => 22,\n        'IL' => 23,\n        'IT' => 27,\n        'KZ' => 20,\n        'KW' => 30,\n        'LV' => 21,\n        'LB' => 28,\n        'LI' => 21,\n        'LT' => 20,\n        'LU' => 20,\n        'MK' => 19,\n        'MT' => 31,\n        'MR' => 27,\n        'MU' => 30,\n        'MD' => 24,\n        'MC' => 27,\n        'ME' => 22,\n        'NL' => 18,\n        'NO' => 15,\n        'PK' => 24,\n        'PS' => 29,\n        'PL' => 28,\n        'PT' => 25,\n        'RO' => 24,\n        'SM' => 27,\n        'SA' => 24,\n        'RS' => 22,\n        'SK' => 24,\n        'SI' => 19,\n        'ES' => 24,\n        'SE' => 24,\n        'CH' => 21,\n        'TN' => 24,\n        'TR' => 26,\n        'AE' => 23,\n        'GB' => 22,\n        'VG' => 24\n    ];\n\n    /**\n     * Business identifier code validation\n     *\n     * @param string $sBIC code to check\n     *\n     * @return bool\n     */\n    public function isValidBIC($sBIC)\n    {\n        $oBICValidator = oxNew(\\OxidEsales\\Eshop\\Core\\SepaBICValidator::class);\n\n        return $oBICValidator->isValid($sBIC);\n    }\n\n    /**\n     * International bank account number validation\n     *\n     * @param string $sIBAN code to check\n     *\n     * @return bool\n     */\n    public function isValidIBAN($sIBAN)\n    {\n        $oIBANValidator = oxNew(\\OxidEsales\\Eshop\\Core\\SepaIBANValidator::class);\n        $oIBANValidator->setCodeLengths($this->getIBANCodeLengths());\n\n        return $oIBANValidator->isValid($sIBAN);\n    }\n\n    /**\n     * Get IBAN length by country data\n     *\n     * @return array\n     */\n    public function getIBANCodeLengths()\n    {\n        return $this->_aIBANCodeLengths;\n    }\n}\n"
  },
  {
    "path": "source/Core/Service/ApplicationServerExporter.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Service;\n\n/**\n * Prepare application servers information for export.\n *\n * @internal Do not make a module extension for this class.\n */\nclass ApplicationServerExporter implements \\OxidEsales\\Eshop\\Core\\Service\\ApplicationServerExporterInterface\n{\n    /**\n     * The service class of application server.\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Service\\ApplicationServerServiceInterface\n     */\n    private $appServerService;\n\n    /**\n     * ApplicationServerExporter constructor.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Service\\ApplicationServerServiceInterface $appServerService\n     */\n    public function __construct(\\OxidEsales\\Eshop\\Core\\Service\\ApplicationServerServiceInterface $appServerService)\n    {\n        $this->appServerService = $appServerService;\n    }\n\n    /**\n     * Return an array of active application servers.\n     *\n     * @return array\n     */\n    public function exportAppServerList()\n    {\n        $activeServerCollection = [];\n\n        $activeServers = $this->appServerService->loadActiveAppServerList();\n        if (is_array($activeServers) && !empty($activeServers)) {\n            foreach ($activeServers as $server) {\n                $activeServerCollection[] = $this->convertToArray($server);\n            }\n        }\n\n        return $activeServerCollection;\n    }\n\n    /**\n     * Converts ApplicationServer object into array for export.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer $server\n     *\n     * @return array\n     */\n    private function convertToArray($server)\n    {\n        $activeServer = [\n            'id' => $server->getId(),\n            'ip' => $server->getIp(),\n            'lastFrontendUsage' => $server->getLastFrontendUsage(),\n            'lastAdminUsage' => $server->getLastAdminUsage()\n        ];\n        return $activeServer;\n    }\n}\n"
  },
  {
    "path": "source/Core/Service/ApplicationServerExporterInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Service;\n\n/**\n * Prepare application servers information for export.\n *\n * @internal Do not make a module extension for this class.\n */\ninterface ApplicationServerExporterInterface\n{\n    /**\n     * Return active server nodes.\n     *\n     * @return array\n     */\n    public function exportAppServerList();\n}\n"
  },
  {
    "path": "source/Core/Service/ApplicationServerService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Service;\n\n/**\n * Manages application server information.\n *\n * @internal Do not make a module extension for this class.\n */\nclass ApplicationServerService implements \\OxidEsales\\Eshop\\Core\\Service\\ApplicationServerServiceInterface\n{\n    /**\n     * The Dao object for application server.\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Dao\\ApplicationServerDaoInterface\n     */\n    private $appServerDao;\n\n    /**\n     * Current checking time - timestamp.\n     *\n     * @var int\n     */\n    private $currentTime = 0;\n\n    /**\n     * Server data manipulation class\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\UtilsServer\n     */\n    private $utilsServer;\n\n    /**\n     * ApplicationServerService constructor.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Dao\\ApplicationServerDaoInterface $appServerDao The Dao of application server.\n     * @param \\OxidEsales\\Eshop\\Core\\UtilsServer                       $utilsServer\n     * @param int                                                      $currentTime  The current time - timestamp.\n     */\n    public function __construct(\n        \\OxidEsales\\Eshop\\Core\\Dao\\ApplicationServerDaoInterface $appServerDao,\n        $utilsServer,\n        $currentTime\n    ) {\n        $this->appServerDao = $appServerDao;\n        $this->utilsServer = $utilsServer;\n        $this->currentTime = $currentTime;\n    }\n\n    /**\n     * Returns an array of all application servers.\n     *\n     * @return array\n     */\n    public function loadAppServerList()\n    {\n        return $this->appServerDao->findAll();\n    }\n\n    /**\n     * Load the application server for given id.\n     *\n     * @param string $id The id of the application server to load.\n     *\n     * @throws \\OxidEsales\\Eshop\\Core\\Exception\\NoResultException\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer\n     */\n    public function loadAppServer($id)\n    {\n        /** @var \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer $appServer */\n        $appServer = $this->appServerDao->findAppServer($id);\n        if ($appServer === null) {\n            /** @var \\OxidEsales\\Eshop\\Core\\Exception\\NoResultException $exception */\n            $exception = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\NoResultException::class);\n            throw $exception;\n        }\n        return $appServer;\n    }\n\n    /**\n     * Removes server node information.\n     *\n     * @param string $serverId The Id of the application server to delete.\n     */\n    public function deleteAppServerById($serverId)\n    {\n        $this->appServerDao->delete($serverId);\n    }\n\n    /**\n     * Saves application server data.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer $appServer\n     */\n    public function saveAppServer($appServer)\n    {\n        $this->appServerDao->save($appServer);\n    }\n\n    /**\n     * Returns an array of all only active application servers.\n     *\n     * @return array\n     */\n    public function loadActiveAppServerList()\n    {\n        $allFoundServers = $this->loadAppServerList();\n        return $this->filterActiveAppServers($allFoundServers);\n    }\n\n    /**\n     * Filter only active application servers from given list.\n     *\n     * @param array $appServerList The list of application servers.\n     *\n     * @return array\n     */\n    protected function filterActiveAppServers($appServerList)\n    {\n        $activeServerList = [];\n        /** @var \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer $server */\n        foreach ($appServerList as $server) {\n            if ($server->isInUse($this->currentTime)) {\n                $activeServerList[$server->getId()] = $server;\n            }\n        }\n        return $activeServerList;\n    }\n\n    /**\n     * Deletes all application servers, that are longer not active.\n     */\n    private function cleanupAppServers()\n    {\n        $allFoundServers = $this->loadAppServerList();\n        /** @var \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer $server */\n        foreach ($allFoundServers as $server) {\n            if ($server->needToDelete($this->currentTime)) {\n                $this->deleteAppServerById($server->getId());\n            }\n        }\n    }\n\n    /**\n     * Renews application server information when it is call in admin area and\n     * if it is outdated or if it does not exist.\n     */\n    public function updateAppServerInformationInAdmin()\n    {\n        $this->updateAppServerInformation(true);\n    }\n\n    /**\n     * Renews application server information when it is call in frontend and\n     * if it is outdated or if it does not exist.\n     */\n    public function updateAppServerInformationInFrontend()\n    {\n        $this->updateAppServerInformation(false);\n    }\n\n    /**\n     * Renews application server information if it is outdated or if it does not exist.\n     *\n     * @throws \\Exception\n     *\n     * @param bool $adminMode The status of admin mode\n     */\n    public function updateAppServerInformation($adminMode)\n    {\n        $this->appServerDao->startTransaction();\n        try {\n            /** @var \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer $appServer */\n            $appServer = $this->appServerDao->findAppServer($this->utilsServer->getServerNodeId());\n            if ($appServer === null) {\n                $this->addNewAppServerData($adminMode);\n            } elseif ($appServer->needToUpdate($this->currentTime)) {\n                $this->updateAppServerData($appServer, $adminMode);\n            }\n        } catch (\\Exception $exception) {\n            $this->appServerDao->rollbackTransaction();\n            throw $exception;\n        }\n        $this->appServerDao->commitTransaction();\n    }\n\n    /**\n     * Updates application server with the newest information.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer $appServer The application server to update.\n     * @param bool                                                $adminMode The status of admin mode.\n     */\n    private function updateAppServerData($appServer, $adminMode)\n    {\n        $appServer->setId($this->utilsServer->getServerNodeId());\n        $appServer->setIp($this->utilsServer->getServerIp());\n        $appServer->setTimestamp($this->currentTime);\n        if ($adminMode) {\n            $appServer->setLastAdminUsage($this->currentTime);\n        } else {\n            $appServer->setLastFrontendUsage($this->currentTime);\n        }\n        $this->saveAppServer($appServer);\n        $this->cleanupAppServers();\n    }\n\n    /**\n     * Adds new application server.\n     *\n     * @param bool $adminMode The status of admin mode.\n     */\n    private function addNewAppServerData($adminMode)\n    {\n        /** @var \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer $appServer */\n        $appServer = oxNew(\\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer::class);\n\n        $appServer->setId($this->utilsServer->getServerNodeId());\n        $appServer->setIp($this->utilsServer->getServerIp());\n        $appServer->setTimestamp($this->currentTime);\n        if ($adminMode) {\n            $appServer->setLastAdminUsage($this->currentTime);\n        } else {\n            $appServer->setLastFrontendUsage($this->currentTime);\n        }\n        $this->saveAppServer($appServer);\n        $this->cleanupAppServers();\n    }\n}\n"
  },
  {
    "path": "source/Core/Service/ApplicationServerServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\Service;\n\n/**\n * Manages application servers information.\n *\n * @internal Do not make a module extension for this class.\n */\ninterface ApplicationServerServiceInterface\n{\n    /**\n     * Returns all servers information array from configuration.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer[]\n     */\n    public function loadAppServerList();\n\n    /**\n     * Load the application server for given id.\n     *\n     * @param string $id The id of the application server to load.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer\n     */\n    public function loadAppServer($id);\n\n    /**\n     * Removes server node information.\n     *\n     * @param string $serverId\n     */\n    public function deleteAppServerById($serverId);\n\n    /**\n     * Saves application server data.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer $appServer\n     */\n    public function saveAppServer($appServer);\n\n    /**\n     * Returns an array of all only active application servers.\n     *\n     * @return array\n     */\n    public function loadActiveAppServerList();\n\n    /**\n     * Renews application server information when it is call in admin area and\n     * if it is outdated or if it does not exist.\n     */\n    public function updateAppServerInformationInAdmin();\n\n    /**\n     * Renews application server information when it is call in frontend and\n     * if it is outdated or if it does not exist.\n     */\n    public function updateAppServerInformationInFrontend();\n}\n"
  },
  {
    "path": "source/Core/Session.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Basket;\nuse OxidEsales\\Eshop\\Application\\Model\\BasketItem;\nuse OxidEsales\\Eshop\\Application\\Model\\User;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\n\n/**\n * Session manager.\n * Performs session managing function, such as variables deletion,\n * initialisation and other session functions.\n */\nclass Session extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Session parameter name\n     *\n     * @var string\n     */\n    protected $_sName = 'sid';\n\n    /**\n     * Session parameter name\n     *\n     * @var string\n     */\n    protected $_sForcedPrefix = 'force_';\n\n    /**\n     * Unique session ID.\n     *\n     * @var string\n     */\n    protected $_sId = null;\n\n    /**\n     * A flag indicating that session was just created, useful for tracking cookie support\n     *\n     * @var bool\n     */\n    protected static $_blIsNewSession = false;\n\n    /**\n     * Active session user object\n     *\n     * @var object\n     */\n    protected static $_oUser = null;\n\n    /**\n     * Indicates if setting of session id is executed in this script. After page transition\n     * This needed to be checked as new session is not written in db until it is closed\n     *\n     * @var bool\n     */\n    protected $_blNewSession = false;\n\n    /**\n     * Forces session to be started and skips checking if session is allowed\n     *\n     * @var bool\n     */\n    protected $_blForceNewSession = false;\n\n    /**\n     * Error message, used for debug purposes only\n     *\n     * @var string\n     */\n    protected $_sErrorMsg = null;\n\n    /**\n     * Basket session object\n     *\n     * @var object\n     */\n    protected $_oBasket = null;\n\n    /**\n     * Basket reservations object\n     *\n     * @var object\n     */\n    protected $_oBasketReservations = null;\n\n    /**\n     * Force session start by defined parameter rules.\n     * First level array keys are parameters to check which point to\n     * array of values which need session.\n     *\n     * @var array\n     * @see _getRequireSessionWithParams()\n     */\n    protected $_aRequireSessionWithParams = [\n        'cl'          => [\n            'register' => true,\n            'account'  => true,\n        ],\n        'fnc'         => [\n            'tobasket'         => true,\n            'login_noredirect' => true,\n            'tocomparelist'    => true,\n        ],\n        '_artperpage' => true,\n        'ldtype'      => true,\n        'listorderby' => true,\n    ];\n\n    /**\n     * Marker if processed urls must contain SID parameter\n     *\n     * @var bool\n     */\n    protected $_blSidNeeded = null;\n\n    /**\n     * Session params to be kept even after session timeout\n     *\n     * @var array\n     */\n    protected $_aPersistentParams = [\"actshop\", \"lang\", \"currency\", \"language\", \"tpllanguage\"];\n\n    /**\n     * Order steps which should not accept force_sid\n     *\n     * @var array\n     */\n    private $orderControllers = [\n        'payment',\n        'order',\n        'thankyou'\n    ];\n\n    /**\n     * Returns session ID\n     *\n     * @return string\n     */\n    public function getId()\n    {\n        return $this->_sId;\n    }\n\n    /**\n     * Sets session id\n     *\n     * @param string $sVal id value\n     */\n    public function setId($sVal)\n    {\n        $this->_sId = $sVal;\n    }\n\n    /**\n     * Sets session param name\n     *\n     * @param string $sVal name value\n     */\n    public function setName($sVal)\n    {\n        $this->_sName = $sVal;\n    }\n\n    /**\n     * Returns forced session id param name\n     *\n     * @return string\n     */\n    public function getForcedName()\n    {\n        return $this->_sForcedPrefix . $this->getName();\n    }\n\n    /**\n     * Returns session param name\n     *\n     * @return string\n     */\n    public function getName()\n    {\n        return $this->_sName;\n    }\n\n    /**\n     * retrieves the session id from the request if any\n     *\n     * @return string|null\n     */\n    protected function getSidFromRequest()\n    {\n        $myConfig = Registry::getConfig();\n        $sid = null;\n\n        $forceSidParam = null;\n        if (\n            !$this->isForceSidBlocked() &&\n            !in_array(Registry::getRequest()->getRequestEscapedParameter('cl'), $this->orderControllers)\n        ) {\n            $forceSidParam = Registry::getRequest()->getRequestEscapedParameter($this->getForcedName());\n        }\n        $sidParam = Registry::getRequest()->getRequestEscapedParameter($this->getName());\n\n        //forcing sid for SSL<->nonSSL transitions\n        if ($forceSidParam) {\n            $sid = $forceSidParam;\n        } elseif ($this->getSessionUseCookies() && $this->getCookieSid()) {\n            $sid = $this->getCookieSid();\n        } elseif ($sidParam) {\n            $sid = $sidParam;\n        }\n\n        return $sid;\n    }\n\n    /**\n     * Starts shop session, generates unique session ID, extracts user IP.\n     *\n     * @return void\n     */\n    public function start()\n    {\n        $this->setName($this->isAdmin() ? 'admin_sid' : 'sid');\n\n        $sid = $this->getSidFromRequest();\n        if ($sid) {\n            $this->setId($sid);\n        }\n\n        if ($this->isSessionStarted() === false && $this->allowSessionStart()) {\n            if (!$sid) {\n                self::$_blIsNewSession = true;\n                $this->initNewSession();\n            } else {\n                self::$_blIsNewSession = false;\n                $this->setSessionId($sid);\n                $this->sessionStart();\n            }\n\n            //special handling for new ZP cluster session, as in that case session_start() regenerates id\n            if ($this->getId() !== session_id()) {\n                $this->setId(session_id());\n            }\n\n            //checking for swapped client\n            $blSwapped = $this->isSwappedClient();\n            if (!self::$_blIsNewSession && $blSwapped) {\n                $this->initNewSession();\n\n                if ($this->_sErrorMsg && ContainerFacade::getParameter('oxid_esales.debug_mode')) {\n                    Registry::getUtilsView()->addErrorToDisplay(oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\StandardException::class, $this->_sErrorMsg));\n                }\n            } elseif (!$blSwapped) {\n                // transferring cookies between hosts\n                Registry::getUtilsServer()->loadSessionCookies();\n            }\n        }\n    }\n\n    /**\n     * retrieve session challenge token from request\n     *\n     * @return string\n     */\n    public function getRequestChallengeToken()\n    {\n        return preg_replace('/[^a-z0-9]/i', '', Registry::getRequest()->getRequestEscapedParameter('stoken') ?? '');\n    }\n\n    /**\n     * retrieve session challenge token from session\n     *\n     * @return string\n     */\n    public function getSessionChallengeToken()\n    {\n        $sRet = preg_replace('/[^a-z0-9]/i', '', $this->getVariable('sess_stoken') ?? '');\n        if (!$sRet) {\n            $this->initNewSessionChallenge();\n            $sRet = $this->getVariable('sess_stoken');\n        }\n\n        return $sRet;\n    }\n\n    /**\n     * check for CSRF, returns true, if request (get/post) token matches session saved var\n     * false, if CSRF is possible\n     *\n     * @return bool\n     */\n    public function checkSessionChallenge()\n    {\n        $sToken = $this->getSessionChallengeToken();\n\n        return $sToken && ($sToken == $this->getRequestChallengeToken());\n    }\n\n    /**\n     * initialize new session challenge token\n     */\n    protected function initNewSessionChallenge()\n    {\n        $this->setVariable('sess_stoken', sprintf('%X', crc32(Registry::getUtilsObject()->generateUID())));\n    }\n\n    /**\n     * Initialize session data (calls php::session_start())\n     *\n     * @return bool\n     */\n    protected function sessionStart()\n    {\n        if ($this->needToSetHeaders()) {\n            //enforcing no caching when session is started\n            session_cache_limiter('nocache');\n        } else {\n            session_cache_limiter('');\n        }\n\n        session_start();\n\n        if (!$this->getSessionChallengeToken()) {\n            $this->initNewSessionChallenge();\n        }\n\n        return $this->isSessionStarted();\n    }\n\n    /**\n     * Assigns new session ID, clean existing data except persistent.\n     */\n    public function initNewSession()\n    {\n        if (!$this->isSessionStarted()) {\n            $this->sessionStart();\n        }\n\n        //saving persistent params if old session exists\n        $aPersistent = [];\n        foreach ($this->_aPersistentParams as $sParam) {\n            if (($sValue = $this->getVariable($sParam))) {\n                $aPersistent[$sParam] = $sValue;\n            }\n        }\n\n        $sessionId = $this->getNewSessionId();\n        $this->setId($sessionId);\n        $this->setSessionCookie($sessionId);\n\n        //restoring persistent params to session\n        foreach ($aPersistent as $sKey => $sParam) {\n            $this->setVariable($sKey, $aPersistent[$sKey]);\n        }\n\n        $this->initNewSessionChallenge();\n\n        // (re)setting actual user agent when initiating new session\n        $this->setVariable(\"sessionagent\", Registry::getUtilsServer()->getServerVar('HTTP_USER_AGENT'));\n    }\n\n    /**\n     * Regenerates session id\n     */\n    public function regenerateSessionId()\n    {\n        if (!$this->isSessionStarted()) {\n            $this->sessionStart();\n\n            // (re)setting actual user agent when initiating new session\n            $this->setVariable(\"sessionagent\", Registry::getUtilsServer()->getServerVar('HTTP_USER_AGENT'));\n        }\n\n        $sessionId = $this->getNewSessionId(false);\n        $this->setId($sessionId);\n        $this->setSessionCookie($sessionId);\n\n        $this->initNewSessionChallenge();\n    }\n\n    /**\n     * Update the current session id with a newly generated one, deletes the\n     * old associated session file, frees all session variables.\n     *\n     * @param bool $blUnset if true, calls session_unset [optional]\n     *\n     * @return string\n     */\n    protected function getNewSessionId($blUnset = true)\n    {\n        session_regenerate_id(true);\n\n        if ($blUnset) {\n            session_unset();\n        }\n\n        return session_id();\n    }\n\n    /**\n     * Ends the current session and store session data.\n     */\n    public function freeze()\n    {\n        // storing basket ..\n        $this->setVariable($this->getBasketName(), serialize($this->getBasket()));\n\n        session_write_close();\n    }\n\n    /**\n     * Destroys all data registered to a session.\n     */\n    public function destroy()\n    {\n        unset($_SESSION);\n        session_destroy();\n    }\n\n    /**\n     * @deprecated use SessionInterface::has() instead\n     *\n     * Checks if variable is set in session. Returns true on success.\n     *\n     * @param string $name Name to check\n     *\n     * @return bool\n     */\n    public function hasVariable($name)\n    {\n        return isset($_SESSION[$name]);\n    }\n\n    /**\n     * @deprecated use SessionInterface::set() instead\n     *\n     * Sets parameter and its value to global session mixedvar array.\n     *\n     * @param string $name  Name of parameter to store\n     * @param mixed  $value Value of parameter\n     */\n    public function setVariable($name, $value)\n    {\n        $_SESSION[$name] = $value;\n    }\n\n    /**\n     * @deprecated use SessionInterface::get() instead\n     *\n     * IF available returns value of parameter, stored in session array.\n     *\n     * @param string $name Name of parameter\n     *\n     * @return mixed\n     */\n    public function getVariable($name)\n    {\n        return isset($_SESSION[$name]) ? $_SESSION[$name] : null;\n    }\n\n    /**\n     * @deprecated use SessionInterface::remove() instead\n     *\n     * Destroys a single element (passed to method) of an session array.\n     *\n     * @param string $name Name of parameter to destroy\n     */\n    public function deleteVariable($name)\n    {\n        $_SESSION[$name] = null;\n        unset($_SESSION[$name]);\n    }\n\n    /**\n     * Returns string prefix to URL with session ID parameter. In some cases\n     * (if client is robot, such as Google) adds parameter shp, to identify,\n     * witch shop is currently running.\n     *\n     * @param bool $blForceSid forces sid getter, ignores cookie check (optional)\n     *\n     * @return string\n     */\n    public function sid($blForceSid = false)\n    {\n        $myConfig = Registry::getConfig();\n        $sRet = '';\n\n        $blDisableSid = Registry::getUtils()->isSearchEngine()\n                        && is_array($myConfig->getConfigParam('aCacheViews'))\n                        && !$this->isAdmin();\n\n        //no cookie?\n        if (!$blDisableSid && $this->getId() && $this->canSendSidWithRequest($blForceSid)) {\n            $sRet = ($blForceSid ? $this->getForcedName() : $this->getName()) . \"=\" . $this->getId();\n        }\n\n        if ($this->isAdmin()) {\n            // admin mode always has to have token\n            if ($sRet) {\n                $sRet .= '&amp;';\n            }\n            $sRet .= 'stoken=' . $this->getSessionChallengeToken() . $this->getShopUrlId();\n        }\n\n        return $sRet;\n    }\n\n    /**\n     * Forms input (\"hidden\" type) to pass session ID after submitting forms.\n     *\n     * @return string\n     */\n    public function hiddenSid()\n    {\n        $sSid = $sToken = '';\n        if ($this->isSidNeeded()) {\n            $sSid = \"<input type=\\\"hidden\\\" name=\\\"\" . $this->getName() . \"\\\" value=\\\"\" . $this->getId() . \"\\\" />\";\n        }\n        if ($this->getId()) {\n            $sToken = \"<input type=\\\"hidden\\\" name=\\\"stoken\\\" value=\\\"\" . $this->getSessionChallengeToken() . \"\\\" />\";\n        }\n\n        return $sToken . $sSid;\n    }\n\n    /**\n     * Returns basket session object.\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\Basket\n     */\n    public function getBasket()\n    {\n        if ($this->_oBasket === null) {\n            $serializedBasket = $this->getVariable($this->getBasketName());\n\n            //init oxbasketitem class first\n            //#1746\n            oxNew(BasketItem::class);\n\n            // init oxbasket through oxNew and not oxAutoload, Mantis-Bug #0004262\n            $emptyBasket = oxNew(Basket::class);\n\n            $basket =\n                $this->isSerializedBasketValid($serializedBasket) &&\n                ($unserializedBasket = unserialize($serializedBasket)) &&\n                $this->isUnserializedBasketValid($unserializedBasket, $emptyBasket) ?\n                    $unserializedBasket : $emptyBasket;\n\n            $this->validateBasket($basket);\n            $this->setBasket($basket);\n        }\n\n        return $this->_oBasket;\n    }\n\n    /**\n     * True if given serialized object is constructed with compatible classes.\n     *\n     * @param string $serializedBasket\n     * @return bool\n     */\n    protected function isSerializedBasketValid($serializedBasket)\n    {\n        $basketClass = get_class(oxNew(Basket::class));\n        $basketItemClass = get_class(oxNew(BasketItem::class));\n        $priceClass = get_class(oxNew(\\OxidEsales\\Eshop\\Core\\Price::class));\n        $priceListClass = get_class(oxNew(\\OxidEsales\\Eshop\\Core\\PriceList::class));\n        $userClass = get_class(oxNew(User::class));\n\n        return $serializedBasket &&\n            $this->isClassInSerializedObject($serializedBasket, $basketClass) &&\n            $this->isClassInSerializedObject($serializedBasket, $basketItemClass) &&\n            $this->isClassOrNullInSerializedObjectAfterField($serializedBasket, \"oPrice\", $priceClass) &&\n            $this->isClassOrNullInSerializedObjectAfterField($serializedBasket, \"oProductsPriceList\", $priceListClass) &&\n            $this->isClassOrNullInSerializedObjectAfterField($serializedBasket, \"oUser\", $userClass);\n    }\n\n    /**\n     * True if given class is found within serialized object.\n     *\n     * @param string $serializedObject\n     * @param string $className\n     *\n     * @return bool\n     */\n    protected function isClassInSerializedObject($serializedObject, $className)\n    {\n        $quotedClassName = sprintf('\"%s\"', $className);\n\n        return strpos($serializedObject, $quotedClassName) !== false;\n    }\n\n    /**\n     * True if given class or null value is found after given field in serialized object.\n     *\n     * @param string $serializedObject\n     * @param string $fieldName\n     * @param string $className\n     *\n     * @return bool\n     */\n    protected function isClassOrNullInSerializedObjectAfterField($serializedObject, $fieldName, $className)\n    {\n        $fieldAndClassPattern = '/' . preg_quote($fieldName, '/') . '\";((?P<null>N);|O:\\d+:\"(?P<class>[\\w\\\\\\\\]+)\":)/';\n        $matchFound = preg_match($fieldAndClassPattern, $serializedObject, $matches) === 1;\n\n        return $matchFound &&\n            (\n                (isset($matches['class']) && $matches['class'] === $className) ||\n                (isset($matches['null']) && $matches['null'] === 'N')\n            );\n    }\n\n    /**\n     * True if both basket objects have been constructed from same class.\n     *\n     * Shop cannot function properly if provided with different basket class.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $basket\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $emptyBasket\n     *\n     * @return bool\n     */\n    protected function isUnserializedBasketValid($basket, $emptyBasket)\n    {\n        return $basket && (get_class($basket) === get_class($emptyBasket));\n    }\n\n    /**\n     * Validate loaded from session basket content. Check for language change.\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket Basket object loaded from session.\n     *\n     * @return null\n     */\n    protected function validateBasket(\\OxidEsales\\Eshop\\Application\\Model\\Basket $oBasket)\n    {\n        $aCurrContent = $oBasket->getContents();\n        if (empty($aCurrContent)) {\n            return;\n        }\n\n        $iCurrLang = Registry::getLang()->getBaseLanguage();\n        foreach ($aCurrContent as $oContent) {\n            if ($oContent->getLanguageId() != $iCurrLang) {\n                $oContent->setLanguageId($iCurrLang);\n            }\n        }\n    }\n\n    /**\n     * Sets basket session object.\n     *\n     * @param object $oBasket basket object\n     */\n    public function setBasket($oBasket)\n    {\n        // sets basket session object\n        $this->_oBasket = $oBasket;\n    }\n\n    /**\n     * Deletes basket session object.\n     */\n    public function delBasket()\n    {\n        $this->setBasket(null);\n        $this->deleteVariable($this->getBasketName());\n    }\n\n    /**\n     * Indicates if setting of session id is executed in this script.\n     *\n     * @return bool\n     */\n    public function isNewSession()\n    {\n        return self::$_blIsNewSession;\n    }\n\n    /**\n     * Forces starting session and skips checking if session is allowed to start\n     * when calling \\OxidEsales\\Eshop\\Core\\Session::start();\n     */\n    public function setForceNewSession()\n    {\n        $this->_blForceNewSession = true;\n    }\n\n    /**\n     * Checks if cookies are not available. Returns TRUE of sid needed\n     *\n     * @param string $sUrl if passed domain does not match current - returns true (optional)\n     *\n     * @return bool\n     */\n    public function isSidNeeded($sUrl = null)\n    {\n        if ($this->isAdmin()) {\n            return true;\n        }\n\n        $oConfig = Registry::getConfig();\n\n        if (!$this->getSessionUseCookies() || ($sUrl && $this->getCookieSid() && !$oConfig->isCurrentProtocol($sUrl))) {\n            // switching from ssl to non ssl or vice versa?\n            return true;\n        }\n\n        if ($sUrl && !$oConfig->isCurrentUrl($sUrl)) {\n            return true;\n        }\n\n        if ($sUrl && $oConfig->isCurrentUrl($sUrl)) {\n            return false;\n        }\n\n        if ($this->_blSidNeeded === null) {\n            // setting initial state\n            $this->_blSidNeeded = false;\n\n            // no SIDs for search engines\n            if (!Registry::getUtils()->isSearchEngine()) {\n                // cookie found - SID is not needed\n                if (Registry::getUtilsServer()->getOxCookie($this->getName())) {\n                    $this->_blSidNeeded = false;\n                } elseif ($this->forceSessionStart()) {\n                    $this->_blSidNeeded = true;\n                } else {\n                    // no cookie, so must check session\n                    if ($blSidNeeded = $this->getVariable('blSidNeeded')) {\n                        $this->_blSidNeeded = true;\n                    } elseif ($this->isSessionRequiredAction() && !count($_COOKIE)) {\n                        $this->_blSidNeeded = true;\n\n                        // storing to session, performance..\n                        $this->setVariable('blSidNeeded', $this->_blSidNeeded);\n                    }\n                }\n            }\n        }\n\n        return $this->_blSidNeeded;\n    }\n\n    /**\n     * Checks if current session id is the same as in originally received cookie.\n     * This method is intended to indicate if new session cookie\n     * is to be sent as header from this script execution.\n     *\n     * @return bool\n     */\n    public function isActualSidInCookie()\n    {\n        return isset($_COOKIE[$this->getName()]) && ($_COOKIE[$this->getName()] == $this->getId());\n    }\n\n    /**\n     * Appends url with session ID, but only if \\OxidEsales\\Eshop\\Core\\Session::_isSidNeeded() returns true\n     * Direct usage of this method to retrieve end url result is discouraged - instead\n     * see \\OxidEsales\\Eshop\\Core\\UtilsUrl::processUrl\n     *\n     * @param string $sUrl url to append with sid\n     *\n     * @see \\OxidEsales\\Eshop\\Core\\UtilsUrl::processUrl\n     *\n     * @return string\n     */\n    public function processUrl($sUrl)\n    {\n        if ($this->isSidNeeded($sUrl)) {\n            $sSid = $this->sid(true);\n            if ($sSid) {\n                $this->sidToUrlEvent();\n\n                $oStr = Str::getStr();\n                $aUrlParts = explode('#', $sUrl);\n                if (!$oStr->preg_match('/(\\?|&(amp;)?)sid=/i', $aUrlParts[0]) && (false === $oStr->strpos($aUrlParts[0], $sSid))) {\n                    if (!$oStr->preg_match('/(\\?|&(amp;)?)$/', $sUrl)) {\n                        $aUrlParts[0] .= ($oStr->strstr($aUrlParts[0], '?') !== false ? '&amp;' : '?');\n                    }\n                    $aUrlParts[0] .= $sSid . '&amp;';\n                }\n                $sUrl = join('#', $aUrlParts);\n            }\n        }\n\n        return $sUrl;\n    }\n\n    /**\n     * Returns remote access key. With this key (called over \"remotekey\" URL parameter) and session id (sid parameter) you can access\n     * session from another client.\n     * The key is generated once per session after the first request.\n     *\n     * @param bool $blGenerateNew Should new token be generated\n     *\n     * @return string\n     */\n    public function getRemoteAccessToken($blGenerateNew = true)\n    {\n        $sToken = $this->getVariable('_rtoken');\n        if (!$sToken && $blGenerateNew) {\n            $sToken = md5(rand() . $this->getId());\n            $sToken = substr($sToken, 0, 8);\n            $this->setVariable('_rtoken', $sToken);\n        }\n\n        return $sToken;\n    }\n\n    /**\n     * Returns true if its not search engine and config option blForceSessionStart = 1/true\n     * or _GET parameter \"su\" (suggested user id) is set.\n     *\n     * @return bool\n     */\n    protected function forceSessionStart()\n    {\n        return !Registry::getUtils()->isSearchEngine() &&\n            (\n                ContainerFacade::getParameter('oxid_esales.force_session_start') ||\n                Registry::getRequest()->getRequestEscapedParameter('su') ||\n                $this->_blForceNewSession\n            );\n    }\n\n    /**\n     * Checks if we can start new session. Returns bool success status\n     *\n     * @return bool\n     */\n    protected function allowSessionStart()\n    {\n        $blAllowSessionStart = true;\n        $myConfig = Registry::getConfig();\n\n        // special handling only in non-admin mode\n        if (!$this->isAdmin()) {\n            if (Registry::getUtils()->isSearchEngine() || Registry::getRequest()->getRequestEscapedParameter('skipSession')) {\n                $blAllowSessionStart = false;\n            } elseif (Registry::getUtilsServer()->getOxCookie('oxid_' . $myConfig->getShopId() . '_autologin') === '1') {\n                $blAllowSessionStart = true;\n            } elseif (!$this->forceSessionStart() && !Registry::getUtilsServer()->getOxCookie('sid_key')) {\n                // session is not needed to start when it is not necessary:\n                // - no sid in request and also user executes no session connected action\n                // - no cookie set and user executes no session connected action\n                if (\n                    !Registry::getUtilsServer()->getOxCookie($this->getName()) &&\n                    !$this->canTakeSidFromRequest() &&\n                    !$this->isSessionRequiredAction()\n                ) {\n                    $blAllowSessionStart = false;\n                }\n            }\n        }\n\n        return $blAllowSessionStart;\n    }\n\n    /**\n     * Saves various visitor parameters and compares with current data.\n     * Returns true if any change is detected.\n     * Using this method we can detect different visitor with same session id.\n     *\n     * @return bool\n     */\n    protected function isSwappedClient()\n    {\n        $blSwapped = false;\n        $myUtilsServer = Registry::getUtilsServer();\n\n        // check only for non search engines\n        if (!Registry::getUtils()->isSearchEngine() && !$myUtilsServer->isTrustedClientIp() && !$this->isValidRemoteAccessToken()) {\n            $myConfig = Registry::getConfig();\n\n            // checking if session user agent matches actual\n            $blSwapped = $this->checkUserAgent($myUtilsServer->getServerVar('HTTP_USER_AGENT'), $this->getVariable('sessionagent'));\n            if (!$blSwapped) {\n                $blDisableCookieCheck = $myConfig->getConfigParam('blDisableCookieCheck');\n                $blUseCookies = $this->getSessionUseCookies();\n                if (!$blDisableCookieCheck && $blUseCookies) {\n                    $blSwapped = $this->checkCookies($myUtilsServer->getOxCookie('sid_key'), $this->getVariable(\"sessioncookieisset\"));\n                }\n            }\n        }\n\n        return $blSwapped;\n    }\n\n    /**\n     * Checking user agent\n     *\n     * @param string $sAgent         current user agent\n     * @param string $sExistingAgent existing user agent\n     *\n     * @return bool\n     */\n    protected function checkUserAgent($sAgent, $sExistingAgent)\n    {\n        $blCheck = false;\n        // processing\n        $oUtils = Registry::getUtilsServer();\n        $sAgent = $oUtils->processUserAgentInfo($sAgent);\n        $sExistingAgent = $oUtils->processUserAgentInfo($sExistingAgent);\n\n        if ($sAgent && $sAgent !== $sExistingAgent) {\n            if ($sExistingAgent) {\n                $this->_sErrorMsg = \"Different browser ({$sExistingAgent}, {$sAgent}), creating new SID...<br>\";\n            }\n            $blCheck = true;\n        }\n\n        return $blCheck;\n    }\n\n    /**\n     * Check for existing cookie.\n     * Cookie info is dropped from time to time.\n     *\n     * @param string $sCookieSid         coockie sid\n     * @param array  $aSessCookieSetOnce if session cookie is set\n     *\n     * @return bool\n     */\n    protected function checkCookies($sCookieSid, $aSessCookieSetOnce)\n    {\n        $blSwapped = false;\n        $myConfig = Registry::getConfig();\n        $currUrl = $myConfig->getShopUrl();\n\n        $blSessCookieSetOnce = false;\n        if (is_array($aSessCookieSetOnce) && isset($aSessCookieSetOnce[$currUrl])) {\n            $blSessCookieSetOnce = $aSessCookieSetOnce[$currUrl];\n        }\n\n        //if cookie was there once but now is gone it means we have to reset\n        if ($blSessCookieSetOnce && !$sCookieSid) {\n            if (ContainerFacade::getParameter('oxid_esales.debug_mode')) {\n                $this->_sErrorMsg = \"Cookie not found, creating new SID...<br>\";\n                $this->_sErrorMsg .= \"Cookie: $sCookieSid<br>\";\n                $this->_sErrorMsg .= \"Session: $blSessCookieSetOnce<br>\";\n                $this->_sErrorMsg .= \"URL: \" . $currUrl . \"<br>\";\n            }\n            $blSwapped = true;\n        }\n\n        //if we detect the cookie then set session var for possible later use\n        if ($sCookieSid == \"oxid\" && !$blSessCookieSetOnce) {\n            if (!is_array($aSessCookieSetOnce)) {\n                $aSessCookieSetOnce = [];\n            }\n\n            $aSessCookieSetOnce[$currUrl] = \"ox_true\";\n            $this->setVariable(\"sessioncookieisset\", $aSessCookieSetOnce);\n        }\n\n        //if we have no cookie then try to set it\n        if (!$sCookieSid) {\n            Registry::getUtilsServer()->setOxCookie('sid_key', 'oxid');\n        }\n\n        return $blSwapped;\n    }\n\n    /**\n     * Sests session id to $sSessId\n     *\n     * @param string $sSessId sesion ID\n     *\n     * @return null\n     */\n    protected function setSessionId($sSessId)\n    {\n        //marking this session as new one, as it might be not writen to db yet\n        if ($sSessId && session_id() != $sSessId) {\n            $this->_blNewSession = true;\n        }\n\n        session_id($sSessId);\n\n        $this->setId($sSessId);\n        $this->setSessionCookie($sSessId);\n    }\n\n    /**\n     * Returns name of shopping basket.\n     *\n     * @return string\n     */\n    protected function getBasketName()\n    {\n        return 'basket';\n    }\n\n    /**\n     * Returns cookie sid value\n     *\n     * @return string\n     */\n    protected function getCookieSid()\n    {\n        return Registry::getUtilsServer()->getOxCookie($this->getName());\n    }\n\n    /**\n     * returns configuration array with info which parameters require session\n     * start\n     *\n     * @return array\n     */\n    protected function getRequireSessionWithParams()\n    {\n        $config = ContainerFacade::getParameter('oxid_esales.session_init_params');\n        $defaults = $this->_aRequireSessionWithParams;\n        if (!$config) {\n            return $defaults;\n        }\n        foreach ($config as $key => $val) {\n            if ($val && !\\is_array($val)) {\n                unset($defaults[$key]);\n            }\n        }\n        return array_replace_recursive($defaults, $config);\n    }\n\n    /**\n     * Tests if current action requires session\n     *\n     * @return bool\n     */\n    protected function isSessionRequiredAction()\n    {\n        foreach ($this->getRequireSessionWithParams() as $sParam => $aValues) {\n            $sValue = Registry::getRequest()->getRequestEscapedParameter($sParam);\n            if (isset($sValue)) {\n                if (is_array($aValues)) {\n                    if (isset($aValues[$sValue]) && $aValues[$sValue]) {\n                        return true;\n                    }\n                } elseif ($aValues) {\n                    return true;\n                }\n            }\n        }\n\n        return (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'POST');\n    }\n\n    /**\n     * return cookies usage for sid possibilities\n     *\n     * @return bool\n     */\n    protected function getSessionUseCookies()\n    {\n        return $this->isAdmin() || ContainerFacade::getParameter('oxid_esales.cookies_session');\n    }\n\n    /**\n     * Checks if token supplied over 'rtoken' parameter matches remote access session token.\n     *\n     * @return bool\n     */\n    protected function isValidRemoteAccessToken()\n    {\n        $inputToken = Registry::getRequest()->getRequestEscapedParameter('rtoken');\n        $token = $this->getRemoteAccessToken(false);\n\n        return !empty($inputToken) ? ($token === $inputToken) : false;\n    }\n\n    /**\n     * return basket reservations handler object\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\BasketReservation\n     */\n    public function getBasketReservations()\n    {\n        if (!$this->_oBasketReservations) {\n            $this->_oBasketReservations = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\BasketReservation::class);\n        }\n\n        return $this->_oBasketReservations;\n    }\n\n    /**\n     * Checks if headers were already outputed\n     *\n     * @return bool\n     */\n    public function isHeaderSent()\n    {\n        return headers_sent();\n    }\n\n    /**\n     * Returns true if session was started\n     *\n     * @return bool\n     */\n    public function isSessionStarted()\n    {\n        return session_status() === PHP_SESSION_ACTIVE;\n    }\n\n    /**\n     * Return Shop IR parameter for Url.\n     *\n     * @return string\n     */\n    protected function getShopUrlId()\n    {\n        return '';\n    }\n\n    /**\n     * Decide if need to set session headers to browser.\n     *\n     * @return bool\n     */\n    protected function needToSetHeaders()\n    {\n        return true;\n    }\n\n    /**\n     * Place to hook when SID is added to URL.\n     */\n    protected function sidToUrlEvent()\n    {\n    }\n\n    /**\n     * Set session cookie\n     *\n     * @param string $sessionId   Session cookie value\n     *\n     * @return void\n     */\n    protected function setSessionCookie($sessionId): void\n    {\n        if ($this->getSessionUseCookies()) {\n            if (!$this->allowSessionStart()) {\n                Registry::getUtilsServer()->setOxCookie($this->getName(), null);\n            } else {\n                Registry::getUtilsServer()->setOxCookie($this->getName(), $sessionId);\n            }\n        }\n    }\n\n    private function isForceSidBlocked(): bool\n    {\n        return ContainerFacade::getParameter('oxid_esales.disallow_force_session_id');\n    }\n\n    private function canSendSidWithRequest(bool $useForceSid): bool\n    {\n        return ($useForceSid || !$this->getSessionUseCookies() || !$this->getCookieSid())\n            && !($useForceSid && $this->isForceSidBlocked());\n    }\n\n    private function canTakeSidFromRequest(): bool\n    {\n        return Registry::getRequest()->getRequestEscapedParameter($this->getName())\n            || (\n                Registry::getRequest()->getRequestEscapedParameter($this->getForcedName())\n                && !$this->isForceSidBlocked()\n            );\n    }\n}\n"
  },
  {
    "path": "source/Core/SettingsHandler.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Settings handler class.\n */\nclass SettingsHandler extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Module type.\n     *\n     * e.g. 'module' or 'theme'\n     *\n     * @var string\n     */\n    protected $moduleType;\n\n    /**\n     * Sets the Module type\n     *\n     * @param string $moduleType can be either 'module' or 'theme'\n     *\n     * @return self\n     */\n    public function setModuleType($moduleType)\n    {\n        $this->moduleType = $moduleType;\n\n        return $this;\n    }\n\n    /**\n     * Get settings and module id and starts import process.\n     *\n     * Run module settings import logic only if it has settings array\n     * On empty settings array, it will remove the settings.\n     *\n     * @param object $module Module or Theme Object\n     */\n    public function run($module)\n    {\n        $moduleSettings = $module->getInfo('settings');\n        $isTheme = $this->isTheme($module->getId());\n        if (!$isTheme || ($isTheme && is_array($moduleSettings))) {\n            $this->addModuleSettings($moduleSettings, $module->getId());\n        }\n    }\n\n    /**\n     * Adds settings to database.\n     *\n     * @param array  $moduleSettings Module settings array\n     * @param string $moduleId       Module id\n     */\n    protected function addModuleSettings($moduleSettings, $moduleId)\n    {\n        $this->removeNotUsedSettings($moduleSettings, $moduleId);\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $shopId = $config->getShopId();\n        $moduleConfigs = $this->getModuleConfigs($moduleId);\n        $db = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        if (is_array($moduleSettings)) {\n            foreach ($moduleSettings as $setting) {\n                $oxid = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsObject()->generateUId();\n\n                $module = $this->getModuleConfigId($moduleId);\n                $name = $setting[\"name\"];\n                $type = $setting[\"type\"];\n\n                if ($this->isTheme($moduleId)) {\n                    $value = array_key_exists($name, $moduleConfigs) ? $moduleConfigs[$name] : $setting[\"value\"];\n                } else {\n                    $value = is_null($config->getConfigParam($name)) ? $setting[\"value\"] : $config->getConfigParam($name);\n                }\n\n                $group = $setting[\"group\"];\n\n                $constraints = \"\";\n                if (isset($setting[\"constraints\"]) && $setting[\"constraints\"]) {\n                    $constraints = $setting[\"constraints\"];\n                } elseif (isset($setting[\"constrains\"]) && $setting[\"constrains\"]) {\n                    $constraints = $setting[\"constrains\"];\n                }\n\n                $position = 1;\n                if (isset($setting[\"position\"])) {\n                    $position = $setting[\"position\"];\n                }\n\n                $config->saveShopConfVar($type, $name, $value, $shopId, $module);\n\n                $deleteSql = \"DELETE FROM `oxconfigdisplay` WHERE OXCFGMODULE = :oxcfgmodule AND OXCFGVARNAME = :oxcfgvarname\";\n                $insertSql = \"INSERT INTO `oxconfigdisplay` (`OXID`, `OXCFGMODULE`, `OXCFGVARNAME`, `OXGROUPING`, `OXVARCONSTRAINT`, `OXPOS`) \" .\n                             \"VALUES (:oxid, :oxcfgmodule, :oxcfgvarname, :oxgrouping, :oxvarconstraint, :oxpos)\";\n\n                $db->execute($deleteSql, [\n                    'oxcfgmodule' => $module,\n                    'oxcfgvarname' => $name,\n                ]);\n                $db->execute($insertSql, [\n                    'oxid' => $oxid,\n                    'oxcfgmodule' => $module,\n                    'oxcfgvarname' => $name,\n                    'oxgrouping' => $group,\n                    'oxvarconstraint' => $constraints,\n                    'oxpos' => $position,\n                ]);\n            }\n        }\n    }\n\n    /**\n     * Check if module is theme.\n     *\n     * @param string $moduleId\n     * @return bool\n     */\n    protected function isTheme($moduleId)\n    {\n        $moduleConfigId = $this->getModuleConfigId($moduleId);\n        $themeTypeCondition = \"@^\" . Config::OXMODULE_THEME_PREFIX . \"@i\";\n        return (bool)preg_match($themeTypeCondition, $moduleConfigId);\n    }\n\n    /**\n     * Removes configs which are removed from module metadata\n     *\n     * @param array  $moduleSettings Module settings\n     * @param string $moduleId       Module id\n     */\n    protected function removeNotUsedSettings($moduleSettings, $moduleId)\n    {\n        $moduleConfigs = array_keys($this->getModuleConfigs($moduleId));\n        $moduleSettings = $this->parseModuleSettings($moduleSettings);\n\n        $configsToRemove = array_diff($moduleConfigs, $moduleSettings);\n        if (!empty($configsToRemove)) {\n            $this->removeModuleConfigs($moduleId, $configsToRemove);\n        }\n    }\n\n    /**\n     * Returns module configuration from database\n     *\n     * @param string $moduleId Module id\n     *\n     * @return array key=>value\n     */\n    protected function getModuleConfigs($moduleId)\n    {\n        $db = DatabaseProvider::getDb();\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $shopId = $config->getShopId();\n        $module = $this->getModuleConfigId($moduleId);\n\n        $moduleConfigsQuery = \"SELECT oxvarname, oxvartype, oxvarvalue FROM oxconfig WHERE oxmodule = :oxmodule AND oxshopid = :oxshopid\";\n        $dbConfigs = $db->getAll($moduleConfigsQuery, [\n            'oxmodule' => $module,\n            'oxshopid' => $shopId\n        ]);\n\n        $result = [];\n        foreach ($dbConfigs as $oneModuleConfig) {\n            $result[$oneModuleConfig['oxvarname']] = $config->decodeValue($oneModuleConfig['oxvartype'], $oneModuleConfig['oxvarvalue']);\n        }\n\n        return $result;\n    }\n\n    /**\n     * Parses module config variable names to array from module settings\n     *\n     * @param array $moduleSettings Module settings\n     *\n     * @return array\n     */\n    protected function parseModuleSettings($moduleSettings)\n    {\n        $settings = [];\n\n        if (is_array($moduleSettings)) {\n            foreach ($moduleSettings as $setting) {\n                $settings[] = $setting['name'];\n            }\n        }\n\n        return $settings;\n    }\n\n    /**\n     * Removes module configs from database\n     *\n     * @param string $moduleId        Module id\n     * @param array  $configsToRemove Configs to remove\n     */\n    protected function removeModuleConfigs($moduleId, $configsToRemove)\n    {\n        $db = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $quotedConfigsToRemove = array_map([$db, 'quote'], $configsToRemove);\n        $deleteSql = \"DELETE\n                       FROM `oxconfig`\n                       WHERE oxmodule = :oxmodule AND\n                             oxshopid = :oxshopid AND\n                             oxvarname IN (\" . implode(\", \", $quotedConfigsToRemove) . \")\";\n\n        $db->execute($deleteSql, [\n            'oxmodule' => $this->getModuleConfigId($moduleId),\n            'oxshopid' => \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopId(),\n        ]);\n    }\n\n    /**\n     * Get config tables specific module id\n     *\n     * @param string $moduleId\n     * @return string\n     */\n    protected function getModuleConfigId($moduleId)\n    {\n        return $this->moduleType . ':' . $moduleId;\n    }\n}\n"
  },
  {
    "path": "source/Core/Sha512Hasher.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Encrypt string with sha512 algorithm.\n *\n * @deprecated since v6.4.0 (2019-03-15); `\\OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\PasswordServiceBridgeInterface`\n *                                        was added as the new default for hashing passwords. Hashing passwords with\n *                                        MD5 and SHA512 is still supported in order support login with older\n *                                        password hashes. Therefor this class might not be\n *                                        compatible with the current passhword hash any more.\n */\nclass Sha512Hasher extends \\OxidEsales\\Eshop\\Core\\Hasher\n{\n    /** Algorithm name. */\n    const HASHING_ALGORITHM_SHA512 = 'sha512';\n\n    /**\n     * Encrypt string.\n     *\n     * @param string $string\n     *\n     * @return string\n     */\n    public function hash($string)\n    {\n        return hash(self::HASHING_ALGORITHM_SHA512, $string);\n    }\n}\n"
  },
  {
    "path": "source/Core/ShopControl.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Application\\Controller\\FrontendController;\nuse OxidEsales\\Eshop\\Core\\Exception\\RoutingException;\nuse OxidEsales\\Eshop\\Core\\Exception\\SystemComponentException;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Controller\\ViewControllerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents\\BeforeHeadersSendEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents\\ViewRenderedEvent;\nuse ReflectionMethod;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass ShopControl extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Used to force handling, it allows other place like widget controller to skip it.\n     *\n     * @var bool\n     */\n    protected $_blMainTasksExecuted = null;\n\n    /**\n     * Profiler start time\n     *\n     * @var double\n     */\n    protected $_dTimeStart = null;\n\n    /**\n     * Profiler end time\n     *\n     * @var double\n     */\n    protected $_dTimeEnd = null;\n\n    /**\n     * errors to be displayed/returned\n     *\n     * @see _getErrors\n     *\n     * @var array\n     */\n    protected $_aErrors = null;\n\n    /**\n     * same as errors in session\n     *\n     * @see _getErrors\n     *\n     * @var array\n     */\n    protected $_aAllErrors = null;\n\n    /**\n     * same as controller errors in session\n     *\n     * @see _getErrors\n     *\n     * @var array\n     */\n    protected $_aControllerErrors = null;\n\n\n    /**\n     * output handler object\n     *\n     * @see _getOuput\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\Output\n     */\n    protected $_oOutput = null;\n\n    /**\n     * Cache manager instance\n     */\n    protected $_oCache = null;\n\n    /**\n     * Main shop manager, that sets shop status, executes configuration methods.\n     * Executes \\OxidEsales\\Eshop\\Core\\ShopControl::_runOnce(), if needed sets default class (according\n     * to admin or regular activities). Additionally its possible to pass class name,\n     * function name and parameters array to view, which will be executed.\n     *\n     * @param string $controllerKey Key of the controller class to be processed\n     * @param string $function      Function name\n     * @param array  $parameters    Parameters array\n     * @param array  $viewsChain    Array of views names that should be initialized also\n     */\n    public function start($controllerKey = null, $function = null, $parameters = null, $viewsChain = null)\n    {\n        try {\n            $this->runOnce();\n\n            $function = !is_null($function) ? $function : Registry::getRequest()->getRequestEscapedParameter('fnc');\n            $controllerKey = !is_null($controllerKey) ? $controllerKey : $this->getStartControllerKey();\n            $controllerClass = $this->getControllerClass($controllerKey);\n\n            $this->process($controllerClass, $function, $parameters, $viewsChain);\n        } catch (SystemComponentException $exception) {\n            $this->handleSystemException($exception);\n        } catch (\\OxidEsales\\Eshop\\Core\\Exception\\CookieException $exception) {\n            $this->handleCookieException($exception);\n        } catch (\\OxidEsales\\Eshop\\Core\\Exception\\RoutingException $exception) {\n            $this->handleRoutingException($exception);\n        } catch (\\OxidEsales\\Eshop\\Core\\Exception\\StandardException $exception) {\n            $this->handleBaseException($exception);\n        }\n    }\n\n    /**\n     * Returns the difference between stored profiler end time and start time. Works only after stopMonitoring() is\n     * called, otherwise returns 0.\n     *\n     * @return double\n     */\n    public function getTotalTime()\n    {\n        if ($this->_dTimeEnd && $this->_dTimeStart) {\n            return $this->_dTimeEnd - $this->_dTimeStart;\n        }\n\n        return 0;\n    }\n\n    /**\n     * Returns class id of controller which should be loaded.\n     * When in doubt returns default start controller class.\n     *\n     * @return string\n     */\n    protected function getStartControllerKey()\n    {\n        $controllerKey = Registry::getConfig()->getRequestControllerId();\n\n        // Use default route in case no controller id is given\n        if (!$controllerKey) {\n            $session = Registry::getSession();\n            if ($this->isAdmin()) {\n                $controllerKey = $session->getVariable(\"auth\") ? 'admin_start' : 'login';\n            } else {\n                $controllerKey = $this->getFrontendStartControllerKey();\n            }\n            $session->setVariable('cl', $controllerKey);\n        }\n\n        return $controllerKey;\n    }\n\n    /**\n     * Returns class id of controller which should be loaded.\n     * When in doubt returns default start controller class.\n     *\n     * @param string $controllerKey Controller id\n     *\n     * @throws RoutingException\n     * @return string\n     */\n    protected function resolveControllerClass($controllerKey)\n    {\n        $resolvedClass = Registry::getControllerClassNameResolver()->getClassNameById($controllerKey);\n\n        // If unmatched controller id is requested throw exception\n        if (!$resolvedClass) {\n            throw new \\OxidEsales\\Eshop\\Core\\Exception\\RoutingException(\n                sprintf('Controller \"%s\" cannot be resolved', $controllerKey)\n            );\n        }\n\n        return $resolvedClass;\n    }\n\n    /**\n     * Returns id of controller that should be loaded at shop start.\n     * Check whether we have to display mall start screen or not.\n     *\n     * @return string\n     */\n    protected function getFrontendStartControllerKey()\n    {\n        return 'start';\n    }\n\n    /**\n     * Initiates object (object::init()), executes passed function\n     * (\\OxidEsales\\Eshop\\Core\\ShopControl::executeFunction(), if method returns some string - will\n     * redirect page and will call another function according to returned\n     * parameters), renders object (object::render()). Performs output processing\n     * \\OxidEsales\\Eshop\\Core\\Output::ProcessViewArray(). Passes template variables to template\n     * engine witch generates output. Output is additionally processed\n     * (\\OxidEsales\\Eshop\\Core\\Output::Process()), fixed links according search engines optimization\n     * rules (configurable in Admin area). Finally echoes the output.\n     *\n     * @param string $class      Class name\n     * @param string $function   Name of function\n     * @param array  $parameters Parameters array\n     * @param array  $viewsChain Array of views names that should be initialized also\n     */\n    protected function process($class, $function, $parameters = null, $viewsChain = null)\n    {\n        startProfile('process');\n        $config = Registry::getConfig();\n\n        // executing maintenance tasks\n        $this->executeMaintenanceTasks();\n\n        // starting resource monitor\n        $this->startMonitor();\n\n        // Initialize view object and it's components.\n        $view = $this->initializeViewObject($class, $function, $parameters, $viewsChain);\n\n        $this->executeAction($view, $view->getFncName());\n\n        $output = $this->formOutput($view);\n\n        ContainerFacade::dispatch(new ViewRenderedEvent($this));\n\n        $outputManager = $this->getOutputManager();\n        $outputManager->setCharset($view->getCharSet());\n\n        if (Registry::getRequest()->getRequestEscapedParameter('renderPartial')) {\n            $outputManager->setOutputFormat(\\OxidEsales\\Eshop\\Core\\Output::OUTPUT_FORMAT_JSON);\n            $outputManager->output('errors', $this->getFormattedErrors($view->getClassKey()));\n        }\n\n        ContainerFacade::dispatch(new BeforeHeadersSendEvent($this, $view));\n\n        $outputManager->sendHeaders();\n\n        //Send headers that have been registered\n        $header = Registry::get(\\OxidEsales\\Eshop\\Core\\Header::class);\n        $header->sendHeader();\n\n        $this->sendAdditionalHeaders($view);\n\n        $outputManager->output('content', $output);\n\n        $config->pageClose();\n\n        stopProfile('process');\n\n        $this->stopMonitoring($view);\n\n        $outputManager->flushOutput();\n    }\n\n    /**\n     * Executes regular maintenance functions..\n     *\n     * @return null\n     */\n    protected function executeMaintenanceTasks()\n    {\n        if (isset($this->_blMainTasksExecuted)) {\n            return;\n        }\n\n        startProfile('executeMaintenanceTasks');\n        oxNew(\\OxidEsales\\Eshop\\Application\\Model\\ArticleList::class)->updateUpcomingPrices();\n        stopProfile('executeMaintenanceTasks');\n    }\n\n    /**\n     * Executes provided function on view object.\n     * If this function can not be executed (is protected or so), a RoutingException is thrown\n     *\n     * @param FrontendController $view\n     * @param string             $functionName\n     */\n    protected function executeAction($view, $functionName)\n    {\n        if (!$this->canExecuteFunction($view, $functionName)) {\n            throw new \\OxidEsales\\Eshop\\Core\\Exception\\RoutingException(\n                sprintf(\"Non public method cannot be accessed: %s::%s\", get_class($view), $functionName)\n            );\n        }\n\n        $view->executeFunction($functionName);\n    }\n\n    /**\n     * Forms output from view object.\n     *\n     * @param FrontendController $view\n     *\n     * @return string\n     */\n    protected function formOutput($view)\n    {\n        return $this->render($view);\n    }\n\n    /**\n     * Method for sending any additional headers on every page requests.\n     *\n     * @param FrontendController $view\n     */\n    protected function sendAdditionalHeaders($view)\n    {\n    }\n\n    /**\n     * Initialize and return view object.\n     *\n     * @param string $class      View class\n     * @param string $function   Function name\n     * @param array  $parameters Parameters array\n     * @param array  $viewsChain Array of views names that should be initialized also\n     *\n     * @return FrontendController\n     */\n    protected function initializeViewObject($class, $function, $parameters = null, $viewsChain = null)\n    {\n        $classKey = Registry::getControllerClassNameResolver()->getIdByClassName($class);\n        $classKey = !is_null($classKey) ? $classKey : $class; //fallback\n\n        /** @var ViewControllerInterface $controller */\n        $controller = $this->isServiceController($classKey, $class)\n            ? ContainerFacade::get($class)\n            : oxNew($class);\n\n        $controller->setClassKey($classKey);\n        $controller->setFncName($function);\n        $controller->setViewParameters($parameters);\n\n        Registry::getConfig()->setActiveView($controller);\n\n        $this->onViewCreation($controller);\n\n        $controller->init();\n\n        return $controller;\n    }\n\n    /**\n     * Event for any actions during view creation.\n     *\n     * @param FrontendController $view\n     */\n    protected function onViewCreation($view)\n    {\n    }\n\n    /**\n     * Check if method can be executed.\n     *\n     * @param FrontendController $view     View object to check if its method can be executed.\n     * @param string             $function Method to check if it can be executed.\n     *\n     * @return bool\n     */\n    protected function canExecuteFunction($view, $function)\n    {\n        $canExecute = true;\n        if ($function && method_exists($view, $function)) {\n            $reflectionMethod = new ReflectionMethod($view, $function);\n            if (!$reflectionMethod->isPublic()) {\n                $canExecute = false;\n            }\n        }\n\n        return $canExecute;\n    }\n\n    /**\n     * Format error messages from _getErrors and return as array.\n     *\n     * @param string $controllerName a class name\n     *\n     * @return array\n     */\n    protected function getFormattedErrors($controllerName)\n    {\n        $errors = $this->getErrors($controllerName);\n        $formattedErrors = [];\n        if (is_array($errors) && count($errors)) {\n            foreach ($errors as $location => $ex2) {\n                foreach ($ex2 as $key => $er) {\n                    $error = unserialize($er);\n                    $formattedErrors[$location][$key] = $error->getOxMessage();\n                }\n            }\n        }\n\n        return $formattedErrors;\n    }\n\n    /**\n     * Render BaseController object.\n     *\n     * @param FrontendController $view view object to render\n     *\n     * @return string\n     */\n    protected function render($view)\n    {\n        $templateName = $view->render();\n        // Output processing. This is useful for modules. As sometimes you may want to process output manually.\n        $outputManager = $this->getOutputManager();\n        $viewData = $outputManager->processViewArray($view->getViewData(), $view->getClassKey());\n        $view->setViewData($viewData);\n\n        $renderer = $this->getRenderer();\n\n        $viewData['oxEngineTemplateId'] = $view->getViewId();\n        $viewData = $this->passSessionErrorsToViewData($view, $viewData);\n        try {\n            $output = $renderer->renderTemplate($templateName, $viewData);\n        } catch (\\Throwable $exception) {\n            $this->processTemplateRenderError($templateName, $exception);\n            $viewData = $this->passSessionErrorsToViewData($view, $viewData);\n            $output = $renderer->renderTemplate('message/exception', $viewData);\n        }\n\n\n        //Output processing - useful for modules as sometimes you may want to process output manually.\n        $output = $outputManager->process($output, $view->getClassKey());\n\n        return $outputManager->addVersionTags($output);\n    }\n\n    /**\n     * @internal\n     *\n     * @return TemplateRendererInterface\n     */\n    private function getRenderer()\n    {\n        return ContainerFacade::get(TemplateRendererBridgeInterface::class)\n            ->getTemplateRenderer();\n    }\n\n    /**\n     * Return output handler.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Output\n     */\n    protected function getOutputManager()\n    {\n        if (!$this->_oOutput) {\n            $this->_oOutput = oxNew(\\OxidEsales\\Eshop\\Core\\Output::class);\n        }\n\n        return $this->_oOutput;\n    }\n\n    /**\n     * Return page errors array.\n     *\n     * @param string $currentControllerName Class name\n     *\n     * @return array\n     */\n    protected function getErrors($currentControllerName)\n    {\n        if (null === $this->_aErrors) {\n            $this->_aErrors = Registry::getSession()->getVariable('Errors');\n            $this->_aControllerErrors = Registry::getSession()->getVariable('ErrorController');\n            if (null === $this->_aErrors) {\n                $this->_aErrors = [];\n            }\n            $this->_aAllErrors = $this->_aErrors;\n        }\n        // resetting errors of current controller or widget from session\n        if (is_array($this->_aControllerErrors) && !empty($this->_aControllerErrors)) {\n            foreach ($this->_aControllerErrors as $errorName => $controllerName) {\n                if ($controllerName == $currentControllerName) {\n                    unset($this->_aAllErrors[$errorName]);\n                    unset($this->_aControllerErrors[$errorName]);\n                }\n            }\n        } else {\n            $this->_aAllErrors = [];\n        }\n        Registry::getSession()->setVariable('ErrorController', $this->_aControllerErrors);\n        Registry::getSession()->setVariable('Errors', $this->_aAllErrors);\n\n        return $this->_aErrors;\n    }\n\n    /**\n     * This function is only executed one time here we perform checks if we\n     * only need once per session.\n     */\n    protected function runOnce()\n    {\n        $config = Registry::getConfig();\n\n        //Ensures config values are available, database connection is established,\n        //session is started, a possible SeoUrl is decoded, globals and environment variables are set.\n        $config->init();\n\n        $runOnceExecuted = Registry::getSession()->getVariable('blRunOnceExecuted');\n        if (!$runOnceExecuted && !$this->isAdmin() && $config->isProductiveMode()) {\n            // check if setup is still there\n            $setupIndexFile = Path::join(\n                ContainerFacade::getParameter('oxid_esales.shop_source_directory'),\n                'Setup',\n                'index.php'\n            );\n            if (file_exists($setupIndexFile)) {\n                $tpl = 'message/err_setup';\n                $activeView = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::class);\n                $context = [\n                    \"oViewConf\" => $activeView->getViewConfig(),\n                    \"oView\"     => $activeView\n                ];\n                $renderer = $this->getRenderer();\n                $errorOutput = $renderer->renderTemplate($tpl, $context);\n                Registry::getUtils()->showMessageAndExit($errorOutput);\n            }\n\n            Registry::getSession()->setVariable('blRunOnceExecuted', true);\n        }\n    }\n\n    /**\n     * Checks if shop is in debug mode.\n     *\n     * @return bool\n     */\n    protected function isDebugMode()\n    {\n        return ContainerFacade::getParameter('oxid_esales.debug_mode');\n    }\n\n    /**\n     * Starts resource monitor.\n     */\n    protected function startMonitor()\n    {\n        if ($this->isDebugMode()) {\n            $this->_dTimeStart = microtime(true);\n        }\n    }\n\n    /**\n     * Stops resource monitor, summarizes and outputs values.\n     *\n     * @param FrontendController $view View object\n     */\n    protected function stopMonitoring($view = null)\n    {\n        if (is_null($view)) {\n            $controllerKey = $this->getStartControllerKey();\n            $controllerClass = $this->getControllerClass($controllerKey);\n            $view = oxNew($controllerClass);\n        }\n\n        if ($this->isDebugMode() && !$this->isAdmin()) {\n            $debugInfo = oxNew(\\OxidEsales\\Eshop\\Core\\DebugInfo::class);\n\n            $logId = md5(time() . rand() . rand());\n            $header = $debugInfo->formatGeneralInfo();\n            $display = 'none';\n            $monitorMessage = $this->formMonitorMessage($view);\n\n            $logMessage = \"\n                <div id='oxidDebugInfo_$logId'>\n                    <div style='color:#630;margin:15px 0 0;cursor:pointer'\n                         onclick='var el=document.getElementById(\\\"debugInfoBlock_$logId\\\"); if (el.style.display==\\\"block\\\")el.style.display=\\\"none\\\"; else el.style.display = \\\"block\\\";'>\n                          $header(show/hide)\n                    </div>\n                    <div id='debugInfoBlock_$logId' style='display:$display' class='debugInfoBlock' align='left'>\n                        $monitorMessage\n                    </div>\n                    <script>\n                        var b = document.getElementById('oxidDebugInfo_$logId');\n                        var c = document.body;\n                        if (c) { c.appendChild(b.parentNode.removeChild(b));}\n                    </script>\n                </div>\";\n\n            $this->getOutputManager()->output('debuginfo', $logMessage);\n        }\n    }\n\n    /**\n     * Forms message for displaying monitoring information on the bottom of the page.\n     *\n     * @param FrontendController $view\n     *\n     * @return string\n     */\n    protected function formMonitorMessage($view)\n    {\n        $debugInfo = oxNew(\\OxidEsales\\Eshop\\Core\\DebugInfo::class);\n\n        // Output timing\n        $this->_dTimeEnd = microtime(true);\n\n        $message = $debugInfo->formatMemoryUsage();\n        $message .= $debugInfo->formatTimeStamp();\n        $message .= $debugInfo->formatExecutionTime($this->getTotalTime());\n\n        return $message;\n    }\n\n    /**\n     * Shows exceptionError page.\n     * possible reason: class does not exist etc. --> just redirect to start page.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Exception\\StandardException $exception\n     */\n    protected function handleSystemException($exception)\n    {\n        Registry::getLogger()->error($exception->getMessage(), [$exception]);\n\n        if ($this->isDebugMode()) {\n            Registry::getUtilsView()->addErrorToDisplay($exception);\n            $this->process('exceptionError', 'displayExceptionError');\n        } else {\n            Registry::getUtils()->redirect(Registry::getConfig()->getShopHomeUrl() . 'cl=start');\n        }\n    }\n\n    protected function handleRoutingException(RoutingException $exception)\n    {\n        Registry::getLogger()->error($exception->getMessage(), [$exception]);\n\n        unset($_GET['fnc'], $_POST['fnc']);\n        error_404_handler($_SERVER['REQUEST_URI']);\n    }\n\n    /**\n     * Redirect to start page, in debug mode shows error message.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Exception\\StandardException $exception Exception\n     */\n    protected function handleCookieException($exception)\n    {\n        if ($this->isDebugMode()) {\n            Registry::getUtilsView()->addErrorToDisplay($exception);\n        }\n        Registry::getUtils()->redirect(Registry::getConfig()->getShopHomeUrl() . 'cl=start', true, 302);\n    }\n\n    /**\n     * Handling other not caught exceptions.\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\Exception\\StandardException $exception\n     */\n    protected function handleBaseException($exception)\n    {\n        $this->logException($exception);\n\n        if ($this->isDebugMode()) {\n            Registry::getUtilsView()->addErrorToDisplay($exception);\n            $this->process('exceptionError', 'displayExceptionError');\n        }\n    }\n\n    /**\n     * Log an exception.\n     *\n     * This method forms part of the exception handling process. Any further exceptions must be caught.\n     *\n     * @param \\Exception $exception\n     */\n    protected function logException(\\Exception $exception)\n    {\n        if (!$exception instanceof \\OxidEsales\\Eshop\\Core\\Exception\\StandardException) {\n            $exception = new \\OxidEsales\\Eshop\\Core\\Exception\\StandardException($exception->getMessage(), $exception->getCode(), $exception);\n        }\n        Registry::getLogger()->error($exception->getMessage(), [$exception]);\n    }\n\n    /**\n     * Get controller class from key.\n     * Fallback is to use key as class if no match can be found.\n     *\n     * @param string $controllerKey\n     *\n     * @return string\n     */\n    protected function getControllerClass($controllerKey)\n    {\n        return $this->resolveControllerClass($controllerKey);\n    }\n\n    private function processTemplateRenderError(string $templateName, \\Throwable $rendererError): void\n    {\n        $displayMessage = sprintf(\n            Registry::getLang()->translateString('EXCEPTION_SYSTEMCOMPONENT_TEMPLATENOTFOUND'),\n            $templateName\n        );\n        $displayedException = oxNew(Exception\\SystemComponentException::class, $displayMessage);\n        $displayedException->setComponent($templateName);\n        if ($this->isDebugMode()) {\n            $this->_aErrors = null;\n            Registry::getUtilsView()->addErrorToDisplay($displayedException);\n        }\n        Registry::getLogger()->error($displayedException->getMessage(), [$rendererError]);\n    }\n\n    private function passSessionErrorsToViewData(ViewControllerInterface $view, array $viewData): array\n    {\n        $errors = $this->getErrors($view->getClassKey());\n        if (\\is_array($errors) && count($errors)) {\n            Registry::getUtilsView()->passAllErrorsToView($viewData, $errors);\n        }\n        return $viewData;\n    }\n\n    private function isServiceController(string $classKey, string $class): bool\n    {\n        return isset(ContainerFacade::getParameter('oxid.view_controllers_map')[$classKey]) && ContainerFacade::has($class);\n    }\n}\n"
  },
  {
    "path": "source/Core/ShopIdCalculator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse Doctrine\\DBAL\\DriverManager;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\ConnectionParameterProvider;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContext;\n\nuse function array_fill_keys;\nuse function array_filter;\nuse function array_merge;\nuse function is_array;\nuse function unserialize;\n\nclass ShopIdCalculator\n{\n    public const BASE_SHOP_ID = 1;\n    private array $urlMap;\n\n    public function __construct(\n        private readonly \\OxidEsales\\Eshop\\Core\\UtilsServer $utilsServer,\n    ) {\n    }\n\n    public function getShopId(): int\n    {\n        return self::BASE_SHOP_ID;\n    }\n\n    protected function getShopUrlMap(): array\n    {\n        if (isset($this->urlMap)) {\n            return $this->urlMap;\n        }\n\n        $urlMap = [];\n        foreach ($this->fetchUrlsFromConfigTable() as $row) {\n            $shopId = (int)$row['oxshopid'];\n            $variableName = $row['oxvarname'];\n            $urlValues = $row['oxvarvalue'];\n\n            if ($variableName === 'aLanguageURLs' || $variableName === 'aLanguageSSLURLs') {\n                $urls = unserialize($urlValues, ['allowed_classes' => false]);\n                if (is_array($urls) && count($urls)) {\n                    $urls = array_filter($urls);\n                    $urls = array_fill_keys($urls, $shopId);\n                    $urlMap = array_merge($urlMap, $urls);\n                }\n            } elseif ($urlValues) {\n                $urlMap[$urlValues] = $shopId;\n            }\n        }\n        $this->urlMap = $urlMap;\n\n        return $urlMap;\n    }\n\n    private function fetchUrlsFromConfigTable(): array\n    {\n        $connection = DriverManager::getConnection(\n            (new ConnectionParameterProvider(new BasicContext()))->getParameters()\n        );\n        $statement = $connection\n            ->prepare(\n                \"SELECT oxshopid, oxvarname, oxvarvalue\n                FROM oxconfig\n                WHERE oxvarname IN ('aLanguageURLs', 'aLanguageSSLURLs', 'sMallShopURL','sMallSSLShopURL')\"\n            );\n\n        return $statement->executeQuery()->fetchAllAssociative();\n    }\n\n    protected function getUtilsServer(): \\OxidEsales\\Eshop\\Core\\UtilsServer\n    {\n        return $this->utilsServer;\n    }\n}"
  },
  {
    "path": "source/Core/ShopVersion.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nclass ShopVersion\n{\n    /**\n     * @return string OXID eShop compilation version.\n     */\n    public static function getVersion()\n    {\n        return '8.0.0-alpha.3';\n    }\n}\n"
  },
  {
    "path": "source/Core/SimpleXml.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse SimpleXMLElement;\n\n/**\n * Parses objects to XML and XML to simple XML objects.\n *\n * Example object:\n * oxStdClass Object\n *   (\n *       [title] => TestTitle\n *       [keys] => oxStdClass Object\n *           (\n *               [key] => Array\n *                   (\n *                       [0] => testKey1\n *                       [1] => testKey2\n *                   )\n *           )\n *   )\n *\n * would produce the following XML:\n * <?xml version=\"1.0\" encoding=\"utf-8\"?>\n * <testXml><title>TestTitle</title><keys><key>testKey1</key><key>testKey2</key></keys></testXml>\n */\nclass SimpleXml\n{\n    /**\n     * Parses object structure to XML string\n     *\n     * @param object $oInput    Input object\n     * @param string $sDocument Document name.\n     *\n     * @return string\n     */\n    public function objectToXml($oInput, $sDocument)\n    {\n        $oXml = new SimpleXMLElement(\"<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"?><$sDocument/>\");\n        $this->addSimpleXmlElement($oXml, $oInput);\n\n        return $oXml->asXml();\n    }\n\n    /**\n     * Parses XML string into object structure\n     *\n     * @param string $sXml XML Input\n     *\n     * @return SimpleXMLElement\n     */\n    public function xmlToObject($sXml)\n    {\n        return simplexml_load_string($sXml);\n    }\n\n    /**\n     * Recursively adds $oInput object data to SimpleXMLElement structure\n     *\n     * @param SimpleXMLElement    $oXml          Xml handler\n     * @param string|array|object $oInput        Input object\n     * @param string              $sPreferredKey Key to use instead of node's key.\n     *\n     * @return SimpleXMLElement\n     */\n    protected function addSimpleXmlElement($oXml, $oInput, $sPreferredKey = null)\n    {\n        $aElements = is_object($oInput) ? get_object_vars($oInput) : (array) $oInput;\n\n        foreach ($aElements as $sKey => $mElement) {\n            $oXml = $this->addChildNode($oXml, $sKey, $mElement, $sPreferredKey);\n        }\n\n        return $oXml;\n    }\n\n    /**\n     * Adds child node to given simple xml object.\n     *\n     * @param SimpleXMLElement    $oXml\n     * @param string              $sKey\n     * @param string|array|object $mElement\n     * @param string              $sPreferredKey\n     *\n     * @return SimpleXMLElement\n     */\n    protected function addChildNode($oXml, $sKey, $mElement, $sPreferredKey = null)\n    {\n        $aAttributes = [];\n        if (is_array($mElement) && array_key_exists('attributes', $mElement) && is_array($mElement['attributes'])) {\n            $aAttributes = $mElement['attributes'];\n            $mElement = $mElement['value'];\n        }\n\n        if (is_object($mElement) || is_array($mElement)) {\n            if (is_array($mElement) && is_int(key($mElement))) {\n                $this->addSimpleXmlElement($oXml, $mElement, $sKey);\n            } else {\n                $oChildNode = $oXml->addChild($sPreferredKey ? $sPreferredKey : $sKey);\n                $this->addNodeAttributes($oChildNode, $aAttributes);\n                $this->addSimpleXmlElement($oChildNode, $mElement);\n            }\n        } else {\n            $oChildNode = $oXml->addChild($sPreferredKey ? $sPreferredKey : $sKey);\n            $oChildNode[0] = $mElement; // $oChildNode[0] is the inner text-node\n            $this->addNodeAttributes($oChildNode, $aAttributes);\n        }\n\n        return $oXml;\n    }\n\n    /**\n     * Adds attributes to given node.\n     *\n     * @param SimpleXMLElement $oNode\n     * @param array            $aAttributes\n     *\n     * @return SimpleXMLElement\n     */\n    protected function addNodeAttributes($oNode, $aAttributes)\n    {\n        $aAttributes = (array) $aAttributes;\n        foreach ($aAttributes as $sKey => $sValue) {\n            $oNode->addAttribute($sKey, $sValue);\n        }\n\n        return $oNode;\n    }\n}\n"
  },
  {
    "path": "source/Core/SortingValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * @internal Please do not use or extend this class.\n */\nclass SortingValidator\n{\n    /**\n     * @param string $sortBy\n     * @param string $sortOrder\n     * @return bool\n     */\n    public function isValid($sortBy, $sortOrder)\n    {\n        $isValid = false;\n        if (\n            $sortBy\n            && $sortOrder\n            && in_array(strtolower($sortOrder), $this->getSortingOrders())\n            && in_array($sortBy, \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('aSortCols'))\n        ) {\n            $isValid = true;\n        }\n\n        return $isValid;\n    }\n\n    /**\n     * @return array\n     */\n    public function getSortingOrders()\n    {\n        return ['desc', 'asc'];\n    }\n}\n"
  },
  {
    "path": "source/Core/Str.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Factory class responsible for redirecting string handling functions to specific\n * string handling class. String handler basically is intended for dealing with multibyte string\n * and is NOT supposed to replace all string handling functions.\n * We use the handler for shop data and user input, but prefer not to use it for ascii strings\n * (eg. field or file names).\n */\nclass Str\n{\n    /**\n     * Specific string handler\n     *\n     * @var \\OxidEsales\\Eshop\\Core\\StrMb|\\OxidEsales\\Eshop\\Core\\StrRegular\n     */\n    protected static $_oHandler;\n\n    /**\n     * Class constructor. The constructor is defined in order to be possible to call parent::__construct() in modules.\n     *\n     * @return null\n     */\n    public function __construct()\n    {\n    }\n\n    /**\n     * Static method initializing new string handler or returning the existing one.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\StrMb|\\OxidEsales\\Eshop\\Core\\StrRegular\n     */\n    public static function getStr()\n    {\n        if (!isset(self::$_oHandler)) {\n            //let's init now non-static instance of oxStr to get the instance of str handler\n            self::$_oHandler = oxNew(\\OxidEsales\\Eshop\\Core\\Str::class)->getStrHandler();\n        }\n\n        return self::$_oHandler;\n    }\n\n    /**\n     * Non static getter returning str handler. The sense of getStr() and _getStrHandler() is\n     * to be possible to call this method statically ( \\OxidEsales\\Eshop\\Core\\Str::getStr() ), yet leaving the\n     * possibility to extend it in modules by overriding _getStrHandler() method.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\StrMb|\\OxidEsales\\Eshop\\Core\\StrRegular\n     */\n    protected function getStrHandler()\n    {\n        if (function_exists('mb_strlen')) {\n            return oxNew(\\OxidEsales\\Eshop\\Core\\StrMb::class);\n        }\n\n        return oxNew(\\OxidEsales\\Eshop\\Core\\StrRegular::class);\n    }\n}\n"
  },
  {
    "path": "source/Core/StrMb.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Class dealing with multibyte strings\n */\nclass StrMb\n{\n    /**\n     * The character encoding.\n     *\n     * @var string\n     */\n    protected $_sEncoding = 'UTF-8';\n\n    /**\n     * Language specific characters (currently german; storen in octal form)\n     *\n     * @var array\n     */\n    protected $_aUmls = [\"\\xc3\\xa4\", \"\\xc3\\xb6\", \"\\xc3\\xbc\", \"\\xC3\\x84\", \"\\xC3\\x96\", \"\\xC3\\x9C\", \"\\xC3\\x9F\"];\n\n    /**\n     * oxUtilsString::$_aUmls equivalent in entities form\n     *\n     * @var array\n     */\n    protected $_aUmlEntities = ['&auml;', '&ouml;', '&uuml;', '&Auml;', '&Ouml;', '&Uuml;', '&szlig;'];\n\n    /**\n     * Class constructor. The constructor is defined in order to be possible to call parent::__construct() in modules.\n     */\n    public function __construct()\n    {\n    }\n\n    /**\n     * PHP  multi byte compliant strlen() function wrapper\n     *\n     * @param string $sStr string to measure its length\n     *\n     * @return int\n     */\n    public function strlen($sStr)\n    {\n        return mb_strlen($sStr ?? '', $this->_sEncoding);\n    }\n\n    /**\n     * PHP multi byte compliant substr() function wrapper\n     *\n     * @param string $sStr    value to truncate\n     * @param int    $iStart  start position\n     * @param int    $iLength length\n     *\n     * @return string\n     */\n    public function substr($sStr, $iStart, $iLength = null)\n    {\n        $iLength = is_null($iLength) ? $this->strlen($sStr) : $iLength;\n\n        return mb_substr($sStr, $iStart, $iLength, $this->_sEncoding);\n    }\n\n    /**\n     * PHP multi byte compliant strpos() function wrapper\n     *\n     * @param string $sHaystack value to search in\n     * @param string $sNeedle   value to search for\n     * @param int    $iOffset   initial search position\n     *\n     * @return string\n     */\n    public function strpos($sHaystack, $sNeedle, $iOffset = null)\n    {\n        $iPos = false;\n        if ($sHaystack && $sNeedle) {\n            $iOffset = is_null($iOffset) ? 0 : $iOffset;\n            $iPos = mb_strpos($sHaystack, $sNeedle, $iOffset, $this->_sEncoding);\n        }\n\n        return $iPos;\n    }\n\n    /**\n     * PHP multi byte compliant strstr() function wrapper\n     *\n     * @param string $sHaystack value to search in\n     * @param string $sNeedle   value to search for\n     *\n     * @return string\n     */\n    public function strstr($sHaystack, $sNeedle)\n    {\n        // additional check according to bug in PHP 5.2.0 version\n        if (!$sHaystack) {\n            return false;\n        }\n\n        return mb_strstr($sHaystack, $sNeedle, false, $this->_sEncoding);\n    }\n\n    /**\n     * PHP multi byte compliant strtolower() function wrapper\n     *\n     * @param string $sString string being lower cased\n     *\n     * @return string\n     */\n    public function strtolower($sString)\n    {\n        return mb_strtolower($sString, $this->_sEncoding);\n    }\n\n    /**\n     * PHP multi byte compliant strtoupper() function wrapper\n     *\n     * @param string $sString string being lower cased\n     *\n     * @return string\n     */\n    public function strtoupper($sString)\n    {\n        return mb_strtoupper($sString, $this->_sEncoding);\n    }\n\n    /**\n     * PHP htmlspecialchars() function wrapper\n     *\n     * @param string $sString    string being converted\n     * @param int    $iQuotStyle quoting rule\n     *\n     * @return string\n     */\n    public function htmlspecialchars($sString, $iQuotStyle = ENT_QUOTES)\n    {\n        return htmlspecialchars($sString, $iQuotStyle, $this->_sEncoding);\n    }\n\n    /**\n     * PHP htmlentities() function wrapper\n     *\n     * @param string $sString    string being converted\n     * @param int    $iQuotStyle quoting rule\n     *\n     * @return string\n     */\n    public function htmlentities($sString, $iQuotStyle = ENT_QUOTES)\n    {\n        return htmlentities($sString, $iQuotStyle, $this->_sEncoding);\n    }\n\n    // @codingStandardsIgnoreStart\n    /**\n     * PHP html_entity_decode() function wrapper\n     *\n     * @param string $sString    string being converted\n     * @param int    $iQuotStyle quoting rule\n     *\n     * @return string\n     */\n    public function html_entity_decode($sString, $iQuotStyle = ENT_QUOTES)\n    {\n        return html_entity_decode($sString, $iQuotStyle, $this->_sEncoding);\n    }\n\n    /**\n     * PHP preg_split() function wrapper\n     *\n     * @param string $sPattern pattern to search for, as a string\n     * @param string $sString  input string\n     * @param int    $iLimit   (optional) only sub strings up to limit are returned\n     * @param int    $iFlag    flags\n     *\n     * @return string\n     */\n    public function preg_split($sPattern, $sString, $iLimit = -1, $iFlag = 0)\n    {\n        return preg_split($sPattern . 'u', $sString, $iLimit, $iFlag);\n    }\n\n    /**\n     * PHP preg_replace() function wrapper\n     *\n     * @param mixed  $aPattern pattern to search for, as a string\n     * @param mixed  $sString  string to replace\n     * @param string $sSubject strings to search and replace\n     * @param int    $iLimit   maximum possible replacements\n     * @param int    $iCount   number of replacements done\n     *\n     * @return string\n     */\n    public function preg_replace($aPattern, $sString, $sSubject, $iLimit = -1, $iCount = null)\n    {\n        if (is_array($aPattern)) {\n            foreach ($aPattern as &$sPattern) {\n                $sPattern = $sPattern . 'u';\n            }\n        } else {\n            $aPattern = $aPattern . 'u';\n        }\n\n        return preg_replace($aPattern, $sString, $sSubject, $iLimit, $iCount);\n    }\n\n    /**\n     * PHP preg_replace() function wrapper\n     *\n     * @param mixed    $pattern  pattern to search for, as a string\n     * @param callable $callback Callback function\n     * @param string   $subject  strings to search and replace\n     * @param int      $limit    maximum possible replacements\n     * @param int      $count    number of replacements done\n     *\n     * @return string\n     */\n    public function preg_replace_callback($pattern, $callback, $subject, $limit = -1, &$count = null)\n    {\n        if (is_array($pattern)) {\n            foreach ($pattern as &$item) {\n                $item = $item . 'u';\n            }\n        } else {\n            $pattern = $pattern . 'u';\n        }\n\n        return preg_replace_callback($pattern, $callback, $subject, $limit, $count);\n    }\n\n    /**\n     * PHP preg_match() function wrapper\n     *\n     * @param string $sPattern pattern to search for, as a string\n     * @param string $sSubject input string\n     * @param array  $aMatches is filled with the results of search\n     * @param int    $iFlags   flags\n     * @param int    $iOffset  place from which to start the search\n     *\n     * @return string\n     */\n    public function preg_match($sPattern, $sSubject, &$aMatches = null, $iFlags = 0, $iOffset = 0)\n    {\n        return preg_match($sPattern . 'u', $sSubject, $aMatches, $iFlags, $iOffset);\n    }\n\n    /**\n     * PHP preg_match_all() function wrapper\n     *\n     * @param string $sPattern pattern to search for, as a string\n     * @param string $sSubject input string\n     * @param array  $aMatches is filled with the results of search\n     * @param int    $iFlags   flags\n     * @param int    $iOffset  place from which to start the search\n     *\n     * @return string\n     */\n    public function preg_match_all($sPattern, $sSubject, &$aMatches = null, $iFlags = null, $iOffset = null)\n    {\n        return preg_match_all($sPattern . 'u', $sSubject, $aMatches, $iFlags, $iOffset);\n    }\n    // @codingStandardsIgnoreEnd\n\n    /**\n     * PHP ucfirst() function wrapper\n     *\n     * @param string $sSubject input string\n     *\n     * @return string\n     */\n    public function ucfirst($sSubject)\n    {\n        $sString = $this->strtoupper($this->substr($sSubject, 0, 1));\n\n        return $sString . $this->substr($sSubject, 1);\n    }\n\n    /**\n     * PHP wordwrap() function wrapper\n     *\n     * @param string $sString input string\n     * @param int    $iLength column width\n     * @param string $sBreak  line is broken using the optional break parameter\n     * @param bool   $blCut   string is always wrapped at the specified width\n     *\n     * @return string\n     */\n    public function wordwrap($sString, $iLength = 75, $sBreak = \"\\n\", $blCut = null)\n    {\n        if (!$blCut) {\n            $sRegexp = \"/^(.{1,{$iLength}}\\r?(\\s|$|\\n)|.{1,{$iLength}}[^\\r\\s\\n]*\\r?(\\n|\\s|$))/u\";\n        } else {\n            $sRegexp = \"/^([^\\s]{{$iLength}}|.{1,{$iLength}}\\s)/u\";\n        }\n\n        $iStrLen = mb_strlen($sString, $this->_sEncoding);\n        $iWraps = floor($iStrLen / $iLength);\n\n        $i = $iWraps;\n        $sReturn = '';\n        $aMatches = [];\n        while ($i > 0) {\n            $iWraps = floor(mb_strlen($sString, $this->_sEncoding) / $iLength);\n\n            $i = $iWraps;\n            if (preg_match($sRegexp, $sString, $aMatches)) {\n                $sStr = $aMatches[0];\n                $sReturn .= preg_replace('/\\s$/s', '', $sStr) . $sBreak;\n                $sString = $this->substr($sString, mb_strlen($sStr, $this->_sEncoding));\n            } else {\n                break;\n            }\n            $i--;\n        }\n        $sReturn = preg_replace(\"/$sBreak$/\", '', $sReturn);\n        if ($sString) {\n            $sReturn .= $sBreak . $sString;\n        }\n\n        return $sReturn;\n    }\n\n    /**\n     * Recodes and returns passed input:\n     * if $blToHtmlEntities == true  ä -> &auml;\n     * if $blToHtmlEntities == false &auml; -> ä\n     *\n     * @param string $sInput           text to recode\n     * @param bool   $blToHtmlEntities recode direction\n     * @param array  $aUmls            language specific characters\n     * @param array  $aUmlEntities     language specific characters equivalents in entities form\n     *\n     * @return string\n     */\n    public function recodeEntities($sInput, $blToHtmlEntities = false, $aUmls = [], $aUmlEntities = [])\n    {\n        $aUmls = (count($aUmls) > 0) ? array_merge($this->_aUmls, $aUmls) : $this->_aUmls;\n        $aUmlEntities = (count($aUmlEntities) > 0) ? array_merge($this->_aUmlEntities, $aUmlEntities) : $this->_aUmlEntities;\n\n        return $blToHtmlEntities ? str_replace($aUmls, $aUmlEntities, $sInput) : str_replace($aUmlEntities, $aUmls, $sInput);\n    }\n\n    /**\n     * Checks if string has special chars\n     *\n     * @param string $sStr string to search in\n     *\n     * @return bool\n     */\n    public function hasSpecialChars($sStr)\n    {\n        return $this->preg_match(\"/(\" . implode(\"|\", $this->_aUmls) . \"|(&amp;))/\", $sStr);\n    }\n\n    /**\n     * Replaces special characters with passed char.\n     * Special chars are: \\n \\r \\t \\xc2\\x95 \\xc2\\xa0 ;\n     *\n     * @param string $sStr      string to cleanup\n     * @param string $sCleanChr which character should be used as a replacement (default is empty space)\n     *\n     * @return string\n     */\n    public function cleanStr($sStr, $sCleanChr = ' ')\n    {\n        return $this->preg_replace(\"/\\n|\\r|\\t|\\xc2\\x95|\\xc2\\xa0|;/\", $sCleanChr, $sStr);\n    }\n\n    /**\n     * wrapper for json encode, which does not work with non utf8 characters\n     *\n     * @param mixed $data data to encode\n     *\n     * @return string\n     */\n    public function jsonEncode($data)\n    {\n        return json_encode($data);\n    }\n\n    // @codingStandardsIgnoreStart\n    /**\n     * PHP strip_tags() function wrapper.\n     *\n     * @param string $sString        the input string\n     * @param string $sAllowableTags an optional parameter to specify tags which should not be stripped\n     *\n     * @return string\n     */\n    public function strip_tags($sString, $sAllowableTags = '')\n    {\n        if (stripos($sAllowableTags, '<style>') === false) {\n            // strip style tags with definitions within\n            $sString = $this->preg_replace(\"'<style[^>]*>.*</style>'siU\", '', $sString);\n        }\n\n        return strip_tags($sString, $sAllowableTags);\n    }\n    // @codingStandardsIgnoreEnd\n\n    /**\n     * Compares two strings. Case sensitive.\n     * For use in sorting with reverse order\n     *\n     * @param string $sStr1 String to compare\n     * @param string $sStr2 String to compare\n     *\n     * @return int > 0 if str1 is less than str2; < 0 if str1 is greater than str2, and 0 if they are equal.\n     */\n    public function strrcmp($sStr1, $sStr2)\n    {\n        return -strcmp($sStr1, $sStr2);\n    }\n}\n"
  },
  {
    "path": "source/Core/StrRegular.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Class dealing with regular string handling\n */\nclass StrRegular\n{\n    /**\n     * The character encoding.\n     *\n     * @var string\n     */\n    protected $_sEncoding = 'ISO8859-15';\n\n    /**\n     * Language specific characters (currently german; storen in octal form)\n     *\n     * @var array\n     */\n    protected $_aUmls = [\"\\344\", \"\\366\", \"\\374\", \"\\304\", \"\\326\", \"\\334\", \"\\337\"];\n\n    /**\n     * oxUtilsString::$_aUmls equivalent in entities form\n     *\n     * @var array\n     */\n    protected $_aUmlEntities = ['&auml;', '&ouml;', '&uuml;', '&Auml;', '&Ouml;', '&Uuml;', '&szlig;'];\n\n    /**\n     * Class constructor. The constructor is defined in order to be possible to call parent::__construct() in modules.\n     */\n    public function __construct()\n    {\n    }\n\n    /**\n     * PHP strlen() function wrapper\n     *\n     * @param string $sStr string to measure its length\n     *\n     * @return int\n     */\n    public function strlen($sStr)\n    {\n        return strlen($sStr);\n    }\n\n    /**\n     * PHP substr() function wrapper\n     *\n     * @param string $sStr    value to truncate\n     * @param int    $iStart  start position\n     * @param int    $iLength length\n     *\n     * @return string\n     */\n    public function substr($sStr, $iStart, $iLength = null)\n    {\n        if (is_null($iLength)) {\n            return substr($sStr, $iStart);\n        }\n        return substr($sStr, $iStart, $iLength);\n    }\n\n    /**\n     * PHP strpos() function wrapper\n     *\n     * @param string $sHaystack value to search in\n     * @param string $sNeedle   value to search for\n     * @param int    $iOffset   initial search position\n     *\n     * @return string\n     */\n    public function strpos($sHaystack, $sNeedle, $iOffset = null)\n    {\n        $iPos = false;\n        if ($sHaystack && $sNeedle) {\n            if (is_null($iOffset)) {\n                $iPos = strpos($sHaystack, $sNeedle);\n            } else {\n                $iPos = strpos($sHaystack, $sNeedle, $iOffset);\n            }\n        }\n\n        return $iPos;\n    }\n\n    /**\n     * PHP strstr() function wrapper\n     *\n     * @param string $sHaystack string searching in\n     * @param string $sNeedle   string to search\n     *\n     * @return mixed\n     */\n    public function strstr($sHaystack, $sNeedle)\n    {\n        return strstr($sHaystack, $sNeedle);\n    }\n\n    /**\n     * PHP multi byte compliant strtolower() function wrapper\n     *\n     * @param string $sString string being lower cased\n     *\n     * @return string\n     */\n    public function strtolower($sString)\n    {\n        return strtolower($sString);\n    }\n\n    /**\n     * PHP strtolower() function wrapper\n     *\n     * @param string $sString string being lower cased\n     *\n     * @return string\n     */\n    public function strtoupper($sString)\n    {\n        return strtoupper($sString);\n    }\n\n    /**\n     * PHP htmlspecialchars() function wrapper\n     *\n     * @param string $sString    string being converted\n     * @param int    $iQuotStyle quoting rule\n     *\n     * @return string\n     */\n    public function htmlspecialchars($sString, $iQuotStyle = ENT_QUOTES)\n    {\n        return htmlspecialchars($sString, $iQuotStyle, $this->_sEncoding);\n    }\n\n    /**\n     * PHP htmlentities() function wrapper\n     *\n     * @param string $sString    string being converted\n     * @param int    $iQuotStyle quoting rule\n     *\n     * @return string\n     */\n    public function htmlentities($sString, $iQuotStyle = ENT_QUOTES)\n    {\n        return htmlentities($sString, $iQuotStyle, $this->_sEncoding);\n    }\n\n    /**\n     * PHP html_entity_decode() function wrapper\n     *\n     * @param string $sString    string being converted\n     * @param int    $iQuotStyle quoting rule\n     *\n     * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps\n     *\n     * @return string\n     */\n    public function html_entity_decode($sString, $iQuotStyle = ENT_QUOTES)\n    {\n        return html_entity_decode($sString, $iQuotStyle, $this->_sEncoding);\n    }\n\n    /**\n     * PHP preg_split() function wrapper\n     *\n     * @param string $sPattern pattern to search for, as a string\n     * @param string $sString  input string\n     * @param int    $iLimit   (optional) only substrings up to limit are returned\n     * @param int    $iFlag    flags\n     *\n     * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps\n     *\n     * @return string\n     */\n    public function preg_split($sPattern, $sString, $iLimit = -1, $iFlag = 0)\n    {\n        return preg_split($sPattern, $sString, $iLimit, $iFlag);\n    }\n\n    /**\n     * PHP preg_replace() function wrapper\n     *\n     * @param mixed  $sPattern pattern to search for, as a string\n     * @param mixed  $sString  string to replace\n     * @param string $sSubject strings to search and replace\n     * @param int    $iLimit   maximum possible replacements\n     * @param int    $iCount   number of replacements done\n     *\n     * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps\n     *\n     * @return string\n     */\n    public function preg_replace($sPattern, $sString, $sSubject, $iLimit = -1, $iCount = null)\n    {\n        return preg_replace($sPattern, $sString, $sSubject, $iLimit, $iCount);\n    }\n\n    /**\n     * PHP preg_replace() function wrapper\n     *\n     * @param mixed    $pattern  pattern to search for, as a string\n     * @param callable $callback Callback function\n     * @param string   $subject  strings to search and replace\n     * @param int      $limit    maximum possible replacements\n     * @param int      $count    number of replacements done\n     *\n     * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps\n     *\n     * @return string\n     */\n    public function preg_replace_callback($pattern, $callback, $subject, $limit = -1, &$count = null)\n    {\n        return preg_replace_callback($pattern, $callback, $subject, $limit, $count);\n    }\n\n    /**\n     * PHP preg_match() function wrapper\n     *\n     * @param string $sPattern pattern to search for, as a string\n     * @param string $sSubject input string\n     * @param array  $aMatches is filled with the results of search\n     * @param int    $iFlags   flags\n     * @param int    $iOffset  place from which to start the search\n     *\n     * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps\n     *\n     * @return string\n     */\n    public function preg_match($sPattern, $sSubject, &$aMatches = null, $iFlags = null, $iOffset = null)\n    {\n        return preg_match($sPattern, $sSubject, $aMatches, $iFlags, $iOffset);\n    }\n\n    /**\n     * PHP preg_match_all() function wrapper\n     *\n     * @param string $sPattern pattern to search for, as a string\n     * @param string $sSubject input string\n     * @param array  $aMatches is filled with the results of search\n     * @param int    $iFlags   flags\n     * @param int    $iOffset  place from which to start the search\n     *\n     * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps\n     *\n     * @return string\n     */\n    public function preg_match_all($sPattern, $sSubject, &$aMatches = null, $iFlags = null, $iOffset = null)\n    {\n        return preg_match_all($sPattern, $sSubject, $aMatches, $iFlags, $iOffset);\n    }\n\n    /**\n     * PHP ucfirst() function wrapper\n     *\n     * @param string $sSubject input string\n     *\n     * @return string\n     */\n    public function ucfirst($sSubject)\n    {\n        $sString = $this->strtoupper($this->substr($sSubject, 0, 1));\n\n        return $sString . $this->substr($sSubject, 1);\n    }\n\n    /**\n     * PHP wordwrap() function wrapper\n     *\n     * @param string $sString input string\n     * @param int    $iLength column width\n     * @param string $sBreak  line is broken using the optional break parameter\n     * @param bool   $blCut   string is always wrapped at the specified width\n     *\n     * @return string\n     */\n    public function wordwrap($sString, $iLength = 75, $sBreak = \"\\n\", $blCut = null)\n    {\n        return wordwrap($sString, $iLength, $sBreak, $blCut);\n    }\n\n    /**\n     * Recodes and returns passed input:\n     * if $blToHtmlEntities == true  ä -> &auml;\n     * if $blToHtmlEntities == false &auml; -> ä\n     *\n     * @param string $sInput           text to recode\n     * @param bool   $blToHtmlEntities recode direction\n     * @param array  $aUmls            language specific characters\n     * @param array  $aUmlEntities     language specific characters equivalents in entities form\n     *\n     * @return string\n     */\n    public function recodeEntities($sInput, $blToHtmlEntities = false, $aUmls = [], $aUmlEntities = [])\n    {\n        $aUmls = (count($aUmls) > 0) ? array_merge($this->_aUmls, $aUmls) : $this->_aUmls;\n        $aUmlEntities = (count($aUmlEntities) > 0)\n            ? array_merge($this->_aUmlEntities, $aUmlEntities)\n            : $this->_aUmlEntities;\n\n        return $blToHtmlEntities\n            ? str_replace($aUmls, $aUmlEntities, $sInput)\n            : str_replace($aUmlEntities, $aUmls, $sInput);\n    }\n\n    /**\n     * Checks if string has special chars\n     *\n     * @param string $sStr string to search in\n     *\n     * @return bool\n     */\n    public function hasSpecialChars($sStr)\n    {\n        return $this->preg_match(\"/(\" . implode(\"|\", $this->_aUmls) . \"|(&amp;))/\", $sStr);\n    }\n\n    /**\n     * Replaces special characters with passed char.\n     * Special chars are: \\n \\r \\t x95 xa0 ;\n     *\n     * @param string $sStr      string to cleanup\n     * @param mixed  $sCleanChr which character should be used as a replacement (default is empty space)\n     *\n     * @return string\n     */\n    public function cleanStr($sStr, $sCleanChr = ' ')\n    {\n        return $this->preg_replace(\"/\\n|\\r|\\t|\\x95|\\xa0|;/\", $sCleanChr, $sStr);\n    }\n\n    /**\n     * wrapper for json encode, which does not work with non utf8 characters\n     *\n     * @param mixed $data data to encode\n     *\n     * @return string\n     */\n    public function jsonEncode($data)\n    {\n        if (is_array($data)) {\n            $ret = \"\";\n            $blWasOne = false;\n            $blNumerical = true;\n            reset($data);\n            while ($blNumerical && $key = key($data)) {\n                $blNumerical = !is_string($key);\n            }\n            if ($blNumerical) {\n                return '[' . implode(',', array_map([$this, 'jsonEncode'], $data)) . ']';\n            } else {\n                foreach ($data as $key => $val) {\n                    if ($blWasOne) {\n                        $ret .= ',';\n                    } else {\n                        $blWasOne = true;\n                    }\n                    $ret .= '\"' . addslashes($key) . '\":' . $this->jsonEncode($val);\n                }\n\n                return \"{\" . $ret . \"}\";\n            }\n        } else {\n            return '\"' . addcslashes((string) $data, \"\\r\\n\\t\\\"\\\\\") . '\"';\n        }\n    }\n\n    /**\n     * PHP strip_tags() function wrapper.\n     *\n     * @param string $sString        the input string\n     * @param string $sAllowableTags an optional parameter to specify tags which should not be stripped\n     *\n     * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps\n     *\n     * @return string\n     */\n    public function strip_tags($sString, $sAllowableTags = '')\n    {\n        if (stripos($sAllowableTags, '<style>') === false) {\n            // strip style tags with definitions within\n            $sString = $this->preg_replace(\"'<style[^>]*>.*</style>'siU\", '', $sString);\n        }\n\n        return strip_tags($sString, $sAllowableTags);\n    }\n\n    /**\n     * Compares two strings. Case sensitive.\n     * For use in sorting with reverse order\n     *\n     * @param string $sStr1 String to compare\n     * @param string $sStr2 String to compare\n     *\n     * @return int > 0 if str1 is less than str2; < 0 if str1 is greater than str2, and 0 if they are equal.\n     */\n    public function strrcmp($sStr1, $sStr2)\n    {\n        return -strcmp($sStr1, $sStr2);\n    }\n}\n"
  },
  {
    "path": "source/Core/SystemEventHandler.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse Exception;\nuse OxidEsales\\Eshop\\Core\\Dao\\ApplicationServerDao;\nuse OxidEsales\\Eshop\\Core\\Module\\ModuleList;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Service\\ApplicationServerExporter;\nuse OxidEsales\\Eshop\\Core\\Service\\ApplicationServerService;\n\n/**\n * Contains system event handler methods\n *\n * @internal Do not make a module extension for this class.\n */\nclass SystemEventHandler\n{\n    /**\n     * @var \\OxidEsales\\Eshop\\Core\\OnlineModuleVersionNotifier\n     */\n    private $onlineModuleVersionNotifier = null;\n\n    /**\n     * @var \\OxidEsales\\Eshop\\Core\\OnlineLicenseCheck\n     */\n    private $onlineLicenseCheck = null;\n\n    /**\n     * OLC dependency setter\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\OnlineLicenseCheck $onlineLicenseCheck\n     */\n    public function setOnlineLicenseCheck(\\OxidEsales\\Eshop\\Core\\OnlineLicenseCheck $onlineLicenseCheck)\n    {\n        $this->onlineLicenseCheck = $onlineLicenseCheck;\n    }\n\n    /**\n     * OLC dependency getter\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\OnlineLicenseCheck\n     */\n    public function getOnlineLicenseCheck()\n    {\n        if (!$this->onlineLicenseCheck) {\n            /** @var \\OxidEsales\\Eshop\\Core\\Curl $curl */\n            $curl = oxNew(\\OxidEsales\\Eshop\\Core\\Curl::class);\n\n            /** @var \\OxidEsales\\Eshop\\Core\\OnlineServerEmailBuilder $emailBuilder */\n            $emailBuilder = oxNew(\\OxidEsales\\Eshop\\Core\\OnlineServerEmailBuilder::class);\n\n            /** @var \\OxidEsales\\Eshop\\Core\\SimpleXml $simpleXml */\n            $simpleXml = oxNew(\\OxidEsales\\Eshop\\Core\\SimpleXml::class);\n\n            /** @var \\OxidEsales\\Eshop\\Core\\OnlineLicenseCheckCaller $licenseCaller */\n            $licenseCaller = oxNew(\\OxidEsales\\Eshop\\Core\\OnlineLicenseCheckCaller::class, $curl, $emailBuilder, $simpleXml);\n\n            /** @var \\OxidEsales\\Eshop\\Core\\UserCounter $userCounter */\n            $userCounter = oxNew(\\OxidEsales\\Eshop\\Core\\UserCounter::class);\n\n            /** @var ApplicationServerExporter $appServerExporter */\n            $appServerExporter = $this->getApplicationServerExporter();\n\n            /** @var \\OxidEsales\\Eshop\\Core\\OnlineLicenseCheck $OLC */\n            $OLC = oxNew(\\OxidEsales\\Eshop\\Core\\OnlineLicenseCheck::class, $licenseCaller);\n            $OLC->setAppServerExporter($appServerExporter);\n            $OLC->setUserCounter($userCounter);\n\n            $this->setOnlineLicenseCheck($OLC);\n        }\n\n        return $this->onlineLicenseCheck;\n    }\n\n    /**\n     * ApplicationServerExporter dependency setter\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Service\\ApplicationServerExporterInterface\n     */\n    protected function getApplicationServerExporter()\n    {\n        $appServerService = $this->getAppServerService();\n        return oxNew(ApplicationServerExporter::class, $appServerService);\n    }\n\n    /**\n     * OnlineModuleVersionNotifier dependency setter\n     *\n     * @param \\OxidEsales\\Eshop\\Core\\OnlineModuleVersionNotifier $onlineModuleVersionNotifier\n     */\n    public function setOnlineModuleVersionNotifier(\\OxidEsales\\Eshop\\Core\\OnlineModuleVersionNotifier $onlineModuleVersionNotifier)\n    {\n        $this->onlineModuleVersionNotifier = $onlineModuleVersionNotifier;\n    }\n\n    /**\n     * OnlineModuleVersionNotifier dependency getter\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\OnlineModuleVersionNotifier\n     */\n    public function getOnlineModuleVersionNotifier()\n    {\n        if (!$this->onlineModuleVersionNotifier) {\n            /** @var \\OxidEsales\\Eshop\\Core\\Curl $curl */\n            $curl = oxNew(\\OxidEsales\\Eshop\\Core\\Curl::class);\n\n            /** @var \\OxidEsales\\Eshop\\Core\\OnlineServerEmailBuilder $mailBuilder */\n            $mailBuilder = oxNew(\\OxidEsales\\Eshop\\Core\\OnlineServerEmailBuilder::class);\n\n            /** @var \\OxidEsales\\Eshop\\Core\\SimpleXml $simpleXml */\n            $simpleXml = oxNew(\\OxidEsales\\Eshop\\Core\\SimpleXml::class);\n\n            /** @var \\OxidEsales\\Eshop\\Core\\OnlineModuleVersionNotifierCaller $onlineModuleVersionNotifierCaller */\n            $onlineModuleVersionNotifierCaller = oxNew(\n                \\OxidEsales\\Eshop\\Core\\OnlineModuleVersionNotifierCaller::class,\n                $curl,\n                $mailBuilder,\n                $simpleXml\n            );\n\n            /** @var \\OxidEsales\\Eshop\\Core\\OnlineModuleVersionNotifier $onlineModuleVersionNotifier */\n            $onlineModuleVersionNotifier = oxNew(\n                \\OxidEsales\\Eshop\\Core\\OnlineModuleVersionNotifier::class,\n                $onlineModuleVersionNotifierCaller,\n                oxNew(ModuleList::class)\n            );\n\n            $this->setOnlineModuleVersionNotifier($onlineModuleVersionNotifier);\n        }\n\n        return $this->onlineModuleVersionNotifier;\n    }\n\n    /**\n     * onAdminLogin() is called on every successful login to the backend\n     */\n    public function onAdminLogin()\n    {\n        try {\n            $this->getOnlineModuleVersionNotifier()->versionNotify();\n        } catch (Exception $o) {\n        }\n    }\n\n    /**\n     * Perform shop startup related actions, like license check.\n     */\n    public function onShopStart()\n    {\n        $this->validateOffline();\n    }\n\n    /**\n     * Perform shop finishing up related actions, like updating app server data.\n     */\n    public function onShopEnd()\n    {\n        $this->validateOnline();\n    }\n\n    /**\n     * Check if shop is valid online.\n     */\n    protected function validateOnline()\n    {\n        try {\n            $appServerService = $this->getAppServerService();\n            if (Registry::getConfig()->isAdmin()) {\n                $appServerService->updateAppServerInformationInAdmin();\n            } else {\n                $appServerService->updateAppServerInformationInFrontend();\n            }\n\n            if (!Registry::getUtils()->isSearchEngine()) {\n                $this->sendShopInformation();\n            }\n        } catch (Exception $exception) {\n            Registry::getLogger()->error($exception->getMessage(), [$exception]);\n        }\n    }\n\n    /**\n     * Sends shop information to oxid servers.\n     */\n    protected function sendShopInformation()\n    {\n        if ($this->needToSendShopInformation()) {\n            $this->updateNextCheckTime();\n            $onlineLicenseCheck = $this->getOnlineLicenseCheck();\n            $onlineLicenseCheck->validateShopSerials();\n        }\n    }\n\n    /**\n     * Check if need to send information.\n     * We will not send information on each request due to possible performance drop.\n     *\n     * @return bool\n     */\n    private function needToSendShopInformation()\n    {\n        return $this->getNextCheckTime() < $this->getCurrentTime();\n    }\n\n    /**\n     * Return time stamp when shop was checked last with white noise from config.\n     *\n     * @return int\n     */\n    private function getNextCheckTime()\n    {\n        return (int) Registry::getConfig()->getSystemConfigParameter('sOnlineLicenseNextCheckTime');\n    }\n\n    /**\n     * Update when shop was checked last time with white noise.\n     * White noise is used to separate call time for different shop.\n     */\n    private function updateNextCheckTime()\n    {\n        $hourToCheck = $this->getCheckTime();\n\n        /** @var \\OxidEsales\\Eshop\\Core\\UtilsDate $utilsDate */\n        $utilsDate = Registry::getUtilsDate();\n        $nextCheckTime = $utilsDate->formTime('tomorrow', $hourToCheck);\n\n        Registry::getConfig()->saveSystemConfigParameter('str', 'sOnlineLicenseNextCheckTime', $nextCheckTime);\n    }\n\n    /**\n     * Returns time (hour minutes seconds) when to perform license check.\n     * Create if does not exist.\n     *\n     * @return string time formed as H:i:s\n     */\n    private function getCheckTime()\n    {\n        $checkTime = Registry::getConfig()->getSystemConfigParameter('sOnlineLicenseCheckTime');\n        if (!$checkTime) {\n            $hourToCheck = rand(8, 23);\n            $minuteToCheck = rand(0, 59);\n            $secondToCheck = rand(0, 59);\n\n            $checkTime = $hourToCheck . ':' . $minuteToCheck . ':' . $secondToCheck;\n            Registry::getConfig()->saveSystemConfigParameter('str', 'sOnlineLicenseCheckTime', $checkTime);\n        }\n\n        return $checkTime;\n    }\n\n    /**\n     * Return current time - time stamp.\n     *\n     * @return int\n     */\n    private function getCurrentTime()\n    {\n        /** @var \\OxidEsales\\Eshop\\Core\\UtilsDate $utilsDate */\n        $utilsDate = Registry::getUtilsDate();\n\n        return $utilsDate->getTime();\n    }\n\n    /**\n     * Check if shop valid and do related actions.\n     */\n    protected function validateOffline()\n    {\n    }\n\n    /**\n     * Gets application server service.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Service\\ApplicationServerServiceInterface\n     */\n    protected function getAppServerService()\n    {\n        $appServerService = oxNew(\n            ApplicationServerService::class,\n            oxNew(\n                ApplicationServerDao::class,\n                \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb(),\n                Registry::getConfig()\n            ),\n            oxNew(\\OxidEsales\\Eshop\\Core\\UtilsServer::class),\n            Registry::get(\"oxUtilsDate\")->getTime()\n        );\n\n        return $appServerService;\n    }\n}\n"
  },
  {
    "path": "source/Core/SystemRequirements.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider as DatabaseConnectionProvider;\nuse OxidEsales\\Eshop\\Core\\Exception\\SystemComponentException;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\SystemRequirements\\SystemSecurityChecker;\n\n/**\n * System requirements class.\n */\nclass SystemRequirements\n{\n    const MODULE_STATUS_UNABLE_TO_DETECT = -1;\n    const MODULE_STATUS_BLOCKS_SETUP = 0;\n    const MODULE_STATUS_FITS_MINIMUM_REQUIREMENTS = 1;\n    const MODULE_STATUS_OK = 2;\n\n    const MODULE_GROUP_ID_SERVER_CONFIG = 'server_config';\n    const MODULE_ID_MOD_REWRITE = 'mod_rewrite';\n    const MODULE_ID_MYSQL_VERSION = 'mysql_version';\n\n    /**\n     * System required modules\n     *\n     * @var array\n     */\n    protected $_aRequiredModules = null;\n\n    /**\n     * System requirements status\n     *\n     * @var bool\n     */\n    protected $_blSysReqStatus = null;\n\n    /**\n     * Columns that should not be check for collation\n     *\n     * @var array\n     */\n    protected $_aException = ['OXDELIVERY' => 'OXDELTYPE', 'OXSELECTLIST' => 'OXIDENT'];\n\n    /**\n     * Columns to check for collation\n     *\n     * @var array\n     */\n    protected $_aColumns = [\n        'OXID',\n        'OXOBJECTID',\n        'OXARTICLENID',\n        'OXACTIONID',\n        'OXARTID',\n        'OXUSERID',\n        'OXADDRESSUSERID',\n        'OXCOUNTRYID',\n        'OXSESSID',\n        'OXITMID',\n        'OXPARENTID',\n        'OXAMITEMID',\n        'OXAMTASKID',\n        'OXVENDORID',\n        'OXMANUFACTURERID',\n        'OXROOTID',\n        'OXATTRID',\n        'OXCATID',\n        'OXDELID',\n        'OXDELSETID',\n        'OXITMARTID',\n        'OXFIELDID',\n        'OXROLEID',\n        'OXCNID',\n        'OXANID',\n        'OXARTICLENID',\n        'OXCATNID',\n        'OXDELIVERYID',\n        'OXDISCOUNTID',\n        'OXGROUPSID',\n        'OXLISTID',\n        'OXPAYMENTID',\n        'OXDELTYPE',\n        'OXROLEID',\n        'OXSELNID',\n        'OXBILLCOUNTRYID',\n        'OXDELCOUNTRYID',\n        'OXPAYMENTID',\n        'OXCARDID',\n        'OXPAYID',\n        'OXIDENT',\n        'OXDEFCAT',\n        'OXBASKETID',\n        'OXPAYMENTSID',\n        'OXORDERID',\n        'OXVOUCHERSERIEID',\n    ];\n\n    /**\n     * Installation requirements info url\n     *\n     * @var string\n     */\n    protected $_sReqInfoUrl = \"https://docs.oxid-esales.com/eshop/en/latest/installation/new-installation/server-and-system-requirements.html\";\n\n    /**\n     * Installation preparation info url\n     *\n     * @var string\n     */\n    protected $_sPreparationInfoUrl = \"https://docs.oxid-esales.com/eshop/en/latest/installation/new-installation/preparing-for-installation.html\";\n\n    /**\n     * Module or system configuration mapping with installation requirements info url anchor\n     *\n     * @var array\n     */\n    protected $_aInfoMap = [\n        \"php_version\"        => \"php\",\n        \"mod_rewrite\"        => \"web-server\",\n        \"mysql_version\"      => \"database\",\n\n        \"allow_url_fopen\"    => \"php\",\n        \"request_uri\"        => \"php\",\n        \"ini_set\"            => \"php\",\n        \"memory_limit\"       => \"php\",\n        \"file_uploads\"       => \"php\",\n        \"session_autostart\"  => \"php\",\n\n        \"php_xml\"            => \"php\",\n        \"j_son\"              => \"php\",\n        \"i_conv\"             => \"php\",\n        \"tokenizer\"          => \"php\",\n        \"mysql_connect\"      => \"php\",\n        \"gd_info\"            => \"php\",\n        \"mb_string\"          => \"php\",\n        \"curl\"               => \"php\",\n        \"bc_math\"            => \"php\",\n        \"open_ssl\"           => \"openssl\",\n        \"soap\"               => \"php\",\n    ];\n\n    /**\n     * Module or system configuration mapping with installation preparations info url anchor\n     *\n     * @var array\n     */\n    protected $_aPreparationInfoMap = [\n        \"server_permissions\" => \"schritt-customising-file-and-directory-permissions\",\n    ];\n\n    /**\n     * Class constructor. The constructor is defined in order to be possible to call parent::__construct() in modules.\n     *\n     * @return null\n     */\n    public function __construct()\n    {\n    }\n\n    /**\n     * Only used for convenience in UNIT tests by doing so we avoid\n     * writing extended classes for testing protected or private methods\n     *\n     * @param string $method Methods name\n     * @param array  $arguments Argument array\n     * @return false|mixed\n     * @throws SystemComponentException\n     */\n    public function __call($method, $arguments)\n    {\n        if (method_exists($this, $method)) {\n            return call_user_func_array([& $this, $method], $arguments);\n        }\n        throw new SystemComponentException(\n            \"Function '$method' does not exist or is not accessible! (\" . get_class($this) . \")\" . PHP_EOL\n        );\n    }\n\n    /**\n     * Possibility to mock isAdmin() function as we do not extend oxsuperconfig.\n     *\n     * @return bool\n     */\n    public function isAdmin()\n    {\n        return isAdmin();\n    }\n\n    /**\n     * Sets system required modules\n     *\n     * @return array\n     */\n    public function getRequiredModules()\n    {\n        if ($this->_aRequiredModules == null) {\n            $aRequiredPHPExtensions = [\n                'php_xml',\n                'j_son',\n                'i_conv',\n                'tokenizer',\n                'mysql_connect',\n                'gd_info',\n                'mb_string',\n                'curl',\n                'bc_math',\n                'open_ssl',\n                'soap',\n            ];\n\n            $aRequiredPHPConfigs = [\n                'allow_url_fopen',\n                'request_uri',\n                'ini_set',\n                'memory_limit',\n                'unicode_support',\n                'file_uploads',\n                'session_autostart',\n            ];\n\n            $aRequiredServerConfigs = [\n                'mod_rewrite',\n                'server_permissions',\n                'cryptographically_sufficient_configuration',\n            ];\n\n            $this->_aRequiredModules = array_fill_keys($aRequiredServerConfigs, 'server_config') +\n                                       array_fill_keys($aRequiredPHPConfigs, 'php_config') +\n                                       array_fill_keys($aRequiredPHPExtensions, 'php_extennsions')\n            ;\n        }\n\n        return $this->_aRequiredModules;\n    }\n\n    /**\n     * Checks if curl extension is loaded\n     *\n     * @return integer\n     */\n    public function checkCurl()\n    {\n        return extension_loaded('curl') ? 2 : 1;\n    }\n\n    /**\n     * Checks if mbstring extension is loaded\n     *\n     * @return integer\n     */\n    public function checkMbString()\n    {\n        return extension_loaded('mbstring') ? 2 : 1;\n    }\n\n    /**\n     * Checks if permissions on servers are correctly setup\n     *\n     * @param string $path    check path [optional]\n     * @param int    $minPerm min permission level, default 777 [optional]\n     *\n     * @return int\n     */\n    public function checkServerPermissions($path = null, $minPerm = 777)\n    {\n        clearstatcache();\n        $path = $path ?: getShopBasePath();\n        $modStat = 2;\n        $permissionIssues = $this->getPermissionIssuesList($path, $minPerm);\n        if (count($permissionIssues['missing']) + count($permissionIssues['not_writable'])) {\n            $modStat = 0;\n        }\n\n        return $modStat;\n    }\n\n    /**\n     * @see cryptographically_sufficient_configuration\n     * @return int\n     */\n    public function checkCryptographicallySufficientConfiguration(): int\n    {\n        return (new SystemSecurityChecker())\n            ->isCryptographicallySecure()\n            ? self::MODULE_STATUS_OK\n            : self::MODULE_STATUS_BLOCKS_SETUP;\n    }\n\n    /**\n     * Get list of permission issues\n     *\n     * @param string $shopPath\n     * @param int $minPerm\n     *\n     * @return array\n     */\n    public function getPermissionIssuesList($shopPath = null, $minPerm = 777)\n    {\n        clearstatcache();\n        $shopPath = $shopPath ?: getShopBasePath();\n        $pathCheckResults = [\n            'missing' => [],\n            'not_writable' => []\n        ];\n\n        $buildDirectory = ContainerFacade::getParameter('oxid_esales.build_directory');\n\n        $pathsToCheck = [\n            $buildDirectory\n        ];\n\n        $onePathToCheck = reset($pathsToCheck);\n        while ($onePathToCheck) {\n            // missing file/folder?\n            if (!file_exists($onePathToCheck)) {\n                $pathCheckResults['missing'][] = str_replace($shopPath, '', $onePathToCheck);\n            }\n\n            if (is_dir($onePathToCheck)) {\n                // adding subfolders\n                $subDirectories = glob($onePathToCheck . '*', GLOB_ONLYDIR);\n                if (is_array($subDirectories)) {\n                    foreach ($subDirectories as $oneSubDirectory) {\n                        $pathsToCheck[] = $oneSubDirectory . '/';\n                    }\n                }\n            }\n\n            // testing if file permissions >= $iMinPerm\n            if (!is_readable($onePathToCheck) || !is_writable($onePathToCheck)) {\n                $pathCheckResults['not_writable'][] = str_replace($shopPath, '', $onePathToCheck);\n            }\n\n            $onePathToCheck = next($pathsToCheck);\n        }\n\n        return $pathCheckResults;\n    }\n\n    /**\n     * returns host, port, base dir, ssl information as associative array, false on error\n     *\n     * @return array|false\n     */\n    protected function getShopSSLHostInfoFromConfig()\n    {\n        $sslShopURL = ContainerFacade::getParameter('oxid_esales.shop_url');\n        if (!preg_match('#^(https?://)?([^/:]+)(:(\\d+))?(/.*)?$#i', $sslShopURL, $shopUrlComponents)) {\n            return false;\n        }\n        $host = $shopUrlComponents[2];\n        $port = (int)$shopUrlComponents[4];\n        $ssl = (strtolower($shopUrlComponents[1]) === 'https://');\n        if (!$port) {\n            $port = $ssl ? 443 : 80;\n        }\n        $script = rtrim($shopUrlComponents[5], '/') . '/';\n\n        return [\n            'host' => $host,\n            'port' => $port,\n            'dir' => $script,\n            'ssl' => $ssl,\n        ];\n    }\n\n    /**\n     * returns host, port, current script, ssl information as assotiative array, false on error\n     * Takes ssl address from config so important only in admin.\n     *\n     * @return array\n     */\n    protected function getShopSSLHostInfo()\n    {\n        if ($this->isAdmin()) {\n            return $this->getShopSSLHostInfoFromConfig();\n        }\n\n        return false;\n    }\n\n    /**\n     * Checks if mod_rewrite extension is loaded.\n     * Checks for all address.\n     *\n     * @return integer\n     */\n    public function checkModRewrite()\n    {\n        $sslHostInfo = $this->getShopSSLHostInfo();\n        $modStat = $this->isModeRewriteExtensionLoaded($sslHostInfo);\n\n        if (0 != $modStat && $sslHostInfo) {\n            $sslModStat = $this->isModeRewriteExtensionLoaded($sslHostInfo);\n\n            // Send if failed, even if you couldn't check another\n            if (0 == $sslModStat) {\n                return 0;\n            } elseif (1 == $sslModStat || 1 == $modStat) {\n                return 1;\n            }\n\n            return min($modStat, $sslModStat);\n        }\n\n        return $modStat;\n    }\n\n    /**\n     * Checks if mod_rewrite extension is loaded.\n     * Checks for one address.\n     *\n     * @param array $aHostInfo host info to open socket\n     *\n     * @return integer\n     */\n    protected function isModeRewriteExtensionLoaded(array $aHostInfo): int\n    {\n        $sHostname = ($aHostInfo['ssl'] ? 'ssl://' : '') . $aHostInfo['host'];\n        if ($rFp = @fsockopen($sHostname, $aHostInfo['port'], $iErrNo, $sErrStr, 10)) {\n            $sReq = \"POST {$aHostInfo['dir']}oxseo.php?mod_rewrite_module_is=off HTTP/1.1\\r\\n\";\n            $sReq .= \"Host: {$aHostInfo['host']}\\r\\n\";\n            $sReq .= \"User-Agent: OXID eShop setup\\r\\n\";\n            $sReq .= \"Content-Type: application/x-www-form-urlencoded\\r\\n\";\n            $sReq .= \"Content-Length: 0\\r\\n\"; // empty post\n            $sReq .= \"Connection: close\\r\\n\\r\\n\";\n\n            $sOut = '';\n            fwrite($rFp, $sReq);\n            while (!feof($rFp)) {\n                $sOut .= fgets($rFp, 100);\n            }\n            fclose($rFp);\n\n            $iModStat = (strpos($sOut, 'mod_rewrite_on') !== false) ? 2 : 0;\n        } else {\n            if (function_exists('apache_get_modules')) {\n                // it does not assure that mod_rewrite is enabled on current host, so setting 1\n                $iModStat = in_array('mod_rewrite', apache_get_modules()) ? 1 : 0;\n            } else {\n                $iModStat = -1;\n            }\n        }\n\n        return $iModStat;\n    }\n\n    /**\n     * Checks if activated allow_url_fopen and fsockopen on port 80 possible\n     *\n     * @return integer\n     */\n    public function checkAllowUrlFopen()\n    {\n        $resultAllowUrlFopen = @ini_get('allow_url_fopen');\n        $resultAllowUrlFopen = strcasecmp('1', $resultAllowUrlFopen);\n\n        if (0 === $resultAllowUrlFopen && 2 === $this->checkFsockopen()) {\n            return 2;\n        }\n        return 1;\n    }\n\n    /**\n     * Check if fsockopen on port 80 possible\n     *\n     * @return integer\n     */\n    public function checkFsockopen()\n    {\n        $result = 1;\n        $iErrNo = 0;\n        $sErrStr = '';\n        if ($oRes = @fsockopen('olc.oxid-esales.com', 80, $iErrNo, $sErrStr, 10)) {\n            $result = 2;\n            fclose($oRes);\n        }\n        return $result;\n    }\n\n    /**\n     * Gets PHP version.\n     *\n     * @return float|string\n     */\n    public function getPhpVersion()\n    {\n        return PHP_VERSION;\n    }\n\n    /**\n     * Checks if apache server variables REQUEST_URI or SCRIPT_URI are set\n     *\n     * @return integer\n     */\n    public function checkRequestUri()\n    {\n        return (isset($_SERVER['REQUEST_URI']) || isset($_SERVER['SCRIPT_URI'])) ? 2 : 0;\n    }\n\n    /**\n     * Check if DOM extension is loaded\n     *\n     * @return integer\n     */\n    public function checkPhpXml()\n    {\n        return extension_loaded('dom') ? 2 : 0;\n    }\n\n    /**\n     * Checks if JSON extension is loaded\n     *\n     * @return integer\n     */\n    public function checkJSon()\n    {\n        return extension_loaded('json') ? 2 : 0;\n    }\n\n    /**\n     * Checks if iconv extension is loaded\n     *\n     * @return integer\n     */\n    public function checkIConv()\n    {\n        return extension_loaded('iconv') ? 2 : 0;\n    }\n\n    /**\n     * Checks if tokenizer extension is loaded\n     *\n     * @return integer\n     */\n    public function checkTokenizer()\n    {\n        return extension_loaded('tokenizer') ? 2 : 0;\n    }\n\n    /**\n     * Checks if bcmath extension is loaded\n     *\n     * @return integer\n     */\n    public function checkBcMath()\n    {\n        return extension_loaded('bcmath') ? 2 : 1;\n    }\n\n    /**\n     * Checks if openssl extension is loaded\n     *\n     * @return integer\n     */\n    public function checkOpenSsl()\n    {\n        return extension_loaded('openssl') ? 2 : 1;\n    }\n\n    /**\n     * Checks if SOAP extension is loaded\n     *\n     * @return integer\n     */\n    public function checkSoap()\n    {\n        return extension_loaded('soap') ? 2 : 1;\n    }\n\n    /**\n     * Checks if mysql5 extension is loaded.\n     *\n     * @return integer\n     */\n    public function checkMysqlConnect()\n    {\n        $iModStat = extension_loaded('pdo_mysql') ? 2 : 0;\n        return $iModStat;\n    }\n\n    /**\n     * Checks if GDlib extension is loaded\n     *\n     * @return integer\n     */\n    public function checkGdInfo()\n    {\n        $iModStat = extension_loaded('gd') ? 1 : 0;\n        $iModStat = function_exists('imagecreatetruecolor') ? 2 : $iModStat;\n        $iModStat = function_exists('imagecreatefromgif') ? $iModStat : 0;\n        $iModStat = function_exists('imagecreatefromjpeg') ? $iModStat : 0;\n        $iModStat = function_exists('imagecreatefrompng') ? $iModStat : 0;\n\n        return $iModStat;\n    }\n\n    /**\n     * Checks if ini set is allowed\n     *\n     * @return integer\n     */\n    public function checkIniSet()\n    {\n        return (@ini_set('memory_limit', @ini_get('memory_limit')) !== false) ? 2 : 0;\n    }\n\n    /**\n     * Checks memory limit.\n     *\n     * @param string $sMemLimit memory limit to compare with requirements\n     *\n     * @return integer\n     */\n    public function checkMemoryLimit($sMemLimit = null)\n    {\n        if ($sMemLimit === null) {\n            $sMemLimit = @ini_get('memory_limit');\n        }\n\n        if ($sMemLimit) {\n            $sDefLimit = $this->getMinimumMemoryLimit();\n            $sRecLimit = $this->getRecommendMemoryLimit();\n\n            $iMemLimit = $this->getBytes($sMemLimit);\n\n            if ($iMemLimit === -1) {\n                // -1 is equivalent to no memory limit\n                $iModStat = 2;\n            } else {\n                $iModStat = ($iMemLimit >= $this->getBytes($sDefLimit)) ? 1 : 0;\n                $iModStat = $iModStat ? (($iMemLimit >= $this->getBytes($sRecLimit)) ? 2 : $iModStat) : $iModStat;\n            }\n        } else {\n            $iModStat = -1;\n        }\n\n        return $iModStat;\n    }\n\n    /**\n     * Additional sql: do not check collation for \\OxidEsales\\Eshop\\Core\\SystemRequirements::$_aException columns\n     *\n     * @return string\n     */\n    protected function getAdditionalCheck()\n    {\n        $sSelect = '';\n        foreach ($this->_aException as $sTable => $sColumn) {\n            $sSelect .= 'and ( TABLE_NAME != \"' . $sTable . '\" and COLUMN_NAME != \"' . $sColumn . '\" ) ';\n        }\n\n        return $sSelect;\n    }\n\n    /**\n     * Checks tables and columns (\\OxidEsales\\Eshop\\Core\\SystemRequirements::$_aColumns) collation\n     *\n     * @return array\n     */\n    public function checkCollation()\n    {\n        $myConfig = Registry::getConfig();\n\n        $aCollations = [];\n        $sCollation = '';\n        $sSelect = 'select TABLE_NAME, COLUMN_NAME, COLLATION_NAME from INFORMATION_SCHEMA.columns\n                    where TABLE_NAME not like \"oxv\\_%\" and table_schema = \"' . $myConfig->getConfigParam('dbName') . '\"\n                    and COLUMN_NAME in (\"' . implode('\", \"', $this->_aColumns) . '\") ' . $this->getAdditionalCheck() .\n                   'ORDER BY TABLE_NAME, COLUMN_NAME DESC;';\n        $aRez = DatabaseConnectionProvider::getDb()->getAll($sSelect);\n        foreach ($aRez as $aRetTable) {\n            if (!$sCollation) {\n                $sCollation = $aRetTable[2];\n            } else {\n                if ($aRetTable[2] && $sCollation != $aRetTable[2]) {\n                    $aCollations[$aRetTable[0]][$aRetTable[1]] = $aRetTable[2];\n                }\n            }\n        }\n\n        if ($this->_blSysReqStatus === null) {\n            $this->_blSysReqStatus = true;\n        }\n        if (count($aCollations) > 0) {\n            $this->_blSysReqStatus = false;\n        }\n\n        return $aCollations;\n    }\n\n    /**\n     * Checks if database cluster is installed\n     *\n     * @return integer\n     */\n    public function checkDatabaseCluster()\n    {\n        return 2;\n    }\n\n    /**\n     * Checks if PCRE unicode support is turned off/on. Should be on.\n     *\n     * @return integer\n     */\n    public function checkUnicodeSupport()\n    {\n        return (@preg_match('/\\pL/u', 'a') == 1) ? 2 : 1;\n    }\n\n    /**\n     * Checks if php_admin_flag file_uploads is ON\n     *\n     * @return integer\n     */\n    public function checkFileUploads()\n    {\n        $dUploadFile = -1;\n        $sFileUploads = @ini_get('file_uploads');\n        if ($sFileUploads !== false) {\n            if ($sFileUploads && ($sFileUploads == '1' || strtolower($sFileUploads) == 'on')) {\n                $dUploadFile = 2;\n            } else {\n                $dUploadFile = 1;\n            }\n        }\n\n        return $dUploadFile;\n    }\n\n    /**\n     * Checks system requirements status\n     *\n     * @return bool\n     */\n    public function getSysReqStatus()\n    {\n        if ($this->_blSysReqStatus == null) {\n            $this->_blSysReqStatus = true;\n            $this->getSystemInfo();\n            $this->checkCollation();\n        }\n\n        return $this->_blSysReqStatus;\n    }\n\n    /**\n     * Runs through modules array and checks if current system fits requirements.\n     * Returns array with module info:\n     *   array( $sGroup, $sModuleName, $sModuleState ):\n     *     $sGroup       - group of module\n     *     $sModuleName  - name of checked module\n     *     $sModuleState - module state:\n     *       -1 - unable to datect, should not block\n     *        0 - missing, blocks setup\n     *        1 - fits min requirements\n     *        2 - exists required or better\n     *\n     * @return array $aSysInfo\n     */\n    public function getSystemInfo()\n    {\n        $aSysInfo = [];\n        $aRequiredModules = $this->getRequiredModules();\n        $this->_blSysReqStatus = true;\n        foreach ($aRequiredModules as $sModule => $sGroup) {\n            if (isset($aSysInfo[$sGroup]) && !$aSysInfo[$sGroup]) {\n                $aSysInfo[$sGroup] = [];\n            }\n            $iModuleState = $this->getModuleInfo($sModule);\n            $aSysInfo[$sGroup][$sModule] = $iModuleState;\n            $this->_blSysReqStatus = $this->_blSysReqStatus && (bool) abs($iModuleState);\n        }\n\n        return $aSysInfo;\n    }\n\n    /**\n     * Apply given filter function to all iterations of SystemRequirementInfo array.\n     *\n     * @param array    $systemRequirementsInfo\n     * @param \\Closure $filterFunction         Filter function used for the update of actual values; Function will\n     *                                         receive the same arguments as provided from\n     *                                         `iterateThroughSystemRequirementsInfo` method.\n     *\n     * @return array An array which is in the same format as the main input argument but with updated data.\n     */\n    public static function filter($systemRequirementsInfo, $filterFunction)\n    {\n        $iterator = static::iterateThroughSystemRequirementsInfo($systemRequirementsInfo);\n\n        foreach ($iterator as [$groupId, $moduleId, $moduleState]) {\n            $systemRequirementsInfo[$groupId][$moduleId] = $filterFunction($groupId, $moduleId, $moduleState);\n        }\n\n        return $systemRequirementsInfo;\n    }\n\n    /**\n     * Returns passed module state\n     *\n     * @param string $sModule module name to check\n     *\n     * @return integer $iModStat\n     */\n    public function getModuleInfo($sModule = null)\n    {\n        if ($sModule) {\n            $iModStat = null;\n            $sCheckFunction = \"check\" . str_replace(\" \", \"\", ucwords(str_replace(\"_\", \" \", $sModule)));\n            $iModStat = $this->$sCheckFunction();\n\n            return $iModStat;\n        }\n    }\n\n    /**\n     * Returns true if given module state is acceptable for setup process to continue.\n     *\n     * @param array $systemRequirementsInfo\n     * @return bool\n     */\n    public static function canSetupContinue($systemRequirementsInfo)\n    {\n        $iterator = static::iterateThroughSystemRequirementsInfo($systemRequirementsInfo);\n\n        foreach ($iterator as [$groupId, $moduleId, $moduleState]) {\n            if ($moduleState === static::MODULE_STATUS_BLOCKS_SETUP) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * Iterates through given SystemRequirementsInfo returning three items:\n     *\n     *   - GroupId\n     *   - ModuleId\n     *   - ModuleState\n     *\n     * @param array $systemRequirementsInfo\n     * @return \\Generator Iterator which yields [group_id, module_id, module_state].\n     */\n    public static function iterateThroughSystemRequirementsInfo($systemRequirementsInfo)\n    {\n        foreach ($systemRequirementsInfo as $groupId => $modules) {\n            foreach ($modules as $moduleId => $moduleState) {\n                yield [$groupId, $moduleId, $moduleState];\n            }\n        }\n    }\n\n    /**\n     * Returns or prints url for info about missing web service configuration\n     *\n     * @param string $sIdent Module identifier\n     *\n     * @return mixed\n     */\n    public function getReqInfoUrl($sIdent)\n    {\n        $sUrl = $this->_sReqInfoUrl;\n        $aInfoMap = $this->_aInfoMap;\n        $aPreparationInfoMap = $this->_aPreparationInfoMap;\n\n        // only known will be anchored\n        if (isset($aInfoMap[$sIdent])) {\n            $sUrl .= \"#\" . $aInfoMap[$sIdent];\n        } elseif (isset($aPreparationInfoMap[$sIdent])) {\n            $sUrl = $this->_sPreparationInfoUrl . \"#\" . $aPreparationInfoMap[$sIdent];\n        }\n\n        return $sUrl;\n    }\n\n    /**\n     * Parses and calculates given string form byte size value\n     *\n     * @param string $bytes string form byte value (64M, 32K etc)\n     *\n     * @return int\n     */\n    protected function getBytes($sBytes)\n    {\n        $sBytes = trim($sBytes);\n        $sLast = strtolower($sBytes[strlen($sBytes) - 1]);\n        $sBytes = (int)$sBytes;\n        switch ($sLast) {\n            // The 'G' modifier is available since PHP 5.1.0\n            // gigabytes\n            case 'g':\n                $sBytes *= 1024;\n            // megabytes\n            // no break\n            case 'm':\n                $sBytes *= 1024;\n            // kilobytes\n            // no break\n            case 'k':\n                $sBytes *= 1024;\n                break;\n        }\n\n        return $sBytes;\n    }\n\n    /**\n     * Check if correct AutoStart setting.\n     *\n     * @return bool\n     */\n    public function checkSessionAutostart()\n    {\n        $sStatus = (strtolower((string) @ini_get('session.auto_start')));\n\n        return in_array($sStatus, ['on', '1']) ? 0 : 2;\n    }\n\n    /**\n     * Return minimum memory limit by edition.\n     *\n     * @return string\n     */\n    protected function getMinimumMemoryLimit()\n    {\n        return '32M';\n    }\n\n    /**\n     * Return recommend memory limit by edition.\n     *\n     * @return string\n     */\n    protected function getRecommendMemoryLimit()\n    {\n        return '60M';\n    }\n}\n"
  },
  {
    "path": "source/Core/TableViewNameGenerator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\n\n/**\n * Generates view name for given table name.\n */\nclass TableViewNameGenerator\n{\n    /** @var \\OxidEsales\\Eshop\\Core\\Config */\n    private $config;\n\n    /** @var \\OxidEsales\\Eshop\\Core\\Language */\n    private $language;\n\n    /**\n     * @param \\OxidEsales\\Eshop\\Core\\Config   $config\n     * @param \\OxidEsales\\Eshop\\Core\\Language $language\n     */\n    public function __construct($config = null, $language = null)\n    {\n        if (!$config) {\n            $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        }\n        $this->config = $config;\n\n        if (!$language) {\n            $language = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n        }\n        $this->language = $language;\n    }\n\n    /**\n     * Return the view name of the given table if a view exists, otherwise the table name itself.\n     * Views usage can be disabled with blSkipViewUsage config option in case admin area is not reachable\n     * due to broken views, so that they could be regenerated.\n     *\n     * @param string $table      Table name\n     * @param int    $languageId Language id [optional]\n     * @param int    $shopId     Shop id, otherwise config->getShopId() is used [optional]\n     *\n     * @return string\n     */\n    public function getViewName($table, $languageId = null, $shopId = null)\n    {\n        $config = $this->getConfig();\n\n        if (!ContainerFacade::getParameter('oxid_esales.skip_database_views_usage')) {\n            $language = $this->getLanguage();\n            $languageId = $languageId !== null ? $languageId : $language->getBaseLanguage();\n            $shopId = $shopId !== null ? $shopId : $config->getShopId();\n            $isMultiLang = in_array($table, $language->getMultiLangTables());\n            $viewSuffix = $this->getViewSuffix($table, $languageId, $shopId, $isMultiLang);\n\n            if ($viewSuffix || (($languageId == -1 || $shopId == -1) && $isMultiLang)) {\n                return \"oxv_{$table}{$viewSuffix}\";\n            }\n        }\n\n        return $table;\n    }\n\n    /**\n     * Generates view suffix.\n     *\n     * @param string $table\n     * @param int    $languageId\n     * @param int    $shopId\n     * @param bool   $isMultiLang\n     *\n     * @return string\n     */\n    protected function getViewSuffix($table, $languageId, $shopId, $isMultiLang)\n    {\n        $viewSuffix = '';\n        if ($languageId != -1 && $isMultiLang) {\n            $languageAbbreviation = $this->getLanguage()->getLanguageAbbr($languageId);\n            $viewSuffix .= \"_{$languageAbbreviation}\";\n        }\n\n        return $viewSuffix;\n    }\n\n    /**\n     * @return \\OxidEsales\\Eshop\\Core\\Config\n     */\n    protected function getConfig()\n    {\n        return $this->config;\n    }\n\n    /**\n     * @return \\OxidEsales\\Eshop\\Core\\Language\n     */\n    protected function getLanguage()\n    {\n        return $this->language;\n    }\n}\n"
  },
  {
    "path": "source/Core/Theme.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Event\\ThemeActivatedEvent;\n\n/**\n * Themes handler class.\n *\n * @internal Do not make a module extension for this class.\n */\nclass Theme extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Theme info array\n     *\n     * @var array\n     */\n    protected $_aTheme = [];\n\n    /**\n     * Theme info list\n     *\n     * @var array\n     */\n    protected $_aThemeList = [];\n\n    /**\n     * Load theme info\n     *\n     * @param string $sOXID theme id\n     *\n     * @return bool\n     */\n    public function load($sOXID)\n    {\n        $sFilePath = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getViewsDir() . $sOXID . \"/theme.php\";\n        if (file_exists($sFilePath) && is_readable($sFilePath)) {\n            $aTheme = [];\n            include $sFilePath;\n            $this->_aTheme = $aTheme;\n            $this->_aTheme['id'] = $sOXID;\n            $this->_aTheme['active'] = ($this->getActiveThemeId() == $sOXID);\n\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Set theme as active\n     */\n    public function activate()\n    {\n        $sError = $this->checkForActivationErrors();\n        if ($sError) {\n            /** @var \\OxidEsales\\Eshop\\Core\\Exception\\StandardException $oException */\n            $oException = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\StandardException::class, $sError);\n            throw $oException;\n        }\n\n        $config = Registry::getConfig();\n        $sParent = $this->getInfo('parentTheme');\n        if ($sParent) {\n            $config->saveShopConfVar(\"str\", 'sTheme', $sParent);\n            $config->saveShopConfVar(\"str\", 'sCustomTheme', $this->getId());\n        } else {\n            $config->saveShopConfVar(\"str\", 'sTheme', $this->getId());\n            $config->saveShopConfVar(\"str\", 'sCustomTheme', '');\n        }\n        $settingsHandler = oxNew(\\OxidEsales\\Eshop\\Core\\SettingsHandler::class);\n        $settingsHandler->setModuleType('theme')->run($this);\n        ContainerFacade::dispatch(new ThemeActivatedEvent($config->getShopId(), $this->getId()));\n    }\n\n    /**\n     * Load theme info list\n     *\n     * @return array\n     */\n    public function getList()\n    {\n        $this->_aThemeList = [];\n        $sOutDir = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getViewsDir();\n        foreach (glob($sOutDir . \"*\", GLOB_ONLYDIR) as $sDir) {\n            $oTheme = oxNew(\\OxidEsales\\Eshop\\Core\\Theme::class);\n            if ($oTheme->load(basename($sDir))) {\n                $this->_aThemeList[$sDir] = $oTheme;\n            }\n        }\n\n        return $this->_aThemeList;\n    }\n\n    /**\n     * Return theme information\n     *\n     * @param string $sName name of info item to retrieve\n     *\n     * @return mixed\n     */\n    public function getInfo($sName)\n    {\n        if (!isset($this->_aTheme[$sName])) {\n            return null;\n        }\n\n        return $this->_aTheme[$sName];\n    }\n\n    /**\n     * Return current active theme, or custom theme if specified\n     *\n     * @return string\n     */\n    public function getActiveThemeId()\n    {\n        $sCustTheme = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('sCustomTheme');\n        if ($sCustTheme) {\n            return $sCustTheme;\n        }\n\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('sTheme');\n    }\n\n    /**\n     * Get active themes list.\n     * Examples:\n     *      if flow theme is active we will get ['flow']\n     *      if azure is extended by some other we will get ['azure', 'extending_theme']\n     *\n     * @return array\n     */\n    public function getActiveThemesList()\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $activeThemeList = [];\n        if (!$this->isAdmin()) {\n            $activeThemeList[] = $config->getConfigParam('sTheme');\n\n            if ($customThemeId = $config->getConfigParam('sCustomTheme')) {\n                $activeThemeList[] = $customThemeId;\n            }\n        }\n\n        return $activeThemeList;\n    }\n\n    /**\n     * Return loaded parent\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Theme\n     */\n    public function getParent()\n    {\n        $sParent = $this->getInfo('parentTheme');\n        if (!$sParent) {\n            return null;\n        }\n        $oTheme = oxNew(\\OxidEsales\\Eshop\\Core\\Theme::class);\n        if ($oTheme->load($sParent)) {\n            return $oTheme;\n        }\n\n        return null;\n    }\n\n    /**\n     * run pre-activation checks and return EXCEPTION_* translation string if error\n     * found or false on success\n     *\n     * @return string\n     */\n    public function checkForActivationErrors()\n    {\n        if (!$this->getId()) {\n            return 'EXCEPTION_THEME_NOT_LOADED';\n        }\n        $oParent = $this->getParent();\n        if ($oParent) {\n            $sParentVersion = $oParent->getInfo('version');\n            if (!$sParentVersion) {\n                return 'EXCEPTION_PARENT_VERSION_UNSPECIFIED';\n            }\n            $aMyParentVersions = $this->getInfo('parentVersions');\n            if (!$aMyParentVersions || !is_array($aMyParentVersions)) {\n                return 'EXCEPTION_UNSPECIFIED_PARENT_VERSIONS';\n            }\n            if (!in_array($sParentVersion, $aMyParentVersions)) {\n                return 'EXCEPTION_PARENT_VERSION_MISMATCH';\n            }\n        } elseif ($this->getInfo('parentTheme')) {\n            return 'EXCEPTION_PARENT_THEME_NOT_FOUND';\n        }\n\n        return false;\n    }\n\n    /**\n     * Get theme ID\n     *\n     * @return string\n     */\n    public function getId()\n    {\n        return $this->getInfo(\"id\");\n    }\n}\n"
  },
  {
    "path": "source/Core/UniversallyUniqueIdGenerator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\OpenSSLFunctionalityChecker;\n\n/**\n * Class oxUniversallyUniqueIdGenerator used as universally unique id generator.\n */\nclass UniversallyUniqueIdGenerator\n{\n    /**\n     * @var OpenSSLFunctionalityChecker\n     */\n    private $_openSSLChecker;\n\n    /**\n     * Sets dependencies.\n     *\n     * @param OpenSSLFunctionalityChecker|null $openSSLChecker\n     */\n    public function __construct(?OpenSSLFunctionalityChecker $openSSLChecker = null)\n    {\n        if (is_null($openSSLChecker)) {\n            $openSSLChecker = oxNew(OpenSSLFunctionalityChecker::class);\n        }\n        $this->_openSSLChecker = $openSSLChecker;\n    }\n\n    /**\n     * Generates UUID based on either openSSL's openssl_random_pseudo_bytes or mt_rand.\n     *\n     * @return string\n     */\n    public function generate()\n    {\n        $sSeed = $this->generateV4();\n\n        return $this->generateV5($sSeed, php_uname('n'));\n    }\n\n    /**\n     * Generates version 4 UUID.\n     *\n     * @return string\n     */\n    public function generateV4()\n    {\n        if ($this->getOpenSSLChecker()->isOpenSslRandomBytesGeneratorAvailable()) {\n            return $this->generateBasedOnOpenSSL();\n        }\n\n        return $this->generateBasedOnMtRand();\n    }\n\n    /**\n     * Generates version 5 UUID.\n     *\n     * @param string $sSeed\n     * @param string $sSalt\n     *\n     * @return string\n     */\n    public function generateV5($sSeed, $sSalt)\n    {\n        $sSeed = str_replace(['-', '{', '}'], '', $sSeed);\n        $sBinarySeed = '';\n        for ($i = 0; $i < strlen($sSeed); $i += 2) {\n            $sBinarySeed .= chr(hexdec($sSeed[$i] . $sSeed[$i + 1]));\n        }\n        $sHash = sha1($sBinarySeed . $sSalt);\n        $sUUID = sprintf(\n            '%08s-%04s-%04x-%04x-%12s',\n            substr($sHash, 0, 8),\n            substr($sHash, 8, 4),\n            (hexdec(substr($sHash, 12, 4)) & 0x0fff) | 0x3000,\n            (hexdec(substr($sHash, 16, 4)) & 0x3fff) | 0x8000,\n            substr($sHash, 20, 12)\n        );\n\n        return $sUUID;\n    }\n\n    /**\n     * gets open SSL checker.\n     *\n     * @return OpenSSLFunctionalityChecker\n     */\n    protected function getOpenSSLChecker()\n    {\n        return $this->_openSSLChecker;\n    }\n\n    /**\n     * Generates UUID based on OpenSSL's openssl_random_pseudo_bytes.\n     *\n     * @return string\n     */\n    protected function generateBasedOnOpenSSL()\n    {\n        $sRandomData = openssl_random_pseudo_bytes(16);\n        $sRandomData[6] = chr(ord($sRandomData[6]) & 0x0f | 0x40); // set version to 0100\n        $sRandomData[8] = chr(ord($sRandomData[8]) & 0x3f | 0x80); // set bits 6-7 to 10\n\n        return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($sRandomData), 4));\n    }\n\n    /**\n     * Generates UUID based on mt_rand.\n     *\n     * @return string\n     */\n    protected function generateBasedOnMtRand()\n    {\n        return sprintf(\n            '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',\n            mt_rand(0, 0xffff),\n            mt_rand(0, 0xffff),\n            mt_rand(0, 0xffff),\n            mt_rand(0, 0x0fff) | 0x4000,\n            mt_rand(0, 0x3fff) | 0x8000,\n            mt_rand(0, 0xffff),\n            mt_rand(0, 0xffff),\n            mt_rand(0, 0xffff)\n        );\n    }\n}\n"
  },
  {
    "path": "source/Core/UserCounter.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\n/**\n * Class used for counting users depending on given attributes.\n */\nclass UserCounter\n{\n    /**\n     * Returns count of admins (mall and subshops). Only counts active admins.\n     *\n     * @return int\n     */\n    public function getAdminCount()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $sQuery = \"SELECT COUNT(1) FROM oxuser WHERE oxrights != 'user'\";\n\n        return (int) $oDb->getOne($sQuery);\n    }\n\n    /**\n     * Returns count of admins (mall and subshops). Only counts active admins.\n     *\n     * @return int\n     */\n    public function getActiveAdminCount()\n    {\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        $sQuery = \"SELECT COUNT(1) FROM oxuser WHERE oxrights != 'user' AND oxactive = 1 \";\n\n        return (int) $oDb->getOne($sQuery);\n    }\n}\n"
  },
  {
    "path": "source/Core/Utils.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents\\ApplicationExitEvent;\nuse Psr\\Cache\\CacheItemPoolInterface;\nuse stdClass;\nuse Symfony\\Contracts\\Cache\\ItemInterface;\nuse Symfony\\Contracts\\Cache\\TagAwareCacheInterface;\nuse Symfony\\Component\\Filesystem\\Path;\n\nuse function is_array;\n\n/**\n * General utils class\n */\nclass Utils extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Cached currency precision\n     *\n     * @var int\n     */\n    protected $_iCurPrecision = null;\n\n    /**\n     * Some files, like object structure should not be deleted, because they are changed rarely\n     * and each regeneration eats additional page load time. This array keeps patterns of file\n     * names which should not be deleted on regular cache cleanup\n     *\n     * @var string\n     */\n    protected $_sPermanentCachePattern = \"/c_fieldnames_|c_tbdsc_|_allfields_/\";\n\n    /**\n     * Pattern used to filter needed to remove language cache files.\n     *\n     * @var string\n     */\n    protected $_sLanguageCachePattern = \"/c_langcache_/i\";\n\n    /**\n     * Pattern used to filter needed to remove admin menu cache files.\n     *\n     * @var string\n     */\n    protected $_sMenuCachePattern = \"/c_menu_/i\";\n\n    /**\n     * File cache contents.\n     *\n     * @var array\n     */\n    protected $_aLockedFileHandles = [];\n\n    /**\n     * Local cache\n     *\n     * @var array\n     */\n    protected $_aFileCacheContents = [];\n\n    /**\n     * Search engine indicator\n     *\n     * @var bool\n     */\n    protected $_blIsSe = null;\n\n    /**\n     * Statically cached data\n     *\n     * @var array\n     */\n    protected $_aStaticCache;\n\n    /**\n     * Seo mode marker - SEO is active or not\n     *\n     * @var bool\n     */\n    protected $_blSeoIsActive = null;\n\n    /**\n     * Returns string witch \".\" symbols were replaced with \"__\".\n     *\n     * @param string $sName String to search replaceable char\n     *\n     * @return string\n     */\n    public function getArrFldName($sName)\n    {\n        return str_replace(\".\", \"__\", $sName);\n    }\n\n    /**\n     * Takes a string and assign all values, returns array with values.\n     *\n     * @param string $sIn  Initial string\n     * @param float  $dVat Article VAT (optional)\n     *\n     * @return array\n     */\n    public function assignValuesFromText($sIn, $dVat = null)\n    {\n        $aRet = [];\n        $aPieces = explode('@@', $sIn);\n        foreach ($aPieces as $sVal) {\n            if ($sVal) {\n                $aName = explode('__', $sVal);\n                if (isset($aName[0]) && isset($aName[1])) {\n                    $aRet[] = $this->fillExplodeArray($aName, $dVat);\n                }\n            }\n        }\n\n        return $aRet;\n    }\n\n    /**\n     * Takes an array and builds again a string. Returns string with values.\n     *\n     * @param array $aIn Initial array of strings\n     *\n     * @return string\n     */\n    public function assignValuesToText($aIn)\n    {\n        $sRet = \"\";\n        reset($aIn);\n        foreach ($aIn as $sKey => $sVal) {\n            $sRet .= $sKey;\n            $sRet .= \"__\";\n            $sRet .= $sVal;\n            $sRet .= \"@@\";\n        }\n\n        return $sRet;\n    }\n\n    /**\n     * Returns formatted currency string, according to formatting standards.\n     *\n     * @param string $sValue Formatted price\n     *\n     * @return float\n     */\n    public function currency2Float($sValue)\n    {\n        $fRet = $sValue;\n        $iPos = strrpos($sValue, \".\");\n        if ($iPos && ((strlen($sValue) - 1 - $iPos) < 2 + 1)) {\n            // replace decimal with \",\"\n            $fRet = substr_replace($fRet, \",\", $iPos, 1);\n        }\n        // remove thousands\n        $fRet = str_replace([\" \", \".\"], \"\", $fRet);\n\n        return (float) str_replace(\",\", \".\", $fRet);\n    }\n\n    /**\n     * Returns formatted float, according to formatting standards.\n     *\n     * @param string $sValue Formatted price\n     *\n     * @return float\n     */\n    public function string2Float($sValue)\n    {\n        $fRet = str_replace(\" \", \"\", $sValue);\n        $iCommaPos = strpos($fRet, \",\");\n        $iDotPos = strpos($fRet, \".\");\n        if (!$iDotPos xor !$iCommaPos) {\n            if (substr_count($fRet, \",\") > 1 || substr_count($fRet, \".\") > 1) {\n                $fRet = str_replace([\",\", \".\"], \"\", $fRet);\n            } else {\n                $fRet = str_replace(\",\", \".\", $fRet);\n            }\n        } else {\n            if ($iDotPos < $iCommaPos) {\n                $fRet = str_replace(\".\", \"\", $fRet);\n                $fRet = str_replace(\",\", \".\", $fRet);\n            }\n        }\n\n        // remove thousands\n        return (float) str_replace([\" \", \",\"], \"\", $fRet);\n    }\n\n    /**\n     * Checks if current web client is Search Engine. Returns true on success.\n     *\n     * @param string $sClient user browser agent\n     *\n     * @return bool\n     */\n    public function isSearchEngine($sClient = null)\n    {\n        if (is_null($this->_blIsSe)) {\n            $this->setSearchEngine(null, $sClient);\n        }\n\n        return $this->_blIsSe;\n    }\n\n    /**\n     * Sets if current web client is Search Engine.\n     *\n     * @param bool $isSearchEngine sets if Search Engine is on\n     * @param string $userAgent user browser agent\n     *\n     * @return null\n     */\n    public function setSearchEngine($isSearchEngine = null, $userAgent = null)\n    {\n        if (isset($isSearchEngine)) {\n            $this->_blIsSe = $isSearchEngine;\n\n            return;\n        }\n        startProfile('isSearchEngine');\n\n        $isSearchEngine = false;\n        if (!(ContainerFacade::getParameter('oxid_esales.debug_mode') && $this->isAdmin())) {\n            $robots = ContainerFacade::getParameter('oxid_esales.search_engine_list');\n            $robots = \\is_array($robots) ? $robots : [];\n\n            $userAgent = $userAgent ?: strtolower(getenv('HTTP_USER_AGENT'));\n            foreach ($robots as $robot) {\n                if (str_contains($userAgent, $robot)) {\n                    $isSearchEngine = true;\n                    break;\n                }\n            }\n        }\n\n        $this->_blIsSe = $isSearchEngine;\n\n        stopProfile('isSearchEngine');\n    }\n\n    /**\n     * Parses profile configuration, loads stored info in cookie\n     *\n     * @param array $aInterfaceProfiles ($myConfig->getConfigParam( 'aInterfaceProfiles' ))\n     *\n     * @return null\n     */\n    public function loadAdminProfile($aInterfaceProfiles)\n    {\n        // improved #533\n        // checking for available profiles list\n        if (is_array($aInterfaceProfiles)) {\n            //checking for previous profiles\n            $sPrevProfile = Registry::getUtilsServer()->getOxCookie('oxidadminprofile');\n            if (isset($sPrevProfile)) {\n                $aPrevProfile = @explode(\"@\", trim($sPrevProfile));\n            }\n\n            //array to store profiles\n            $aProfiles = [];\n            foreach ($aInterfaceProfiles as $iPos => $sProfile) {\n                $aProfileSettings = [$iPos, $sProfile];\n                $aProfiles[] = $aProfileSettings;\n            }\n            // setting previous used profile as active\n            if (isset($aPrevProfile[0]) && isset($aProfiles[$aPrevProfile[0]])) {\n                $aProfiles[$aPrevProfile[0]][2] = 1;\n            }\n\n            Registry::getSession()->setVariable(\"aAdminProfiles\", $aProfiles);\n\n            return $aProfiles;\n        }\n\n        return null;\n    }\n\n    /**\n     * Rounds the value to currency cents. This method does NOT format the number.\n     *\n     * @param string $value the value that should be rounded\n     * @param object $currency\n     *\n     * @return float\n     */\n    public function fRound($value, $currency = null)\n    {\n        startProfile('fround');\n        //cached currency precision, this saves about 1% of execution time\n        if (is_null($this->_iCurPrecision)) {\n            $currency = $currency ?: Registry::getConfig()->getActShopCurrencyObject();\n            $this->_iCurPrecision = $currency->decimal;\n        }\n        $roundedValue = round((float)$value, $this->_iCurPrecision);\n        stopProfile('fround');\n\n        return $roundedValue;\n    }\n\n    /**\n     * Alphanumeric oxid and pure numeric oxid that start with the numeric part and only differ\n     * in postfixed alphabetical characters (e.g. \"123\" and \"123X\") are cast to the wrong type\n     * php internally which might result in wrong array_search results.\n     *\n     * Wrapper for php internal array_search function, ony usable for string search.\n     * In case we get unclear results make sure we typecast all data\n     * to string before performing array search.\n     *\n     * @param string $needle\n     * @param array  $haystack\n     *\n     * @return mixed\n     */\n    public function arrayStringSearch($needle, $haystack)\n    {\n        $result = array_search((string) $needle, $haystack);\n        $second = array_search((string) $needle, $haystack, true);\n\n        //got a different result when using strict and not strict?\n        //do a detail check\n        if ($result != $second) {\n            $stringstack = [];\n            foreach ($haystack as $value) {\n                $stringstack[] = (string) $value;\n            }\n            $result = array_search((string) $needle, $stringstack, true);\n        }\n\n        return $result;\n    }\n\n    /**\n     * Stores something into static cache to avoid double loading\n     *\n     * @param string $sName    name of the content\n     * @param mixed  $sContent the content\n     * @param string $sKey     optional key, where to store the content\n     */\n    public function toStaticCache($sName, $sContent, $sKey = null)\n    {\n        // if it's an array then we add\n        if ($sKey) {\n            $this->_aStaticCache[$sName][$sKey] = $sContent;\n        } else {\n            $this->_aStaticCache[$sName] = $sContent;\n        }\n    }\n\n    /**\n     * Retrieves something from static cache\n     *\n     * @param string $sName name under which the content is stored in the static cache\n     *\n     * @return mixed\n     */\n    public function fromStaticCache($sName)\n    {\n        if (isset($this->_aStaticCache[$sName])) {\n            return $this->_aStaticCache[$sName];\n        }\n    }\n\n    /**\n     * Cleans all or specific data from static cache\n     *\n     * @param string $sCacheName Cache name\n     */\n    public function cleanStaticCache($sCacheName = null)\n    {\n        if ($sCacheName) {\n            unset($this->_aStaticCache[$sCacheName]);\n        } else {\n            $this->_aStaticCache = null;\n        }\n    }\n\n    /**\n     * @deprecated will be removed in next major version\n     *\n     * Adds contents to cache contents by given key. Returns true on success.\n     *\n     * @param string $sKey      Cache key\n     * @param mixed  $mContents Contents to cache\n     * @param int    $iTtl      Time to live in seconds (0 for forever).\n     *\n     * @return bool\n     */\n    public function toFileCache($sKey, $mContents, $iTtl = 0)\n    {\n        $cache = ContainerFacade::get(CacheItemPoolInterface::class);\n        $cacheItem = $cache->getItem($sKey)->set($mContents);\n        if ($iTtl) {\n            $cacheItem->expiresAfter($iTtl);\n        }\n        $cache->save($cacheItem);\n\n        return true;\n    }\n\n    /**\n     * @deprecated will be removed in next major version\n     *\n     * Fetches contents from file cache.\n     *\n     * @param string $sKey Cache key\n     *\n     * @return mixed\n     */\n    public function fromFileCache($sKey)\n    {\n        $cache = ContainerFacade::get(CacheItemPoolInterface::class);\n        if ($cache->hasItem($sKey)) {\n            $cacheItem = $cache->getItem($sKey);\n            return $cacheItem->get();\n        }\n\n        return null;\n    }\n\n    /**\n     * @deprecated will be removed in next major version\n     */\n    public function oxResetFileCache()\n    {\n        $cache = ContainerFacade::get(CacheItemPoolInterface::class);\n        $cache->clear();\n    }\n\n    /**\n     * @deprecated will be removed in next major version\n     *\n     * Removes language constant cache\n     */\n    public function resetLanguageCache()\n    {\n\n        $cache = ContainerFacade::get(TagAwareCacheInterface::class);\n        $cache->invalidateTags(['oxid_esales.cache.language']);\n    }\n\n    /**\n     * @deprecated will be removed in next major version\n     *\n     * Removes admin menu cache\n     */\n    public function resetMenuCache()\n    {\n        $cache = ContainerFacade::get(TagAwareCacheInterface::class);\n        $cache->invalidateTags(['oxid_esales.cache.menu']);\n    }\n\n    /**\n     * Checks if preview mode is ON\n     *\n     * @return bool\n     */\n    public function canPreview()\n    {\n        $blCan = null;\n        if (\n            ($sPrevId = Registry::getRequest()->getRequestEscapedParameter('preview')) &&\n            ($sAdminSid = Registry::getUtilsServer()->getOxCookie('admin_sid'))\n        ) {\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n            $sTable = $tableViewNameGenerator->getViewName('oxuser');\n            $sQ = \"SELECT 1 FROM $sTable WHERE MD5( CONCAT( :adminsid, {$sTable}.oxid, {$sTable}.oxpassword, {$sTable}.oxrights ) ) = :previd\";\n            $blCan = (bool) \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->getOne($sQ, [\n                'adminsid' => $sAdminSid,\n                'previd'   => $sPrevId\n            ]);\n        }\n\n        return $blCan;\n    }\n\n    /**\n     * Returns id which is used for product preview in shop during administration\n     *\n     * @return string\n     */\n    public function getPreviewId()\n    {\n        $sAdminSid = Registry::getUtilsServer()->getOxCookie('admin_sid');\n        if (($oUser = $this->getUser())) {\n            return md5($sAdminSid . $oUser->getId() . $oUser->oxuser__oxpassword->value . $oUser->oxuser__oxrights->value);\n        }\n    }\n\n    /**\n     * This function checks if logged in user has access to admin or not\n     *\n     * @return bool\n     */\n    public function checkAccessRights()\n    {\n        $myConfig = Registry::getConfig();\n\n        $blIsAuth = false;\n\n        $sUserID = Registry::getSession()->getVariable(\"auth\");\n\n        // deleting admin marker\n        Registry::getSession()->setVariable(\"malladmin\", 0);\n        Registry::getSession()->setVariable(\"blIsAdmin\", 0);\n        Registry::getSession()->deleteVariable(\"blIsAdmin\");\n        $myConfig->setConfigParam('blMallAdmin', false);\n        //#1552T\n        $myConfig->setConfigParam('blAllowInheritedEdit', false);\n\n        if ($sUserID) {\n            // escaping\n            $sRights = $this->fetchRightsForUser($sUserID);\n\n            if ($sRights != \"user\") {\n                // malladmin ?\n                if ($sRights == \"malladmin\") {\n                    Registry::getSession()->setVariable(\"malladmin\", 1);\n                    $myConfig->setConfigParam('blMallAdmin', true);\n\n                    //#1552T\n                    //So far this blAllowSharedEdit is Equal to blMallAdmin but in future to be solved over rights and roles\n                    $myConfig->setConfigParam('blAllowSharedEdit', true);\n\n                    $sShop = Registry::getSession()->getVariable(\"actshop\");\n                    if (!isset($sShop)) {\n                        Registry::getSession()->setVariable(\"actshop\", $myConfig->getBaseShopId());\n                    }\n                    $blIsAuth = true;\n                } else {\n                    // Shopadmin... check if this shop is valid and exists\n                    $sShopID = $this->fetchShopAdminById($sRights);\n                    if (isset($sShopID) && $sShopID) {\n                        // success, this shop exists\n\n                        Registry::getSession()->setVariable(\"actshop\", $sRights);\n                        Registry::getSession()->setVariable(\"currentadminshop\", $sRights);\n                        Registry::getSession()->setVariable(\"shp\", $sRights);\n\n                        // check if this subshop admin is evil.\n                        if ('chshp' == Registry::getRequest()->getRequestEscapedParameter('fnc')) {\n                            // dont allow this call\n                            $blIsAuth = false;\n                        } else {\n                            $blIsAuth = true;\n\n                            $aShopIdVars = ['actshop', 'shp', 'currentadminshop'];\n                            foreach ($aShopIdVars as $sShopIdVar) {\n                                if ($sGotShop = Registry::getRequest()->getRequestEscapedParameter($sShopIdVar)) {\n                                    if ($sGotShop != $sRights) {\n                                        $blIsAuth = false;\n                                        break;\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n                // marking user as admin\n                Registry::getSession()->setVariable(\"blIsAdmin\", 1);\n            }\n        }\n\n        return $blIsAuth;\n    }\n\n    /**\n     * Fetch the rights for the user given by its oxid\n     *\n     * @param string $userOxId The oxId of the user we want the rights for.\n     *\n     * @return mixed The rights\n     */\n    protected function fetchRightsForUser($userOxId)\n    {\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        return $database->getOne(\"SELECT oxrights FROM oxuser WHERE oxid = :oxid \", [\n            'oxid' => $userOxId\n        ]);\n    }\n\n    /**\n     * Fetch the oxId from the oxshops table.\n     *\n     * @param string $oxId The oxId of the shop.\n     *\n     * @return mixed The oxId of the shop with the given oxId.\n     */\n    protected function fetchShopAdminById($oxId)\n    {\n        $database = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        return $database->getOne(\"SELECT oxid FROM oxshops WHERE oxid = :oxid\", [\n            'oxid' => $oxId\n        ]);\n    }\n\n    /**\n     * Checks if Seo mode should be used\n     *\n     * @param bool $reset used to reset cached SEO mode\n     * @param string $shopId shop id (optional; if not passed active session shop id will be used)\n     * @param int $languageId language id (optional; if not passed active session language will be used)\n     *\n     * @return bool\n     */\n    public function seoIsActive($reset = false, $shopId = null, $languageId = null)\n    {\n        if (!isset($this->_blSeoIsActive) || $reset) {\n            $this->_blSeoIsActive = $this->isSeoEnabled() && !$this->isSeoDisabledForShopAndLanguage(\n                (int)$shopId ?: Registry::getConfig()->getActiveShop()->getId(),\n                (int)$languageId ?: (int)Registry::getLang()->getBaseLanguage()\n            );\n        }\n\n        return $this->_blSeoIsActive;\n    }\n\n    /**\n     * Checks if string is only alpha numeric  symbols\n     *\n     * @param string $sField field name to test\n     *\n     * @return bool\n     */\n    public function isValidAlpha($sField)\n    {\n        return (bool) Str::getStr()->preg_match('/^[a-zA-Z0-9_]*$/', $sField);\n    }\n\n    /**\n     * redirects browser to given url, nothing else done just header send\n     * may be used for redirection in case of an exception or similar things\n     *\n     * @param string $sUrl        the URL to redirect to\n     * @param string $sHeaderCode code to add to the header(e.g. \"HTTP/1.1 301 Moved Permanently\", or \"HTTP/1.1 500 Internal Server Error\"\n     */\n    protected function simpleRedirect($sUrl, $sHeaderCode)\n    {\n        $oHeader = oxNew(\\OxidEsales\\Eshop\\Core\\Header::class);\n        $oHeader->setHeader($sHeaderCode);\n        $oHeader->setHeader(\"Location: $sUrl\");\n        $oHeader->setHeader(\"Connection: close\");\n        $oHeader->sendHeader();\n    }\n\n    /**\n     * Shows offline page.\n     * Directly displays the offline page to the client (browser)\n     * with a 500 status code header.\n     */\n    public function showOfflinePage()\n    {\n        \\oxTriggerOfflinePageDisplay();\n        $this->showMessageAndExit('');\n    }\n\n    /**\n     * redirect user to the specified URL\n     *\n     * @param string $sUrl               URL to be redirected\n     * @param bool   $blAddRedirectParam add \"redirect\" param\n     * @param int    $iHeaderCode        header code, default 302\n     *\n     * @return null or exit\n     */\n    public function redirect($sUrl, $blAddRedirectParam = true, $iHeaderCode = 302)\n    {\n        //preventing possible cyclic redirection\n        //#M341 and check only if redirect parameter must be added\n        if ($blAddRedirectParam && Registry::getRequest()->getRequestEscapedParameter('redirected')) {\n            return;\n        }\n\n        if ($blAddRedirectParam) {\n            $sUrl = $this->addUrlParameters($sUrl, ['redirected' => 1]);\n        }\n\n        $sUrl = str_ireplace(\"&amp;\", \"&\", $sUrl);\n\n        switch ($iHeaderCode) {\n            case 301:\n                $sHeaderCode = \"HTTP/1.1 301 Moved Permanently\";\n                break;\n            case 500:\n                $sHeaderCode = \"HTTP/1.1 500 Internal Server Error\";\n                break;\n            case 302:\n            default:\n                $sHeaderCode = \"HTTP/1.1 302 Found\";\n        }\n\n        $this->simpleRedirect($sUrl, $sHeaderCode);\n\n        try { //may occur in case db is lost\n            $session = Registry::getSession();\n            $session->freeze();\n        } catch (\\OxidEsales\\Eshop\\Core\\Exception\\StandardException $exception) {\n            Registry::getLogger()->error($exception->getMessage(), [$exception]);\n            //do nothing else to make sure the redirect takes place\n        }\n\n        $this->showMessageAndExit('');\n    }\n\n    /**\n     * shows given message and quits\n     * message might be whole content like 404 page.\n     *\n     * @param string $sMsg message to show\n     */\n    public function showMessageAndExit($sMsg)\n    {\n        $this->prepareToExit();\n        exit($sMsg);\n    }\n\n    /**\n     * helper with commands to run before exit action\n     */\n    protected function prepareToExit()\n    {\n        $session = Registry::getSession();\n        $session->freeze();\n\n        ContainerFacade::dispatch(new ApplicationExitEvent());\n\n        if ($this->isSearchEngine()) {\n            $header = Registry::get(\\OxidEsales\\Eshop\\Core\\Header::class);\n            $header->setNonCacheable();\n        }\n\n        //Send headers that have been registered\n        $header = Registry::get(\\OxidEsales\\Eshop\\Core\\Header::class);\n        $header->sendHeader();\n    }\n\n    /**\n     * set header sent to browser\n     *\n     * @param string $sHeader header to sent\n     */\n    public function setHeader($sHeader)\n    {\n        header($sHeader);\n    }\n\n    /**\n     * adds the given parameters at the end of the given url\n     *\n     * @param string $sUrl    a url\n     * @param array  $aParams the params which will be added\n     *\n     * @return string\n     */\n    protected function addUrlParameters($sUrl, $aParams)\n    {\n        $sDelimiter = ((Str::getStr()->strpos($sUrl, '?') !== false)) ? '&' : '?';\n        foreach ($aParams as $sName => $sVal) {\n            $sUrl = $sUrl . $sDelimiter . $sName . '=' . $sVal;\n            $sDelimiter = '&';\n        }\n\n        return $sUrl;\n    }\n\n    /**\n     * Fill array.\n     *\n     * @param array $aName Initial array of strings\n     * @param float $dVat  Article VAT\n     *\n     * @return string\n     *\n     * @todo rename function more closely to actual purpose\n     * @todo finish refactoring\n     */\n    protected function fillExplodeArray($aName, $dVat = null)\n    {\n        $myConfig = Registry::getConfig();\n        $oObject = new stdClass();\n        $aPrice = explode('!P!', $aName[0]);\n\n        if (($myConfig->getConfigParam('bl_perfLoadSelectLists') && $myConfig->getConfigParam('bl_perfUseSelectlistPrice') && isset($aPrice[0]) && isset($aPrice[1])) || $this->isAdmin()) {\n            // yes, price is there\n            $oObject->price = isset($aPrice[1]) ? $aPrice[1] : 0;\n            $aName[0] = isset($aPrice[0]) ? $aPrice[0] : '';\n\n            $iPercPos = Str::getStr()->strpos($oObject->price, '%');\n            if ($iPercPos !== false) {\n                $oObject->priceUnit = '%';\n                $oObject->fprice = $oObject->price;\n                $oObject->price = substr($oObject->price, 0, $iPercPos);\n            } else {\n                $oCur = $myConfig->getActShopCurrencyObject();\n                $oObject->price = str_replace(',', '.', $oObject->price);\n                $oObject->fprice = Registry::getLang()->formatCurrency($oObject->price * $oCur->rate, $oCur);\n                $oObject->priceUnit = 'abs';\n            }\n\n            // add price info into list\n            if (!$this->isAdmin() && $oObject->price != 0) {\n                $aName[0] .= \" \";\n\n                $dPrice = $this->preparePrice($oObject->price, $dVat);\n\n                if ($oObject->price > 0) {\n                    $aName[0] .= \"+\";\n                }\n                //V FS#2616\n                if ($dVat != null && $oObject->priceUnit == 'abs') {\n                    $oPrice = oxNew(\\OxidEsales\\Eshop\\Core\\Price::class);\n                    $oPrice->setPrice($oObject->price, $dVat);\n                    $aName[0] .= Registry::getLang()->formatCurrency($dPrice * $oCur->rate, $oCur);\n                } else {\n                    $aName[0] .= $oObject->fprice;\n                }\n                if ($oObject->priceUnit == 'abs') {\n                    $aName[0] .= \" \" . $oCur->sign;\n                }\n            }\n        } elseif (isset($aPrice[0]) && isset($aPrice[1])) {\n            // A. removing unused part of information\n            $aName[0] = Str::getStr()->preg_replace(\"/!P!.*/\", \"\", $aName[0]);\n        }\n\n        $oObject->name = $aName[0];\n        $oObject->value = $aName[1];\n\n        return $oObject;\n    }\n\n    /**\n     * Prepares price depending what options are used(show as net, brutto, etc.) for displaying\n     *\n     * @param double $dPrice Price\n     * @param double $dVat   VAT\n     *\n     * @return float\n     */\n    protected function preparePrice($dPrice, $dVat)\n    {\n        $blCalculationModeNetto = $this->isPriceViewModeNetto();\n\n        $oCurrency = Registry::getConfig()->getActShopCurrencyObject();\n\n        $blEnterNetPrice = Registry::getConfig()->getConfigParam('blEnterNetPrice');\n        if ($blCalculationModeNetto && !$blEnterNetPrice) {\n            $dPrice = round(\\OxidEsales\\Eshop\\Core\\Price::brutto2Netto($dPrice, $dVat), $oCurrency->decimal);\n        } elseif (!$blCalculationModeNetto && $blEnterNetPrice) {\n            $dPrice = round(\\OxidEsales\\Eshop\\Core\\Price::netto2Brutto($dPrice, $dVat), $oCurrency->decimal);\n        }\n\n        return $dPrice;\n    }\n\n    /**\n     * Checks and return true if price view mode is netto.\n     *\n     * @return bool\n     */\n    protected function isPriceViewModeNetto()\n    {\n        $blResult = (bool) Registry::getConfig()->getConfigParam('blShowNetPrice');\n        $oUser = $this->getArticleUser();\n        if ($oUser) {\n            $blResult = $oUser->isPriceViewModeNetto();\n        }\n\n        return $blResult;\n    }\n\n    /**\n     * Return article user.\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\User\n     */\n    protected function getArticleUser()\n    {\n        if (isset($this->_oUser) && $this->_oUser) {\n            return $this->_oUser;\n        }\n\n        return $this->getUser();\n    }\n\n    /**\n     * returns manually set mime types\n     *\n     * @param string $sFileName the file\n     *\n     * @return string\n     */\n    public function oxMimeContentType($sFileName)\n    {\n        $sFileName = strtolower($sFileName);\n        $iLastDot = strrpos($sFileName, '.');\n\n        $sType = false;\n        if ($iLastDot !== false) {\n            $sType = substr($sFileName, $iLastDot + 1);\n            $sType = match ($sType) {\n                'gif'         => 'image/gif',\n                'jpeg', 'jpg' => 'image/jpeg',\n                'png'         => 'image/png',\n                'webp'        => 'image/webp',\n                default       => false,\n            };\n        }\n\n        return $sType;\n    }\n\n    /**\n     * @deprecated will be removed in next major version\n     *\n     * @return array\n     */\n    public function getLangCache($cacheName)\n    {\n        $cache = ContainerFacade::get(CacheItemPoolInterface::class);\n        if (!$cache->hasItem($cacheName)) {\n            return null;\n        }\n\n        return $cache->getItem($cacheName)->get();\n    }\n\n    /**\n     * @deprecated will be removed in next major version\n     */\n    public function setLangCache($cacheName, $langCache)\n    {\n        $cache = ContainerFacade::get(TagAwareCacheInterface::class);\n        $cache->get($cacheName, function (ItemInterface $item) use ($langCache) {\n            $item->tag('oxid_esales.cache.language');\n\n            return $langCache;\n        });\n\n        return true;\n    }\n\n    /**\n     * Checks if url has ending slash / - if not, adds it\n     *\n     * @param string $sUrl url string\n     *\n     * @return string\n     */\n    public function checkUrlEndingSlash($sUrl)\n    {\n        if (!Str::getStr()->preg_match(\"/\\/$/\", $sUrl)) {\n            $sUrl .= '/';\n        }\n\n        return $sUrl;\n    }\n\n    /**\n     * handler for 404 (page not found) error\n     *\n     * @param string $sUrl url which was given, can be not specified in some cases\n     */\n    public function handlePageNotFoundError($sUrl = '')\n    {\n        $this->setHeader(\"HTTP/1.0 404 Not Found\");\n        $this->setHeader(\"Content-Type: text/html; charset=UTF-8\");\n\n        $sReturn = \"Page not found.\";\n        $oView = oxNew(\\OxidEsales\\Eshop\\Application\\Controller\\FrontendController::class);\n        $oView->init();\n        $oView->render();\n        $oView->setClassKey('oxUBase');\n        $oView->addTplParam('sUrl', $sUrl);\n        if ($sRet = Registry::getUtilsView()->getTemplateOutput('message/err_404', $oView)) {\n            $sReturn = $sRet;\n        }\n        $this->showMessageAndExit($sReturn);\n    }\n\n    /**\n     * Extracts domain name from given host\n     *\n     * @param string $sHost host name\n     *\n     * @return string\n     */\n    public function extractDomain($sHost)\n    {\n        $oStr = Str::getStr();\n        if (\n            !$oStr->preg_match('/[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}/', $sHost) &&\n            ($iLastDot = strrpos($sHost, '.')) !== false\n        ) {\n            $iLen = $oStr->strlen($sHost);\n            if (($iNextDot = strrpos($sHost, '.', ($iLen - $iLastDot + 1) * -1)) !== false) {\n                $sHost = trim($oStr->substr($sHost, $iNextDot), '.');\n            }\n        }\n\n        return $sHost;\n    }\n\n    private function isSeoEnabled(): bool\n    {\n        return (bool)ContainerFacade::getParameter('oxid_esales.seo_mode');\n    }\n\n    private function isSeoDisabledForShopAndLanguage(int $shopId, int $languageId): bool\n    {\n        $seoModes = Registry::getConfig()->getconfigParam('aSeoModes');\n\n        return is_array($seoModes) && isset($seoModes[$shopId][$languageId]) && !$seoModes[$shopId][$languageId];\n    }\n}\n"
  },
  {
    "path": "source/Core/UtilsCount.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\n/**\n * Counting utility class\n */\nclass UtilsCount extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Users view id, used to identify current state cache\n     *\n     * @var string\n     */\n    protected $_sUserViewId = null;\n\n    /**\n     * Returns category article count\n     *\n     * @param string $sCatId Category Id\n     *\n     * @return int\n     */\n    public function getCatArticleCount($sCatId)\n    {\n        // current status unique ident\n        $sActIdent = $this->getUserViewId();\n\n        // loading from cache\n        $aCatData = $this->getCatCache();\n\n        if (!$aCatData || !isset($aCatData[$sCatId][$sActIdent])) {\n            $iCnt = $this->setCatArticleCount($aCatData, $sCatId, $sActIdent);\n        } else {\n            $iCnt = $aCatData[$sCatId][$sActIdent];\n        }\n\n        return $iCnt;\n    }\n\n    /**\n     * Returns category article count price\n     *\n     * @param string $sCatId     Category Id\n     * @param float $dPriceFrom from price\n     * @param float $dPriceTo   to price\n     *\n     * @return int\n     */\n    public function getPriceCatArticleCount($sCatId, $dPriceFrom, $dPriceTo)\n    {\n        // current status unique ident\n        $sActIdent = $this->getUserViewId();\n\n        // loading from cache\n        $aCatData = $this->getCatCache();\n\n        if (!$aCatData || !isset($aCatData[$sCatId][$sActIdent])) {\n            $iCnt = $this->setPriceCatArticleCount($aCatData, $sCatId, $sActIdent, $dPriceFrom, $dPriceTo);\n        } else {\n            $iCnt = $aCatData[$sCatId][$sActIdent];\n        }\n\n        return $iCnt;\n    }\n\n    /**\n     * Returns vendor article count\n     *\n     * @param string $sVendorId Vendor category Id\n     *\n     * @return int\n     */\n    public function getVendorArticleCount($sVendorId)\n    {\n        // current category unique ident\n        $sActIdent = $this->getUserViewId();\n\n        // loading from cache\n        $aVendorData = $this->getVendorCache();\n\n        if (!$aVendorData || !isset($aVendorData[$sVendorId][$sActIdent])) {\n            $iCnt = $this->setVendorArticleCount($aVendorData, $sVendorId, $sActIdent);\n        } else {\n            $iCnt = $aVendorData[$sVendorId][$sActIdent];\n        }\n\n        return $iCnt;\n    }\n\n    /**\n     * Returns Manufacturer article count\n     *\n     * @param string $sManufacturerId Manufacturer category Id\n     *\n     * @return int\n     */\n    public function getManufacturerArticleCount($sManufacturerId)\n    {\n        // current category unique ident\n        $sActIdent = $this->getUserViewId();\n\n        // loading from cache\n        $aManufacturerData = $this->getManufacturerCache();\n        if (!$aManufacturerData || !isset($aManufacturerData[$sManufacturerId][$sActIdent])) {\n            $iCnt = $this->setManufacturerArticleCount($aManufacturerData, $sManufacturerId, $sActIdent);\n        } else {\n            $iCnt = $aManufacturerData[$sManufacturerId][$sActIdent];\n        }\n\n        return $iCnt;\n    }\n\n    /**\n     * Saves and returns category article count into cache\n     *\n     * @param array  $aCache    Category cache data\n     * @param string $sCatId    Unique category identifier\n     * @param string $sActIdent ID\n     *\n     * @return int\n     */\n    public function setCatArticleCount($aCache, $sCatId, $sActIdent)\n    {\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $sTable = $oArticle->getViewName();\n        $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n        $sO2CView = $tableViewNameGenerator->getViewName('oxobject2category');\n        $oDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb();\n\n        // we use distinct if article is assigned to category twice\n        $sQ = \"SELECT COUNT( DISTINCT $sTable.`oxid` )\n               FROM $sO2CView\n                   INNER JOIN $sTable ON $sO2CView.`oxobjectid` = $sTable.`oxid` AND $sTable.`oxparentid` = ''\n               WHERE $sO2CView.`oxcatnid` = :oxcatnid AND \" . $oArticle->getSqlActiveSnippet();\n\n        $aCache[$sCatId][$sActIdent] = $oDb->getOne($sQ, [\n            'oxcatnid' => $sCatId\n        ]);\n\n        $this->setCatCache($aCache);\n\n        return $aCache[$sCatId][$sActIdent];\n    }\n\n    /**\n     * Saves (if needed) and returns price category article count into cache\n     *\n     * @param array  $aCache     Category cache data\n     * @param string $sCatId     Unique category ident\n     * @param string $sActIdent  Category ID\n     * @param int    $dPriceFrom Price from\n     * @param int    $dPriceTo   Price to\n     *\n     * @return null\n     */\n    public function setPriceCatArticleCount($aCache, $sCatId, $sActIdent, $dPriceFrom, $dPriceTo)\n    {\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $sTable = $oArticle->getViewName();\n\n        $params = [];\n        $sSelect = \"SELECT count({$sTable}.oxid) FROM {$sTable} WHERE oxvarminprice >= 0\";\n        if ($dPriceTo) {\n            $sSelect .= \" AND oxvarminprice <= :oxvarpriceto\";\n            $params['oxvarpriceto'] = (float) $dPriceTo;\n        }\n\n        if ($dPriceFrom) {\n            $sSelect .= \" AND oxvarminprice  >= :oxvarpricefrom\";\n            $params['oxvarpricefrom'] = (float) $dPriceFrom;\n        }\n\n        $sSelect .=  \" AND {$sTable}.oxissearch = 1 AND \" . $oArticle->getSqlActiveSnippet();\n\n        $aCache[$sCatId][$sActIdent] = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->getOne($sSelect, $params);\n\n        $this->setCatCache($aCache);\n\n        return $aCache[$sCatId][$sActIdent];\n    }\n\n    /**\n     * Saves and returns vendors category article count into cache\n     *\n     * @param array  $aCache    Category cache data\n     * @param string $sCatId    Unique vendor category ident\n     * @param string $sActIdent Vendor category ID\n     *\n     * @return int\n     */\n    public function setVendorArticleCount($aCache, $sCatId, $sActIdent)\n    {\n        // if vendor/category name is 'root', skip counting\n        if ($sCatId == 'root') {\n            return 0;\n        }\n\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $sTable = $oArticle->getViewName();\n\n        // select each vendor articles count\n        $sQ = \"select $sTable.oxvendorid AS vendorId, count(*) from $sTable where \";\n        $sQ .= \"$sTable.oxvendorid <> '' and $sTable.oxparentid = '' and \" . $oArticle->getSqlActiveSnippet() . \" group by $sTable.oxvendorid \";\n        $aDbResult = $this->getAssoc($sQ);\n\n        foreach ($aDbResult as $sKey => $sValue) {\n            $aCache[$sKey][$sActIdent] = $sValue;\n        }\n\n        $this->setVendorCache($aCache);\n\n        return isset($aCache[$sCatId][$sActIdent]) ? $aCache[$sCatId][$sActIdent] : 0;\n    }\n\n    /**\n     * Returns the query result as a two dimensional associative array.\n     * The keys of the first level are the firsts value of each row.\n     * The values of the first level arrays with numeric key that hold the all the values of each row but the first one,\n     * which is used a a key in the first level.\n     *\n     * @param string $query\n     * @param array  $parameters\n     *\n     * @return array\n     */\n    protected function getAssoc($query, $parameters = [])\n    {\n        $database = DatabaseProvider::getDb();\n\n        $resultSet = $database->select($query, $parameters);\n\n        $rows = $resultSet->fetchAll();\n\n        if (!$rows) {\n            return [];\n        }\n\n        $result = [];\n\n        foreach ($rows as $row) {\n            $firstColumn = array_keys($row)[0];\n            $key = $row[$firstColumn];\n\n            $values = array_values($row);\n\n            if (2 <= count($values)) {\n                $result[$key] = $values[1];\n            }\n        }\n\n        return $result;\n    }\n\n    /**\n     * Saves and returns Manufacturers category article count into cache\n     *\n     * @param array  $aCache    Category cache data\n     * @param string $sMnfId    Unique Manufacturer ident\n     * @param string $sActIdent Unique user context ID\n     *\n     * @return int\n     */\n    public function setManufacturerArticleCount($aCache, $sMnfId, $sActIdent)\n    {\n        // if Manufacturer/category name is 'root', skip counting\n        if ($sMnfId == 'root') {\n            return 0;\n        }\n\n        $oArticle = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Article::class);\n        $sArtTable = $oArticle->getViewName();\n\n        // select each Manufacturer articles count\n        //#3485\n        $sQ = \"SELECT count($sArtTable.oxid) FROM $sArtTable WHERE $sArtTable.oxparentid = '' AND oxmanufacturerid = :manufacturerId AND \" . $oArticle->getSqlActiveSnippet();\n        $iValue = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getDb()->getOne($sQ, [\n            'manufacturerId' => $sMnfId\n        ]);\n\n        $aCache[$sMnfId][$sActIdent] = (int) $iValue;\n\n        $this->setManufacturerCache($aCache);\n\n        return $aCache[$sMnfId][$sActIdent];\n    }\n\n    /**\n     * Resets category (all categories) article count\n     *\n     * @param string $sCatId Category/vendor/manufacturer ID\n     */\n    public function resetCatArticleCount($sCatId = null)\n    {\n        if (!$sCatId) {\n            \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->setGlobalParameter('aLocalCatCache', null);\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->toFileCache('aLocalCatCache', '');\n        } else {\n            // loading from cache\n            $aCatData = $this->getCatCache();\n            if (isset($aCatData[$sCatId])) {\n                unset($aCatData[$sCatId]);\n                $this->setCatCache($aCatData);\n            }\n        }\n    }\n\n    /**\n     * Resets price categories article count\n     *\n     * @param int $iPrice article price\n     */\n    public function resetPriceCatArticleCount($iPrice)\n    {\n        // loading from cache\n        if ($aCatData = $this->getCatCache()) {\n            $tableViewNameGenerator = oxNew(TableViewNameGenerator::class);\n\n            // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n            $categoriesIds = DatabaseProvider::getMaster()->getCol(\n                sprintf(\n                    \"SELECT oxid FROM %s WHERE :oxpricefrom >= oxpricefrom AND :oxpriceto <= oxpriceto\",\n                    $tableViewNameGenerator->getViewName('oxcategories')\n                ),\n                [\n                    'oxpricefrom' => (float) $iPrice,\n                    'oxpriceto' => (float) $iPrice\n                ]\n            );\n            foreach ($categoriesIds as $categoryId) {\n                if (isset($aCatData[$categoryId])) {\n                    unset($aCatData[$categoryId]);\n                }\n            }\n\n            if (!empty($categoriesIds)) {\n                $this->setCatCache($aCatData);\n            }\n        }\n    }\n\n    /**\n     * Resets vendor (all vendors) article count\n     *\n     * @param string $sVendorId Category/vendor ID\n     */\n    public function resetVendorArticleCount($sVendorId = null)\n    {\n        if (!$sVendorId) {\n            \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->setGlobalParameter('aLocalVendorCache', null);\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->toFileCache('aLocalVendorCache', '');\n        } else {\n            // loading from cache\n            $aVendorData = $this->getVendorCache();\n            if (isset($aVendorData[$sVendorId])) {\n                unset($aVendorData[$sVendorId]);\n                $this->setVendorCache($aVendorData);\n            }\n        }\n    }\n\n    /**\n     * Resets Manufacturer (all Manufacturers) article count\n     *\n     * @param string $sManufacturerId Category/Manufacturer ID\n     */\n    public function resetManufacturerArticleCount($sManufacturerId = null)\n    {\n        if (!$sManufacturerId) {\n            \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->setGlobalParameter('aLocalManufacturerCache', null);\n            \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->toFileCache('aLocalManufacturerCache', '');\n        } else {\n            // loading from cache\n            $aManufacturerData = $this->getManufacturerCache();\n            if (isset($aManufacturerData[$sManufacturerId])) {\n                unset($aManufacturerData[$sManufacturerId]);\n                $this->setManufacturerCache($aManufacturerData);\n            }\n        }\n    }\n\n    /**\n     * Loads and returns category cache data array\n     *\n     * @return array\n     */\n    protected function getCatCache()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        // first look at the local cache\n        $aLocalCatCache = $myConfig->getGlobalParameter('aLocalCatCache');\n\n        // if local cache is not set - loading from file cache\n        if (!$aLocalCatCache) {\n            $sLocalCatCache = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->fromFileCache('aLocalCatCache');\n            if ($sLocalCatCache) {\n                $aLocalCatCache = $sLocalCatCache;\n            } else {\n                $aLocalCatCache = null;\n            }\n            $myConfig->setGlobalParameter('aLocalCatCache', $aLocalCatCache);\n        }\n\n        return $aLocalCatCache;\n    }\n\n    /**\n     * Writes category data into cache\n     *\n     * @param array $aCache A cacheable data\n     */\n    protected function setCatCache($aCache)\n    {\n        \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->setGlobalParameter('aLocalCatCache', $aCache);\n        \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->toFileCache('aLocalCatCache', $aCache);\n    }\n\n    /**\n     * Writes vendor data into cache\n     *\n     * @param array $aCache A cacheable data\n     */\n    protected function setVendorCache($aCache)\n    {\n        \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->setGlobalParameter('aLocalVendorCache', $aCache);\n        \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->toFileCache('aLocalVendorCache', $aCache);\n    }\n\n    /**\n     * Writes Manufacturer data into cache\n     *\n     * @param array $aCache A cacheable data\n     */\n    protected function setManufacturerCache($aCache)\n    {\n        \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->setGlobalParameter('aLocalManufacturerCache', $aCache);\n        \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->toFileCache('aLocalManufacturerCache', $aCache);\n    }\n\n    /**\n     * Loads and returns category/vendor cache data array\n     *\n     * @return array\n     */\n    protected function getVendorCache()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        // first look at the local cache\n        $aLocalVendorCache = $myConfig->getGlobalParameter('aLocalVendorCache');\n        // if local cache is not set - loading from file cache\n        if (!$aLocalVendorCache) {\n            $sLocalVendorCache = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->fromFileCache('aLocalVendorCache');\n            if ($sLocalVendorCache) {\n                $aLocalVendorCache = $sLocalVendorCache;\n            } else {\n                $aLocalVendorCache = null;\n            }\n            $myConfig->setGlobalParameter('aLocalVendorCache', $aLocalVendorCache);\n        }\n\n        return $aLocalVendorCache;\n    }\n\n    /**\n     * Loads and returns category/Manufacturer cache data array\n     *\n     * @return array\n     */\n    protected function getManufacturerCache()\n    {\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        // first look at the local cache\n        $aLocalManufacturerCache = $myConfig->getGlobalParameter('aLocalManufacturerCache');\n        // if local cache is not set - loading from file cache\n        if (!$aLocalManufacturerCache) {\n            $sLocalManufacturerCache = \\OxidEsales\\Eshop\\Core\\Registry::getUtils()->fromFileCache('aLocalManufacturerCache');\n            if ($sLocalManufacturerCache) {\n                $aLocalManufacturerCache = $sLocalManufacturerCache;\n            } else {\n                $aLocalManufacturerCache = null;\n            }\n            $myConfig->setGlobalParameter('aLocalManufacturerCache', $aLocalManufacturerCache);\n        }\n\n        return $aLocalManufacturerCache;\n    }\n\n    /**\n     * Returns user view id (Shop, language, RR group index...)\n     *\n     * @param bool $blReset optional, default = false\n     *\n     * @return string\n     */\n    protected function getUserViewId($blReset = false)\n    {\n        if ($this->_sUserViewId != null && !$blReset) {\n            return $this->_sUserViewId;\n        }\n\n        // loading R&R data from session\n        $userSessionGroups = $this->getCurrentUserSessionGroups();\n        $this->_sUserViewId = md5(\\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopID() . \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getLanguageTag() . serialize($userSessionGroups) . (int) $this->isAdmin());\n\n        return $this->_sUserViewId;\n    }\n\n    /**\n     * Get current user groups\n     *\n     * @return array|null\n     */\n    protected function getCurrentUserSessionGroups()\n    {\n        return null;\n    }\n}\n"
  },
  {
    "path": "source/Core/UtilsDate.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse DateTime;\nuse OxidEsales\\Eshop\\Core\\Str;\n\n/**\n * Date manipulation utility class\n */\nclass UtilsDate extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Format date to user defined format.\n     *\n     * @param string $sDBDateIn         Date to reformat\n     * @param bool   $blForceEnglishRet Force to return primary value(default false)\n     *\n     * @return string\n     */\n    public function formatDBDate($sDBDateIn, $blForceEnglishRet = false)\n    {\n        // convert english format to output format\n        if (!$sDBDateIn) {\n            return null;\n        }\n\n        $oStr = Str::getStr();\n        if ($blForceEnglishRet && $oStr->strstr($sDBDateIn, '-')) {\n            return $sDBDateIn;\n        }\n\n        if ($this->isEmptyDate($sDBDateIn) && $sDBDateIn != '-') {\n            return '-';\n        } elseif ($sDBDateIn == '-') {\n            return '0000-00-00 00:00:00';\n        }\n\n        // is it a timestamp ?\n        if (is_numeric($sDBDateIn)) {\n            // db timestamp : 20030322100409\n            $sNew = substr($sDBDateIn, 0, 4) . '-' . substr($sDBDateIn, 4, 2) . '-' . substr($sDBDateIn, 6, 2) . ' ';\n            // check if it is a timestamp or wrong data: 20030322\n            if (strlen($sDBDateIn) > 8) {\n                $sNew .= substr($sDBDateIn, 8, 2) . ':' . substr($sDBDateIn, 10, 2) . ':' . substr($sDBDateIn, 12, 2);\n            }\n            // convert it to english format\n            $sDBDateIn = $sNew;\n        }\n\n        // remove time as it is same in english as in german\n        $aData = explode(' ', trim($sDBDateIn));\n\n        // preparing time array\n        $sTime = (isset($aData[1]) && $oStr->strstr($aData[1], ':')) ? $aData[1] : '';\n        $aTime = $sTime ? explode(':', $sTime) : [0, 0, 0];\n\n        // preparing date array\n        $sDate = isset($aData[0]) ? $aData[0] : '';\n        $aDate = preg_split('/[\\/.-]/', $sDate);\n\n        // choosing format..\n        if ($sTime) {\n            $sFormat = $blForceEnglishRet ? 'Y-m-d H:i:s' : \\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('fullDateFormat');\n        } else {\n            $sFormat = $blForceEnglishRet ? 'Y-m-d' : \\OxidEsales\\Eshop\\Core\\Registry::getLang()->translateString('simpleDateFormat');\n        }\n\n        if (count($aDate) != 3) {\n            return date($sFormat);\n        } else {\n            return $this->processDate($aTime, $aDate, $oStr->strstr($sDate, '.'), $sFormat);\n        }\n    }\n\n    /**\n     * Bidirectional converter for date/datetime field\n     *\n     * @param object $oObject       data field object\n     * @param bool   $blToTimeStamp set TRUE to format MySQL compatible value\n     * @param bool   $blOnlyDate    set TRUE to format \"date\" type field\n     *\n     * @return string\n     */\n    public function convertDBDateTime($oObject, $blToTimeStamp = false, $blOnlyDate = false)\n    {\n        $sDate = $oObject->value;\n\n        // defining time format\n        $sLocalDateFormat = $this->defineAndCheckDefaultDateValues($blToTimeStamp);\n        $sLocalTimeFormat = $this->defineAndCheckDefaultTimeValues($blToTimeStamp);\n\n        // default date/time patterns\n        $aDefDatePatterns = $this->defaultDatePattern();\n\n        // regexps to validate input\n        $aDatePatterns = $this->regexp2ValidateDateInput();\n        $aTimePatterns = $this->regexp2ValidateTimeInput();\n\n        // date/time formatting rules\n        $aDFormats = $this->defineDateFormattingRules();\n        $aTFormats = $this->defineTimeFormattingRules();\n\n        // empty date field value ? setting default value\n        if (!$sDate) {\n            $this->setDefaultDateTimeValue($oObject, $sLocalDateFormat, $sLocalTimeFormat, $blOnlyDate);\n\n            return $oObject->value;\n        }\n\n        $blDefDateFound = false;\n        $oStr = Str::getStr();\n\n        // looking for default values that are formatted by MySQL\n        foreach (array_keys($aDefDatePatterns) as $sDefDatePattern) {\n            if ($oStr->preg_match($sDefDatePattern, $sDate)) {\n                $blDefDateFound = true;\n                break;\n            }\n        }\n\n        // default value is set ?\n        if ($blDefDateFound) {\n            $this->setDefaultFormatedValue($oObject, $sDate, $sLocalDateFormat, $sLocalTimeFormat, $blOnlyDate);\n\n            return $oObject->value;\n        }\n\n        $blDateFound = false;\n        $blTimeFound = false;\n        $aDateMatches = [];\n        $aTimeMatches = [];\n\n        // looking for date field\n        foreach ($aDatePatterns as $sPattern => $sType) {\n            if ($oStr->preg_match($sPattern, $sDate, $aDateMatches)) {\n                $blDateFound = true;\n\n                // now we know the type of passed date\n                $sDateFormat = $aDFormats[$sLocalDateFormat][0];\n                $aDFields = $aDFormats[$sType][1];\n                break;\n            }\n        }\n\n        // no such date field available ?\n        if (!$blDateFound) {\n            return $sDate;\n        }\n\n        if ($blOnlyDate) {\n            $this->setDate($oObject, $sDateFormat, $aDFields, $aDateMatches);\n\n            return $oObject->value;\n        }\n\n        // looking for time field\n        foreach ($aTimePatterns as $sPattern => $sType) {\n            if ($oStr->preg_match($sPattern, $sDate, $aTimeMatches)) {\n                $blTimeFound = true;\n\n                // now we know the type of passed time\n                $sTimeFormat = $aTFormats[$sLocalTimeFormat][0];\n                $aTFields = $aTFormats[$sType][1];\n\n                //\n                if ($sType == \"USA\" && isset($aTimeMatches[4])) {\n                    $iIntVal = (int) $aTimeMatches[1];\n                    if ($aTimeMatches[4] == \"PM\") {\n                        if ($iIntVal < 13) {\n                            $iIntVal += 12;\n                        }\n                    } elseif ($aTimeMatches[4] == \"AM\" && $aTimeMatches[1] == \"12\") {\n                        $iIntVal = 0;\n                    }\n\n                    $aTimeMatches[1] = sprintf(\"%02d\", $iIntVal);\n                }\n\n                break;\n            }\n        }\n\n        if (!$blTimeFound) {\n            //return $sDate;\n            // #871A. trying to keep date as possible correct\n            $this->setDate($oObject, $sDateFormat, $aDFields, $aDateMatches);\n\n            return $oObject->value;\n        }\n\n        $this->formatCorrectTimeValue($oObject, $sDateFormat, $sTimeFormat, $aDateMatches, $aTimeMatches, $aTFields, $aDFields);\n\n        // on some cases we get empty value\n        if (!$oObject->fldmax_length) {\n            return $this->convertDBDateTime($oObject, $blToTimeStamp, $blOnlyDate);\n        }\n\n        return $oObject->value;\n    }\n\n    /**\n     * Bidirectional converter for timestamp field\n     *\n     * @param object $oObject       oxField type object that keeps db field info\n     * @param bool   $blToTimeStamp if true - converts value to database compatible timestamp value\n     *\n     * @return string\n     */\n    public function convertDBTimestamp($oObject, $blToTimeStamp = false)\n    {\n        // on this case usually means that we gonna save value, and value is formatted, not plain\n        $sSQLTimeStampPattern = \"/^([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})$/\";\n        $sISOTimeStampPattern = \"/^([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})$/\";\n        $aMatches = [];\n        $oStr = Str::getStr();\n\n        // preparing value to save\n        if ($blToTimeStamp) {\n            // reformatting value to ISO\n            $this->convertDBDateTime($oObject, $blToTimeStamp);\n\n            if ($oStr->preg_match($sISOTimeStampPattern, $oObject->value, $aMatches)) {\n                // changing layout\n                $oObject->setValue($aMatches[1] . $aMatches[2] . $aMatches[3] . $aMatches[4] . $aMatches[5] . $aMatches[6]);\n                $oObject->fldmax_length = strlen($oObject->value);\n\n                return $oObject->value;\n            }\n        } else {\n            // loading and formatting value\n            // checking and parsing SQL timestamp value\n            //$sSQLTimeStampPattern = \"/^([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})$/\";\n            if ($oStr->preg_match($sSQLTimeStampPattern, $oObject->value, $aMatches)) {\n                $iTimestamp = mktime(\n                    $aMatches[4], //h\n                    $aMatches[5], //m\n                    $aMatches[6], //s\n                    $aMatches[2], //M\n                    $aMatches[3], //d\n                    $aMatches[1]\n                ); //y\n                if (!$iTimestamp) {\n                    $iTimestamp = \"0\";\n                }\n\n                $oObject->setValue(trim(date(\"Y-m-d H:i:s\", $iTimestamp)));\n                $oObject->fldmax_length = strlen($oObject->value);\n                $this->convertDBDateTime($oObject, $blToTimeStamp);\n\n                return $oObject->value;\n            }\n        }\n    }\n\n    /**\n     * Bidirectional converter for date field\n     *\n     * @param object $oObject       oxField type object that keeps db field info\n     * @param bool   $blToTimeStamp if true - converts value to database compatible timestamp value\n     *\n     * @return string\n     */\n    public function convertDBDate($oObject, $blToTimeStamp = false)\n    {\n        return $this->convertDBDateTime($oObject, $blToTimeStamp, true);\n    }\n\n    /**\n     * sets default formatted value\n     *\n     * @param object $oObject          date field object\n     * @param string $sDate            preferred date\n     * @param string $sLocalDateFormat input format\n     * @param string $sLocalTimeFormat local format\n     * @param bool   $blOnlyDate       marker to format only date field (no time)\n     *\n     * @return null\n     */\n    protected function setDefaultFormatedValue($oObject, $sDate, $sLocalDateFormat, $sLocalTimeFormat, $blOnlyDate)\n    {\n        $aDefTimePatterns = $this->defaultTimePattern();\n        $aDFormats = $this->defineDateFormattingRules();\n        $aTFormats = $this->defineTimeFormattingRules();\n        $oStr = Str::getStr();\n\n        foreach (array_keys($aDefTimePatterns) as $sDefTimePattern) {\n            if ($oStr->preg_match($sDefTimePattern, $sDate)) {\n                $blDefTimeFound = true;\n                break;\n            }\n        }\n\n        // setting and returning default formatted value\n        if ($blOnlyDate) {\n            $oObject->setValue(trim($aDFormats[$sLocalDateFormat][2])); // . \" \" . @$aTFormats[$sLocalTimeFormat][2]);\n            // increasing(decreasing) field length\n            $oObject->fldmax_length = strlen($oObject->value);\n\n            return;\n        } elseif ($blDefTimeFound) {\n            // setting value\n            $oObject->setValue(trim($aDFormats[$sLocalDateFormat][2] . \" \" . $aTFormats[$sLocalTimeFormat][2]));\n            // increasing(decreasing) field length\n            $oObject->fldmax_length = strlen($oObject->value);\n\n            return;\n        }\n    }\n\n    /**\n     * defines and checks default time values\n     *\n     * @param bool $blToTimeStamp -\n     *\n     * @return string\n     */\n    protected function defineAndCheckDefaultTimeValues($blToTimeStamp)\n    {\n        // defining time format\n        // checking for default values\n        $sLocalTimeFormat = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('sLocalTimeFormat');\n        if (!$sLocalTimeFormat || $blToTimeStamp) {\n            $sLocalTimeFormat = \"ISO\";\n        }\n\n        return $sLocalTimeFormat;\n    }\n\n    /**\n     * defines and checks default date values\n     *\n     * @param bool $blToTimeStamp marker how to format\n     *\n     * @return string\n     */\n    protected function defineAndCheckDefaultDateValues($blToTimeStamp)\n    {\n        // defining time format\n        // checking for default values\n        $sLocalDateFormat = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('sLocalDateFormat');\n        if (!$sLocalDateFormat || $blToTimeStamp) {\n            $sLocalDateFormat = \"ISO\";\n        }\n\n        return $sLocalDateFormat;\n    }\n\n    /**\n     * sets default date pattern\n     *\n     * @return array\n     */\n    protected function defaultDatePattern()\n    {\n        return [\"/^0000-00-00/\"   => \"ISO\",\n                     \"/^00\\.00\\.0000/\" => \"EUR\",\n                     \"/^00\\/00\\/0000/\" => \"USA\"\n        ];\n    }\n\n    /**\n     * sets default time pattern\n     *\n     * @return array\n     */\n    protected function defaultTimePattern()\n    {\n        return [\"/00:00:00$/\"    => \"ISO\",\n                     \"/00\\.00\\.00$/\"  => \"EUR\",\n                     \"/00:00:00 AM$/\" => \"USA\"\n        ];\n    }\n\n    /**\n     * regular expressions to validate date input\n     *\n     * @return array\n     */\n    protected function regexp2ValidateDateInput()\n    {\n        return [\"/^([0-9]{4})-([0-9]{2})-([0-9]{2})/\"   => \"ISO\",\n                     \"/^([0-9]{2})\\.([0-9]{2})\\.([0-9]{4})/\" => \"EUR\",\n                     \"/^([0-9]{2})\\/([0-9]{2})\\/([0-9]{4})/\" => \"USA\"\n        ];\n    }\n\n    /**\n     * regular expressions to validate time input\n     *\n     * @return array\n     */\n    protected function regexp2ValidateTimeInput()\n    {\n        return [\"/([0-9]{2}):([0-9]{2}):([0-9]{2})$/\"                 => \"ISO\",\n                     \"/([0-9]{2})\\.([0-9]{2})\\.([0-9]{2})$/\"               => \"EUR\",\n                     \"/([0-9]{2}):([0-9]{2}):([0-9]{2}) ([AP]{1}[M]{1})$/\" => \"USA\"\n        ];\n    }\n\n    /**\n     * define date formatting rules\n     *\n     * @return array\n     */\n    protected function defineDateFormattingRules()\n    {\n        return [\"ISO\" => [\"Y-m-d\", [2, 3, 1], \"0000-00-00\"],\n                     \"EUR\" => [\"d.m.Y\", [2, 1, 3], \"00.00.0000\"],\n                     \"USA\" => [\"m/d/Y\", [1, 2, 3], \"00/00/0000\"]\n        ];\n    }\n\n    /**\n     * defines time formatting rules\n     *\n     * @return array\n     */\n    protected function defineTimeFormattingRules()\n    {\n        return [\"ISO\" => [\"H:i:s\", [1, 2, 3], \"00:00:00\"],\n                     \"EUR\" => [\"H.i.s\", [1, 2, 3], \"00.00.00\"],\n                     \"USA\" => [\"h:i:s A\", [1, 2, 3], \"00:00:00 AM\"]\n        ];\n    }\n\n    /**\n     * Sets default date time value\n     *\n     * @param object $oObject          date field object\n     * @param string $sLocalDateFormat input format\n     * @param string $sLocalTimeFormat local format\n     * @param bool   $blOnlyDate       marker to format only date field (no time)\n     */\n    protected function setDefaultDateTimeValue($oObject, $sLocalDateFormat, $sLocalTimeFormat, $blOnlyDate)\n    {\n        $aDFormats = $this->defineDateFormattingRules();\n        $aTFormats = $this->defineTimeFormattingRules();\n\n        $sReturn = $aDFormats[$sLocalDateFormat][2];\n        if (!$blOnlyDate) {\n            $sReturn .= \" \" . $aTFormats[$sLocalTimeFormat][2];\n        }\n\n        if ($oObject instanceof \\OxidEsales\\Eshop\\Core\\Field) {\n            $oObject->setValue(trim($sReturn));\n        } else {\n            $oObject->value = trim($sReturn);\n        }\n        // increasing(decreasing) field lenght\n        $oObject->fldmax_length = strlen($oObject->value);\n    }\n\n    /**\n     * sets date\n     *\n     * @param object $oObject      date field object\n     * @param string $sDateFormat  date format\n     * @param array  $aDFields     days\n     * @param array  $aDateMatches new date as array (month, year)\n     */\n    protected function setDate($oObject, $sDateFormat, $aDFields, $aDateMatches)\n    {\n        // formatting correct time value\n        $iTimestamp = mktime(\n            0,\n            0,\n            0,\n            $aDateMatches[$aDFields[0]],\n            $aDateMatches[$aDFields[1]],\n            $aDateMatches[$aDFields[2]]\n        );\n\n        if ($oObject instanceof \\OxidEsales\\Eshop\\Core\\Field) {\n            $oObject->setValue(@date($sDateFormat, $iTimestamp));\n        } else {\n            $oObject->value = @date($sDateFormat, $iTimestamp);\n        }\n        // we should increase (decrease) field lenght\n        $oObject->fldmax_length = strlen($oObject->value);\n    }\n\n    /**\n     * Formatting correct time value\n     *\n     * @param object $oObject      data field object\n     * @param string $sDateFormat  date format\n     * @param string $sTimeFormat  time format\n     * @param array  $aDateMatches new new date\n     * @param array  $aTimeMatches new time\n     * @param array  $aTFields     defines the time fields\n     * @param array  $aDFields     defines the date fields\n     */\n    protected function formatCorrectTimeValue($oObject, $sDateFormat, $sTimeFormat, $aDateMatches, $aTimeMatches, $aTFields, $aDFields)\n    {\n        // formatting correct time value\n        $iTimestamp = @mktime(\n            (int) $aTimeMatches[$aTFields[0]],\n            (int) $aTimeMatches[$aTFields[1]],\n            (int) $aTimeMatches[$aTFields[2]],\n            (int) $aDateMatches[$aDFields[0]],\n            (int) $aDateMatches[$aDFields[1]],\n            (int) $aDateMatches[$aDFields[2]]\n        );\n\n        if ($oObject instanceof \\OxidEsales\\Eshop\\Core\\Field) {\n            $oObject->setValue(trim(@date($sDateFormat . \" \" . $sTimeFormat, $iTimestamp)));\n        } else {\n            $oObject->value = trim(@date($sDateFormat . \" \" . $sTimeFormat, $iTimestamp));\n        }\n\n        // we should increase (decrease) field lenght\n        $oObject->fldmax_length = strlen($oObject->value);\n    }\n\n    /**\n     * Returns time according shop timezone configuration. Configures in\n     * Admin -> Main menu -> Core Settings -> General\n     * @see getRequestTime\n     * @return int current (modified according timezone) time\n     */\n    public function getTime()\n    {\n        return $this->shiftServerTime(time());\n    }\n\n    /**\n     * Returns time wen the request was started according shop timezone configuration. Configures in\n     * Admin -> Main menu -> Core Settings -> General\n     * REQUEST TIME is faster because it is not an syscall like time\n     * @return int current (modified according timezone) time\n     */\n    public function getRequestTime()\n    {\n        return $this->shiftServerTime($_SERVER['REQUEST_TIME']);\n    }\n\n    /**\n     * Returns the the timestamp formatted as date string for the database\n     *\n     * @param int $iTimestamp the timestamp to be formatted\n     *\n     * @return bool|string timestamp formatted as date string for the database, false on error\n     */\n    public function formatDBTimestamp($iTimestamp)\n    {\n        return date('Y-m-d H:i:s', $iTimestamp);\n    }\n\n    /**\n     * Returns the the timestamp formatted as date string for the database\n     * @param int $roundTo a amount of seconds to be rounded to e.g. 300 for rounding to 5 minutes\n     *\n     * @return bool|string  the data string formatted for the database (SQL), false on error\n     */\n    public function getRoundedRequestDateDBFormatted($roundTo)\n    {\n        $timestamp = $this->getRequestTime();\n        //round up x minutes so query cache can work\n        $timestamp = ceil($timestamp / $roundTo) * $roundTo;\n\n        //format date for sql query\n        return $this->formatDBTimestamp($timestamp);\n    }\n\n    /**\n     * Returns the the request time formatted as date string for the database\n     *\n     * @return bool|string\n     */\n    public function getRequestTimeDBFormated()\n    {\n        return $this->formatDBTimestamp($this->getRequestTime());\n    }\n\n    /**\n     * Form time\n     *\n     * @param string $sTime  time to create timestamp.\n     * @param string $sTime2 hours, minutes and seconds to update created timestamp.\n     *\n     * @return int formed (modified according timezone) time\n     */\n    public function formTime($sTime = 'now', $sTime2 = null)\n    {\n        $oDate = new DateTime($sTime);\n\n        if ($sTime2) {\n            $aHourToCheck = explode(':', $sTime2);\n            $iHour = $aHourToCheck[0];\n            $iMinutes = $aHourToCheck[1];\n            $iSecond = $aHourToCheck[2];\n            $oDate->setTime($iHour, $iMinutes, $iSecond);\n        }\n\n        return $this->shiftServerTime($oDate->getTimestamp());\n    }\n\n    /**\n     * Shift time if needed by configured timezone.\n     *\n     * @param int $iTime\n     *\n     * @return int\n     */\n    public function shiftServerTime($iTime)\n    {\n        $iServerTimeShift = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iServerTimeShift');\n        if ($iServerTimeShift) {\n            $iTime = $iTime + ((int) $iServerTimeShift * 3600);\n        }\n        return $iTime;\n    }\n\n    /**\n     * Returns number of the week according to numeration standards (configurable in admin):\n     * %U - week number, starting with the first Sunday as the first day of the first week;\n     * %W - week number, starting with the first Monday as the first day of the first week.\n     *\n     * @param int    $iFirstWeekDay if set formats with %U, otherwise with %W ($myConfig->getConfigParam( 'iFirstWeekDay' ))\n     * @param string $sTimestamp    timestamp, default is null (returns current week number);\n     * @param string $sFormat       calculation format ( \"%U\" or \"%w\"), default is null (returns \"%W\" or defined in admin ).\n     *\n     * @return int\n     */\n    public function getWeekNumber($iFirstWeekDay, $sTimestamp = null, $sFormat = null)\n    {\n        if ($sTimestamp == null) {\n            $sTimestamp = time();\n        }\n        if ($sFormat == null) {\n            $sFormat = '%W';\n            if ($iFirstWeekDay) {\n                $sFormat = '%U';\n            }\n        }\n\n        return (int) strftime($sFormat, $sTimestamp);\n    }\n\n    /**\n     * Reformats and returns German date string to English.\n     *\n     * @param string $sDate German format date string\n     *\n     * @return string\n     */\n    public function german2English($sDate)\n    {\n        $aDate = explode(\".\", $sDate);\n\n        if (isset($aDate) && count($aDate) > 1) {\n            if (count($aDate) == 2) {\n                $sDate = $aDate[1] . \"-\" . $aDate[0];\n            } else {\n                $sDate = $aDate[2] . \"-\" . $aDate[1] . \"-\" . $aDate[0];\n            }\n        }\n\n        return $sDate;\n    }\n\n    /**\n     * Checks if date string is empty date field. Empty string or string with\n     * all date values equal to 0 is treated as empty.\n     *\n     * @param array $sDate date or date time string\n     *\n     * @return bool\n     */\n    public function isEmptyDate($sDate)\n    {\n        if (!empty($sDate)) {\n            $sDate = preg_replace(\"/[^0-9a-z]/i\", \"\", $sDate);\n            if (!is_numeric($sDate) || $sDate != 0) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * Processes amd formats date / time.\n     *\n     * @param string $aTime    splitted time ( array( H, m, s ) )\n     * @param array  $aDate    splitted date ( array( Y, m, d ) )\n     * @param bool   $blGerman true if incoming string is in German format (dotted)\n     * @param string $sFormat  date format to produce\n     *\n     * @return string formatted string\n     */\n    protected function processDate($aTime, $aDate, $blGerman, $sFormat)\n    {\n        if ($blGerman) {\n            return date($sFormat, mktime($aTime[0], $aTime[1], $aTime[2], $aDate[1], $aDate[0], $aDate[2]));\n        }\n\n        return date($sFormat, mktime($aTime[0], $aTime[1], $aTime[2], $aDate[1], $aDate[2], $aDate[0]));\n    }\n}\n"
  },
  {
    "path": "source/Core/UtilsFile.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay;\nuse OxidEsales\\Eshop\\Core\\Exception\\StandardException;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\Bridge\\MasterImageHandlerBridgeInterface;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass UtilsFile extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    const PROMO_PICTURE_DIR = 'promo';\n\n    protected $_aTypeToPath = [\n        'TC'    => 'master/category/thumb',\n        'CICO'  => 'master/category/icon',\n        'PICO'  => 'master/category/promo_icon',\n        'MTHU'  => 'master/manufacturer/thumb',\n        'MPIC'  => 'master/manufacturer/picture',\n        'MICO'  => 'master/manufacturer/icon',\n        'MPICO' => 'master/manufacturer/promo_icon',\n        'VICO'  => 'master/vendor/icon',\n        'PROMO' => self::PROMO_PICTURE_DIR,\n        'ICO'   => 'master/product/icon',\n        'TH'    => 'master/product/thumb',\n        'M1'    => 'master/product/1',\n        'M2'    => 'master/product/2',\n        'M3'    => 'master/product/3',\n        'M4'    => 'master/product/4',\n        'M5'    => 'master/product/5',\n        'M6'    => 'master/product/6',\n        'M7'    => 'master/product/7',\n        'M8'    => 'master/product/8',\n        'M9'    => 'master/product/9',\n        'M10'   => 'master/product/10',\n        'M11'   => 'master/product/11',\n        'M12'   => 'master/product/12',\n        //\n        'P1'    => '1',\n        'P2'    => '2',\n        'P3'    => '3',\n        'P4'    => '4',\n        'P5'    => '5',\n        'P6'    => '6',\n        'P7'    => '7',\n        'P8'    => '8',\n        'P9'    => '9',\n        'P10'   => '10',\n        'P11'   => '11',\n        'P12'   => '12',\n        'Z1'    => 'z1',\n        'Z2'    => 'z2',\n        'Z3'    => 'z3',\n        'Z4'    => 'z4',\n        'Z5'    => 'z5',\n        'Z6'    => 'z6',\n        'Z7'    => 'z7',\n        'Z8'    => 'z8',\n        'Z9'    => 'z9',\n        'Z10'   => 'z10',\n        'Z11'   => 'z11',\n        'Z12'   => 'z12',\n        //\n        'WP'    => 'master/wrapping',\n        'FL'    => 'media',\n    ];\n\n    /**\n     * Denied file types\n     *\n     * @var array\n     */\n    protected $_aBadFiles = ['php', 'php3', 'php4', 'php5', 'phps', 'php6', 'jsp', 'cgi', 'cmf', 'exe', 'phtml', 'pht', 'phar'];\n\n    /**\n     * Allowed to upload files in demo mode ( \"white list\")\n     *\n     * @var array\n     */\n    protected $_aAllowedFiles = ['gif', 'jpg', 'jpeg', 'png', 'webp', 'pdf'];\n\n    /**\n     * Counts how many new files added.\n     *\n     * @var integer\n     */\n    protected $_iNewFilesCounter = 0;\n\n    public function getNewFilesCounter()\n    {\n        return $this->_iNewFilesCounter;\n    }\n\n    protected function setNewFilesCounter($iNewFilesCounter)\n    {\n        $this->_iNewFilesCounter = (int) $iNewFilesCounter;\n    }\n\n    public function normalizeDir($sDir)\n    {\n        if (isset($sDir) && $sDir != \"\" && substr($sDir, -1) !== '/') {\n            $sDir .= \"/\";\n        }\n\n        return $sDir;\n    }\n\n    public function copyDir($sSourceDir, $sTargetDir)\n    {\n        $oStr = Str::getStr();\n        $handle = opendir($sSourceDir);\n        while (false !== ($file = readdir($handle))) {\n            if ($file != '.' && $file != '..') {\n                if (is_dir($sSourceDir . '/' . $file)) {\n                    $sNewSourceDir = $sSourceDir . '/' . $file;\n                    $sNewTargetDir = $sTargetDir . '/' . $file;\n                    if (strcasecmp($file, 'CVS') && strcasecmp($file, '.svn')) {\n                        @mkdir($sNewTargetDir, 0777);\n                        $this->copyDir($sNewSourceDir, $sNewTargetDir);\n                    }\n                } else {\n                    $sSourceFile = $sSourceDir . '/' . $file;\n                    $sTargetFile = $sTargetDir . '/' . $file;\n\n                    if (!$oStr->strstr($sSourceDir, 'dyn_images') || $file == 'nopic.jpg' || $file == 'nopic_ico.jpg') {\n                        @copy($sSourceFile, $sTargetFile);\n                    }\n                }\n            }\n        }\n        closedir($handle);\n    }\n\n    public function deleteDir($sSourceDir)\n    {\n        if (is_dir($sSourceDir)) {\n            if ($oDir = dir($sSourceDir)) {\n                while (false !== $sFile = $oDir->read()) {\n                    if ($sFile == '.' || $sFile == '..') {\n                        continue;\n                    }\n\n                    if (!$this->deleteDir($oDir->path . DIRECTORY_SEPARATOR . $sFile)) {\n                        $oDir->close();\n\n                        return false;\n                    }\n                }\n\n                $oDir->close();\n\n                return rmdir($sSourceDir);\n            }\n        } elseif (file_exists($sSourceDir)) {\n            return unlink($sSourceDir);\n        }\n    }\n\n    public function readRemoteFileAsString($sPath)\n    {\n        $sRet = '';\n        $hFile = @fopen($sPath, 'r');\n        if ($hFile) {\n            socket_set_timeout($hFile, 2);\n            while (!feof($hFile)) {\n                $sLine = fgets($hFile, 4096);\n                $sRet .= $sLine;\n            }\n            fclose($hFile);\n        }\n\n        return $sRet;\n    }\n\n    /**\n     * Prepares image file name\n     *\n     * @param object $sValue     uploadable file name\n     * @param string $sType      image type\n     * @param object $blDemo     if true = whecks if file type is defined in \\OxidEsales\\Eshop\\Core\\UtilsFile::_aAllowedFiles\n     * @param string $sImagePath final image file location\n     * @param bool   $blUnique   if TRUE - generates unique file name\n     *\n     * @return string\n     */\n    protected function prepareImageName($sValue, $sType, $blDemo, $sImagePath, $blUnique = true)\n    {\n        if ($sValue) {\n            // add type to name\n            $aFilename = explode(\".\", $sValue);\n\n            $sFileType = trim($aFilename[count($aFilename) - 1]);\n\n            if (isset($sFileType)) {\n                // unallowed files ?\n                if (in_array($sFileType, $this->_aBadFiles) || ($blDemo && !in_array($sFileType, $this->_aAllowedFiles))) {\n                    Registry::getUtils()->showMessageAndExit(\"File didn't pass our allowed files filter.\");\n                }\n\n                // removing file type\n                if (count($aFilename) > 0) {\n                    unset($aFilename[count($aFilename) - 1]);\n                }\n\n                $sFName = '';\n                if (isset($aFilename[0])) {\n                    $sFName = Str::getStr()->preg_replace('/[^a-zA-Z0-9()_\\.-]/', '', implode('.', $aFilename));\n                }\n\n                $sValue = $this->getUniqueFileName($sImagePath, \"{$sFName}\", $sFileType, \"\", $blUnique);\n            }\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns image storage path\n     *\n     * @param string $sType image type\n     *\n     * @return string\n     */\n    protected function getImagePath($sType)\n    {\n        $sFolder = array_key_exists($sType, $this->_aTypeToPath) ? $this->_aTypeToPath[$sType] : '0';\n\n        return $this->normalizeDir(Registry::getConfig()->getPictureDir(false)) . \"{$sFolder}/\";\n    }\n\n\n    /**\n     * Uploaded file processor (filters, etc), sets configuration parameters to\n     * passed object and returns it.\n     *\n     * @param object $oObject          object, that parameters are modified according to passed files\n     * @param array  $aFiles           name of files to process\n     * @param bool   $blUseMasterImage use master image as source for processing\n     * @param bool   $blUnique         TRUE - forces new file creation with unique name\n     *\n     * @return object\n     */\n    public function processFiles($oObject = null, $aFiles = [], $blUseMasterImage = false, $blUnique = true)\n    {\n        $aFiles = $aFiles ?: $_FILES;\n        if (isset($aFiles['myfile']['name'])) {\n            $oConfig = Registry::getConfig();\n\n            // A. protection for demoshops - strictly defining allowed file extensions\n            $blDemo = (bool) $oConfig->isDemoShop();\n\n            // folder where images will be processed\n            $sTmpFolder = ContainerFacade::getParameter('oxid_esales.build_directory');\n\n            $iNewFilesCounter = 0;\n            $aSource = $aFiles['myfile']['tmp_name'];\n            $aError = $aFiles['myfile']['error'] ?? [];\n\n            $oEx = oxNew(ExceptionToDisplay::class);\n            // process all files\n            foreach ($aFiles['myfile']['name'] as $sKey => $sValue) {\n                $sSource = $aSource[$sKey];\n                $iError = $aError[$sKey] ?? null;\n                $aFiletype = explode(\"@\", $sKey);\n                $sKey = $aFiletype[1] ?? null;\n                $sType = $aFiletype[0];\n\n                $sValue = strtolower($sValue);\n                $sImagePath = $this->getImagePath($sType);\n\n                // Should translate error to user if file was uploaded\n                if (UPLOAD_ERR_OK !== $iError && UPLOAD_ERR_NO_FILE !== $iError) {\n                    $sErrorsDescription = $this->translateError($iError);\n                    $oEx->setMessage($sErrorsDescription);\n                    Registry::getUtilsView()->addErrorToDisplay($oEx, false);\n                }\n\n                // checking file type and building final file name\n                if ($sSource && ($sValue = $this->prepareImageName($sValue, $sType, $blDemo, $sImagePath, $blUnique))) {\n                    // moving to tmp folder for processing as safe mode or spec. open_basedir setup\n                    // usually does not allow file modification in php's temp folder\n                    $sProcessPath = $sTmpFolder . basename($sSource);\n\n                    if ($sProcessPath) {\n                        $destination = Path::join(\"$sImagePath$sValue\");\n                        $blMoved = $blUseMasterImage\n                            ? $this->copyMasterImage($sSource, $destination)\n                            : $this->uploadMasterImage($sSource, $destination);\n\n                        if ($blMoved) {\n                            // New image successfully add.\n                            $iNewFilesCounter++;\n                            // assign the name\n                            if ($oObject && isset($oObject->$sKey)) {\n                                $oObject->{$sKey}->setValue($sValue);\n                            }\n                        }\n                    }\n                }\n            }\n\n            $this->setNewFilesCounter($iNewFilesCounter);\n        }\n\n        return $oObject;\n    }\n\n    /**\n     * Checks if passed file exists and may be opened for reading. Returns true\n     * on success.\n     *\n     * @param string $sFile Name of file to check\n     *\n     * @return bool\n     */\n    public function checkFile($sFile)\n    {\n        $aCheckCache = Registry::getSession()->getVariable(\"checkcache\");\n\n        if (isset($aCheckCache[$sFile])) {\n            return $aCheckCache[$sFile];\n        }\n\n        $blRet = true;\n        if (!is_readable($sFile)) {\n            $blRet = $this->urlValidate($sFile);\n        }\n\n        $aCheckCache[$sFile] = $blRet;\n        Registry::getSession()->setVariable(\"checkcache\", $aCheckCache);\n\n        return $blRet;\n    }\n\n    /**\n     * Checks if given URL is accessible (HTTP-Code: 200)\n     *\n     * @param string $url\n     *\n     * @return boolean\n     */\n    public function urlValidate($url)\n    {\n        return $this->isUrlSchemaValid($url)\n            && $this->isUrlAccessible($url);\n    }\n\n    /**\n     * Process uploaded files. Returns unique file name, on fail false\n     *\n     * @param string $filename form file item name\n     * @param string $uploadPath RELATIVE (to container parameter oxid_shop_source_directory)\n     * path for uploaded file to be copied\n     *\n     * @return string\n     * @throws StandardException if file is not valid\n     */\n    public function processFile($filename, $uploadPath)\n    {\n        $fileInfo = $_FILES[$filename];\n\n        $absoluteUploadPath = Path::join(\n            ContainerFacade::getParameter('oxid_esales.shop_source_directory'),\n            $uploadPath\n        );\n\n        if (!isset($fileInfo['name']) || !isset($fileInfo['tmp_name'])) {\n            throw oxNew(StandardException::class, 'EXCEPTION_NOFILE');\n        }\n\n        if (!Str::getStr()->preg_match('/^[\\-_a-z0-9\\.]+$/i', $fileInfo['name'])) {\n            throw oxNew(StandardException::class, 'EXCEPTION_FILENAMEINVALIDCHARS');\n        }\n\n        if (isset($fileInfo['error']) && $fileInfo['error']) {\n            throw oxNew(StandardException::class, 'EXCEPTION_FILEUPLOADERROR_' . ((int)$fileInfo['error']));\n        }\n\n        $pathInfo = pathinfo($fileInfo['name']);\n\n        $extension = $pathInfo['extension'];\n        $filename = $pathInfo['filename'];\n\n        $allowedUploadTypes = ContainerFacade::getParameter('oxid_esales.allowed_uploaded_types');\n        $allowedUploadTypes = array_map(\"strtolower\", $allowedUploadTypes);\n\n        if (!\\in_array(strtolower($extension), $allowedUploadTypes, true)) {\n            throw oxNew(StandardException::class, 'EXCEPTION_NOTALLOWEDTYPE');\n        }\n\n        $filename = $this->getUniqueFileName($absoluteUploadPath, $filename, $extension);\n\n        $destination = Path::join($absoluteUploadPath, $filename);\n        if ($this->uploadMasterImage($fileInfo['tmp_name'], $destination)) {\n            return $filename;\n        }\n\n        return false;\n    }\n\n    /**\n     * @param string $directory\n     * @param string $filename\n     * @param string $extension\n     * @param string $suffix\n     * @param bool $unique\n     * @return string\n     */\n    protected function getUniqueFileName($directory, $filename, $extension, $suffix = \"\", $unique = true)\n    {\n        if (!$unique) {\n            return \"$filename$suffix.$extension\";\n        }\n        $directory = $this->normalizeDir($directory);\n        $fileCounter = 0;\n        $temporaryName = $filename;\n        $stringHandler = Str::getStr();\n        $masterImageHandler = ContainerFacade::get(MasterImageHandlerBridgeInterface::class);\n        while (\n            $masterImageHandler->exists(\n                $this->makePathRelativeToShopSource(Path::join($directory, \"$filename$suffix.$extension\"))\n            )\n        ) {\n            $fileCounter++;\n            //removing \"(any digit)\" from file name end\n            $temporaryName = $stringHandler->preg_replace(\"/\\($fileCounter\\)/\", '', $temporaryName);\n            $filename = \"{$temporaryName}($fileCounter)\";\n        }\n        return \"$filename$suffix.$extension\";\n    }\n\n    /**\n     * Returns image storage path\n     *\n     * @param string $sType       image type\n     * @param bool   $blGenerated generated image dir.\n     *\n     * @return string\n     */\n    public function getImageDirByType($sType, $blGenerated = false)\n    {\n        $sFolder = array_key_exists($sType, $this->_aTypeToPath) ? $this->_aTypeToPath[$sType] : '0';\n        $sDir = $this->normalizeDir($sFolder);\n\n        if ($blGenerated === true) {\n            $sDir = str_replace('master/', 'generated/', $sDir);\n        }\n\n        return $sDir;\n    }\n\n    /**\n     * Translate php file upload errors to user readable format.\n     *\n     * @param integer $iError php file upload error number\n     *\n     * @return string\n     */\n    public function translateError($iError)\n    {\n        $message = '';\n        // Translate only if translation exist\n        if ($iError > 0 && $iError < 9 && 5 !== $iError) {\n            $message = 'EXCEPTION_FILEUPLOADERROR_' . ((int) $iError);\n        }\n\n        return $message;\n    }\n\n    /**\n     * @param string $url\n     * @return bool\n     */\n    private function isUrlSchemaValid(string $url): bool\n    {\n        return filter_var($url, FILTER_VALIDATE_URL) === false ? false : true;\n    }\n\n    private function isUrlAccessible(string $url): bool\n    {\n        $curl = curl_init($url);\n\n        curl_setopt($curl, CURLOPT_NOBODY, true);\n\n        $result = curl_exec($curl);\n\n        if ($result !== false) {\n            $statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);\n\n            if ($statusCode === 200) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * @param string $source\n     * @param string $destination\n     * @return bool\n     */\n    private function copyMasterImage(string $source, string $destination): bool\n    {\n        $copied = false;\n        try {\n            ContainerFacade::get(MasterImageHandlerBridgeInterface::class)\n                ->copy(\n                    $source,\n                    $this->makePathRelativeToShopSource($destination)\n                );\n            $copied = true;\n        } catch (\\Throwable $exception) {\n            $this->addErrorMessageToDisplay($exception->getMessage());\n        }\n        return $copied;\n    }\n\n    /**\n     * @param string $source\n     * @param string $destination\n     * @return bool\n     */\n    private function uploadMasterImage(string $source, string $destination): bool\n    {\n        $uploaded = false;\n        try {\n            ContainerFacade::get(MasterImageHandlerBridgeInterface::class)\n                ->upload(\n                    $source,\n                    $this->makePathRelativeToShopSource($destination)\n                );\n            $uploaded = true;\n        } catch (\\Throwable $exception) {\n            $this->addErrorMessageToDisplay($exception->getMessage());\n        }\n        return $uploaded;\n    }\n\n    private function addErrorMessageToDisplay($message): void\n    {\n        $exception = oxNew(ExceptionToDisplay::class);\n        $exception->setMessage($message);\n        Registry::getUtilsView()->addErrorToDisplay($exception, false);\n    }\n\n    private function makePathRelativeToShopSource(string $path): string\n    {\n\n        return Path::makeRelative(\n            $path,\n            ContainerFacade::getParameter('oxid_esales.shop_source_directory')\n        );\n    }\n}\n"
  },
  {
    "path": "source/Core/UtilsObject.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Exception\\SystemComponentException;\nuse OxidEsales\\Eshop\\Core\\Module\\ModuleChainsGenerator;\n\n/**\n * Object Factory implementation (oxNew() method is implemented in this class).\n *\n * @internal Do not make a module extension for this class.\n */\nclass UtilsObject\n{\n    /**\n     * Cache class names\n     *\n     * @var array\n     */\n    protected $_aClassNameCache = [];\n\n    /**\n     * The array of already loaded articles\n     *\n     * @var array\n     */\n    protected static $_aLoadedArticles = [];\n\n    /**\n     * The array of already initialised instances\n     *\n     * @var array\n     */\n    protected static $_aInstanceCache = [];\n\n    /**\n     * UtilsObject class instance.\n     *\n     * @var UtilsObject instance\n     */\n    protected static $_instance = null;\n\n    /** @var BackwardsCompatibleClassNameProvider */\n    private $classNameProvider = null;\n\n    /** @var ModuleChainsGenerator */\n    private $moduleChainsGenerator = null;\n\n    /** @var ShopIdCalculator */\n    private $shopIdCalculator = null;\n\n    /**\n     * This class is a singleton and should be instantiated with getInstance()\n     */\n    private function __construct()\n    {\n    }\n\n    /**\n     * Returns object instance\n     *\n     * @return UtilsObject\n     */\n    public static function getInstance()\n    {\n        if (null === static::$_instance) {\n            static::$_instance = new static();\n        }\n\n        return static::$_instance;\n    }\n\n    /**\n     * Factory instance setter. Sets the instance to be returned over later called oxNew().\n     * This method is mostly intended to be used by phpUnit tests.\n     *\n     * @param string $className Class name expected to be later supplied over oxNew\n     * @param object $instance  Instance object\n     */\n    public static function setClassInstance($className, $instance)\n    {\n        //Get storage key as the class might be aliased.\n        $storageKey = Registry::getStorageKey($className);\n\n        static::$_aClassInstances[$storageKey] = $instance;\n    }\n\n    /**\n     * Resets previously set instances\n     */\n    public static function resetClassInstances()\n    {\n        static::$_aClassInstances = [];\n    }\n\n    /**\n     * Resets instance cache\n     *\n     * @param string $className class name in the cache\n     *\n     * @return null\n     */\n    public function resetInstanceCache($className = null)\n    {\n        if ($className && isset(static::$_aInstanceCache[$className])) {\n            unset(static::$_aInstanceCache[$className]);\n            return;\n        }\n\n        //Get storage key as the class might be aliased.\n        $storageKey = Registry::getStorageKey($className);\n\n        if ($className && isset(static::$_aInstanceCache[$storageKey])) {\n            unset(static::$_aInstanceCache[$storageKey]);\n            return;\n        }\n\n        //looping due to possible memory \"leak\".\n        if (is_array(static::$_aInstanceCache)) {\n            foreach (static::$_aInstanceCache as $key => $instance) {\n                unset(static::$_aInstanceCache[$key]);\n            }\n        }\n\n        static::$_aInstanceCache = [];\n    }\n\n    /**\n     * Creates and returns new object. If creation is not available, dies and outputs\n     * error message.\n     *\n     * @param string $className Name of class\n     * @param array  $arguments constructor arguments\n     *\n     * @throws SystemComponentException in case that class does not exists\n     *\n     * @return object\n     */\n    public function oxNew($className, ...$arguments)\n    {\n        $argumentsCount = count($arguments);\n        $shouldUseCache = $this->shouldCacheObject($className, $arguments);\n        if (!\\OxidEsales\\Eshop\\Core\\NamespaceInformationProvider::isNamespacedClass($className)) {\n            $className = strtolower($className);\n        }\n\n        //Get storage key as the class might be aliased.\n        $storageKey = Registry::getStorageKey($className);\n\n        if ($shouldUseCache) {\n            $cacheKey = ($argumentsCount) ? $storageKey . md5(serialize($arguments)) : $storageKey;\n            if (isset(static::$_aInstanceCache[$cacheKey])) {\n                return clone static::$_aInstanceCache[$cacheKey];\n            }\n        }\n\n        if (!defined('OXID_PHP_UNIT') && isset($this->_aClassNameCache[$className])) {\n            $realClassName = $this->_aClassNameCache[$className];\n        } else {\n            $realClassName = $this->getClassName($className);\n            //expect __autoload() (oxfunctions.php) to do its job when class_exists() is called\n            if (!class_exists($realClassName)) {\n                $exception =  new \\OxidEsales\\Eshop\\Core\\Exception\\SystemComponentException();\n                /** Use setMessage here instead of passing it in constructor in order to test exception message */\n                $exception->setMessage('EXCEPTION_SYSTEMCOMPONENT_CLASSNOTFOUND' . ' ' . $realClassName);\n                throw $exception;\n            }\n\n            $this->_aClassNameCache[$className] = $realClassName;\n        }\n\n        $object = new $realClassName(...$arguments);\n        if (isset($cacheKey) && $shouldUseCache && $object instanceof \\OxidEsales\\Eshop\\Core\\Model\\BaseModel) {\n            static::$_aInstanceCache[$cacheKey] = clone $object;\n        }\n\n        return $object;\n    }\n\n    /**\n     * Returns generated unique ID.\n     *\n     * @deprecated use Id::generate() instead\n     *\n     * @return string\n     */\n    public function generateUId()\n    {\n        return md5(uniqid('', true) . '|' . microtime());\n    }\n\n    /**\n     * Returns name of class file, according to class name.\n     *\n     * @param string $classAlias Class name\n     *\n     * @return string\n     */\n    public function getClassName($classAlias)\n    {\n        $classNameProvider = $this->getClassNameProvider();\n\n        $class = $classNameProvider->getClassName($classAlias);\n        /**\n         * Backwards compatibility for ox... classes,\n         * when a class is instance build upon the unified namespace\n         */\n        if ($class == $classAlias) {\n            $classAlias = $classNameProvider->getClassAliasName($class);\n        }\n\n        return $this->getModuleChainsGenerator()->createClassChain($class, $classAlias);\n    }\n\n    /**\n     * Method returns class alias by given class name.\n     *\n     * @param string $className with namespace.\n     *\n     * @return string|null\n     */\n    public function getClassAliasName($className)\n    {\n        return $this->getClassNameProvider()->getClassAliasName($className);\n    }\n\n    /**\n     * @return BackwardsCompatibleClassNameProvider\n     */\n    protected function getClassNameProvider()\n    {\n        if (is_null($this->classNameProvider)) {\n            $backwardsCompatibleClassMap = include 'Autoload/BackwardsCompatibilityClassMap.php';\n            $this->classNameProvider = new BackwardsCompatibleClassNameProvider($backwardsCompatibleClassMap);\n        }\n        return $this->classNameProvider;\n    }\n\n    /**\n     * @return ModuleChainsGenerator\n     */\n    protected function getModuleChainsGenerator()\n    {\n        if (is_null($this->moduleChainsGenerator)) {\n            $this->moduleChainsGenerator = new \\OxidEsales\\Eshop\\Core\\Module\\ModuleChainsGenerator();\n        }\n        return $this->moduleChainsGenerator;\n    }\n\n    /**\n     * @return ShopIdCalculator\n     */\n    protected function getShopIdCalculator()\n    {\n        if (is_null($this->shopIdCalculator)) {\n            $this->shopIdCalculator = new ShopIdCalculator(\n                new \\OxidEsales\\Eshop\\Core\\FileCache(),\n                new \\OxidEsales\\Eshop\\Core\\UtilsServer()\n            );\n        }\n        return $this->shopIdCalculator;\n    }\n\n    /**\n     * Checks whether class with arguments should be cached.\n     * Cache only when object has none or one scalar argument.\n     *\n     * @param string $className\n     * @param array  $arguments\n     *\n     * @return bool\n     */\n    protected function shouldCacheObject($className, $arguments)\n    {\n        return count($arguments) < 2 && (!isset($arguments[0]) || is_scalar($arguments[0]));\n    }\n}\n"
  },
  {
    "path": "source/Core/UtilsPic.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\Bridge\\MasterImageHandlerBridgeInterface;\nuse Symfony\\Component\\Filesystem\\Path;\n\n/**\n * Including pictures generator functions file\n */\nrequire_once __DIR__ . \"/utils/oxpicgenerator.php\";\n\nclass UtilsPic extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Image types 'enum'\n     *\n     * @var array\n     */\n    protected $_aImageTypes = [\"GIF\" => IMAGETYPE_GIF, \"JPG\" => IMAGETYPE_JPEG, \"PNG\" => IMAGETYPE_PNG, \"JPEG\" => IMAGETYPE_JPEG];\n\n    /**\n     * Resizes image to desired width and height, returns true on success.\n     *\n     * @param string $sSrc           Source of image file\n     * @param string $sTarget        Target to write resized image file\n     * @param mixed  $iDesiredWidth  Width of resized image\n     * @param mixed  $iDesiredHeight Height of resized image\n     *\n     * @return bool\n     */\n    public function resizeImage($sSrc, $sTarget, $iDesiredWidth, $iDesiredHeight)\n    {\n        if (file_exists($sSrc) && ($aImageInfo = @getimagesize($sSrc))) {\n            $myConfig = Registry::getConfig();\n            list($iWidth, $iHeight) = calcImageSize($iDesiredWidth, $iDesiredHeight, $aImageInfo[0], $aImageInfo[1]);\n\n            return $this->resize($aImageInfo, $sSrc, null, $sTarget, $iWidth, $iHeight, getGdVersion(), $myConfig->getConfigParam('blDisableTouch'), $myConfig->getConfigParam('sDefaultImageQuality'));\n        }\n\n        return false;\n    }\n\n    /**\n     * deletes the given picutre and checks before if the picture is deletable\n     *\n     * @param string $sPicName        Name of picture file\n     * @param string $sAbsDynImageDir the absolute image diectory, where to delete the given image ($myConfig->getPictureDir(false))\n     * @param string $sTable          in which table\n     * @param string $sField          table field value\n     *\n     * @return bool\n     */\n    public function safePictureDelete($sPicName, $sAbsDynImageDir, $sTable, $sField)\n    {\n        $blDelete = false;\n        if ($this->isPicDeletable($sPicName, $sTable, $sField)) {\n            $blDelete = $this->deletePicture($sPicName, $sAbsDynImageDir);\n        }\n\n        return $blDelete;\n    }\n\n    /**\n     * @param $filename\n     * @param $masterImagePath\n     * @return bool\n     */\n    protected function deletePicture($filename, $masterImagePath)\n    {\n        if ($this->isPlaceholderImage($filename) || Registry::getConfig()->isDemoShop()) {\n            return false;\n        }\n        $removed = $this->removeMasterFile(Path::join($masterImagePath, $filename));\n\n        if (!ContainerFacade::getParameter('oxid_esales.alternative_image_url')) {\n            $generatedImagePath = str_replace('/master/', '/generated/', $masterImagePath);\n            $files = glob(Path::join($generatedImagePath,'*',$filename));\n            if (\\is_array($files)) {\n                foreach ($files as $file) {\n                    $removed = unlink($file);\n                }\n            }\n        }\n        return $removed;\n    }\n\n    /**\n     * Checks if current picture file is used in more than one table entry, returns\n     * true if one, false if more than one.\n     *\n     * @param string $filename Name of picture file\n     * @param string $tabl   in which table\n     * @param string $field   table field value\n     *\n     * @return bool\n     */\n    protected function isPicDeletable($filename, $tabl, $field)\n    {\n        if (!$filename || $this->isPlaceholderImage($filename)) {\n            return false;\n        }\n        $usageCount = $this->fetchIsImageDeletable($filename, $tabl, $field);\n        return $usageCount <= 1;\n    }\n\n    /**\n     * Fetch the information, if the given image is deletable from the database.\n     *\n     * @param string $sPicName Name of image file.\n     * @param string $sTable   The table in which we search for the image.\n     * @param string $sField   The value of the table field.\n     *\n     * @return mixed\n     */\n    protected function fetchIsImageDeletable($sPicName, $sTable, $sField)\n    {\n        // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804).\n        $masterDb = \\OxidEsales\\Eshop\\Core\\DatabaseProvider::getMaster();\n\n        $query = \"SELECT count(*) FROM $sTable WHERE $sField = :picturename group by $sField \";\n\n        return $masterDb->getOne($query, [\n            'picturename' => (string) $sPicName\n        ]);\n    }\n\n    /**\n     * Deletes picture if new is uploaded or changed\n     *\n     * @param object $oObject         in whitch obejct search for old values\n     * @param string $sPicTable       pictures table\n     * @param string $sPicField       where picture are stored\n     * @param string $sPicType        how does it call in $_FILE array\n     * @param string $sPicDir         directory of pic\n     * @param array  $aParams         new input text array\n     * @param string $sAbsDynImageDir the absolute image diectory, where to delete the given image ($myConfig->getPictureDir(false))\n     *\n     * @return null\n     */\n    public function overwritePic($oObject, $sPicTable, $sPicField, $sPicType, $sPicDir, $aParams, $sAbsDynImageDir)\n    {\n        $sPic = $sPicTable . '__' . $sPicField;\n        if (\n            isset($oObject->{$sPic}) &&\n            ($_FILES['myfile']['size'][$sPicType . '@' . $sPic] > 0 || $aParams[$sPic] != $oObject->{$sPic}->value)\n        ) {\n            $sImgDir = $sAbsDynImageDir . Registry::getUtilsFile()->getImageDirByType($sPicType);\n            return $this->safePictureDelete($oObject->{$sPic}->value, $sImgDir, $sPicTable, $sPicField);\n        }\n\n        return false;\n    }\n\n    /**\n     * Resizes and saves GIF image. This method was separated due to GIF transparency problems.\n     *\n     * @param string $sSrc            image file\n     * @param string $sTarget         destination file\n     * @param int    $iNewWidth       new width\n     * @param int    $iNewHeight      new height\n     * @param int    $iOriginalWidth  original width\n     * @param int    $iOriginalHeigth original height\n     * @param int    $iGDVer          GD packet version @deprecated\n     * @param bool   $blDisableTouch  false if \"touch()\" should be called\n     *\n     * @return bool\n     */\n    protected function resizeGif($sSrc, $sTarget, $iNewWidth, $iNewHeight, $iOriginalWidth, $iOriginalHeigth, $iGDVer, $blDisableTouch)\n    {\n        return resizeGif($sSrc, $sTarget, $iNewWidth, $iNewHeight, $iOriginalWidth, $iOriginalHeigth, $iGDVer, $blDisableTouch);\n    }\n\n    /**\n     * type dependant image resizing\n     *\n     * @param array  $aImageInfo        Contains information on image's type / width / height\n     * @param string $sSrc              source image\n     * @param string $hDestinationImage Destination Image\n     * @param string $sTarget           Resized Image target\n     * @param int    $iNewWidth         Resized Image's width\n     * @param int    $iNewHeight        Resized Image's height\n     * @param mixed  $iGdVer            used GDVersion, if null or false returns false @deprecated\n     * @param bool   $blDisableTouch    false if \"touch()\" should be called for gif resizing\n     * @param string $iDefQuality       quality for \"imagejpeg\" function\n     *\n     * @return bool\n     */\n    protected function resize($aImageInfo, $sSrc, $hDestinationImage, $sTarget, $iNewWidth, $iNewHeight, $iGdVer, $blDisableTouch, $iDefQuality)\n    {\n        startProfile(\"PICTURE_RESIZE\");\n\n        $blSuccess = false;\n        switch ($aImageInfo[2]) { //Image type\n            case ($this->_aImageTypes[\"GIF\"]):\n                //php does not process gifs until 7th July 2004 (see lzh licensing)\n                if (function_exists(\"imagegif\")) {\n                    $blSuccess = resizeGif($sSrc, $sTarget, $iNewWidth, $iNewHeight, $aImageInfo[0], $aImageInfo[1], $iGdVer);\n                }\n                break;\n            case ($this->_aImageTypes[\"JPEG\"]):\n            case ($this->_aImageTypes[\"JPG\"]):\n                $blSuccess = resizeJpeg($sSrc, $sTarget, $iNewWidth, $iNewHeight, $aImageInfo, $iGdVer, $hDestinationImage, $iDefQuality);\n                break;\n            case ($this->_aImageTypes[\"PNG\"]):\n                $blSuccess = resizePng($sSrc, $sTarget, $iNewWidth, $iNewHeight, $aImageInfo, $iGdVer, $hDestinationImage);\n                break;\n        }\n\n        if ($blSuccess && !$blDisableTouch) {\n            @touch($sTarget);\n        }\n\n        stopProfile(\"PICTURE_RESIZE\");\n\n        return $blSuccess;\n    }\n\n    /**\n     * create and copy the resized image\n     *\n     * @param string $sDestinationImage file + path of destination\n     * @param string $sSourceImage      file + path of source\n     * @param int    $iNewWidth         new width of the image\n     * @param int    $iNewHeight        new height of the image\n     * @param array  $aImageInfo        additional info\n     * @param string $sTarget           target file path\n     * @param int    $iGdVer            used gd version @deprecated\n     * @param bool   $blDisableTouch    wether Touch() should be called or not\n     *\n     * @return null\n     */\n    protected function copyAlteredImage($sDestinationImage, $sSourceImage, $iNewWidth, $iNewHeight, $aImageInfo, $sTarget, $iGdVer, $blDisableTouch)\n    {\n        $blSuccess = copyAlteredImage($sDestinationImage, $sSourceImage, $iNewWidth, $iNewHeight, $aImageInfo, $sTarget, $iGdVer);\n        if (!$blDisableTouch && $blSuccess) {\n            @touch($sTarget);\n        }\n\n        return $blSuccess;\n    }\n\n    /**\n     * @param string $filename\n     * @return bool\n     */\n    private function isPlaceholderImage(string $filename): bool\n    {\n        return strpos($filename, 'nopic.jpg') !== false || strpos($filename, 'nopic_ico.jpg') !== false;\n    }\n\n    /**\n     * @param string $filepath\n     * @return bool\n     */\n    private function removeMasterFile(string $filepath): bool\n    {\n        $removed = false;\n        try {\n            $filepath = $this->makePathRelativeToShopSource($filepath);\n            if (ContainerFacade::get(MasterImageHandlerBridgeInterface::class)->exists($filepath)) {\n                ContainerFacade::get(MasterImageHandlerBridgeInterface::class)->remove($filepath);\n                $removed = true;\n            }\n        } catch (\\Throwable $exception) {\n            $ex = oxNew(ExceptionToDisplay::class);\n            $ex->setMessage($exception->getMessage());\n            Registry::getUtilsView()->addErrorToDisplay($ex, false);\n        }\n        return $removed;\n    }\n\n    /**\n     * @param string $path\n     * @return string\n     */\n    private function makePathRelativeToShopSource(string $path): string\n    {\n        return Path::makeRelative(\n            $path,\n            ContainerFacade::getParameter('oxid_esales.shop_source_directory')\n        );\n    }\n}\n"
  },
  {
    "path": "source/Core/UtilsServer.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Application\\Model\\User;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\PasswordServiceBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\n\nuse function array_key_exists;\nuse function in_array;\n\n/**\n * Server data manipulation class\n */\nclass UtilsServer extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * user cookies\n     *\n     * @var array\n     */\n    protected $_aUserCookie = [];\n\n    /**\n     * Session cookie parameter name\n     *\n     * @var string\n     */\n    protected $_sSessionCookiesName = 'aSessionCookies';\n\n    /**\n     * Session stored cookies\n     *\n     * @var array\n     */\n    protected $_sSessionCookies = [];\n\n    /**\n     * sets cookie\n     *\n     * @param string $sName cookie name\n     * @param string $sValue value\n     * @param int $iExpire expire time\n     * @param string $sPath The path on the server in which the cookie will be available on\n     * @param string $sDomain The domain that the cookie is available.\n     * @param bool $blToSession is true, records cookie information to session\n     * @param bool $blSecure if true, transfer cookie only via SSL\n     * @param bool $blHttpOnly if true, only accessible via HTTP\n     *\n     * @return bool\n     */\n    public function setOxCookie(\n        $sName,\n        $sValue = \"\",\n        $iExpire = 0,\n        $sPath = '/',\n        $sDomain = null,\n        $blToSession = true,\n        $blSecure = false,\n        $blHttpOnly = true\n    ) {\n        if ($blToSession && !$this->isAdmin()) {\n            $this->saveSessionCookie($sName, $sValue, $iExpire, $sPath, $sDomain);\n        }\n\n        if (php_sapi_name() === 'cli') {\n            // do NOT set cookies in cli because it would issue warnings\n            return;\n        }\n\n        //if shop runs in https only mode we can set secure flag to all cookies\n        $blSecure = $blSecure || Registry::getConfig()->isSsl();\n        return setcookie(\n            $sName,\n            $sValue,\n            $iExpire,\n            $this->getCookiePath($sPath),\n            $this->getCookieDomain($sDomain),\n            $blSecure,\n            $blHttpOnly\n        );\n    }\n\n    protected $_blSaveToSession = null;\n\n    /**\n     * Checks if cookie must be saved to session in order to transfer it to different domain\n     *\n     * @return bool\n     */\n    protected function mustSaveToSession()\n    {\n        if ($this->_blSaveToSession === null) {\n            $this->_blSaveToSession = false;\n\n            $myConfig = Registry::getConfig();\n            if ($myConfig->getShopUrl()) {\n                return true;\n            }\n        }\n\n        return $this->_blSaveToSession;\n    }\n\n    /**\n     * Returns session cookie key\n     *\n     * @param bool $blGet mode - true - get, false - set cookie\n     *\n     * @return string\n     */\n    protected function getSessionCookieKey($blGet)\n    {\n        $blSsl = Registry::getConfig()->isSsl();\n        $sKey = $blSsl ? 'nossl' : 'ssl';\n\n        if ($blGet) {\n            $sKey = $blSsl ? 'ssl' : 'nossl';\n        }\n\n        return $sKey;\n    }\n\n    /**\n     * Copies cookie info to session\n     *\n     * @param string $sName cookie name\n     * @param string $sValue cookie value\n     * @param int $iExpire expiration time\n     * @param string $sPath cookie path\n     * @param string $sDomain cookie domain\n     */\n    protected function saveSessionCookie($sName, $sValue, $iExpire, $sPath, $sDomain)\n    {\n        if ($this->mustSaveToSession()) {\n            $aCookieData = ['value' => $sValue, 'expire' => $iExpire, 'path' => $sPath, 'domain' => $sDomain];\n\n            $aSessionCookies = (array)Registry::getSession()->getVariable($this->_sSessionCookiesName);\n            $aSessionCookies[$this->getSessionCookieKey(false)][$sName] = $aCookieData;\n\n            Registry::getSession()->setVariable($this->_sSessionCookiesName, $aSessionCookies);\n        }\n    }\n\n    /**\n     * Stored all session cookie info to cookies\n     */\n    public function loadSessionCookies()\n    {\n        $sessionCookies = Registry::getSession()->getVariable($this->_sSessionCookiesName);\n        if ($sessionCookies) {\n            $sKey = $this->getSessionCookieKey(true);\n            if (isset($sessionCookies[$sKey])) {\n                // writing session data to cookies\n                foreach ($sessionCookies[$sKey] as $sName => $aCookieData) {\n                    $this->setOxCookie(\n                        $sName,\n                        $aCookieData['value'],\n                        $aCookieData['expire'],\n                        $aCookieData['path'],\n                        $aCookieData['domain'],\n                        false\n                    );\n                    $this->_sSessionCookies[$sName] = $aCookieData['value'];\n                }\n                // cleanup\n                unset($sessionCookies[$sKey]);\n                Registry::getSession()->setVariable($this->_sSessionCookiesName, $sessionCookies);\n            }\n        }\n    }\n\n    /**\n     * @param string $path\n     *\n     * @return string\n     */\n    protected function getCookiePath($path)\n    {\n        return ContainerFacade::getParameter(\n            'oxid_esales.cookie_paths'\n        )[ContainerFacade::get(ContextInterface::class)->getCurrentShopId()] ??\n            $path ?:\n            '';\n    }\n\n    /**\n     * @param string $domain\n     *\n     * @return string\n     */\n    protected function getCookieDomain($domain)\n    {\n        return $domain ?:\n            ContainerFacade::getParameter(\n                'oxid_esales.cookie_domains'\n            )[ContainerFacade::get(ContextInterface::class)->getCurrentShopId()] ??\n            '';\n    }\n\n    /**\n     * Returns cookie $sName value.\n     * If optional parameter $sName is not set then getCookie() returns whole cookie array\n     *\n     * @param string $sName cookie param name\n     *\n     * @return mixed\n     */\n    public function getOxCookie($sName = null)\n    {\n        $sValue = null;\n        if ($sName && isset($_COOKIE[$sName])) {\n            $sValue = Registry::getConfig()->checkParamSpecialChars($_COOKIE[$sName]);\n        } elseif ($sName && !isset($_COOKIE[$sName])) {\n            $sValue = isset($this->_sSessionCookies[$sName]) ? $this->_sSessionCookies[$sName] : null;\n        } elseif (!$sName && isset($_COOKIE)) {\n            $sValue = $_COOKIE;\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns remote IP address\n     *\n     * @return string\n     */\n    public function getRemoteAddress()\n    {\n        if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {\n            $sIP = $_SERVER['HTTP_X_FORWARDED_FOR'];\n            $sIP = preg_replace('/,.*$/', '', $sIP);\n        } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {\n            $sIP = $_SERVER['HTTP_CLIENT_IP'];\n        } else {\n            $sIP = $_SERVER['REMOTE_ADDR'] ?? null;\n        }\n\n        return $sIP;\n    }\n\n    /**\n     * returns a server constant\n     *\n     * @param string $sServVar optional - which server var should be returned, if null returns whole $_SERVER\n     *\n     * @return mixed\n     */\n    public function getServerVar($sServVar = null)\n    {\n        $sValue = null;\n        if (isset($_SERVER)) {\n            if ($sServVar && isset($_SERVER[$sServVar])) {\n                $sValue = $_SERVER[$sServVar];\n            } elseif (!$sServVar) {\n                $sValue = $_SERVER;\n            }\n        }\n\n        return $sValue;\n    }\n\n    public function setUserCookie(\n        $userName,\n        $passwordHash,\n        $shopId = null,\n        $timeout = 31536000,\n        $salt = User::USER_COOKIE_SALT\n    ) {\n        $myConfig = Registry::getConfig();\n        $shopId = $shopId ?? $myConfig->getShopId();\n        $sslUrl = $myConfig->getShopUrl();\n        $passwordServiceBridge = ContainerFacade::get(PasswordServiceBridgeInterface::class);\n\n        $this->_aUserCookie[$shopId] = $userName . '@@@' .  $passwordServiceBridge->hash($passwordHash . $salt);\n        $this->setOxCookie(\n            'oxid_' . $shopId,\n            $this->_aUserCookie[$shopId],\n            Registry::getUtilsDate()->getTime() + $timeout,\n            '/',\n            null,\n            true,\n            strncasecmp($sslUrl, 'https', 5) === 0\n        );\n        $this->setOxCookie(\n            'oxid_' . $shopId . '_autologin',\n            '1',\n            Registry::getUtilsDate()->getTime() + $timeout\n        );\n    }\n\n    public function deleteUserCookie($shopId = null)\n    {\n        $myConfig = Registry::getConfig();\n        $shopId = (!$shopId) ? Registry::getConfig()->getShopId() : $shopId;\n        $sslUrl = $myConfig->getShopUrl();\n        $this->_aUserCookie[$shopId] = '';\n        $this->setOxCookie(\n            'oxid_' . $shopId,\n            '',\n            Registry::getUtilsDate()->getTime() - 3600,\n            '/',\n            null,\n            true,\n            strncasecmp($sslUrl, 'https', 5) === 0\n        );\n        $this->setOxCookie(\n            'oxid_' . $shopId . '_autologin',\n            '0',\n            Registry::getUtilsDate()->getTime() - 3600\n        );\n    }\n\n    /**\n     * Returns cookie stored used login data\n     *\n     * @param string $sShopId shop ID (default null)\n     *\n     * @return string\n     */\n    public function getUserCookie($sShopId = null)\n    {\n        $myConfig = Registry::getConfig();\n        $sShopId = (!$sShopId) ? $myConfig->getShopId() : $sShopId;\n        // check for SSL connection\n        if (!$myConfig->isSsl() && $this->getOxCookie('oxid_' . $sShopId . '_autologin') == '1') {\n            $sslUrl = rtrim($myConfig->getShopUrl(), '/') . $_SERVER['REQUEST_URI'];\n            if (strncasecmp($sslUrl, 'https', 5) === 0) {\n                Registry::getUtils()->redirect($sslUrl, true, 302);\n            }\n        }\n\n        if (array_key_exists($sShopId, $this->_aUserCookie) && $this->_aUserCookie[$sShopId] !== null) {\n            return $this->_aUserCookie[$sShopId] ?: null;\n        }\n\n        return $this->_aUserCookie[$sShopId] = $this->getOxCookie('oxid_' . $sShopId);\n    }\n\n    /**\n     * @return bool\n     */\n    public function isTrustedClientIp()\n    {\n        return in_array(\n            $this->getRemoteAddress(),\n            ContainerFacade::getParameter('oxid_esales.trusted_ips'),\n            true\n        );\n    }\n\n    /**\n     * Removes MSIE(\\s)?(\\S)*(\\s) from browser agent information\n     *\n     * @param string $sAgent browser user agent idenfitier\n     *\n     * @return string\n     */\n    public function processUserAgentInfo($sAgent)\n    {\n        if ($sAgent) {\n            $sAgent = Str::getStr()->preg_replace(\"/MSIE(\\s)?(\\S)*(\\s)/\", \"\", (string)$sAgent);\n        }\n\n        return $sAgent;\n    }\n\n    /**\n     * Compares current URL to supplied string\n     *\n     * @param string $sURL URL\n     *\n     * @return bool true if $sURL is equal to current page URL\n     */\n    public function isCurrentUrl($sURL)\n    {\n        // Missing protocol, cannot proceed, assuming true.\n        if (!$sURL || (strncmp($sURL, 'http', 4) !== 0)) {\n            return true;\n        }\n\n        $sServerHost = $this->getServerVar('HTTP_HOST');\n        $blIsCurrentUrl = $this->isUrlHostServerHost($sURL, $sServerHost);\n        if (!$blIsCurrentUrl) {\n            $sServerHost = $this->getServerVar('HTTP_X_FORWARDED_HOST');\n            if ($sServerHost) {\n                $blIsCurrentUrl = $this->isUrlHostServerHost($sURL, $sServerHost);\n            }\n        }\n\n        return $blIsCurrentUrl;\n    }\n\n    /**\n     * Check if the given URL is same as used for request.\n     * The URL in this context is the base address for the shop e.g. https://www.domain.com/shop/\n     * the protocol is optional (www.domain.com/shop/)\n     * but the protocol relative syntax (//www.domain.com/shop/) is not yet supported.\n     *\n     * @param string $sURL URL to check if is same as request.\n     * @param string $sServerHost request host.\n     *\n     * @return bool true if $sURL is equal to current page URL\n     */\n    public function isUrlHostServerHost($sURL, $sServerHost): bool\n    {\n        // #4010: force_sid added in https to every link\n        preg_match(\"/^(https?:\\/\\/)?(www\\.)?([^\\/]+)/i\", $sURL, $matches);\n        $sUrlHost = $matches[3] ?? null;\n\n        preg_match(\"/^(https?:\\/\\/)?(www\\.)?([^\\/]+)/i\", (string)$sServerHost, $matches);\n        $sRealHost = $matches[3] ?? null;\n\n\n        //fetch the path from SCRIPT_NAME and ad it to the $sServerHost\n        $sScriptName = $this->getServerVar('SCRIPT_NAME');\n        $sCurrentHost = preg_replace('/\\/(modules\\/[\\w\\/]*)?\\w*\\.php.*/', '', $sServerHost . $sScriptName);\n\n        //remove double slashes all the way\n        $sCurrentHost = str_replace('/', '', $sCurrentHost);\n        $sURL = str_replace('/', '', $sURL);\n\n        if ($sURL && $sCurrentHost && strpos($sURL, $sCurrentHost) !== false) {\n            //bug fix #0002991\n            if ($sUrlHost == $sRealHost) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Return server id by server system information.\n     *\n     * @return string\n     */\n    public function getServerNodeId()\n    {\n        return md5($this->getServerName() . $this->getServerIp());\n    }\n\n    /**\n     * Return local machine ip.\n     *\n     * @return string\n     */\n    public function getServerIp()\n    {\n        return $this->getServerVar('SERVER_ADDR');\n    }\n\n    /**\n     * Return server system parameter similar as unix uname.\n     *\n     * @return string\n     */\n    private function getServerName()\n    {\n        return php_uname();\n    }\n}\n"
  },
  {
    "path": "source/Core/UtilsString.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Str;\n\n/**\n * String manipulation class\n */\nclass UtilsString\n{\n    /**\n     * Class constructor. The constructor is defined in order to be possible to call parent::__construct() in modules.\n     */\n    public function __construct()\n    {\n    }\n\n    /**\n     * Prepares passed string for CSV format\n     *\n     * @param string $sInField String to prepare\n     *\n     * @return string\n     */\n    public function prepareCSVField($sInField)\n    {\n        $oStr = Str::getStr();\n        if ($oStr->strstr($sInField, '\"')) {\n            return '\"' . str_replace('\"', '\"\"', $sInField) . '\"';\n        } elseif ($oStr->strstr($sInField, ';')) {\n            return '\"' . $sInField . '\"';\n        }\n\n        return $sInField;\n    }\n\n    /**\n     * shortens a string to a size $iLenght, multiple spaces are removed\n     * and leading and ending whitespaces are removed. If string ends with \",\" then\n     * \",\" is removed from string end\n     *\n     * @param string $sString input string\n     * @param int    $iLength maximum length of result string , -1 -> no truncation\n     *\n     * @return string a string of maximum length $iLength without multiple spaces and commas\n     */\n    public function minimizeTruncateString($sString, $iLength)\n    {\n        //leading and ending whitespaces\n        $sString = trim($sString);\n        $oStr = Str::getStr();\n\n        //multiple whitespaces\n        $sString = $oStr->preg_replace(\"/[ \\t\\n\\r]+/\", \" \", $sString);\n        if ($oStr->strlen($sString) > $iLength && $iLength != -1) {\n            $sString = $oStr->substr($sString, 0, $iLength);\n        }\n\n        return $oStr->preg_replace(\"/,+$/\", \"\", $sString);\n    }\n\n    /**\n     * Prepares and returns string for search engines.\n     *\n     * @param string $sSearchStr String to prepare for search engines\n     *\n     * @return string\n     */\n    public function prepareStrForSearch($sSearchStr)\n    {\n        $oStr = Str::getStr();\n        if ($oStr->hasSpecialChars($sSearchStr)) {\n            return $oStr->recodeEntities($sSearchStr, true, ['&amp;'], ['&']);\n        }\n\n        return '';\n    }\n}\n"
  },
  {
    "path": "source/Core/UtilsUrl.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\n\n/**\n * URL utility class\n */\nclass UtilsUrl extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    const PARAMETER_SEPARATOR = '&amp;';\n\n    /**\n     * Additional url parameters which should be appended to seo/std urls.\n     *\n     * @var array\n     */\n    protected $_aAddUrlParams = null;\n\n    /**\n     * Current shop hosts array.\n     *\n     * @var array\n     */\n    protected $_aHosts = null;\n\n    /**\n     * Returns core parameters which must be added to each url.\n     *\n     * @return array\n     */\n    public function getBaseAddUrlParams()\n    {\n        return [];\n    }\n\n    /**\n     * Returns parameters which should be appended to seo or std url.\n     *\n     * @return array\n     */\n    public function getAddUrlParams()\n    {\n        if ($this->_aAddUrlParams === null) {\n            $this->_aAddUrlParams = $this->getBaseAddUrlParams();\n\n            // appending currency\n            if (($iCur = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopCurrency())) {\n                $this->_aAddUrlParams['cur'] = $iCur;\n            }\n        }\n\n        return $this->_aAddUrlParams;\n    }\n\n    /**\n     * prepareUrlForNoSession adds extra url params making it usable without session\n     * also removes sid=xxxx&.\n     *\n     * @param string $sUrl given url\n     *\n     * @access public\n     * @return string\n     */\n    public function prepareUrlForNoSession($sUrl)\n    {\n        $oStr = Str::getStr();\n\n        // cleaning up session id..\n        $sUrl = $oStr->preg_replace('/(\\?|&(amp;)?)(force_)?(admin_)?sid=[a-z0-9\\._]+&?(amp;)?/i', '\\1', $sUrl);\n        $sUrl = $oStr->preg_replace('/(&amp;|\\?)$/', '', $sUrl);\n\n        if (\\OxidEsales\\Eshop\\Core\\Registry::getUtils()->seoIsActive()) {\n            return $sUrl;\n        }\n\n        if ($qpos = $oStr->strpos($sUrl, '?')) {\n            if ($qpos == $oStr->strlen($sUrl) - 1) {\n                $sSep = '';\n            } else {\n                $sSep = '&amp;';\n            }\n        } else {\n            $sSep = '?';\n        }\n\n        if (!$oStr->preg_match('/[&?](amp;)?lang=[0-9]+/i', $sUrl)) {\n            $sUrl .= \"{$sSep}lang=\" . \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage();\n            $sSep = '&amp;';\n        }\n\n        $oConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        if (!$oStr->preg_match('/[&?](amp;)?cur=[0-9]+/i', $sUrl)) {\n            $iCur = (int) $oConfig->getShopCurrency();\n            if ($iCur) {\n                $sUrl .= \"{$sSep}cur=\" . $iCur;\n            }\n        }\n\n        return $sUrl;\n    }\n\n    /**\n     * Prepares canonical url.\n     *\n     * @param string $sUrl given url\n     *\n     * @access public\n     * @return string\n     */\n    public function prepareCanonicalUrl($sUrl)\n    {\n        $oConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $oStr = Str::getStr();\n\n        // cleaning up session id..\n        $sUrl = $oStr->preg_replace('/(\\?|&(amp;)?)(force_)?(admin_)?sid=[a-z0-9\\._]+&?(amp;)?/i', '\\1', $sUrl);\n        $sUrl = $oStr->preg_replace('/(&amp;|\\?)$/', '', $sUrl);\n        $sSep = ($oStr->strpos($sUrl, '?') === false) ? '?' : '&amp;';\n\n        if (!\\OxidEsales\\Eshop\\Core\\Registry::getUtils()->seoIsActive()) {\n            // non seo url has no language identifier..\n            $iLang = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage();\n            if (\n                !$oStr->preg_match('/[&?](amp;)?lang=[0-9]+/i', $sUrl) &&\n                $iLang != $oConfig->getConfigParam('sDefaultLang')\n            ) {\n                $sUrl .= \"{$sSep}lang=\" . $iLang;\n            }\n        }\n\n        return $sUrl;\n    }\n\n    /**\n     * Appends url with given parameters.\n     *\n     * @param string $sUrl                    url to append\n     * @param array  $parametersToAdd         parameters to append\n     * @param bool   $blFinalUrl              final url\n     * @param bool   $allowParameterOverwrite Decides if same parameters should overwrite query parameters.\n     *\n     * @return string\n     */\n    public function appendUrl($sUrl, $parametersToAdd, $blFinalUrl = false, $allowParameterOverwrite = false)\n    {\n        $paramSeparator = self::PARAMETER_SEPARATOR;\n        $finalParameters = $this->removeNotSetParameters($parametersToAdd);\n\n        if (is_array($finalParameters) && !empty($finalParameters)) {\n            $urlWithoutQuery = $sUrl;\n            $separatorPlace = strpos($sUrl, '?');\n            if ($separatorPlace !== false) {\n                $urlWithoutQuery = substr($sUrl, 0, $separatorPlace);\n                $urlQueryEscaped = substr($sUrl, $separatorPlace + 1);\n                $urlQuery = str_replace($paramSeparator, '&', $urlQueryEscaped);\n\n                $finalParameters = $this->mergeDuplicatedParameters($finalParameters, $urlQuery, $allowParameterOverwrite);\n            }\n\n            $sUrl = $this->appendParamSeparator($urlWithoutQuery);\n            $sUrl .= http_build_query($finalParameters, '', $paramSeparator);\n        }\n\n        if ($sUrl && !$blFinalUrl) {\n            $sUrl = $this->appendParamSeparator($sUrl);\n        }\n\n        return $sUrl;\n    }\n\n    /**\n     * Removes any or specified dynamic parameter from given url.\n     *\n     * @param string $sUrl    url to clean.\n     * @param array  $aParams parameters to remove [optional].\n     *\n     * @return string\n     */\n    public function cleanUrl($sUrl, $aParams = null)\n    {\n        $oStr = Str::getStr();\n        if (is_array($aParams)) {\n            foreach ($aParams as $sParam) {\n                $sUrl = $oStr->preg_replace(\n                    '/(\\?|&(amp;)?)' . preg_quote($sParam) . '=[a-z0-9\\.]+&?(amp;)?/i',\n                    '\\1',\n                    $sUrl\n                );\n            }\n        } else {\n            $sUrl = $oStr->preg_replace('/(\\?|&(amp;)?).+/i', '\\1', $sUrl);\n        }\n\n        return trim($sUrl, \"?\");\n    }\n\n    public function addShopHost($url)\n    {\n        if (!preg_match(\"#^https?://#i\", $url)) {\n            $url = ContainerFacade::getParameter('oxid_esales.shop_url') . $url;\n        }\n\n        return $url;\n    }\n\n    /**\n     * Performs base url processing - adds required parameters to given url.\n     *\n     * @param string $sUrl       url to process.\n     * @param bool   $blFinalUrl should url be finalized or should it end with ? or &amp; (default true).\n     * @param array  $aParams    additional parameters (default null).\n     * @param int    $iLang      url target language (default null).\n     *\n     * @return string\n     */\n    public function processUrl($sUrl, $blFinalUrl = true, $aParams = null, $iLang = null)\n    {\n        $sUrl = $this->appendUrl($sUrl, $aParams, $blFinalUrl);\n\n        if ($this->isCurrentShopHost($sUrl)) {\n            $sUrl = $this->processShopUrl($sUrl, $blFinalUrl, $iLang);\n        }\n\n        return $sUrl;\n    }\n\n    /**\n     * Adds additional shop url parameters, session id, language id when needed.\n     *\n     * @param string $sUrl       url to process.\n     * @param bool   $blFinalUrl should url be finalized or should it end with ? or &amp;.\n     * @param int    $iLang      url target language.\n     *\n     * @return string\n     */\n    public function processShopUrl($sUrl, $blFinalUrl = true, $iLang = null)\n    {\n        $aAddParams = $this->getAddUrlParams();\n\n        $sUrl = $this->appendUrl($sUrl, $aAddParams, $blFinalUrl);\n        $sUrl = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->processUrl($sUrl, $iLang);\n        $sUrl = \\OxidEsales\\Eshop\\Core\\Registry::getSession()->processUrl($sUrl);\n\n        if ($blFinalUrl) {\n            $sUrl = $this->rightTrimAmp($sUrl);\n        }\n\n        return $sUrl;\n    }\n\n    /**\n     * Method returns active shop host.\n     *\n     * @return string\n     */\n    public function getActiveShopHost()\n    {\n        $shopUrl = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopUrl();\n\n        return $this->extractHost($shopUrl);\n    }\n\n    /**\n     * Extract host from url.\n     *\n     * @param string $url\n     *\n     * @return string\n     */\n    public function extractHost($url)\n    {\n        return $this->parseUrlAndAppendSchema($url, PHP_URL_HOST) ?: $url;\n    }\n\n    /**\n     * Method returns shop URL part - path.\n     *\n     * @return null|string\n     */\n    public function getActiveShopUrlPath()\n    {\n        $shopUrl = \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getShopUrl();\n\n        return $this->extractUrlPath($shopUrl);\n    }\n\n    /**\n     * Method returns URL part - path.\n     *\n     * @param string $shopUrl\n     *\n     * @return string|null\n     */\n    public function extractUrlPath($shopUrl)\n    {\n        return $this->parseUrlAndAppendSchema($shopUrl, PHP_URL_PATH);\n    }\n\n    /**\n     * Compares current URL to supplied string.\n     *\n     * @param string $sUrl\n     *\n     * @return bool true if $sUrl is equal to current page URL.\n     */\n    public function isCurrentShopHost($sUrl)\n    {\n        $blCurrent = false;\n        $sUrlHost = @parse_url($sUrl, PHP_URL_HOST);\n        // checks if it is relative url.\n        if (is_null($sUrlHost)) {\n            $blCurrent = true;\n        } else {\n            $aHosts = $this->getHosts();\n\n            foreach ($aHosts as $sHost) {\n                if ($sHost === $sUrlHost) {\n                    $blCurrent = true;\n                    break;\n                }\n            }\n        }\n\n        return $blCurrent;\n    }\n\n    /**\n     * Improved url parsing with parse_url as base and scheme checking improvement in url preprocessing\n     *\n     * @param string $url\n     * @param string $flag\n     * @param string $appendScheme Append this scheme to url if no scheme found\n     *\n     * @return string\n     */\n    private function parseUrlAndAppendSchema($url, $flag, $appendScheme = 'http')\n    {\n        if (!filter_var($url, FILTER_VALIDATE_URL)) {\n            $url = $appendScheme . '://' . $url;\n        }\n\n        return parse_url($url, $flag);\n    }\n\n    /**\n     * Seo url processor: adds various needed parameters, like currency, shop id.\n     *\n     * @param string $sUrl url to process.\n     *\n     * @return string\n     */\n    public function processSeoUrl($sUrl)\n    {\n        if (!$this->isAdmin()) {\n            $session = \\OxidEsales\\Eshop\\Core\\Registry::getSession();\n            $sUrl = $session->processUrl($this->appendUrl($sUrl, $this->getAddUrlParams()));\n        }\n\n        $sUrl = $this->cleanUrlParams($sUrl);\n\n        return $this->rightTrimAmp($sUrl);\n    }\n\n    /**\n     * Remove duplicate GET parameters and clean &amp; and duplicate &.\n     *\n     * @param string $sUrl       url to process.\n     * @param string $sConnector GET elements connector.\n     *\n     * @return string\n     */\n    public function cleanUrlParams($sUrl, $sConnector = '&amp;')\n    {\n        $aUrlParts = explode('?', $sUrl);\n\n        // check for params part\n        if (!is_array($aUrlParts) || count($aUrlParts) != 2) {\n            return $sUrl;\n        }\n\n        $sUrl = $aUrlParts[0];\n        $sUrlParams = $aUrlParts[1];\n\n        $oStrUtils = Str::getStr();\n        $sUrlParams = $oStrUtils->preg_replace(\n            ['@(\\&(amp;){1,})@ix', '@\\&{1,}@', '@\\?&@x'],\n            ['&', '&', '?'],\n            $sUrlParams\n        );\n\n        // remove duplicate entries\n        parse_str($sUrlParams, $aUrlParams);\n        $sUrl .= '?' . http_build_query($aUrlParams, '', $sConnector);\n\n        // replace brackets\n        $sUrl = str_replace(\n            ['%5B', '%5D'],\n            ['[', ']'],\n            $sUrl\n        );\n\n        return $sUrl;\n    }\n\n    /**\n     * Appends parameter separator - '?' if it is not in the url or &amp; otherwise.\n     *\n     * @param string $sUrl url\n     *\n     * @return string\n     */\n    public function appendParamSeparator($sUrl)\n    {\n        return $sUrl . $this->getUrlParametersSeparator($sUrl);\n    }\n\n    /**\n     * Return current url.\n     *\n     * @return string\n     */\n    public function getCurrentUrl()\n    {\n        $oUtilsServer = \\OxidEsales\\Eshop\\Core\\Registry::getUtilsServer();\n\n        $aServerParams[\"HTTPS\"] = $oUtilsServer->getServerVar(\"HTTPS\");\n        $aServerParams[\"HTTP_X_FORWARDED_PROTO\"] = $oUtilsServer->getServerVar(\"HTTP_X_FORWARDED_PROTO\");\n        $aServerParams[\"HTTP_HOST\"] = $oUtilsServer->getServerVar(\"HTTP_HOST\");\n        $aServerParams[\"REQUEST_URI\"] = $oUtilsServer->getServerVar(\"REQUEST_URI\");\n\n        $sProtocol = \"http://\";\n\n        if (\n            isset($aServerParams['HTTPS']) && (($aServerParams['HTTPS'] == 'on' || $aServerParams['HTTPS'] == 1))\n            || (isset($aServerParams['HTTP_X_FORWARDED_PROTO']) && $aServerParams['HTTP_X_FORWARDED_PROTO'] == 'https')\n        ) {\n            $sProtocol = 'https://';\n        }\n\n        return $sProtocol . $aServerParams['HTTP_HOST'] . $aServerParams['REQUEST_URI'];\n    }\n\n    /**\n     * Forms parameters array out of a string.\n     * Takes & and &amp; as delimiters.\n     * Returns associative array with parameters.\n     *\n     * @param string $sValue String\n     *\n     * @return array\n     */\n    public function stringToParamsArray($sValue)\n    {\n        // url building\n        // replace possible ampersands, explode, and filter out empty values\n        $sValue = str_replace(\"&amp;\", \"&\", $sValue);\n        $aNavParams = explode(\"&\", $sValue);\n        $aNavParams = array_filter($aNavParams);\n        $aParams = [];\n        foreach ($aNavParams as $sValue) {\n            $exp = explode(\"=\", $sValue);\n            $aParams[$exp[0]] = $exp[1] ?? null;\n        }\n\n        return $aParams;\n    }\n\n    /**\n     * Return array of language key and language value.\n     *\n     * @param integer $languageId\n     *\n     * @return array\n     */\n    public function getUrlLanguageParameter($languageId)\n    {\n        return [\\OxidEsales\\Eshop\\Core\\Registry::getLang()->getName() => $languageId];\n    }\n\n    /**\n     * Extracts host from given url and appends $aHosts with it\n     *\n     * @param string $sUrl   url to extract\n     * @param array  $aHosts hosts array\n     */\n    protected function addHost($sUrl, &$aHosts)\n    {\n        if ($sUrl && ($sHost = @parse_url($sUrl, PHP_URL_HOST))) {\n            if (!in_array($sHost, $aHosts)) {\n                $aHosts[] = $sHost;\n            }\n        }\n    }\n\n    /**\n     * Appends language urls to $aHosts.\n     *\n     * @param array $aLanguageUrls array of language urls to extract\n     * @param array $aHosts        hosts array\n     */\n    protected function addLanguageHost($aLanguageUrls, &$aHosts)\n    {\n        $iLanguageId = \\OxidEsales\\Eshop\\Core\\Registry::getLang()->getBaseLanguage();\n\n        if (isset($aLanguageUrls[$iLanguageId])) {\n            $this->addHost($aLanguageUrls[$iLanguageId], $aHosts);\n        }\n    }\n\n    /**\n     * Collects and returns current shop hosts array.\n     *\n     * @return array\n     */\n    protected function getHosts()\n    {\n        if ($this->_aHosts === null) {\n            $this->_aHosts = [];\n            $oConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n            $this->addMallHosts($this->_aHosts);\n\n            // language url\n            $this->addLanguageHost($oConfig->getConfigParam('aLanguageURLs'), $this->_aHosts);\n            $this->addLanguageHost($oConfig->getConfigParam('aLanguageSSLURLs'), $this->_aHosts);\n\n            // current url\n            $this->addHost(ContainerFacade::getParameter('oxid_esales.shop_url'), $this->_aHosts);\n\n            if ($this->isAdmin()) {\n                $this->addHost(ContainerFacade::getParameter('oxid_esales.shop_admin_url'), $this->_aHosts);\n            }\n        }\n\n        return $this->_aHosts;\n    }\n\n    /**\n     * Appends shop mall urls to $aHosts if needed\n     *\n     * @param array $aHosts hosts array\n     */\n    protected function addMallHosts(&$aHosts)\n    {\n    }\n\n    /**\n     * Returns url separator (?,&amp;) for adding new parameters.\n     *\n     * @param string $url\n     *\n     * @return string\n     */\n    private function getUrlParametersSeparator($url)\n    {\n        $oStr = Str::getStr();\n\n        $urlSeparator = '&amp;';\n        if ($oStr->preg_match('/(\\?|&(amp;)?)$/i', $url)) {\n            $urlSeparator = '';\n        } elseif ($oStr->strpos($url, '?') === false) {\n            $urlSeparator = '?';\n        }\n\n        return $urlSeparator;\n    }\n\n    /**\n     * Removes parameters which are not set.\n     *\n     * @param string $parametersToAdd\n     *\n     * @return string\n     */\n    private function removeNotSetParameters($parametersToAdd)\n    {\n        if (is_array($parametersToAdd) && !empty($parametersToAdd)) {\n            foreach ($parametersToAdd as $key => $value) {\n                if (is_null($value)) {\n                    unset($parametersToAdd[$key]);\n                }\n            }\n        }\n\n        return $parametersToAdd;\n    }\n\n    /**\n     * @param array  $aAddParams              parameters to add to URL\n     * @param string $query                   URL query part\n     * @param bool   $allowParameterOverwrite Decides if same parameters should overwrite query parameters\n     *\n     * @return array\n     */\n    private function mergeDuplicatedParameters($aAddParams, $query, $allowParameterOverwrite = true)\n    {\n        parse_str($query, $currentUrlParameters);\n        if ($allowParameterOverwrite) {\n            $newParameters = array_merge($currentUrlParameters, $aAddParams);\n        } else {\n            $newFilteredParameters = array_diff_key($aAddParams, $currentUrlParameters);\n            $newParameters = array_merge($currentUrlParameters, $newFilteredParameters);\n        }\n\n        return $newParameters;\n    }\n\n    /**\n     * @param string $url\n     *\n     * @return string\n     */\n    private function rightTrimAmp($url)\n    {\n        return Str::getStr()->preg_replace('/(\\?|&(amp;)?)$/i', '', $url);\n    }\n}\n"
  },
  {
    "path": "source/Core/UtilsView.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Contract\\IDisplayError;\nuse OxidEsales\\Eshop\\Core\\Exception\\StandardException;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererInterface;\n\nclass UtilsView extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Templating instance getter\n     *\n     * @return TemplateRendererInterface\n     */\n    private function getRenderer()\n    {\n        return ContainerFacade::get(TemplateRendererBridgeInterface::class)\n            ->getTemplateRenderer();\n    }\n\n    /**\n     * Returns rendered template output. According to debug configuration outputs\n     * debug information.\n     *\n     * @param string $templateName template file name\n     * @param object $oObject      object, witch template we wish to output\n     *\n     * @return string\n     */\n    public function getTemplateOutput($templateName, $oObject)\n    {\n        $viewData = $oObject->getViewData();\n        if (!is_array($viewData)) {\n            $viewData = [];\n        }\n\n        return $this->getRenderer()->renderTemplate($templateName, $viewData);\n    }\n\n    /**\n     * adds the given errors to the view array\n     *\n     * @param array $aView  view data array\n     * @param array $errors array of errors to pass to view\n     */\n    public function passAllErrorsToView(&$aView, $errors)\n    {\n        if (count($errors) > 0) {\n            foreach ($errors as $sLocation => $aEx2) {\n                foreach ($aEx2 as $sKey => $oEr) {\n                    $aView['Errors'][$sLocation][$sKey] = unserialize($oEr);\n                }\n            }\n        }\n    }\n\n    /**\n     * Adds an exception to the array of displayed exceptions for the view\n     * by default is displayed in the inc_header, but with the custom destination set to true\n     * the exception won't be displayed by default but can be displayed where ever wanted in the tpl\n     *\n     * @param StandardException|IDisplayError|string $exception            an exception object or just a language local (string),\n     *                                                                     which will be converted into a oxExceptionToDisplay object\n     * @param bool                                   $blFull               if true the whole object is add to display (default false)\n     * @param bool                                   $useCustomDestination true if the exception shouldn't be displayed\n     *                                                                     at the default position (default false)\n     * @param string                                 $customDestination    defines a name of the view variable containing\n     *                                                                     the messages, overrides Parameter 'CustomError' (\"default\")\n     * @param string                                 $activeController     defines a name of the controller, which should\n     *                                                                     handle the error.\n     */\n    public function addErrorToDisplay($exception, $blFull = false, $useCustomDestination = false, $customDestination = \"\", $activeController = \"\")\n    {\n        //default\n        $destination = 'default';\n        $customDestination = $customDestination ? $customDestination : Registry::getRequest()->getRequestEscapedParameter('CustomError');\n        if ($useCustomDestination && $customDestination) {\n            $destination = $customDestination;\n        }\n\n        //starting session if not yet started as all exception\n        //messages are stored in session\n        $session = Registry::getSession();\n        if (!$session->getId() && !$session->isHeaderSent()) {\n            $session->setForceNewSession();\n            $session->start();\n        }\n\n        $sessionErrors = Registry::getSession()->getVariable('Errors');\n        if ($exception instanceof \\OxidEsales\\Eshop\\Core\\Exception\\StandardException) {\n            $exceptionToDisplay = oxNew(\\OxidEsales\\Eshop\\Core\\Exception\\ExceptionToDisplay::class);\n            $exceptionToDisplay->setMessage($exception->getMessage());\n            $exceptionToDisplay->setExceptionType($exception->getType());\n\n            if ($exception instanceof \\OxidEsales\\Eshop\\Core\\Exception\\SystemComponentException) {\n                $exceptionToDisplay->setMessageArgs($exception->getComponent());\n            }\n\n            $exceptionToDisplay->setValues($exception->getValues());\n            $exceptionToDisplay->setStackTrace($exception->getTraceAsString());\n            $exceptionToDisplay->setDebug($blFull);\n            $exception = $exceptionToDisplay;\n        } elseif ($exception instanceof \\Throwable) {\n            $tempException = $exception;\n            $exception = oxNew(\\OxidEsales\\Eshop\\Core\\DisplayError::class);\n            $exception->setMessage($tempException->getMessage());\n        } elseif ($exception && !($exception instanceof \\OxidEsales\\Eshop\\Core\\Contract\\IDisplayError)) {\n            $tempException = $exception;\n            $exception = oxNew(\\OxidEsales\\Eshop\\Core\\DisplayError::class);\n            $exception->setMessage($tempException);\n        } elseif ($exception instanceof \\OxidEsales\\Eshop\\Core\\Contract\\IDisplayError) {\n            // take the object\n        } else {\n            $exception = null;\n        }\n\n        if ($exception) {\n            $sessionErrors[$destination][] = serialize($exception);\n            Registry::getSession()->setVariable('Errors', $sessionErrors);\n\n            if ($activeController == '') {\n                $activeController = Registry::getRequest()->getRequestEscapedParameter('actcontrol');\n            }\n            if ($activeController) {\n                $aControllerErrors[$destination] = $activeController;\n                Registry::getSession()->setVariable('ErrorController', $aControllerErrors);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Core/UtilsXml.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse DOMDocument;\n\n/**\n * XML document handler\n */\nclass UtilsXml extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Takes XML string and makes DOMDocument\n     * Returns DOMDocument or false, if it can't be loaded\n     *\n     * @param string      $sXml         XML as a string\n     * @param DOMDocument $oDomDocument DOM handler\n     *\n     * @return DOMDocument|bool\n     */\n    public function loadXml($sXml, $oDomDocument = null)\n    {\n        if (!$oDomDocument) {\n            $oDomDocument = new DOMDocument('1.0', 'utf-8');\n        }\n\n        libxml_use_internal_errors(true);\n        $oDomDocument->loadXML($sXml);\n        $errors = libxml_get_errors();\n        $blLoaded = empty($errors);\n        libxml_clear_errors();\n\n        if ($blLoaded) {\n            return $oDomDocument;\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "source/Core/ViewConfig.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Exception\\FileException;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Str;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModuleAssetsPathResolverBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridgeInterface;\n\n/**\n * View config data access class. Keeps most\n * of getters needed for formatting various urls,\n * config parameters, session information etc.\n */\nclass ViewConfig extends \\OxidEsales\\Eshop\\Core\\Base\n{\n    /**\n     * Active shop object. Can only be accessed when it is assigned\n     *\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\Shop\n     */\n    protected $_oShop = null;\n\n    /**\n     * View data array, may only be accedded when it is assigned tohether with shop object\n     *\n     * @var array\n     */\n    protected $_aViewData = null;\n\n    /**\n     * View config parameters cache array\n     *\n     * @var array\n     */\n    protected $_aConfigParams = [];\n\n    /**\n     * Help page link\n     *\n     * @return string\n     */\n    protected $_sHelpPageLink = null;\n\n    /**\n     * @var \\OxidEsales\\Eshop\\Application\\Model\\CountryList\n     */\n    protected $_oCountryList = null;\n\n    /**\n     * Active theme name\n     *\n     * @var null\n     */\n    protected $_sActiveTheme = null;\n\n    /**\n     * Shop logo\n     *\n     * @var string\n     */\n    protected $_sShopLogo = null;\n\n    /**\n     * Returns shops home link\n     *\n     * @return string\n     */\n    public function getHomeLink()\n    {\n        if (($sValue = $this->getViewConfigParam('homeLink')) === null) {\n            $sValue = null;\n\n            $blAddStartCl = $this->isStartClassRequired();\n            if ($blAddStartCl) {\n                $baseLanguage = Registry::getLang()->getBaseLanguage();\n                $sValue = Registry::getSeoEncoder()->getStaticUrl($this->getSelfLink() . 'cl=start', $baseLanguage);\n                $sValue = Registry::getUtilsUrl()->appendUrl(\n                    $sValue,\n                    Registry::getUtilsUrl()->getBaseAddUrlParams()\n                );\n                $sValue = Str::getStr()->preg_replace('/(\\?|&(amp;)?)$/', '', $sValue);\n            }\n\n            if (!$sValue) {\n                $sValue = Str::getStr()->preg_replace('#index.php\\??$#', '', $this->getSelfLink());\n            }\n\n            $this->setViewConfigParam('homeLink', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Check if some shop selection page must be shown\n     *\n     * @return bool\n     */\n    protected function isStartClassRequired()\n    {\n        $baseLanguage = Registry::getLang()->getBaseLanguage();\n        $shopConfig = Registry::getConfig();\n        $isSeoActive = Registry::getUtils()->seoIsActive();\n\n        return $isSeoActive && ($baseLanguage != $shopConfig->getConfigParam('sDefaultLang'));\n    }\n\n    /**\n     * Returns active template name (if set)\n     *\n     * @return string\n     */\n    public function getActContentLoadId()\n    {\n        $sTplName = Registry::getRequest()->getRequestEscapedParameter('oxloadid');\n        // #M1176: Logout from CMS page\n        if (!$sTplName && Registry::getConfig()->getTopActiveView()) {\n            $sTplName = Registry::getConfig()->getTopActiveView()->getViewConfig()->getViewConfigParam('oxloadid');\n        }\n\n        return $sTplName ? basename($sTplName) : null;\n    }\n\n    /**\n     * Returns active manufacturer id\n     *\n     * @return string\n     */\n    public function getActTplName()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('tpl');\n    }\n\n    /**\n     * Returns active currency id\n     *\n     * @return string\n     */\n    public function getActCurrency()\n    {\n        return Registry::getConfig()->getShopCurrency();\n    }\n\n    /**\n     * Returns shop logout link\n     *\n     * @return string\n     */\n    public function getLogoutLink()\n    {\n        $sClass = $this->getTopActionClassName();\n        $sCatnid = $this->getActCatId();\n        $sMnfid = $this->getActManufacturerId();\n        $sArtnid = $this->getActArticleId();\n        $sTplName = $this->getActTplName();\n        $sContentLoadId = $this->getActContentLoadId();\n        $sSearchParam = $this->getActSearchParam();\n        // @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n        $sRecommId = $this->getActRecommendationId();\n        // END deprecated\n        $sListType = $this->getActListType();\n\n        $oConfig = Registry::getConfig();\n\n        return ($oConfig->isSsl() ? $oConfig->getShopSecureHomeUrl() : $oConfig->getShopHomeUrl())\n               . \"cl={$sClass}\"\n               . ($sCatnid ? \"&amp;cnid={$sCatnid}\" : '')\n               . ($sArtnid ? \"&amp;anid={$sArtnid}\" : '')\n               . ($sMnfid ? \"&amp;mnid={$sMnfid}\" : '')\n               . ($sSearchParam ? \"&amp;searchparam={$sSearchParam}\" : '')\n               // @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n               . ($sRecommId ? \"&amp;recommid={$sRecommId}\" : '')\n               // END deprecated\n               . ($sListType ? \"&amp;listtype={$sListType}\" : '')\n               . \"&amp;fnc=logout\"\n               . ($sTplName ? \"&amp;tpl=\" . basename($sTplName) : '')\n               . ($sContentLoadId ? \"&amp;oxloadid=\" . $sContentLoadId : '')\n               . \"&amp;redirect=1\";\n    }\n\n    /**\n     * Returns help content link idents\n     *\n     * @return array\n     */\n    protected function getHelpContentIdents()\n    {\n        $sClass = $this->getActiveClassName();\n\n        return ['oxhelp' . strtolower($sClass), 'oxhelpdefault'];\n    }\n\n    public function getMediaPictureUrl(): string\n    {\n        return Registry::getConfig()->getPictureUrl('media/');\n    }\n\n    /**\n     * Returns shop help link\n     *\n     * @return string\n     */\n    public function getHelpPageLink()\n    {\n        if ($this->_sHelpPageLink === null) {\n            $this->_sHelpPageLink = \"\";\n            $aContentIdents = $this->getHelpContentIdents();\n            $oContent = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\Content::class);\n            foreach ($aContentIdents as $sIdent) {\n                if ($oContent->loadByIdent($sIdent, true)) {\n                    $this->_sHelpPageLink = $oContent->getLink();\n                    break;\n                }\n            }\n        }\n\n        return $this->_sHelpPageLink;\n    }\n\n    /**\n     * Returns active category id\n     *\n     * @return string\n     */\n    public function getActCatId()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('cnid');\n    }\n\n    /**\n     * Returns active article id\n     *\n     * @return string\n     */\n    public function getActArticleId()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('anid');\n    }\n\n    /**\n     * Returns active search parameter\n     *\n     * @return string\n     */\n    public function getActSearchParam()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('searchparam');\n    }\n\n    /**\n     * Returns active recommendation id parameter\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @return string\n     */\n    public function getActRecommendationId()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('recommid');\n    }\n\n    /**\n     * Returns active listtype parameter\n     *\n     * @return string\n     */\n    public function getActListType()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('listtype');\n    }\n\n    /**\n     * Returns active manufacturer id\n     *\n     * @return string\n     */\n    public function getActManufacturerId()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('mnid');\n    }\n\n    /**\n     * Returns active content id\n     *\n     * @return string\n     */\n    public function getContentId()\n    {\n        return Registry::getRequest()->getRequestEscapedParameter('oxcid');\n    }\n\n    /**\n     * Sets view config parameter, which can be accessed in templates in two ways:\n     *\n     * $oViewConf->getViewConfigParam( $sName )\n     *\n     * @param string $sName  name of parameter\n     * @param mixed  $sValue parameter value\n     */\n    public function setViewConfigParam($sName, $sValue)\n    {\n        startProfile('\\OxidEsales\\Eshop\\Core\\ViewConfig::setViewConfigParam');\n\n        $this->_aConfigParams[$sName] = $sValue;\n\n        stopProfile('\\OxidEsales\\Eshop\\Core\\ViewConfig::setViewConfigParam');\n    }\n\n    /**\n     * Returns current view config parameter\n     *\n     * @param string $sName name of parameter to get\n     *\n     * @return mixed\n     */\n    public function getViewConfigParam($sName)\n    {\n        startProfile('\\OxidEsales\\Eshop\\Core\\ViewConfig::getViewConfigParam');\n\n        if ($this->_oShop && isset($this->_oShop->$sName)) {\n            $sValue = $this->_oShop->$sName;\n        } elseif ($this->_aViewData && isset($this->_aViewData[$sName])) {\n            $sValue = $this->_aViewData[$sName];\n        } else {\n            $sValue = (isset($this->_aConfigParams[$sName]) ? $this->_aConfigParams[$sName] : null);\n        }\n\n        stopProfile('\\OxidEsales\\Eshop\\Core\\ViewConfig::getViewConfigParam');\n\n        return $sValue;\n    }\n\n    /**\n     * Sets shop object and view data to view config. This is needed mostly for\n     * old templates\n     *\n     * @param \\OxidEsales\\Eshop\\Application\\Model\\Shop $oShop     shop object\n     * @param array                                    $aViewData view data array\n     */\n    public function setViewShop($oShop, $aViewData)\n    {\n        $this->_oShop = $oShop;\n        $this->_aViewData = $aViewData;\n    }\n\n    /**\n     * Returns forms hidden session parameters\n     *\n     * @return string\n     */\n    public function getHiddenSid()\n    {\n        if (($sValue = $this->getViewConfigParam('hiddensid')) === null) {\n            $session = Registry::getSession();\n            $sValue = $session->hiddenSid();\n\n            // appending language info to form\n            $language = Registry::getLang()->getFormLang();\n            if ($language) {\n                $sValue .= \"\\n{$language}\";\n            }\n\n            $sValue .= $this->getAdditionalRequestParameters();\n\n            $this->setViewConfigParam('hiddensid', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * If any hidden parameters needed for sending with request\n     *\n     * @return string\n     */\n    protected function getAdditionalRequestParameters()\n    {\n        return '';\n    }\n\n    public function getShopUrl()\n    {\n        return Registry::getConfig()->getShopUrl();\n    }\n\n    /**\n     * Returns shops self link\n     *\n     * @return string\n     */\n    public function getSelfLink()\n    {\n        if (($sValue = $this->getViewConfigParam('selflink')) === null) {\n            $sValue = Registry::getConfig()->getShopHomeUrl();\n            $this->setViewConfigParam('selflink', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns shops self ssl link\n     *\n     * @return string\n     */\n    public function getSslSelfLink()\n    {\n        if ($this->isAdmin()) {\n            // using getSelfLink() method in admin mode (#2745)\n            return $this->getSelfLink();\n        }\n\n        if (($sValue = $this->getViewConfigParam('sslselflink')) === null) {\n            $sValue = Registry::getConfig()->getShopSecureHomeURL();\n            $this->setViewConfigParam('sslselflink', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns shops base directory path\n     *\n     * @return string\n     */\n    public function getBaseDir()\n    {\n        if (($basedir = $this->getViewConfigParam('basedir')) === null) {\n            $basedir = Registry::getConfig()->getShopURL();\n\n            $this->setViewConfigParam('basedir', $basedir);\n        }\n\n        return $basedir;\n    }\n\n    /**\n     * Returns shops utility directory path\n     *\n     * @return string\n     */\n    public function getCoreUtilsDir()\n    {\n        if (($sValue = $this->getViewConfigParam('coreutilsdir')) === null) {\n            $sValue = Registry::getConfig()->getCoreUtilsURL();\n            $this->setViewConfigParam('coreutilsdir', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns shops action link\n     *\n     * @return string\n     */\n    public function getSelfActionLink()\n    {\n        if (($sValue = $this->getViewConfigParam('selfactionlink')) === null) {\n            $sValue = Registry::getConfig()->getShopCurrentUrl();\n            $this->setViewConfigParam('selfactionlink', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns shops home path\n     *\n     * @return string\n     */\n    public function getCurrentHomeDir()\n    {\n        if (($sValue = $this->getViewConfigParam('currenthomedir')) === null) {\n            $sValue = Registry::getConfig()->getCurrentShopUrl();\n            $this->setViewConfigParam('currenthomedir', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns shops basket link\n     *\n     * @return string\n     */\n    public function getBasketLink()\n    {\n        if (($sValue = $this->getViewConfigParam('basketlink')) === null) {\n            $sValue = Registry::getConfig()->getShopHomeUrl() . 'cl=basket';\n            $this->setViewConfigParam('basketlink', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns shops order link\n     *\n     * @return string\n     */\n    public function getOrderLink()\n    {\n        if (($sValue = $this->getViewConfigParam('orderlink')) === null) {\n            $sValue = Registry::getConfig()->getShopSecureHomeUrl() . 'cl=user';\n            $this->setViewConfigParam('orderlink', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns shops payment link\n     *\n     * @return string\n     */\n    public function getPaymentLink()\n    {\n        if (($sValue = $this->getViewConfigParam('paymentlink')) === null) {\n            $sValue = Registry::getConfig()->getShopSecureHomeUrl() . 'cl=payment';\n            $this->setViewConfigParam('paymentlink', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns shops order execution link\n     *\n     * @return string\n     */\n    public function getExeOrderLink()\n    {\n        if (($sValue = $this->getViewConfigParam('exeorderlink')) === null) {\n            $sValue = Registry::getConfig()->getShopSecureHomeUrl() . 'cl=order&amp;fnc=execute';\n            $this->setViewConfigParam('exeorderlink', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns shops order confirmation link\n     *\n     * @return string\n     */\n    public function getOrderConfirmLink()\n    {\n        if (($sValue = $this->getViewConfigParam('orderconfirmlink')) === null) {\n            $sValue = Registry::getConfig()->getShopSecureHomeUrl() . 'cl=order';\n            $this->setViewConfigParam('orderconfirmlink', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns shops resource url\n     *\n     * @param string $sFile resource file name\n     *\n     * @return string\n     */\n    public function getResourceUrl($sFile = null)\n    {\n        if ($sFile) {\n            $sValue = Registry::getConfig()->getResourceUrl($sFile, $this->isAdmin());\n        } elseif (($sValue = $this->getViewConfigParam('basetpldir')) === null) {\n            $sValue = Registry::getConfig()->getResourceUrl('', $this->isAdmin());\n            $this->setViewConfigParam('basetpldir', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns shops current (related to language) templates path\n     *\n     * @return string\n     */\n    public function getTemplateDir()\n    {\n        if (($sValue = $this->getViewConfigParam('templatedir')) === null) {\n            $sValue = Registry::getConfig()->getTemplateDir($this->isAdmin());\n            $this->setViewConfigParam('templatedir', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns shops current templates url\n     *\n     * @return string\n     */\n    public function getUrlTemplateDir()\n    {\n        if (($sValue = $this->getViewConfigParam('urltemplatedir')) === null) {\n            $sValue = Registry::getConfig()->getTemplateUrl($this->isAdmin());\n            $this->setViewConfigParam('urltemplatedir', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns image url\n     *\n     * @param string $sFile Image file name\n     * @param bool   $bSsl  Whether to force SSL\n     *\n     * @return string\n     */\n    public function getImageUrl($sFile = null, $bSsl = null)\n    {\n        if ($sFile) {\n            $sValue = Registry::getConfig()->getImageUrl($this->isAdmin(), $bSsl, null, $sFile);\n        } elseif (($sValue = $this->getViewConfigParam('imagedir')) === null) {\n            $sValue = Registry::getConfig()->getImageUrl($this->isAdmin(), $bSsl);\n            $this->setViewConfigParam('imagedir', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns non ssl image url\n     *\n     * @return string\n     */\n    public function getNoSslImageDir()\n    {\n        if (($sValue = $this->getViewConfigParam('nossl_imagedir')) === null) {\n            $sValue = Registry::getConfig()->getImageUrl($this->isAdmin(), false);\n            $this->setViewConfigParam('nossl_imagedir', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns url to pictures directory.\n     *\n     * @return string\n     */\n    public function getPictureDir()\n    {\n        if (($sValue = $this->getViewConfigParam('picturedir')) === null) {\n            $sValue = Registry::getConfig()->getPictureUrl(null, $this->isAdmin());\n            $this->setViewConfigParam('picturedir', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns admin path\n     *\n     * @return string\n     */\n    public function getAdminDir()\n    {\n        if (($sValue = $this->getViewConfigParam('sAdminDir')) === null) {\n            $sValue = Registry::getConfig()->getConfigParam('sAdminDir');\n            $this->setViewConfigParam('sAdminDir', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns currently open shop id\n     *\n     * @return string\n     */\n    public function getActiveShopId()\n    {\n        if (($sValue = $this->getViewConfigParam('shopid')) === null) {\n            $sValue = Registry::getConfig()->getShopId();\n            $this->setViewConfigParam('shopid', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns ssl mode (on/off)\n     *\n     * @return string\n     */\n    public function isSsl()\n    {\n        if (($sValue = $this->getViewConfigParam('isssl')) === null) {\n            $sValue = Registry::getConfig()->isSsl();\n            $this->setViewConfigParam('isssl', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns visitor ip address\n     *\n     * @return string\n     */\n    public function getRemoteAddress()\n    {\n        if (($sValue = $this->getViewConfigParam('ip')) === null) {\n            $sValue = Registry::getUtilsServer()->getRemoteAddress();\n            $this->setViewConfigParam('ip', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns basket popup identifier\n     *\n     * @return string\n     */\n    public function getPopupIdent()\n    {\n        if (($sValue = $this->getViewConfigParam('popupident')) === null) {\n            $sValue = md5(Registry::getConfig()->getShopUrl());\n            $this->setViewConfigParam('popupident', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns random basket popup identifier\n     *\n     * @return string\n     */\n    public function getPopupIdentRand()\n    {\n        if (($sValue = $this->getViewConfigParam('popupidentrand')) === null) {\n            $sValue = md5(time());\n            $this->setViewConfigParam('popupidentrand', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns list view paging url\n     *\n     * @return string\n     */\n    public function getArtPerPageForm()\n    {\n        if (($sValue = $this->getViewConfigParam('artperpageform')) === null) {\n            $sValue = Registry::getConfig()->getShopCurrentUrl();\n            $this->setViewConfigParam('artperpageform', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns \"blVariantParentBuyable\" parent article config state\n     *\n     * @return string\n     */\n    public function isBuyableParent()\n    {\n        return Registry::getConfig()->getConfigParam('blVariantParentBuyable');\n    }\n\n    /**\n     * Returns config param \"blShowBirthdayFields\" value\n     *\n     * @return string\n     */\n    public function showBirthdayFields()\n    {\n        return Registry::getConfig()->getConfigParam('blShowBirthdayFields');\n    }\n\n    /**\n     * Returns config param \"aNrofCatArticles\" value\n     *\n     * @return array\n     */\n    public function getNrOfCatArticles()\n    {\n        $sListType = Registry::getSession()->getVariable('ldtype');\n\n        if (is_null($sListType)) {\n            $sListType = Registry::getConfig()->getConfigParam('sDefaultListDisplayType');\n        }\n\n        if ('grid' === $sListType) {\n            $aNrOfCatArticles = Registry::getConfig()->getConfigParam('aNrofCatArticlesInGrid');\n        } else {\n            $aNrOfCatArticles = Registry::getConfig()->getConfigParam('aNrofCatArticles');\n        }\n\n        return $aNrOfCatArticles;\n    }\n\n    /**\n     * Returns config param \"bl_showWishlist\" value\n     *\n     * @return bool\n     */\n    public function getShowWishlist()\n    {\n        return Registry::getConfig()->getConfigParam('bl_showWishlist');\n    }\n\n    /**\n     * Returns config param \"bl_showCompareList\" value\n     *\n     * @return bool\n     */\n    public function getShowCompareList()\n    {\n        $myConfig = Registry::getConfig();\n        $blShowCompareList = true;\n\n        if (\n            !$myConfig->getConfigParam('bl_showCompareList') ||\n            ($myConfig->getConfigParam('blDisableNavBars') && $myConfig->getActiveView()->getIsOrderStep())\n        ) {\n            $blShowCompareList = false;\n        }\n\n        return $blShowCompareList;\n    }\n\n    /**\n     * Returns config param \"bl_showListmania\" value\n     *\n     * @deprecated since v5.3 (2016-06-17); Listmania will be moved to an own module.\n     *\n     * @return bool\n     */\n    public function getShowListmania()\n    {\n        return Registry::getConfig()->getConfigParam('bl_showListmania');\n    }\n\n    /**\n     * Returns config param \"bl_showVouchers\" value\n     *\n     * @return bool\n     */\n    public function getShowVouchers()\n    {\n        return Registry::getConfig()->getConfigParam('bl_showVouchers');\n    }\n\n    /**\n     * Returns config param \"bl_showGiftWrapping\" value\n     *\n     * @return bool\n     */\n    public function getShowGiftWrapping()\n    {\n        return Registry::getConfig()->getConfigParam('bl_showGiftWrapping');\n    }\n\n    /**\n     * Returns session language id\n     *\n     * @return string\n     */\n    public function getActLanguageId()\n    {\n        $sValue = $this->getViewConfigParam('lang');\n        if ($sValue === null) {\n            $languageService = Registry::getLang();\n            $request = Registry::getRequest();\n\n            $iLang = $request->getRequestParameter('lang');\n            $sValue = ($iLang !== null) ?\n                $languageService->validateLanguage($iLang) :\n                $languageService->getBaseLanguage();\n            $this->setViewConfigParam('lang', $sValue);\n        }\n\n        return $sValue;\n    }\n\n    /**\n     * Returns session language id\n     *\n     * @return string\n     */\n    public function getActLanguageAbbr()\n    {\n        return Registry::getLang()->getLanguageAbbr($this->getActLanguageId());\n    }\n\n    /**\n     * Returns name of active view class\n     *\n     * @return string\n     */\n    public function getActiveClassName()\n    {\n        return Registry::getConfig()->getActiveView()->getClassKey();\n    }\n\n    /**\n     * Returns name of a class of top view in the chain\n     * (given a generic fnc, e.g. logout)\n     *\n     * @return string\n     */\n    public function getTopActiveClassName()\n    {\n        return Registry::getConfig()->getTopActiveView()->getClassKey();\n    }\n\n    /**\n     * Returns max number of items shown on page\n     *\n     * @return int\n     */\n    public function getArtPerPageCount()\n    {\n        return $this->getViewConfigParam('iartPerPage');\n    }\n\n    /**\n     * Returns navigation url parameters\n     *\n     * @return string\n     */\n    public function getNavUrlParams()\n    {\n        if (($sParams = $this->getViewConfigParam('navurlparams')) === null) {\n            $sParams = '';\n            $aNavParams = Registry::getConfig()->getActiveView()->getNavigationParams();\n            foreach ($aNavParams as $sName => $sValue) {\n                if (isset($sValue)) {\n                    if ($sParams) {\n                        $sParams .= '&amp;';\n                    }\n                    $sParams .= \"{$sName}=\" . rawurlencode($sValue);\n                }\n            }\n            if ($sParams) {\n                $sParams = '&amp;' . $sParams;\n            }\n            $this->setViewConfigParam('navurlparams', $sParams);\n        }\n\n        return $sParams;\n    }\n\n    /**\n     * Returns navigation forms parameters\n     *\n     * @return string\n     */\n    public function getNavFormParams()\n    {\n        $configParameters = $this->getViewConfigParam('navformparams');\n        if ($configParameters !== null) {\n            return $configParameters;\n        }\n        $parameters = '';\n        foreach (Registry::getConfig()->getTopActiveView()->getNavigationParams() as $name => $value) {\n            if (isset($value)) {\n                $parameters .= \\sprintf(\n                    '<input type=\"hidden\" name=\"%s\" value=\"%s\">' . \"\\n\",\n                    $name,\n                    Str::getStr()->htmlentities($value)\n                );\n            }\n        }\n        $this->setViewConfigParam('navformparams', $parameters);\n\n        return $parameters;\n    }\n\n    public function getStockOnDefaultMessage()\n    {\n        return Registry::getConfig()->getConfigParam('blStockOnDefaultMessage');\n    }\n\n    public function getStockOffDefaultMessage()\n    {\n        return Registry::getConfig()->getConfigParam('blStockOffDefaultMessage');\n    }\n\n    public function getStockLowDefaultMessage(): bool\n    {\n        return (bool) Registry::getConfig()->getConfigParam('blStockLowDefaultMessage');\n    }\n\n    /**\n     * Returns shop version defined in view\n     *\n     * @return string\n     */\n    public function getShopVersion()\n    {\n        return $this->getViewConfigParam('sShopVersion');\n    }\n\n    /**\n     * Returns AJAX request url\n     *\n     * @return  string\n     */\n    public function getAjaxLink()\n    {\n        return $this->getViewConfigParam('ajaxlink');\n    }\n\n    /**\n     * Returns multishop status\n     *\n     * @return bool\n     */\n    public function isMultiShop()\n    {\n        $oShop = Registry::getConfig()->getActiveShop();\n\n        return isset($oShop->oxshops__oxismultishop) ? ((bool) $oShop->oxshops__oxismultishop->value) : false;\n    }\n\n    /**\n     * Returns session Remote Access token. Later you can pass the token over rtoken URL param\n     * when you want to access the shop, for example, from different client.\n     *\n     * @return string\n     */\n    public function getRemoteAccessToken()\n    {\n        return Registry::getSession()->getRemoteAccessToken();\n    }\n\n    /**\n     * Returns name of a view class, which will be active for an action\n     * (given a generic fnc, e.g. logout)\n     *\n     * @return string\n     */\n    public function getActionClassName()\n    {\n        return Registry::getConfig()->getActiveView()->getActionClassName();\n    }\n\n    /**\n     * Returns name of a class of top view in the chain\n     * (given a generic fnc, e.g. logout)\n     *\n     * @return string\n     */\n    public function getTopActionClassName()\n    {\n        return Registry::getConfig()->getTopActiveView()->getActionClassName();\n    }\n\n    /**\n     * should basket timeout counter be shown?\n     *\n     * @return bool\n     */\n    public function getShowBasketTimeout()\n    {\n        $session = Registry::getSession();\n        return Registry::getConfig()->getConfigParam('blPsBasketReservationEnabled')\n               && ($session->getBasketReservations()->getTimeLeft() > 0);\n    }\n\n    /**\n     * return the seconds left until basket expiration\n     *\n     * @return int\n     */\n    public function getBasketTimeLeft()\n    {\n        if (!isset($this->_dBasketTimeLeft)) {\n            $session = Registry::getSession();\n            $this->_dBasketTimeLeft = $session->getBasketReservations()->getTimeLeft();\n        }\n\n        return $this->_dBasketTimeLeft;\n    }\n\n    /**\n     * min length of password\n     *\n     * @return int\n     */\n    public function getPasswordLength()\n    {\n        return Registry::getInputValidator()->getPasswordLength();\n    }\n\n    /**\n     * Return country list\n     *\n     * @return \\OxidEsales\\Eshop\\Application\\Model\\CountryList\n     */\n    public function getCountryList()\n    {\n        if ($this->_oCountryList === null) {\n            // passing country list\n            $this->_oCountryList = oxNew(\\OxidEsales\\Eshop\\Application\\Model\\CountryList::class);\n            $this->_oCountryList->loadActiveCountries();\n        }\n\n        return $this->_oCountryList;\n    }\n\n\n    /**\n     * Return path to the requested module file\n     *\n     * @param string $moduleId\n     * @param string $filePath\n     *\n     * @return string\n     * @throws FileException\n     *\n     */\n    public function getModulePath(string $moduleId, string $filePath = ''): string\n    {\n        if (!$filePath || ($filePath[0] !== '/')) {\n            $filePath = '/' . $filePath;\n        }\n\n        $filePath = ContainerFacade::get(ModuleAssetsPathResolverBridgeInterface::class)\n            ->getAssetsPath($moduleId) . $filePath;\n\n        $this->validateModuleFile($filePath, $moduleId);\n\n        return $filePath;\n    }\n\n    /**\n     * return url to the requested module file\n     *\n     * @param string $sModule module name (directory name in modules dir)\n     * @param string $sFile   file name to lookup\n     *\n     * @throws \\oxFileException\n     *\n     * @return string\n     */\n    public function getModuleUrl($sModule, $sFile = '')\n    {\n        $config = Registry::getConfig();\n\n        $moduleUrl = str_replace(\n            rtrim(ContainerFacade::getParameter('oxid_esales.shop_source_directory'), '/'),\n            rtrim($config->getCurrentShopUrl(false), '/'),\n            $this->getModulePath($sModule, $sFile)\n        );\n\n        return $moduleUrl;\n    }\n\n    /**\n     * return param value\n     *\n     * @param string $sName param name\n     *\n     * @return mixed\n     */\n    public function getViewThemeParam($sName)\n    {\n        $sValue = false;\n        if (Registry::getConfig()->isThemeOption($sName)) {\n            $sValue = Registry::getConfig()->getConfigParam($sName);\n        }\n\n        return $sValue;\n    }\n\n\n    /**\n     * Returns true if selection lists must be displayed in details page\n     *\n     * @return bool\n     */\n    public function showSelectLists()\n    {\n        return (bool) Registry::getConfig()->getConfigParam('bl_perfLoadSelectLists');\n    }\n\n    /**\n     * Returns true if selection lists must be displayed in details page\n     *\n     * @return bool\n     */\n    public function showSelectListsInList()\n    {\n        return $this->showSelectLists() && Registry::getConfig()->getConfigParam('bl_perfLoadSelectListsInAList');\n    }\n\n    /**\n     * Checks if alternative image server is configured.\n     *\n     * @return bool\n     */\n    public function isAltImageServerConfigured()\n    {\n        return (bool) ContainerFacade::getParameter('oxid_esales.alternative_image_url');\n    }\n\n    /**\n     * Get config parameter for view to check if functionality is turned on or off.\n     *\n     * @param string $sParamName config parameter name.\n     *\n     * @return bool\n     */\n    public function isFunctionalityEnabled($sParamName)\n    {\n        return (bool) Registry::getConfig()->getConfigParam($sParamName);\n    }\n\n    /**\n     * Returns active theme name\n     *\n     * @return string\n     */\n    public function getActiveTheme()\n    {\n        if ($this->_sActiveTheme === null) {\n            $oTheme = oxNew(\\OxidEsales\\Eshop\\Core\\Theme::class);\n            $this->_sActiveTheme = $oTheme->getActiveThemeId();\n        }\n\n        return $this->_sActiveTheme;\n    }\n\n    /**\n     * Returns shop logo image file name from config option\n     *\n     * @return string\n     */\n    public function getShopLogo()\n    {\n        if ($this->_sShopLogo === null) {\n            $sLogoImage = ContainerFacade::getParameter('oxid_esales.shop_logo');\n            if (empty($sLogoImage)) {\n                $sLogoImage = 'logo_' . strtolower(Registry::getConfig()->getEdition()->value) . '.png';\n            }\n            $this->setShopLogo($sLogoImage);\n        }\n\n        return $this->_sShopLogo;\n    }\n\n    /**\n     * Sets shop logo\n     *\n     * @param string $sLogo shop logo image file name\n     */\n    public function setShopLogo($sLogo)\n    {\n        $this->_sShopLogo = $sLogo;\n    }\n\n    /**\n     * retrieve session challenge token from session\n     *\n     * @return string\n     */\n    public function getSessionChallengeToken()\n    {\n        if (Registry::getSession()->isSessionStarted()) {\n            $session = Registry::getSession();\n            $sessionChallengeToken = $session->getSessionChallengeToken();\n        } else {\n            $sessionChallengeToken = \"\";\n        }\n\n        return $sessionChallengeToken;\n    }\n\n    /**\n     * Checks whether module is enabled.\n     *\n     * @param string $moduleId Module id\n     *\n     * @return bool\n     */\n    private function isModuleEnabled($moduleId): bool\n    {\n        return ContainerFacade::get(ModuleActivationBridgeInterface::class)\n            ->isActive(\n                $moduleId,\n                Registry::getConfig()->getShopId()\n            );\n    }\n\n    /**\n     * Return shop edition (EE|CE|PE)\n     *\n     * @return string\n     */\n    public function getEdition()\n    {\n        return Registry::getConfig()->getEdition()->value;\n    }\n\n    /**\n     * Hook for modules.\n     * Returns array of params => values which are used in hidden forms and as additional url params.\n     * NOTICE: this method SHOULD return raw (non encoded into entities) parameters, because values\n     * are processed by htmlentities() to avoid security and broken templates problems\n     *\n     * @return array\n     */\n    public function getAdditionalNavigationParameters()\n    {\n        return [];\n    }\n\n    /**\n     * Hook for modules.\n     * Template variable getter. Returns additional params for url\n     *\n     * @return string\n     */\n    public function getAdditionalParameters()\n    {\n        return '';\n    }\n\n    /**\n     * Hook for modules.\n     * Collects additional _GET parameters used by eShop\n     *\n     * @return string\n     */\n    public function addRequestParameters()\n    {\n        return '';\n    }\n\n    /**\n     * Hook for modules.\n     * returns additional url params for dynamic url building\n     *\n     * @param string $listType\n     *\n     * @return string\n     */\n    public function getDynUrlParameters($listType)\n    {\n        return '';\n    }\n\n    /**\n     * @param string $filePath\n     * @param string $moduleId\n     * @throws FileException\n     */\n    private function validateModuleFile(string $filePath, string $moduleId): void\n    {\n        if (!file_exists($filePath)) {\n            $exception = new FileException(\"Requested file not found for module $moduleId ($filePath)\");\n            if (ContainerFacade::getParameter('oxid_esales.debug_mode')) {\n                throw $exception;\n            }\n            Registry::getLogger()->error($exception->getMessage(), [$exception]);\n        }\n    }\n}\n"
  },
  {
    "path": "source/Core/ViewHelper/BaseRegistrator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\ViewHelper;\n\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Core\\Registry;\n\n/**\n * Base class for preparing JavaScript and Stylesheets.\n */\nabstract class BaseRegistrator\n{\n    const TAG_NAME = 'base';\n\n    /** @var \\OxidEsales\\Eshop\\Core\\Config */\n    protected $config;\n\n    /**\n     * BaseRegistrator constructor.\n     *\n     * provide config as class-property\n     */\n    public function __construct()\n    {\n        $this->config = Registry::getConfig();\n    }\n\n    /**\n     * Separate query part, appends query part if needed, append file modification timestamp.\n     *\n     * @param string $fullUrl\n     *\n     * @return string\n     */\n    protected function formLocalFileUrl($fullUrl)\n    {\n        $parts = explode('?', $fullUrl);\n        $url = $parts[0];\n        $parameters = $parts[1] ?? '';\n        if (empty($parameters)) {\n            if (preg_match('#^(https?:)?//#', $fullUrl) && Registry::getUtilsUrl()->isCurrentShopHost($url)) {\n                $path = $this->getPathByUrl($url);\n            } else {\n                $path = $this->config->getResourcePath($url, $this->config->isAdmin());\n                $url = $this->config->getResourceUrl($url, $this->config->isAdmin());\n            }\n            $parameters = $this->getFileModificationTime($path);\n        }\n\n        if (empty($url) && ContainerFacade::getParameter('oxid_esales.debug_mode')) {\n            $error = \"{\" . static::TAG_NAME . \"} resource not found: \" . \\OxidEsales\\Eshop\\Core\\Str::getStr()->htmlspecialchars($url);\n            trigger_error($error, E_USER_WARNING);\n        }\n\n        return $url . ($parameters ? '?' . $parameters : '');\n    }\n\n    /**\n     * Returns modification time for given file\n     *\n     * @param string $file path to file\n     *\n     * @return string UNIX-timestamp or empty string\n     */\n    protected function getFileModificationTime($file)\n    {\n        $result = '';\n        if (file_exists($file)) {\n            $result = filemtime($file);\n        }\n\n        return $result;\n    }\n\n    /**\n     * get absolute path to file from url\n     *\n     * @param string $url url to file\n     *\n     * @return string path to file\n     */\n    protected function getPathByUrl($url)\n    {\n        $config = Registry::getConfig();\n        return str_replace(\n            rtrim($config->getCurrentShopUrl(false), '/'),\n            rtrim(ContainerFacade::getParameter('oxid_esales.shop_source_directory'), '/'),\n            $url\n        );\n    }\n}\n"
  },
  {
    "path": "source/Core/ViewHelper/JavaScriptRegistrator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\ViewHelper;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class for preparing JavaScript.\n */\nclass JavaScriptRegistrator extends BaseRegistrator\n{\n    const SNIPPETS_PARAMETER_NAME = 'scripts';\n    const FILES_PARAMETER_NAME = 'includes';\n    const TAG_NAME = 'oxscript';\n\n    /**\n     * Register JavaScript code snippet for rendering.\n     *\n     * @param string $script\n     * @param bool   $isDynamic\n     */\n    public function addSnippet($script, $isDynamic = false)\n    {\n        $suffix = $isDynamic ? '_dynamic' : '';\n        $scriptsParameterName = static::SNIPPETS_PARAMETER_NAME . $suffix;\n        $scripts = (array) $this->config->getGlobalParameter($scriptsParameterName);\n        $script = trim($script);\n        if (!in_array($script, $scripts)) {\n            $scripts[] = $script;\n        }\n        $this->config->setGlobalParameter($scriptsParameterName, $scripts);\n    }\n\n    /**\n     * Register JavaScript file (local or remote) for rendering.\n     *\n     * @param string $file\n     * @param int    $priority\n     * @param bool   $isDynamic\n     */\n    public function addFile($file, $priority, $isDynamic = false)\n    {\n        $suffix = $isDynamic ? '_dynamic' : '';\n        $filesParameterName = static::FILES_PARAMETER_NAME . $suffix;\n        $includes = (array) $this->config->getGlobalParameter($filesParameterName);\n\n        if (!preg_match('#^https?://#', $file) || Registry::getUtilsUrl()->isCurrentShopHost($file)) {\n            $file = $this->formLocalFileUrl($file);\n        }\n\n        if ($file) {\n            $includes[$priority][] = $file;\n            $includes[$priority] = array_unique($includes[$priority]);\n            $this->config->setGlobalParameter($filesParameterName, $includes);\n        }\n    }\n}\n"
  },
  {
    "path": "source/Core/ViewHelper/JavaScriptRenderer.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\ViewHelper;\n\n/**\n * Class for preparing JavaScript.\n */\nclass JavaScriptRenderer\n{\n    /**\n     * Renders all registered JavaScript snippets and files.\n     *\n     * @param string $widget      Widget name\n     * @param bool   $forceRender Force rendering of scripts.\n     * @param bool   $isDynamic   Force rendering of scripts.\n     *\n     * @return string\n     */\n    public function render($widget, $forceRender, $isDynamic = false)\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $output = '';\n        $suffix = $isDynamic ? '_dynamic' : '';\n        $filesParameterName = \\OxidEsales\\Eshop\\Core\\ViewHelper\\JavaScriptRegistrator::FILES_PARAMETER_NAME . $suffix;\n        $scriptsParameterName = \\OxidEsales\\Eshop\\Core\\ViewHelper\\JavaScriptRegistrator::SNIPPETS_PARAMETER_NAME . $suffix;\n\n        $isAjaxRequest = $this->isAjaxRequest();\n        $forceRender = $this->shouldForceRender($forceRender, $isAjaxRequest);\n\n        if (!$widget || $forceRender) {\n            if (!$isAjaxRequest) {\n                $files = $this->prepareFilesForRendering($config->getGlobalParameter($filesParameterName), $widget);\n                $output .= $this->formFilesOutput($files, $widget);\n                $config->setGlobalParameter($filesParameterName, null);\n                if ($widget) {\n                    $dynamicIncludes = (array)$config->getGlobalParameter(\\OxidEsales\\Eshop\\Core\\ViewHelper\\JavaScriptRegistrator::FILES_PARAMETER_NAME . '_dynamic');\n                    $output .= $this->formFilesOutput($dynamicIncludes, $widget);\n                    $config->setGlobalParameter(\\OxidEsales\\Eshop\\Core\\ViewHelper\\JavaScriptRegistrator::FILES_PARAMETER_NAME . '_dynamic', null);\n                }\n            }\n\n            // Form output for adds.\n            $snippets = (array)$config->getGlobalParameter($scriptsParameterName);\n            $scriptOutput = $this->formSnippetsOutput($snippets, $widget, $isAjaxRequest);\n            $config->setGlobalParameter($scriptsParameterName, null);\n            if ($widget) {\n                $dynamicScripts = (array) $config->getGlobalParameter(\\OxidEsales\\Eshop\\Core\\ViewHelper\\JavaScriptRegistrator::SNIPPETS_PARAMETER_NAME . '_dynamic');\n                $scriptOutput .= $this->formSnippetsOutput($dynamicScripts, $widget, $isAjaxRequest);\n                $config->setGlobalParameter(\\OxidEsales\\Eshop\\Core\\ViewHelper\\JavaScriptRegistrator::SNIPPETS_PARAMETER_NAME . '_dynamic', null);\n            }\n            $output .= $this->enclose($scriptOutput, $widget, $isAjaxRequest);\n        }\n\n        return $output;\n    }\n\n    /**\n     * Returns if it is ajax request.\n     *\n     * @return bool\n     */\n    protected function isAjaxRequest()\n    {\n        return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';\n    }\n\n    /**\n     * Returns whether rendering of scripts should be forced.\n     *\n     * @param bool $forceRender\n     * @param bool $isAjaxRequest\n     *\n     * @return bool\n     */\n    protected function shouldForceRender($forceRender, $isAjaxRequest)\n    {\n        return $isAjaxRequest ? true : $forceRender;\n    }\n\n    /**\n     * Returns files list for rendering.\n     *\n     * @param array  $files\n     * @param string $widget\n     *\n     * @return array\n     */\n    protected function prepareFilesForRendering($files, $widget)\n    {\n        return (array) $files;\n    }\n\n    /**\n     * Form output for includes.\n     *\n     * @param array  $includes String files to include.\n     * @param string $widget   Widget name.\n     *\n     * @return string\n     */\n    protected function formFilesOutput($includes, $widget)\n    {\n        if (!count($includes)) {\n            return '';\n        }\n\n        ksort($includes); // Sort by priority.\n        $usedSources = [];\n        $widgets = [];\n        $widgetTemplate = \"WidgetsHandler.registerFile('%s', '%s');\";\n        $scriptTemplate = '<script src=\"%s\"></script>';\n        foreach ($includes as $priority) {\n            foreach ($priority as $source) {\n                if (!in_array($source, $usedSources)) {\n                    $widgets[] = sprintf(($widget ? $widgetTemplate : $scriptTemplate), $source, $widget);\n                    $usedSources[] = $source;\n                }\n            }\n        }\n        $output = implode(PHP_EOL, $widgets);\n        if ($widget && !empty($output)) {\n            $output = <<<JS\n<script>\n    window.addEventListener('load', function() {\n        $output\n    }, false)\n</script>\nJS;\n        }\n\n        return $output;\n    }\n\n    /**\n     * Forms how javascript should look like when output.\n     * If varnish is active, javascript should be passed to WidgetsHandler instead of direct call.\n     *\n     * @param array  $scripts     Scripts to execute (from add).\n     * @param string $widgetName  Widget name.\n     * @param bool   $ajaxRequest Is ajax request.\n     *\n     * @return string\n     */\n    protected function formSnippetsOutput($scripts, $widgetName, $ajaxRequest)\n    {\n        $preparedScripts = [];\n        foreach ($scripts as $script) {\n            if ($widgetName && !$ajaxRequest) {\n                $sanitizedScript = $this->sanitize($script);\n                $script = \"WidgetsHandler.registerFunction('$sanitizedScript', '$widgetName');\";\n            }\n            $preparedScripts[] = $script;\n        }\n\n        return implode(PHP_EOL, $preparedScripts);\n    }\n\n    /**\n     * Sanitize javascript, which will be passed to WidgetsHandler.\n     *\n     * @param string $scripts\n     *\n     * @return string\n     */\n    protected function sanitize($scripts)\n    {\n        return strtr($scripts, ['\\\\' => '\\\\\\\\', \"'\" => \"\\\\'\", \"\\r\" => '', \"\\n\" => '\\n']);\n    }\n\n    /**\n     * Enclose with script tag or add in function for wiget.\n     *\n     * @param string $scriptsOutput javascript to be enclosed.\n     * @param string $widget        widget name.\n     * @param bool   $isAjaxRequest is ajax request\n     *\n     * @return string\n     */\n    protected function enclose($scriptsOutput, $widget, $isAjaxRequest)\n    {\n        if ($scriptsOutput) {\n            if ($widget && !$isAjaxRequest) {\n                $scriptsOutput = \"window.addEventListener('load', function() { $scriptsOutput }, false )\";\n            }\n\n            return \"<script>$scriptsOutput</script>\";\n        }\n    }\n}\n"
  },
  {
    "path": "source/Core/ViewHelper/StyleRegistrator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\ViewHelper;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\n/**\n * Class for preparing Stylesheets.\n */\nclass StyleRegistrator extends BaseRegistrator\n{\n    const CONDITIONAL_STYLES_PARAMETER_NAME = 'conditional_styles';\n    const STYLES_PARAMETER_NAME = 'styles';\n    const TAG_NAME = 'oxstyle';\n\n    /**\n     * Separate query part #3305.\n     *\n     * @param string $style\n     * @param string $condition\n     * @param bool   $isDynamic\n     */\n    public function addFile($style, $condition, $isDynamic)\n    {\n        $suffix = $isDynamic ? '_dynamic' : '';\n\n        if (!preg_match('#^https?://#', $style) || Registry::getUtilsUrl()->isCurrentShopHost($style)) {\n            $style = $this->formLocalFileUrl($style);\n        }\n\n        if ($style) {\n            if (!empty($condition)) {\n                $conditionalStylesParameterName = static::CONDITIONAL_STYLES_PARAMETER_NAME . $suffix;\n                $conditionalStyles = (array) $this->config->getGlobalParameter($conditionalStylesParameterName);\n                $conditionalStyles[$style] = $condition;\n                $this->config->setGlobalParameter($conditionalStylesParameterName, $conditionalStyles);\n            } else {\n                $stylesParameterName = static::STYLES_PARAMETER_NAME . $suffix;\n                $styles = (array) $this->config->getGlobalParameter($stylesParameterName);\n                $styles[] = $style;\n                $styles = array_unique($styles);\n                $this->config->setGlobalParameter($stylesParameterName, $styles);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Core/ViewHelper/StyleRenderer.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core\\ViewHelper;\n\n/**\n * Class for preparing Stylesheets.\n */\nclass StyleRenderer\n{\n    /**\n     * @param string $widget\n     * @param bool   $forceRender\n     * @param bool   $isDynamic\n     *\n     * @return string\n     */\n    public function render($widget, $forceRender, $isDynamic)\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $suffix = $isDynamic ? '_dynamic' : '';\n        $output = '';\n\n        if (!$widget || $this->shouldForceRender($forceRender)) {\n            $styles = (array) $config->getGlobalParameter(\\OxidEsales\\Eshop\\Core\\ViewHelper\\StyleRegistrator::STYLES_PARAMETER_NAME . $suffix);\n            $output .= $this->formStylesOutput($styles);\n            $output .= PHP_EOL;\n            $conditionalStyles = (array) $config->getGlobalParameter(\\OxidEsales\\Eshop\\Core\\ViewHelper\\StyleRegistrator::CONDITIONAL_STYLES_PARAMETER_NAME . $suffix);\n            $output .= $this->formConditionalStylesOutput($conditionalStyles);\n        }\n\n        return $output;\n    }\n\n    /**\n     * Returns whether rendering of scripts should be forced.\n     *\n     * @param bool $forceRender\n     *\n     * @return bool\n     */\n    protected function shouldForceRender($forceRender)\n    {\n        return $forceRender;\n    }\n\n    /**\n     * @param array $styles\n     *\n     * @return string\n     */\n    protected function formStylesOutput($styles)\n    {\n        $preparedStyles = [];\n        $template = '<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\" />';\n        foreach ($styles as $style) {\n            $preparedStyles[] = sprintf($template, $style);\n        }\n\n        return implode(PHP_EOL, $preparedStyles);\n    }\n\n    /**\n     * @param array $styles\n     *\n     * @return string\n     */\n    protected function formConditionalStylesOutput($styles)\n    {\n        $preparedStyles = [];\n        $template = '<!--[if %s]><link rel=\"stylesheet\" type=\"text/css\" href=\"%s\"><![endif]-->';\n        foreach ($styles as $style => $condition) {\n            $preparedStyles[] = sprintf($template, $condition, $style);\n        }\n\n        return implode(PHP_EOL, $preparedStyles);\n    }\n}\n"
  },
  {
    "path": "source/Core/WidgetControl.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Core;\n\nuse OxidEsales\\Eshop\\Application\\Component\\Widget\\WidgetController;\nuse OxidEsales\\Eshop\\Core\\Exception\\ObjectException;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererInterface;\n\n/**\n * Main shop actions controller. Processes user actions, logs\n * them (if needed), controls output, redirects according to\n * processed methods logic. This class is initialized from index.php\n */\nclass WidgetControl extends \\OxidEsales\\Eshop\\Core\\ShopControl\n{\n    /**\n     * Skip main tasks as it already handled in oxShopControl.\n     *\n     * @var bool\n     */\n    protected $_blMainTasksExecuted = true;\n\n    /**\n     * Array of Views added to the view chain\n     *\n     * @var array\n     */\n    protected $parentsAdded = [];\n\n    /**\n     * Main shop widget manager. Sets needed parameters and calls parent::start method.\n     *\n     * Session variables:\n     * <b>actshop</b>\n     *\n     * @param string $class      Class name\n     * @param string $function   Function name\n     * @param array  $parameters Parameters array\n     * @param array  $viewsChain Array of views names that should be initialized also\n     */\n    public function start($class = null, $function = null, $parameters = null, $viewsChain = null)\n    {\n        if (!isset($viewsChain) && Registry::getRequest()->getRequestEscapedParameter('oxwparent')) {\n            $viewsChain = explode(\"|\", Registry::getRequest()->getRequestEscapedParameter('oxwparent'));\n        }\n\n        parent::start($class, $function, $parameters, $viewsChain);\n\n        //perform tasks that should be done at the end of widget processing\n        $this->runLast();\n    }\n\n    /**\n     * Runs actions that should be performed at the controller finish.\n     */\n    protected function runLast()\n    {\n        $oConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        if ($oConfig->hasActiveViewsChain()) {\n            // Removing current active view.\n            $oConfig->dropLastActiveView();\n\n            foreach ($this->parentsAdded as $sParentClassName) {\n                $oConfig->dropLastActiveView();\n            }\n\n            // Setting back last active view.\n            $engine = $this->getRenderer()->getTemplateEngine();\n            $engine->addGlobal('oView', $oConfig->getActiveView());\n        }\n    }\n\n    /**\n     * Initialize and return widget view object.\n     *\n     * @param string $class      View class\n     * @param string $function   Function name\n     * @param array  $parameters Parameters array\n     * @param array  $viewsChain Array of views keys that should be initialized as well\n     *\n     * @throws ObjectException\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Controller\\BaseController Current active view\n     */\n    protected function initializeViewObject($class, $function, $parameters = null, $viewsChain = null)\n    {\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $activeViewsIds = $config->getActiveViewsIds();\n        $activeViewsIds = array_map(\"strtolower\", $activeViewsIds);\n        $classKey = Registry::getControllerClassNameResolver()->getIdByClassName($class);\n        $classKey = !is_null($classKey) ? $classKey : $class; //fallback\n\n        // if exists views chain, initializing these view at first\n        if (is_array($viewsChain) && !empty($viewsChain)) {\n            foreach ($viewsChain as $parentClassKey) {\n                $parentClass = Registry::getControllerClassNameResolver()->getClassNameById($parentClassKey);\n\n                if ($parentClassKey != $classKey && !in_array(strtolower($parentClassKey), $activeViewsIds) && $parentClass) {\n                    // creating parent view object\n                    $viewObject = oxNew($parentClass);\n                    if ('oxubase' != strtolower($parentClassKey)) {\n                        $viewObject->setClassKey($parentClassKey);\n                    }\n                    $config->setActiveView($viewObject);\n                    $this->parentsAdded[] = $parentClassKey;\n                }\n            }\n        }\n\n        $widgetViewObject = parent::initializeViewObject($class, $function, $parameters, null);\n\n        if (!is_a($widgetViewObject, WidgetController::class)) {\n            /** @var ObjectException $exception */\n            $exception = oxNew(ObjectException::class, get_class($widgetViewObject) . ' is not an instance of ' . WidgetController::class);\n            throw $exception;\n        }\n\n        // Set template name for current widget.\n        if (!empty($parameters['oxwtemplate'])) {\n            $widgetViewObject->setTemplateName($parameters['oxwtemplate']);\n        }\n\n        return $widgetViewObject;\n    }\n\n    /**\n     * @internal\n     *\n     * @return TemplateRendererInterface\n     */\n    private function getRenderer()\n    {\n        return ContainerFacade::get(TemplateRendererBridgeInterface::class)\n            ->getTemplateRenderer();\n    }\n}\n"
  },
  {
    "path": "source/Core/utils/oxpicgenerator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\n// checks if GD library version getter does not exist\nif (!function_exists(\"getGdVersion\")) {\n    /**\n     * Returns GD library version\n     *\n     * @return int\n     */\n    function getGdVersion()\n    {\n        return \\OxidEsales\\Eshop\\Core\\Registry::getConfig()->getConfigParam('iUseGDVersion');\n    }\n}\n\n// checks if image creation function does not exist\nif (!function_exists(\"copyAlteredImage\")) {\n    /**\n     * Creates and copies the resized image\n     *\n     * @param string $sDestinationImage file + path of destination\n     * @param string $sSourceImage      file + path of source\n     * @param int    $iNewWidth         new width of the image\n     * @param int    $iNewHeight        new height of the image\n     * @param array  $aImageInfo        additional info\n     * @param string $sTarget           target file path @deprecated\n     * @param int    $iGdVer            used gd version @deprecated\n     *\n     * @return bool\n     */\n    function copyAlteredImage($sDestinationImage, $sSourceImage, $iNewWidth, $iNewHeight, $aImageInfo, $sTarget = null, $iGdVer = null)\n    {\n        return imagecopyresampled($sDestinationImage, $sSourceImage, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $aImageInfo[0], $aImageInfo[1]);\n    }\n}\n\n// checks if image size calculator does nor exist\nif (!function_exists(\"calcImageSize\")) {\n    /**\n     * Calculates proportional new image size\n     *\n     * @param int $iDesiredWidth  expected image width\n     * @param int $iDesiredHeight expected image height\n     * @param int $iPrefWidth     original image width\n     * @param int $iPrefHeight    original image height\n     *\n     * @return array\n     */\n    function calcImageSize($iDesiredWidth, $iDesiredHeight, $iPrefWidth, $iPrefHeight)\n    {\n        // #1837/1177M - do not resize smaller pictures\n        if ($iDesiredWidth < $iPrefWidth || $iDesiredHeight < $iPrefHeight) {\n            if ($iPrefWidth >= $iPrefHeight * ((float) ($iDesiredWidth / $iDesiredHeight))) {\n                $iNewHeight = round(($iPrefHeight * (float) ($iDesiredWidth / $iPrefWidth)), 0);\n                $iNewWidth = $iDesiredWidth;\n            } else {\n                $iNewHeight = $iDesiredHeight;\n                $iNewWidth = round(($iPrefWidth * (float) ($iDesiredHeight / $iPrefHeight)), 0);\n            }\n        } else {\n            $iNewWidth = $iPrefWidth;\n            $iNewHeight = $iPrefHeight;\n        }\n\n        return [$iNewWidth, $iNewHeight];\n    }\n}\n\nif (!function_exists(\"checkSizeAndCopy\")) {\n    /**\n     * Checks if preferred image dimensions size matches defined in config;\n     * in case it matches - copies original image to new location, returns\n     * copying state - TRUE/FALSe else - returns array with new dimensions\n     * array( $iNewWidth, $iNewHeight );\n     *\n     * @param string $sSrc        image source file name\n     * @param string $sTarget     target location\n     * @param int    $iWidth      preferred width\n     * @param int    $iHeight     preferred height\n     * @param int    $iOrigWidth  original width\n     * @param int    $iOrigHeight preferred height\n     *\n     * @return mixed\n     */\n    function checkSizeAndCopy($sSrc, $sTarget, $iWidth, $iHeight, $iOrigWidth, $iOrigHeight)\n    {\n        list($iNewWidth, $iNewHeight) = calcImageSize($iWidth, $iHeight, $iOrigWidth, $iOrigHeight);\n        if ($iNewWidth == $iOrigWidth && $iNewHeight == $iOrigHeight) {\n            return copy($sSrc, $sTarget);\n        } else {\n            return [$iNewWidth, $iNewHeight];\n        }\n    }\n}\n\n// checks if GIF resizer does not exist\nif (!function_exists(\"resizeGif\")) {\n    /**\n     * Creates resized GIF image. Returns path of new file if creation\n     * succeed. On error returns FALSE\n     *\n     * @param string $sSrc            GIF source\n     * @param string $sTarget         new image location\n     * @param int    $iWidth          new width\n     * @param int    $iHeight         new height\n     * @param int    $iOriginalWidth  original width\n     * @param int    $iOriginalHeight original height\n     * @param int    $iGDVer          GD library version @deprecated\n     *\n     * @return string|false\n     */\n    function resizeGif($sSrc, $sTarget, $iWidth, $iHeight, $iOriginalWidth, $iOriginalHeight, $iGDVer)\n    {\n        $aResult = checkSizeAndCopy($sSrc, $sTarget, $iWidth, $iHeight, $iOriginalWidth, $iOriginalHeight);\n        if (is_array($aResult)) {\n            list($iNewWidth, $iNewHeight) = $aResult;\n            $hDestinationImage = imagecreatetruecolor($iNewWidth, $iNewHeight);\n            $hSourceImage = imagecreatefromgif($sSrc);\n\n            $iFillColor = imagecolorresolve($hDestinationImage, 255, 255, 255);\n            imagefill($hDestinationImage, 0, 0, $iFillColor);\n            imagecolortransparent($hDestinationImage, $iFillColor);\n\n            imagecopyresampled($hDestinationImage, $hSourceImage, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $iOriginalWidth, $iOriginalHeight);\n\n            imagegif($hDestinationImage, $sTarget);\n        }\n\n        return $sTarget;\n    }\n}\n\n// checks if PNG resizer does not exist\nif (!function_exists(\"resizePng\")) {\n    /**\n     * Creates resized PNG image. Returns path of new file if creation\n     * succeded. On error returns FALSE\n     *\n     * @param string   $sSrc              JPG source\n     * @param string   $sTarget           new image location\n     * @param int      $iWidth            new width\n     * @param int      $iHeight           new height\n     * @param int      $aImageInfo        original width\n     * @param int      $iGdVer            GD library version @deprecated\n     * @param resource $hDestinationImage destination image handle\n     *\n     * @return string|false\n     */\n    function resizePng($sSrc, $sTarget, $iWidth, $iHeight, $aImageInfo, $iGdVer, $hDestinationImage)\n    {\n        $aResult = checkSizeAndCopy($sSrc, $sTarget, $iWidth, $iHeight, $aImageInfo[0], $aImageInfo[1]);\n        if (is_array($aResult)) {\n            list($iNewWidth, $iNewHeight) = $aResult;\n            if ($hDestinationImage === null) {\n                $hDestinationImage = imagecreatetruecolor($iNewWidth, $iNewHeight);\n            }\n            $hSourceImage = imagecreatefrompng($sSrc);\n            if (!imageistruecolor($hSourceImage)) {\n                $hDestinationImage = imagecreate($iNewWidth, $iNewHeight);\n                // fix for transparent images sets image to transparent\n                $imgWhite = imagecolorallocate($hDestinationImage, 255, 255, 255);\n                imagefill($hDestinationImage, 0, 0, $imgWhite);\n                imagecolortransparent($hDestinationImage, $imgWhite);\n            //end of fix\n            } else {\n                imagealphablending($hDestinationImage, false);\n                imagesavealpha($hDestinationImage, true);\n            }\n            if (\n                copyAlteredImage(\n                    $hDestinationImage,\n                    $hSourceImage,\n                    $iNewWidth,\n                    $iNewHeight,\n                    $aImageInfo,\n                    $sTarget,\n                    $iGdVer\n                )\n            ) {\n                imagepng($hDestinationImage, $sTarget);\n            }\n        }\n\n        return $sTarget;\n    }\n}\n\n// checks if JPG resizer does not exist\nif (!function_exists(\"resizeJpeg\")) {\n    /**\n     * Creates resized JPG image. Returns path of new file if creation\n     * succeed. On error returns FALSE\n     *\n     * @param string   $sSrc              JPG source\n     * @param string   $sTarget           new image location\n     * @param int      $iWidth            new width\n     * @param int      $iHeight           new height\n     * @param int      $aImageInfo        original width\n     * @param int      $iGdVer            GD library version @deprecated\n     * @param resource $hDestinationImage destination image handle\n     * @param int      $iDefQuality       new image quality\n     *\n     * @return string|false\n     */\n    function resizeJpeg($sSrc, $sTarget, $iWidth, $iHeight, $aImageInfo, $iGdVer, $hDestinationImage, $iDefQuality)\n    {\n        $aResult = checkSizeAndCopy($sSrc, $sTarget, $iWidth, $iHeight, $aImageInfo[0], $aImageInfo[1]);\n        if (is_array($aResult)) {\n            list($iNewWidth, $iNewHeight) = $aResult;\n            if ($hDestinationImage === null) {\n                $hDestinationImage = imagecreatetruecolor($iNewWidth, $iNewHeight);\n            }\n            $hSourceImage = imagecreatefromstring(file_get_contents($sSrc));\n            if (\n                copyAlteredImage(\n                    $hDestinationImage,\n                    $hSourceImage,\n                    $iNewWidth,\n                    $iNewHeight,\n                    $aImageInfo,\n                    $sTarget,\n                    $iGdVer\n                )\n            ) {\n                imagejpeg($hDestinationImage, $sTarget, $iDefQuality);\n            }\n        }\n\n        return $sTarget;\n    }\n}\n\n// checks if WebP resizer doesn't exist\nif (!function_exists(\"resizeWebp\")) {\n    function resizeWebp(string $source, string $target, int $width, int $height, int $quality): string\n    {\n        list($origWidth, $origHeight) = @getimagesize($source);\n        $result = checkSizeAndCopy($source, $target, $width, $height, $origWidth, $origHeight);\n\n        if (is_array($result)) {\n            list($newWidth, $newHeight) = $result;\n            $destinationImage = imagecreatetruecolor($newWidth, $newHeight);\n            $sourceImage = imagecreatefromwebp($source);\n            imagealphablending($destinationImage, false);\n            imagesavealpha($destinationImage, true);\n\n            if (copyAlteredImage($destinationImage, $sourceImage, $newWidth, $newHeight, [$origWidth, $origHeight])) {\n                imagewebp($destinationImage, $target, $quality);\n            }\n        }\n\n        return $target;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Container/BootstrapContainerBuilder.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Container;\n\nuse Symfony\\Component\\Config\\FileLocator;\nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder;\nuse Symfony\\Component\\DependencyInjection\\Loader\\YamlFileLoader;\nuse Symfony\\Component\\EventDispatcher\\DependencyInjection\\RegisterListenersPass;\n\n/**\n * @internal\n */\nclass BootstrapContainerBuilder\n{\n    public function create(): ContainerBuilder\n    {\n        $symfonyContainer = new ContainerBuilder();\n        $symfonyContainer->addCompilerPass(new RegisterListenersPass());\n\n        $loader = new YamlFileLoader($symfonyContainer, new FileLocator(__DIR__));\n        $loader->load('bootstrap-services.yaml');\n\n        return $symfonyContainer;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Container/BootstrapContainerFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Container;\n\nuse Psr\\Container\\ContainerInterface;\n\nclass BootstrapContainerFactory\n{\n    /**\n     * This is a minimal container that does not need the shop\n     * to be installed.\n     *\n     * @return ContainerInterface\n     */\n    public static function getBootstrapContainer(): ContainerInterface\n    {\n        $symfonyContainer = (new BootstrapContainerBuilder())->create();\n        $symfonyContainer->compile(true);\n\n        return $symfonyContainer;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Container/ContainerFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Container;\n\nuse OxidEsales\\Eshop\\Core\\ShopIdCalculator;\nuse OxidEsales\\Eshop\\Core\\UtilsServer;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\ContainerBuilder;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Service\\ContainerCacheInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Service\\FilesystemContainerCache;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContext;\nuse Psr\\Container\\ContainerInterface;\nuse Symfony\\Component\\Filesystem\\Filesystem;\n\n/**\n * @deprecated use OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade\n */\nclass ContainerFactory implements ContainerProviderInterface\n{\n    private static $instance;\n    private ContainerInterface $symfonyContainer;\n    private ContainerCacheInterface $cache;\n    private static ?int $shopId;\n\n    /**\n     * The constructor's private to make class a singleton\n     */\n    private function __construct()\n    {\n        $this->cache = new FilesystemContainerCache(new BasicContext(), new Filesystem());\n    }\n\n    public static function get(): ContainerInterface\n    {\n        return self::getInstance()->getContainer();\n    }\n\n    public function getContainer(): ContainerInterface\n    {\n        $customContainerProvider = getenv('OXID_CONTAINER_PROVIDER');\n        if ($customContainerProvider) {\n           return $customContainerProvider::get();\n        }\n\n        if (!isset($this->symfonyContainer)) {\n            $this->initializeContainer();\n        }\n\n        return $this->symfonyContainer;\n    }\n\n    private function initializeContainer(): void\n    {\n        if ($this->cache->exists(self::getShopId())) {\n            $this->symfonyContainer = $this->cache->get(self::getShopId());\n        } else {\n            $this->compileSymfonyContainer();\n            $this->cache->put($this->symfonyContainer, self::getShopId());\n        }\n    }\n\n    private function compileSymfonyContainer(): void\n    {\n        $containerBuilder = new ContainerBuilder(new BasicContext(), self::getShopId());\n        $this->symfonyContainer = $containerBuilder->getContainer();\n        $this->symfonyContainer->compile(true);\n    }\n\n    public static function getInstance(): ContainerFactory\n    {\n        if (self::$instance === null) {\n            self::$instance = new ContainerFactory();\n        }\n        return self::$instance;\n    }\n\n    public static function resetContainer(): void\n    {\n        $customContainerProvider = getenv('OXID_CONTAINER_PROVIDER');\n        if ($customContainerProvider) {\n            $customContainerProvider::resetContainer();\n        }\n\n        self::$shopId = null;\n        self::getInstance()->cache->invalidate(self::getShopId());\n        self::$instance = null;\n    }\n\n    private static function getShopId(): int\n    {\n        if (!isset(self::$shopId)) {\n            self::$shopId = (new ShopIdCalculator(new UtilsServer()))->getShopId();\n        }\n        return self::$shopId;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Container/ContainerProviderInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Container;\n\nuse Psr\\Container\\ContainerInterface;\n\ninterface ContainerProviderInterface\n{\n    public static function get(): ContainerInterface;\n\n    public static function resetContainer();\n}\n"
  },
  {
    "path": "source/Internal/Container/Event/ConfigurationChangedEventSubscriber.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Container\\Event;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\ContainerFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Event\\ProjectYamlChangedEvent;\nuse Symfony\\Component\\EventDispatcher\\EventSubscriberInterface;\n\nclass ConfigurationChangedEventSubscriber implements EventSubscriberInterface\n{\n    /**\n     * @param ProjectYamlChangedEvent $event\n     */\n    public function resetContainer(ProjectYamlChangedEvent $event)\n    {\n        ContainerFactory::resetContainer();\n    }\n\n    /**\n     * @return array\n     */\n    public static function getSubscribedEvents(): array\n    {\n        return [ProjectYamlChangedEvent::class => 'resetContainer'];\n    }\n}\n"
  },
  {
    "path": "source/Internal/Container/bootstrap-services.yaml",
    "content": "imports:\n    - { resource: ../Framework/DIContainer/services.yaml }\n    - { resource: ../Framework/Event/services.yaml }\n    - { resource: ../Framework/FileSystem/bootstrap-services.yaml }\n    - { resource: ../Framework/Module/Install/bootstrap-services.yaml }\n    - { resource: ../Framework/Module/MetaData/bootstrap-services.yaml }\n    - { resource: ../Framework/Module/Configuration/bootstrap-services.yaml }\n    - { resource: ../Framework/Module/Path/bootstrap-services.yaml }\n    - { resource: ../Transition/Utility/bootstrap-services.yaml }\n"
  },
  {
    "path": "source/Internal/Container/services.yaml",
    "content": "imports:\n    - { resource: bootstrap-services.yaml }\n\nservices:\n    _defaults:\n        autowire: true\n\n    OxidEsales\\EshopCommunity\\Internal\\Container\\Event\\ConfigurationChangedEventSubscriber:\n        class: OxidEsales\\EshopCommunity\\Internal\\Container\\Event\\ConfigurationChangedEventSubscriber\n        tags:\n            - { name: kernel.event_subscriber }\n"
  },
  {
    "path": "source/Internal/Domain/Admin/Command/CreateUserCommand.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Command;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\DataObject\\Admin;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Exception\\EmailAlreadyTakenException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Exception\\InvalidEmailException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Service\\AdminUserServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Email\\EmailValidatorServiceInterface;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\nclass CreateUserCommand extends Command\n{\n    private const ADMIN_EMAIL = 'admin-email';\n    private const ADMIN_PASSWORD = 'admin-password';\n\n    public function __construct(\n        private readonly EmailValidatorServiceInterface $emailValidatorService,\n        private readonly AdminUserServiceInterface $adminService,\n        private readonly BasicContextInterface $basicContext\n    ) {\n        parent::__construct();\n    }\n\n    protected function configure(): void\n    {\n        $this\n            ->addArgument(self::ADMIN_EMAIL, InputArgument::REQUIRED)\n            ->addArgument(self::ADMIN_PASSWORD, InputArgument::REQUIRED);\n        $this->setDescription('Creates admin user');\n    }\n\n    /**\n     * @throws InvalidEmailException\n     * @throws EmailAlreadyTakenException\n     */\n    protected function execute(InputInterface $input, OutputInterface $output): int\n    {\n        $this->validateAdminEmail($input->getArgument(self::ADMIN_EMAIL));\n\n        $output->writeln('<info>Creating administrator account...</info>');\n        $this->createAdmin($input);\n\n        $output->writeln('<info>Administrator account has been created.</info>');\n\n        return Command::SUCCESS;\n    }\n\n    /**\n     * @throws InvalidEmailException\n     */\n    private function validateAdminEmail(string $email): void\n    {\n        if (!$this->emailValidatorService->isEmailValid($email)) {\n            throw new InvalidEmailException($email);\n        }\n    }\n\n    private function createAdmin(InputInterface $input): void\n    {\n        $this->adminService->createAdmin(\n            $input->getArgument(self::ADMIN_EMAIL),\n            $input->getArgument(self::ADMIN_PASSWORD),\n            Admin::MALL_ADMIN,\n            $this->basicContext->getDefaultShopId()\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Admin/Command/services.yaml",
    "content": "services:\n    _defaults:\n        autowire: true\n\n    OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Command\\CreateUserCommand:\n        class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Command\\CreateUserCommand\n        tags:\n            - { name: 'console.command', command: 'oe:admin:create-user' }\n"
  },
  {
    "path": "source/Internal/Domain/Admin/Dao/AdminDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\DataObject\\Admin;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Exception\\EmailAlreadyTakenException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface;\n\nclass AdminDao implements AdminDaoInterface\n{\n    public function __construct(private QueryBuilderFactoryInterface $queryBuilderFactory)\n    {\n    }\n\n    /**\n     * @param Admin $admin\n     * @throws EmailAlreadyTakenException\n     */\n    public function create(Admin $admin): void\n    {\n        $this->checkEmailNotTaken($admin->getEmail(), $admin->getShopId());\n\n        $queryBuilder = $this->queryBuilderFactory->create();\n        $queryBuilder\n            ->insert('oxuser')\n            ->values([\n                'OXID'        => ':OXID',\n                'OXUSERNAME'  => ':OXUSERNAME',\n                'OXPASSWORD'  => ':OXPASSWORD',\n                'OXRIGHTS'    => ':OXRIGHTS',\n                'OXSHOPID'    => ':OXSHOPID',\n            ])\n            ->setParameters([\n                'OXID' => $admin->getId(),\n                'OXUSERNAME' => $admin->getEmail(),\n                'OXPASSWORD' => $admin->getPasswordHash(),\n                'OXRIGHTS' => $admin->getRights(),\n                'OXSHOPID' => $admin->getShopId(),\n            ]);\n        $queryBuilder->executeStatement();\n    }\n\n    /**\n     * @throws EmailAlreadyTakenException\n     */\n    private function checkEmailNotTaken(string $email, int $shopId): void\n    {\n        $queryBuilder = $this->queryBuilderFactory->create();\n        $queryBuilder\n            ->select('1')\n            ->from('oxuser')\n            ->where('OXUSERNAME = :OXUSERNAME')\n            ->andWhere('OXSHOPID = :OXSHOPID')\n            ->setParameters([\n                'OXUSERNAME' => $email,\n                'OXSHOPID' => $shopId,\n            ])\n            ->setMaxResults(1);\n\n        if ($queryBuilder->executeQuery()->fetchOne()) {\n            throw new EmailAlreadyTakenException(\"Can not create an admin, the email '$email' is already in use.\");\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Admin/Dao/AdminDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\DataObject\\Admin;\n\ninterface AdminDaoInterface\n{\n    /**\n     * @param Admin $admin\n     */\n    public function create(Admin $admin): void;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Admin/Dao/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n    public: false\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Dao\\AdminDaoInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Dao\\AdminDao"
  },
  {
    "path": "source/Internal/Domain/Admin/DataObject/Admin.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\DataObject;\n\nclass Admin\n{\n    public const MALL_ADMIN = 'malladmin';\n\n    public function __construct(\n        /** @var string */\n        private $id,\n        /** @var string */\n        private $email,\n        /** @var string */\n        private $passwordHash,\n        /** @var string */\n        private $rights,\n        /** @var int */\n        private $shopId\n    ) {\n    }\n\n    public function getId(): string\n    {\n        return $this->id;\n    }\n\n    public function getEmail(): string\n    {\n        return $this->email;\n    }\n\n    public function getPasswordHash(): string\n    {\n        return $this->passwordHash;\n    }\n\n    public function getRights(): string\n    {\n        return $this->rights;\n    }\n\n    public function getShopId(): int\n    {\n        return $this->shopId;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Admin/Event/AdminModeChangedEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Event;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\nclass AdminModeChangedEvent extends Event\n{\n}\n"
  },
  {
    "path": "source/Internal/Domain/Admin/Exception/EmailAlreadyTakenException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Exception;\n\nfinal class EmailAlreadyTakenException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Domain/Admin/Exception/InvalidEmailException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Exception;\n\nuse function sprintf;\n\nclass InvalidEmailException extends \\Exception\n{\n    public function __construct(string $email)\n    {\n        parent::__construct(sprintf('Provided email string %s is not a valid email.', $email));\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Admin/Exception/InvalidRightsException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Exception;\n\nuse function sprintf;\n\nclass InvalidRightsException extends \\Exception\n{\n    public function __construct(string $right)\n    {\n        parent::__construct(sprintf('Provided right %s is not a valid shop right.', $right));\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Admin/Exception/InvalidShopException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Exception;\n\nuse function sprintf;\n\nclass InvalidShopException extends \\Exception\n{\n    public function __construct(int $id)\n    {\n        parent::__construct(sprintf('Provided shopId %d is not a valid shop id.', $id));\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Admin/Factory/AdminFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Factory;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\DataObject\\Admin;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Exception\\InvalidEmailException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Exception\\InvalidRightsException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Exception\\InvalidShopException;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Email\\EmailValidatorServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\ShopAdapterInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Hash\\Service\\PasswordHashServiceInterface;\n\nclass AdminFactory implements AdminFactoryInterface\n{\n    public function __construct(\n        private ShopAdapterInterface $shopAdapter,\n        private EmailValidatorServiceInterface $emailValidatorService,\n        private PasswordHashServiceInterface $passwordHashService\n    ) {\n    }\n\n    /**\n     * @param string $email\n     * @param string $password\n     * @param string $rights\n     * @param int $shopId\n     * @return Admin\n     * @throws InvalidEmailException\n     * @throws InvalidRightsException\n     * @throws InvalidShopException\n     */\n    public function createAdmin(\n        string $email,\n        string $password,\n        string $rights,\n        int $shopId\n    ): Admin {\n        if (!$this->emailValidatorService->isEmailValid($email)) {\n            throw new InvalidEmailException($email);\n        }\n\n        $this->checkRights($rights);\n\n        if (!$this->shopAdapter->validateShopId($shopId)) {\n            throw new InvalidShopException($shopId);\n        }\n\n        return new Admin(\n            $this->shopAdapter->generateUniqueId(),\n            $email,\n            $this->passwordHashService->hash(($password)),\n            $rights,\n            $shopId\n        );\n    }\n\n    /**\n     * @throws InvalidRightsException\n     */\n    private function checkRights(string $rights): void\n    {\n        if (\n            $rights != Admin::MALL_ADMIN &&\n            !is_numeric($rights) &&\n            !$this->shopAdapter->validateShopId((int) $rights)\n        ) {\n            throw new InvalidRightsException($rights);\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Admin/Factory/AdminFactoryInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Factory;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\DataObject\\Admin;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Exception\\InvalidEmailException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Exception\\InvalidRightsException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Exception\\InvalidShopException;\n\ninterface AdminFactoryInterface\n{\n    /**\n     * @throws InvalidEmailException\n     * @throws InvalidShopException\n     * @throws InvalidRightsException\n     */\n    public function createAdmin(\n        string $email,\n        string $password,\n        string $rights,\n        int $shopId\n    ): Admin;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Admin/Factory/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n    public: false\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Factory\\AdminFactoryInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Factory\\AdminFactory"
  },
  {
    "path": "source/Internal/Domain/Admin/Service/AdminUserService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Dao\\AdminDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Factory\\AdminFactoryInterface;\n\nclass AdminUserService implements AdminUserServiceInterface\n{\n    public function __construct(\n        private AdminDaoInterface $adminDao,\n        private AdminFactoryInterface $adminFactory\n    ) {\n    }\n\n    /**\n     * @inheritDoc\n     * @throws \\InvalidArgumentException\n     */\n    public function createAdmin(\n        string $email,\n        string $password,\n        string $rights,\n        int $shopId\n    ): void {\n        $this->adminDao->create($this->adminFactory->createAdmin(\n            $email,\n            $password,\n            $rights,\n            $shopId\n        ));\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Admin/Service/AdminUserServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Service;\n\ninterface AdminUserServiceInterface\n{\n    public function createAdmin(\n        string $email,\n        string $password,\n        string $rights,\n        int $shopId\n    ): void;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Admin/Service/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n    public: false\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Service\\AdminUserServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Service\\AdminUserService"
  },
  {
    "path": "source/Internal/Domain/Admin/services.yaml",
    "content": "imports:\n    - { resource: Dao/services.yaml }\n    - { resource: Factory/services.yaml }\n    - { resource: Service/services.yaml }\n    - { resource: Command/services.yaml }"
  },
  {
    "path": "source/Internal/Domain/Authentication/Bridge/PasswordServiceBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Hash\\Service\\PasswordHashServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Service\\PasswordVerificationServiceInterface;\n\nclass PasswordServiceBridge implements PasswordServiceBridgeInterface\n{\n    public function __construct(\n        private PasswordHashServiceInterface $passwordHashService,\n        private PasswordVerificationServiceInterface $passwordVerificationService\n    ) {\n    }\n\n    /**\n     * @param string $password\n     *\n     * @return string\n     */\n    public function hash(string $password): string\n    {\n        return $this->passwordHashService->hash($password);\n    }\n\n    /**\n     * @param string $passwordHash\n     *\n     * @return bool\n     */\n    public function passwordNeedsRehash(string $passwordHash): bool\n    {\n        return $this->passwordHashService->passwordNeedsRehash($passwordHash);\n    }\n\n    /**\n     * Verify that a given password matches a given hash\n     *\n     * @param string $password\n     * @param string $passwordHash\n     *\n     * @return bool\n     */\n    public function verifyPassword(string $password, string $passwordHash): bool\n    {\n        return $this->passwordVerificationService->verifyPassword($password, $passwordHash);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Authentication/Bridge/PasswordServiceBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\ninterface PasswordServiceBridgeInterface\n{\n    /**\n     * @param string $password\n     *\n     * @return string\n     */\n    public function hash(string $password): string;\n\n\n    /**\n     * @param string $passwordHash\n     *\n     * @return bool\n     */\n    public function passwordNeedsRehash(string $passwordHash): bool;\n\n    /**\n     * Verify that a given password matches a given hash\n     *\n     * @param string $password\n     * @param string $passwordHash\n     *\n     * @return bool\n     */\n    public function verifyPassword(string $password, string $passwordHash): bool;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Authentication/Bridge/RandomTokenGeneratorBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Generator\\RandomTokenGeneratorInterface;\n\nclass RandomTokenGeneratorBridge implements RandomTokenGeneratorBridgeInterface\n{\n    public function __construct(\n        private RandomTokenGeneratorInterface $randomTokenGenerator\n    ) {\n    }\n\n    /** @inheritdoc */\n    public function getAlphanumericToken(int $length): string\n    {\n        return $this->randomTokenGenerator->getAlphanumericToken($length);\n    }\n\n    /** @inheritdoc */\n    public function getHexToken(int $length): string\n    {\n        return $this->randomTokenGenerator->getHexToken($length);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Authentication/Bridge/RandomTokenGeneratorBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Exception\\UnavailableSourceOfRandomnessException;\n\ninterface RandomTokenGeneratorBridgeInterface\n{\n    /**\n     * @param int $length\n     * @return string\n     * @throws UnavailableSourceOfRandomnessException\n     */\n    public function getAlphanumericToken(int $length): string;\n\n    /**\n     * @param int $length\n     * @return string\n     * @throws UnavailableSourceOfRandomnessException\n     */\n    public function getHexToken(int $length): string;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Authentication/Exception/UnavailableSourceOfRandomnessException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Exception;\n\nuse Exception;\n\nclass UnavailableSourceOfRandomnessException extends Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Domain/Authentication/Generator/RandomTokenGenerator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Generator;\n\nuse Exception;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Exception\\UnavailableSourceOfRandomnessException;\n\nuse function base64_encode;\nuse function bin2hex;\nuse function random_bytes;\nuse function str_replace;\nuse function strlen;\nuse function substr;\n\nclass RandomTokenGenerator implements RandomTokenGeneratorInterface\n{\n    private const BASE_64_NON_ALPHANUMERIC_CHARACTERS = ['+', '/', '='];\n\n    /** @inheritDoc */\n    public function getAlphanumericToken(int $length): string\n    {\n        $token = '';\n        while (strlen($token) < $length) {\n            $token .= $this->getAlphanumericString($length);\n        }\n        return substr($token, 0, $length);\n    }\n\n    /** @inheritDoc */\n    public function getHexToken(int $length): string\n    {\n        return substr($this->getHexString($length), 0, $length);\n    }\n\n    private function getAlphanumericString(int $length): string\n    {\n        $base64String = base64_encode(\n            $this->getRandomBytes($length)\n        );\n        return $this->removeNonAlphanumericCharacters($base64String);\n    }\n\n    private function getHexString(int $length): string\n    {\n        return bin2hex(\n            $this->getRandomBytes($length)\n        );\n    }\n\n    private function removeNonAlphanumericCharacters(string $base64string): string\n    {\n        return str_replace(\n            self::BASE_64_NON_ALPHANUMERIC_CHARACTERS,\n            '',\n            $base64string\n        );\n    }\n\n    private function getRandomBytes(int $length): string\n    {\n        try {\n            return random_bytes($length);\n        } catch (Exception $exception) {\n            throw new UnavailableSourceOfRandomnessException($exception);\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Authentication/Generator/RandomTokenGeneratorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Generator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Exception\\UnavailableSourceOfRandomnessException;\n\ninterface RandomTokenGeneratorInterface\n{\n    /**\n     * Generates random string of alphanumeric characters\n     * @param int $length\n     * @return string\n     * @throws UnavailableSourceOfRandomnessException\n     */\n    public function getAlphanumericToken(int $length): string;\n\n    /**\n     * Generates random string of hex characters\n     * @param int $length\n     * @return string\n     * @throws UnavailableSourceOfRandomnessException\n     */\n    public function getHexToken(int $length): string;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Authentication/Service/PasswordVerificationService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Authentication\\Policy\\PasswordPolicyInterface;\n\nclass PasswordVerificationService implements PasswordVerificationServiceInterface\n{\n    public function __construct(private PasswordPolicyInterface $passwordPolicy)\n    {\n    }\n\n    /**\n     * Verify that a given password matches a given hash\n     *\n     * @param string $password\n     * @param string $passwordHash\n     *\n     * @return bool\n     */\n    public function verifyPassword(string $password, string $passwordHash): bool\n    {\n        $this->passwordPolicy->enforcePasswordPolicy($password);\n\n        return password_verify($password, $passwordHash);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Authentication/Service/PasswordVerificationServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Service;\n\ninterface PasswordVerificationServiceInterface\n{\n    /**\n     * Verify that a given password matches a given hash\n     *\n     * @param string $password\n     * @param string $passwordHash\n     *\n     * @return bool\n     */\n    public function verifyPassword(string $password, string $passwordHash): bool;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Authentication/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\PasswordServiceBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\PasswordServiceBridge\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Service\\PasswordVerificationServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Service\\PasswordVerificationService\n\n  OxidEsales\\EshopCommunity\\Internal\\Utility\\Authentication\\Policy\\PasswordPolicyInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Utility\\Authentication\\Policy\\PasswordPolicy\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Generator\\RandomTokenGeneratorInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Generator\\RandomTokenGenerator\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\RandomTokenGeneratorBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\RandomTokenGeneratorBridge\n    public: true\n"
  },
  {
    "path": "source/Internal/Domain/Contact/Form/ContactFormBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FormConfigurationInterface;\n\nclass ContactFormBridge implements ContactFormBridgeInterface\n{\n    public function __construct(\n        private FormFactoryInterface $contactFormFactory,\n        private ContactFormMessageBuilderInterface $contactFormMessageBuilder,\n        private FormConfigurationInterface $contactFormConfiguration\n    ) {\n    }\n\n    /**\n     * @return FormInterface\n     */\n    public function getContactForm()\n    {\n        return $this->contactFormFactory->getForm();\n    }\n\n    /**\n     * @param FormInterface $form\n     * @return string\n     */\n    public function getContactFormMessage(FormInterface $form)\n    {\n        return $this->contactFormMessageBuilder->getContent($form);\n    }\n\n    /**\n     * @return FormConfigurationInterface\n     */\n    public function getContactFormConfiguration()\n    {\n        return $this->contactFormConfiguration;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Contact/Form/ContactFormBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FormConfigurationInterface;\n\ninterface ContactFormBridgeInterface\n{\n    /**\n     * @return FormInterface\n     */\n    public function getContactForm();\n\n    /**\n     * @param FormInterface $form\n     * @return string\n     */\n    public function getContactFormMessage(FormInterface $form);\n\n    /**\n     * @return FormConfigurationInterface\n     */\n    public function getContactFormConfiguration();\n}\n"
  },
  {
    "path": "source/Internal/Domain/Contact/Form/ContactFormConfigurationFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FieldConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FieldConfigurationInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FormConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FormConfigurationFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FormConfigurationInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FormFieldsConfigurationDataProviderInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\n\nclass ContactFormConfigurationFactory implements FormConfigurationFactoryInterface\n{\n    public function __construct(\n        private FormFieldsConfigurationDataProviderInterface $contactFormConfigurationDataProvider,\n        private ContextInterface $context\n    ) {\n    }\n\n    /**\n     * @return FormConfigurationInterface\n     */\n    public function getFormConfiguration()\n    {\n        $formConfiguration = new FormConfiguration();\n\n        $fieldsConfigurationData = $this\n            ->contactFormConfigurationDataProvider\n            ->getFormFieldsConfiguration();\n\n        foreach ($fieldsConfigurationData as $fieldConfigurationData) {\n            $fieldConfiguration = $this->getFieldConfiguration($fieldConfigurationData);\n            $formConfiguration->addFieldConfiguration($fieldConfiguration);\n        }\n\n        return $formConfiguration;\n    }\n\n    /**\n     * @param array $fieldConfigurationData\n     * @return FieldConfiguration\n     */\n    private function getFieldConfiguration($fieldConfigurationData)\n    {\n        $fieldConfiguration = new FieldConfiguration();\n        $fieldConfiguration->setName($fieldConfigurationData['name']);\n        $fieldConfiguration->setLabel($fieldConfigurationData['label']);\n\n        if ($this->isFieldRequired($fieldConfiguration)) {\n            $fieldConfiguration->setIsRequired(true);\n        }\n\n        return $fieldConfiguration;\n    }\n\n    /**\n     * @param FieldConfigurationInterface $fieldConfiguration\n     * @return bool\n     */\n    private function isFieldRequired(FieldConfigurationInterface $fieldConfiguration)\n    {\n        return in_array(\n            $fieldConfiguration->getName(),\n            $this->context->getRequiredContactFormFields()\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Contact/Form/ContactFormEmailValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Email\\EmailValidatorServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormFieldInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormValidatorInterface;\n\nclass ContactFormEmailValidator implements FormValidatorInterface\n{\n    /**\n     * @var array\n     */\n    private $errors;\n\n    public function __construct(private EmailValidatorServiceInterface $emailValidatorService)\n    {\n    }\n\n    /**\n     * @param FormInterface $form\n     * @return bool\n     */\n    public function isValid(FormInterface $form)\n    {\n        $isValid = true;\n        $email = $form->email;\n\n        if ($this->isValidationNeeded($email)) {\n            $isValid = $this\n                ->emailValidatorService\n                ->isEmailValid($email->getValue());\n\n            if ($isValid !== true) {\n                $this->errors[] = 'ERROR_MESSAGE_INPUT_NOVALIDEMAIL';\n            }\n        }\n\n        return $isValid;\n    }\n\n    /**\n     * @param FormFieldInterface $email\n     * @return bool\n     */\n    private function isValidationNeeded(FormFieldInterface $email)\n    {\n        return $this->isNotEmptyEmail($email) || $email->isRequired();\n    }\n\n    /**\n     * @param FormFieldInterface $email\n     * @return bool\n     */\n    private function isNotEmptyEmail(FormFieldInterface $email)\n    {\n        return $email->getValue() !== '';\n    }\n\n    /**\n     * @return array\n     */\n    public function getErrors()\n    {\n        return $this->errors;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Contact/Form/ContactFormFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\Form;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormField;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormFieldInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormValidatorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FieldConfigurationInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FormConfigurationInterface;\n\nclass ContactFormFactory implements FormFactoryInterface\n{\n    public function __construct(\n        private FormConfigurationInterface $contactFormConfiguration,\n        private FormValidatorInterface $contactFormEmailValidator,\n        private FormValidatorInterface $requiredFieldsValidator\n    ) {\n    }\n\n    /**\n     * @return FormInterface\n     */\n    public function getForm()\n    {\n        $form = new Form();\n\n        foreach ($this->contactFormConfiguration->getFieldConfigurations() as $fieldConfiguration) {\n            $field = $this->getFormField($fieldConfiguration);\n            $form->add($field);\n        }\n\n        $form->addValidator($this->requiredFieldsValidator);\n        $form->addValidator($this->contactFormEmailValidator);\n\n        return $form;\n    }\n\n    /**\n     * @param FieldConfigurationInterface $fieldConfiguration\n     * @return FormFieldInterface\n     */\n    private function getFormField(FieldConfigurationInterface $fieldConfiguration)\n    {\n        $field = new FormField();\n        $field\n            ->setName($fieldConfiguration->getName())\n            ->setLabel($fieldConfiguration->getLabel())\n            ->setIsRequired($fieldConfiguration->isRequired());\n\n        return $field;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Contact/Form/ContactFormFieldsConfigurationDataProvider.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FormFieldsConfigurationDataProviderInterface;\n\nclass ContactFormFieldsConfigurationDataProvider implements FormFieldsConfigurationDataProviderInterface\n{\n    /**\n     * @return array\n     */\n    public function getFormFieldsConfiguration()\n    {\n        return [\n            [\n                'name'  => 'email',\n                'label' => 'EMAIL',\n            ],\n            [\n                'name'  => 'firstName',\n                'label' => 'FIRST_NAME',\n            ],\n            [\n                'name'  => 'lastName',\n                'label' => 'LAST_NAME',\n            ],\n            [\n                'name'  => 'salutation',\n                'label' => 'TITLE',\n            ],\n            [\n                'name'  => 'subject',\n                'label' => 'SUBJECT',\n            ],\n            [\n                'name'  => 'message',\n                'label' => 'MESSAGE',\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Contact/Form/ContactFormMessageBuilder.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\ShopAdapterInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormInterface;\n\nclass ContactFormMessageBuilder implements ContactFormMessageBuilderInterface\n{\n    public function __construct(private ShopAdapterInterface $shopAdapter)\n    {\n    }\n\n    /**\n     * @param FormInterface $form\n     * @return string\n     */\n    public function getContent(FormInterface $form)\n    {\n        $message = $this->shopAdapter->translateString('MESSAGE_FROM') . ' ';\n\n        $salutation = $form->salutation->getValue();\n        if ($salutation) {\n            $message .= $this->shopAdapter->translateString($salutation) . ' ';\n        }\n\n        if ($form->firstName->getValue()) {\n            $message .= $form->firstName->getValue() . ' ';\n        }\n\n        if ($form->lastName->getValue()) {\n            $message .= $form->lastName->getValue() . ' ';\n        }\n\n        $message .= '(' . $form->email->getValue() . ')<br /><br />';\n\n        if ($form->message->getValue()) {\n            $message .= nl2br($form->message->getValue());\n        }\n\n        return $message;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Contact/Form/ContactFormMessageBuilderInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormInterface;\n\ninterface ContactFormMessageBuilderInterface\n{\n    /**\n     * @param FormInterface $form\n     * @return string\n     */\n    public function getContent(FormInterface $form);\n}\n"
  },
  {
    "path": "source/Internal/Domain/Contact/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form\\ContactFormBridgeInterface:\n      class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form\\ContactFormBridge\n      arguments:\n        $contactFormFactory: '@form.contact_form.contact_form_factory'\n        $contactFormMessageBuilder: '@OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form\\ContactFormMessageBuilderInterface'\n        $contactFormConfiguration: '@form.contact_form.contact_form_configuration'\n      public: true\n\n  form.contact_form.contact_form_factory:\n      class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form\\ContactFormFactory\n      arguments:\n        $contactFormConfiguration: '@form.contact_form.contact_form_configuration'\n        $contactFormEmailValidator: '@form.contact_form.contact_form_email_validator'\n        $requiredFieldsValidator: '@common.form.required_fields_validator'\n\n  form.contact_form.contact_form_configuration:\n      class: OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FormConfiguration\n      factory: ['@form.contact_form.contact_form_configuration_factory', 'getFormConfiguration']\n\n  form.contact_form.contact_form_email_validator:\n      class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form\\ContactFormEmailValidator\n      arguments:\n        - '@OxidEsales\\EshopCommunity\\Internal\\Utility\\Email\\EmailValidatorServiceInterface'\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form\\ContactFormMessageBuilderInterface:\n      class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form\\ContactFormMessageBuilder\n      arguments:\n        - '@OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\ShopAdapterInterface'\n\n  form.contact_form.contact_form_configuration_factory:\n      class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form\\ContactFormConfigurationFactory\n      arguments:\n        - '@form.contact_form.contact_form_fields_configuration_data_provider'\n        - '@OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface'\n\n  form.contact_form.contact_form_fields_configuration_data_provider:\n      class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form\\ContactFormFieldsConfigurationDataProvider\n"
  },
  {
    "path": "source/Internal/Domain/Media/Dao/MediaDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataMapper\\DataMapperInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface;\n\nuse function sprintf;\n\nreadonly class MediaDao implements MediaDaoInterface\n{\n    private const MEDIA_TABLE = 'oxmedia';\n\n    public function __construct(\n        private QueryBuilderFactoryInterface $queryBuilderFactory,\n        private DataMapperInterface $dataMapper,\n    ) {\n    }\n\n    public function get(Id $id): Media\n    {\n        $result = $this->queryBuilderFactory\n            ->create()\n            ->select('id', 'path', 'type')\n            ->from(self::MEDIA_TABLE)\n            ->where('id = :id')\n            ->setParameter('id', $id)\n            ->executeQuery()\n            ->fetchAssociative();\n        if (!$result) {\n            throw new EntryDoesNotExistDaoException(\n                sprintf('Media entry with ID \"%s\" does not exist.', $id)\n            );\n        }\n\n        return $this->dataMapper->fromData($result);\n    }\n\n    public function add(Media $media): void\n    {\n        $this->queryBuilderFactory\n            ->create()\n            ->insert(self::MEDIA_TABLE)\n            ->values([\n                'id' => ':id',\n                'path' => ':path',\n                'type' => ':type'\n            ])\n            ->setParameters(\n                $this->dataMapper->toData($media)\n            )\n            ->executeStatement();\n    }\n\n    public function delete(Id $id): void\n    {\n        $this->queryBuilderFactory\n            ->create()\n            ->delete(self::MEDIA_TABLE)\n            ->where('id = :id')\n            ->setParameter('id', $id)\n            ->executeStatement();\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/Dao/MediaDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\n\ninterface MediaDaoInterface\n{\n    /** @throws EntryDoesNotExistDaoException */\n    public function get(Id $id): Media;\n\n    public function add(Media $media): void;\n\n    public function delete(Id $id): void;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/DataMapper/DataMapper.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataMapper;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaType;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\n\nclass DataMapper implements DataMapperInterface\n{\n    public function toData(Media $media): array\n    {\n        return [\n            'id'   => (string) $media->getId(),\n            'path' => (string) $media->getMediaPath(),\n            'type' => (string) $media->getMediaType(),\n        ];\n    }\n\n    public function fromData(array $data): Media\n    {\n        return new Media(\n            Id::fromString($data['id']),\n            new MediaPath($data['path']),\n            new MediaType($data['type']),\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/DataMapper/DataMapperInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataMapper;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\n\ninterface DataMapperInterface\n{\n    public function toData(Media $media): array;\n\n    public function fromData(array $data): Media;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/DataObject/Media.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\n\nreadonly class Media\n{\n    public function __construct(\n        private Id $id,\n        private MediaPath $mediaPath,\n        private MediaType $mediaType,\n    ) {\n    }\n\n    public function getId(): Id\n    {\n        return $this->id;\n    }\n\n    public function getMediaPath(): MediaPath\n    {\n        return $this->mediaPath;\n    }\n\n    public function getMediaType(): MediaType\n    {\n        return $this->mediaType;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/DataObject/MediaPath.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject;\n\nreadonly class MediaPath\n{\n    public function __construct(\n        private string $path\n    ) {\n        $this->validate($path);\n    }\n\n    public function __toString(): string\n    {\n        return $this->path;\n    }\n\n    private function validate(string $path): void\n    {\n        if ($path === '') {\n            throw new \\InvalidArgumentException('Media path cannot be empty.');\n        }\n        if (preg_match('/[<>:\"|?*]/', $path)) {\n            throw new \\InvalidArgumentException('Media path contains invalid characters.');\n        }\n        if (str_starts_with($path, '/') || str_starts_with($path, '\\\\')) {\n            throw new \\InvalidArgumentException('Media path must be relative, not absolute.');\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/DataObject/MediaType.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject;\n\nreadonly class MediaType\n{\n    public function __construct(\n        private string $type\n    ) {\n        $this->validate($type);\n    }\n\n    public function __toString(): string\n    {\n        return $this->type;\n    }\n\n    private function validate(string $type): void\n    {\n        if ($type !== '' && !preg_match('#^[\\w.\\-]+/[\\w.\\-+]+$#', $type)) {\n            throw new \\InvalidArgumentException('Invalid MIME type format.');\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/MediaUploader.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\ImageHandlerInterface;\nuse Symfony\\Component\\HttpFoundation\\File\\UploadedFile;\n\nreadonly class MediaUploader implements MediaUploaderInterface\n{\n    public function __construct(\n        private ImageHandlerInterface $imageHandler,\n    ) {\n    }\n\n    public function uploadTo(UploadedFile $uploadedFile, MediaPath $targetPath): MediaPath\n    {\n        $this->imageHandler->upload(\n            $uploadedFile->getPathname(),\n            (string) $targetPath\n        );\n\n        return $targetPath;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/MediaUploaderInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse Symfony\\Component\\HttpFoundation\\File\\UploadedFile;\n\ninterface MediaUploaderInterface\n{\n    public function uploadTo(UploadedFile $uploadedFile, MediaPath $targetPath): MediaPath;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/MediaUrlGenerator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Dao\\ShopConfigurationSettingDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass MediaUrlGenerator implements MediaUrlGeneratorInterface\n{\n    private string $generatedBaseUrl;\n    private string $picturesRoot;\n    private int $imageQuality;\n\n    public function __construct(\n        ContextInterface $context,\n        ShopConfigurationSettingDaoInterface $shopConfigurationSettingDao,\n        string $alternativeImageUrl = ''\n    ) {\n        $this->generatedBaseUrl = $alternativeImageUrl\n            ? Path::join($alternativeImageUrl, 'generated')\n            : Path::join($context->getShopBaseUrl(), 'out', 'pictures', 'generated');\n\n        $this->picturesRoot = Path::join(\n            Path::makeRelative($context->getOutPath(), $context->getSourcePath()),\n            'pictures'\n        );\n\n        $this->imageQuality = (int) $shopConfigurationSettingDao\n            ->get('sDefaultImageQuality', $context->getCurrentShopId())\n            ->getValue();\n    }\n\n    public function generateSizedImageUrl(Media $media, string $size): string\n    {\n        $relativeMediaPath = Path::makeRelative((string) $media->getMediaPath(), $this->picturesRoot);\n\n        return Path::join(\n            $this->generatedBaseUrl,\n            dirname($relativeMediaPath),\n            $this->buildSizePath($size),\n            rawurlencode(basename($relativeMediaPath))\n        );\n    }\n\n    private function buildSizePath(string $size): string\n    {\n        [$width, $height] = explode('*', $size);\n        return \"{$width}_{$height}_{$this->imageQuality}\";\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/MediaUrlGeneratorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\n\ninterface MediaUrlGeneratorInterface\n{\n    public function generateSizedImageUrl(Media $media, string $size): string;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/Validator/Exception/FileExtensionMismatchException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception;\n\nclass FileExtensionMismatchException extends MediaValidationException\n{\n    public function __construct(private readonly string $clientExtension, private readonly array $validExtensions)\n    {\n        parent::__construct('Extension ' . $clientExtension . ' not valid for detected MIME type');\n    }\n\n    public function getClientExtension(): string\n    {\n        return $this->clientExtension;\n    }\n\n    /**\n     * @return array<string>\n     */\n    public function getValidExtensions(): array\n    {\n        return $this->validExtensions;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/Validator/Exception/FileSizeTooLargeException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\FileSizeLogic;\n\nclass FileSizeTooLargeException extends MediaValidationException\n{\n    public function __construct(private readonly int $actualBytes, private readonly int $maxKb)\n    {\n        parent::__construct('File too large: ' . $actualBytes . ' bytes, max ' . $maxKb . ' KB');\n    }\n\n    public function getActualBytes(): int\n    {\n        return $this->actualBytes;\n    }\n\n    public function getMaxKb(): int\n    {\n        return $this->maxKb;\n    }\n\n    public function getActualFormatted(): string\n    {\n        return (new FileSizeLogic())->getFileSize($this->actualBytes);\n    }\n\n    public function getMaxFormatted(): string\n    {\n        return (new FileSizeLogic())->getFileSize($this->maxKb * 1024);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/Validator/Exception/FileSizeTooSmallException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\FileSizeLogic;\n\nclass FileSizeTooSmallException extends MediaValidationException\n{\n    public function __construct(private readonly int $actualBytes, private readonly int $minKb)\n    {\n        parent::__construct('File too small: ' . $actualBytes . ' bytes, min ' . $minKb . ' KB');\n    }\n\n    public function getActualBytes(): int\n    {\n        return $this->actualBytes;\n    }\n\n    public function getMinKb(): int\n    {\n        return $this->minKb;\n    }\n\n    public function getActualFormatted(): string\n    {\n        return (new FileSizeLogic())->getFileSize($this->actualBytes);\n    }\n\n    public function getMinFormatted(): string\n    {\n        return (new FileSizeLogic())->getFileSize($this->minKb * 1024);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/Validator/Exception/MediaValidationException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception;\n\nclass MediaValidationException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/Validator/Exception/MimeBaseTypeMismatchException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception;\n\nclass MimeBaseTypeMismatchException extends MediaValidationException\n{\n    public function __construct(private readonly string $guessedMime, private readonly string $requiredBasePrefix)\n    {\n        parent::__construct('MIME type ' . $guessedMime . ' does not match required base ' . $requiredBasePrefix);\n    }\n\n    public function getGuessedMime(): string\n    {\n        return $this->guessedMime;\n    }\n\n    public function getRequiredBasePrefix(): string\n    {\n        return $this->requiredBasePrefix;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/Validator/Exception/MimeGuessMismatchException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception;\n\nclass MimeGuessMismatchException extends MediaValidationException\n{\n    public function __construct(private readonly string $guessedMime, private readonly string $clientMime)\n    {\n        parent::__construct('Guessed MIME ' . $guessedMime . ' does not match client MIME ' . $clientMime);\n    }\n\n    public function getGuessedMime(): string\n    {\n        return $this->guessedMime;\n    }\n\n    public function getClientMime(): string\n    {\n        return $this->clientMime;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/Validator/Exception/MimeTypeGuessFailedException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception;\n\nclass MimeTypeGuessFailedException extends MediaValidationException\n{\n    private string $path;\n\n    public function __construct(string $path)\n    {\n        $this->path = $path;\n\n        parent::__construct(\\sprintf('Unable to guess MIME type for file \"%s\".', $path));\n    }\n\n    public function getPath(): string\n    {\n        return $this->path;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/Validator/Exception/UploadInvalidException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception;\n\nclass UploadInvalidException extends MediaValidationException\n{\n    public function __construct(private readonly int $errorCode)\n    {\n        parent::__construct('Upload error with PHP code ' . $errorCode);\n    }\n\n    public function getErrorCode(): int\n    {\n        return $this->errorCode;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/Validator/FileExtensionConstraintValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\FileExtensionMismatchException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\MimeTypeGuessFailedException;\nuse Symfony\\Component\\HttpFoundation\\File\\UploadedFile;\nuse Symfony\\Component\\Mime\\MimeTypesInterface;\n\nreadonly class FileExtensionConstraintValidator implements MediaConstraintValidatorInterface\n{\n    public function __construct(\n        private MimeTypesInterface $mimeTypeGuesser\n    ) {\n    }\n\n    public function validate(UploadedFile $uploadedFile): void\n    {\n        $clientExtension = strtolower($uploadedFile->getClientOriginalExtension());\n        $path = $uploadedFile->getPathname();\n\n        $guessedMimeType = $this->mimeTypeGuesser->guessMimeType($path)\n            ?? throw new MimeTypeGuessFailedException($path);\n\n        $validExtensions = array_map(\n            'strtolower',\n            $this->mimeTypeGuesser->getExtensions($guessedMimeType)\n        );\n\n        if (empty($validExtensions) || !in_array($clientExtension, $validExtensions, true)) {\n            throw new FileExtensionMismatchException($clientExtension, $validExtensions);\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/Validator/FileSizeConstraintValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\FileSizeTooLargeException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\FileSizeTooSmallException;\nuse Symfony\\Component\\HttpFoundation\\File\\UploadedFile;\n\nclass FileSizeConstraintValidator implements MediaConstraintValidatorInterface\n{\n    private int $minSizeBytes;\n    private int $maxSizeBytes;\n\n    public function __construct(\n        private readonly int $minSizeKb,\n        private readonly int $maxSizeKb\n    ) {\n        $this->minSizeBytes = $minSizeKb * 1024;\n        $this->maxSizeBytes = $maxSizeKb * 1024;\n    }\n\n    public function validate(UploadedFile $uploadedFile): void\n    {\n        $filesize = $uploadedFile->getSize();\n\n        if ($filesize < $this->minSizeBytes) {\n            throw new FileSizeTooSmallException(\n                $filesize,\n                $this->minSizeKb\n            );\n        }\n\n        if ($filesize > $this->maxSizeBytes) {\n            throw new FileSizeTooLargeException(\n                $filesize,\n                $this->maxSizeKb\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/Validator/MediaConstraintValidatorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator;\n\nuse Symfony\\Component\\HttpFoundation\\File\\UploadedFile;\n\ninterface MediaConstraintValidatorInterface\n{\n    public function validate(UploadedFile $uploadedFile): void;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/Validator/MimeTypeConstraintValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\MimeBaseTypeMismatchException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\MimeGuessMismatchException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\MimeTypeGuessFailedException;\nuse Symfony\\Component\\HttpFoundation\\File\\UploadedFile;\nuse Symfony\\Component\\Mime\\MimeTypeGuesserInterface;\n\nreadonly class MimeTypeConstraintValidator implements MediaConstraintValidatorInterface\n{\n    public function __construct(\n        private string $baseTypePrefix,\n        private MimeTypeGuesserInterface $mimeTypeGuesser\n    ) {\n    }\n\n    public function validate(UploadedFile $uploadedFile): void\n    {\n        $path = $uploadedFile->getPathname();\n\n        $guessedMimeType = $this->mimeTypeGuesser->guessMimeType($path)\n            ?? throw new MimeTypeGuessFailedException($path);\n\n        $clientMimeType = $uploadedFile->getClientMimeType();\n\n        if (!str_starts_with($guessedMimeType, $this->baseTypePrefix)) {\n            throw new MimeBaseTypeMismatchException(\n                $guessedMimeType,\n                $this->baseTypePrefix\n            );\n        }\n\n        if ($guessedMimeType !== $clientMimeType) {\n            throw new MimeGuessMismatchException(\n                $guessedMimeType,\n                $clientMimeType\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/Validator/UploadValidityConstraintValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\UploadInvalidException;\nuse Symfony\\Component\\HttpFoundation\\File\\UploadedFile;\n\nreadonly class UploadValidityConstraintValidator implements MediaConstraintValidatorInterface\n{\n    public function validate(UploadedFile $uploadedFile): void\n    {\n        if (!$uploadedFile->isValid()) {\n            throw new UploadInvalidException(\n                $uploadedFile->getError()\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Media/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Dao\\MediaDaoInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Dao\\MediaDao\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataMapper\\DataMapperInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataMapper\\DataMapper\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\MediaUrlGeneratorInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\MediaUrlGenerator\n    public: true\n    arguments:\n      $alternativeImageUrl: '%oxid_esales.alternative_image_url%'\n"
  },
  {
    "path": "source/Internal/Domain/Newsletter/Bridge/NewsletterRecipientsDaoBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\Dao\\NewsletterRecipientsDaoInterface;\n\nclass NewsletterRecipientsDaoBridge implements NewsletterRecipientsDaoInterface\n{\n    public function __construct(private NewsletterRecipientsDaoInterface $newsletterRecipientsDao)\n    {\n    }\n\n    /**\n     * @param int $shopId\n     *\n     * @return array\n     */\n    public function getNewsletterRecipients(int $shopId): array\n    {\n        return $this->newsletterRecipientsDao->getNewsletterRecipients($shopId);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Newsletter/Bridge/NewsletterRecipientsDaoBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\Bridge;\n\ninterface NewsletterRecipientsDaoBridgeInterface\n{\n    /**\n     * @param int $shopId\n     *\n     * @return array\n     */\n    public function get(int $shopId): array;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Newsletter/Dao/NewsletterRecipientsDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\Dao;\n\nuse Doctrine\\DBAL\\Exception;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\DataObject\\NewsletterRecipient;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface;\n\nclass NewsletterRecipientsDao implements NewsletterRecipientsDaoInterface\n{\n    public function __construct(private QueryBuilderFactoryInterface $queryBuilderFactory)\n    {\n    }\n\n    /**\n     * @param int $shopId\n     *\n     * @return NewsletterRecipient[]\n     * @throws Exception\n     */\n    public function getNewsletterRecipients(int $shopId): array\n    {\n        $recipientList = [];\n\n        $subscribersList = $this->getSubscribersList($shopId);\n\n        foreach ($subscribersList as $row) {\n            $recipient = new NewsletterRecipient();\n            $recipient->setSalutation(trim($row['Salutation']));\n            $recipient->setFistName($this->decodeHtmlEntities($row['Firstname']));\n            $recipient->setLastName($this->decodeHtmlEntities($row['Lastname']));\n            $recipient->setEmail($row['Email']);\n            $recipient->setOtpInState((string) $row['OptInState']);\n            $recipient->setCountry($row['Country']);\n            $recipient->setUserGroups($this->decodeHtmlEntities((string)$row['UserGroups']));\n            $recipientList[] = $recipient;\n        }\n\n        return $recipientList;\n    }\n\n    /**\n     * @param string $value\n     *\n     * @return string\n     */\n    private function decodeHtmlEntities(string $value): string\n    {\n        return html_entity_decode($value, ENT_QUOTES, 'utf-8');\n    }\n\n    /**\n     * @param int $shopId\n     *\n     * @return array\n     * @throws Exception\n     */\n    private function getSubscribersList(int $shopId): array\n    {\n        $queryBuilder = $this->queryBuilderFactory->create();\n        $queryBuilder\n            ->select(\n                'n.oxsal AS Salutation',\n                'n.oxfname AS Firstname',\n                'n.oxlname AS Lastname',\n                'u.oxusername AS Email',\n                'n.oxdboptin AS OptInState',\n                'c.oxtitle AS Country',\n                'GROUP_CONCAT(g.oxtitle ORDER BY g.oxtitle ASC) AS UserGroups',\n            )\n            ->from('oxnewssubscribed', 'n')\n            ->join('n', 'oxuser', 'u', 'u.oxid=n.oxuserid')\n            ->join('u', 'oxcountry', 'c', 'u.oxcountryid=c.oxid')\n            ->leftJoin('u', 'oxobject2group', 'o2g', 'u.oxid=o2g.oxobjectid')\n            ->leftJoin('o2g', 'oxgroups', 'g', 'o2g.oxgroupsid=g.oxid')\n            ->where('n.oxshopid = :shopId')\n            ->setParameters([\"shopId\" => $shopId])\n            ->groupBy('n.oxsal, n.oxfname, n.oxlname, u.oxusername, n.oxdboptin, c.oxtitle, u.oxcreate')\n            ->addOrderBy('u.oxcreate', 'ASC');\n\n        return $queryBuilder->executeQuery()->fetchAllAssociative();\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Newsletter/Dao/NewsletterRecipientsDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\Dao;\n\ninterface NewsletterRecipientsDaoInterface\n{\n    /**\n     * @param int $shopId\n     *\n     * @return array\n     */\n    public function getNewsletterRecipients(int $shopId): array;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Newsletter/DataMapper/NewsletterRecipientsDataMapper.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\DataMapper;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\DataObject\\NewsletterRecipient;\n\n/**\n * Class NewsletterRecipientsDataMapper\n */\nclass NewsletterRecipientsDataMapper implements NewsletterRecipientsDataMapperInterface\n{\n    public const SALUTATION = 'Salutation';\n    public const FIRST_NAME = 'Firstname';\n    public const LAST_NAME = 'LastName';\n    public const EMAIL = 'Email';\n    public const OPT_IN_STATE = 'Opt-In state';\n    public const COUNTRY = 'Country';\n    public const ASSIGNED_USER_GROUPS = 'Assigned user groups';\n\n    /**\n     * @param NewsletterRecipient[] $newsletterRecipient\n     *\n     * @return array\n     */\n    public function mapRecipientListDataToArray(array $newsletterRecipient): array\n    {\n        $result = [\n            [\n                self::SALUTATION,\n                self::FIRST_NAME,\n                self::LAST_NAME,\n                self::EMAIL,\n                self::OPT_IN_STATE,\n                self::COUNTRY,\n                self::ASSIGNED_USER_GROUPS,\n            ],\n        ];\n\n        foreach ($newsletterRecipient as $value) {\n            $result[] = [\n                $value->getSalutation(),\n                $value->getFistName(),\n                $value->getLastName(),\n                $value->getEmail(),\n                $value->getOtpInState(),\n                $value->getCountry(),\n                $value->getUserGroups(),\n            ];\n        }\n\n        return $result;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Newsletter/DataMapper/NewsletterRecipientsDataMapperInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\DataMapper;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\DataObject\\NewsletterRecipient;\n\ninterface NewsletterRecipientsDataMapperInterface\n{\n    /**\n     * @param NewsletterRecipient[] $newsletterRecipient\n     *\n     * @return array\n     */\n    public function mapRecipientListDataToArray(array $newsletterRecipient): array;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Newsletter/DataObject/NewsletterRecipient.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\DataObject;\n\nclass NewsletterRecipient\n{\n    /**\n     * @var string\n     */\n    private $salutation;\n\n    /**\n     * @var string\n     */\n    private $fistName;\n\n    /**\n     * @var string\n     */\n    private $lastName;\n\n    /**\n     * @var string\n     */\n    private $email;\n\n    /**\n     * @var string\n     */\n    private $otpInState;\n\n    /**\n     * @var string\n     */\n    private $country;\n\n    /**\n     * @var string\n     */\n    private $userGroups;\n\n    private const OPT_IN_STATE_SUBSCRIBED = 'subscribed';\n    private const OPT_IN_STATE_NOT_CONFIRMED = 'not confirmed';\n    private const OPT_IN_STATE_NOT_SUBSCRIBED = 'not subscribed';\n\n    private $otpInStateList = [\n        0 => self::OPT_IN_STATE_NOT_SUBSCRIBED,\n        1 => self::OPT_IN_STATE_SUBSCRIBED,\n        2 => self::OPT_IN_STATE_NOT_CONFIRMED,\n    ];\n\n    /**\n     * @return string\n     */\n    public function getSalutation(): string\n    {\n        return $this->salutation;\n    }\n\n    /**\n     * @param string $salutation\n     *\n     * @return NewsletterRecipient\n     */\n    public function setSalutation(string $salutation): NewsletterRecipient\n    {\n        $this->salutation = $salutation;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getFistName(): string\n    {\n        return $this->fistName;\n    }\n\n    /**\n     * @param string $fistName\n     *\n     * @return NewsletterRecipient\n     */\n    public function setFistName(string $fistName): NewsletterRecipient\n    {\n        $this->fistName = $fistName;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getLastName(): string\n    {\n        return $this->lastName;\n    }\n\n    /**\n     * @param string $lastName\n     *\n     * @return NewsletterRecipient\n     */\n    public function setLastName(string $lastName): NewsletterRecipient\n    {\n        $this->lastName = $lastName;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getEmail(): string\n    {\n        return $this->email;\n    }\n\n    /**\n     * @param string $email\n     *\n     * @return NewsletterRecipient\n     */\n    public function setEmail(string $email): NewsletterRecipient\n    {\n        $this->email = $email;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getOtpInState(): string\n    {\n        return $this->otpInState;\n    }\n\n    /**\n     * @param string $otpInState\n     *\n     * @return NewsletterRecipient\n     */\n    public function setOtpInState(string $otpInState): NewsletterRecipient\n    {\n        $this->otpInState = self::OPT_IN_STATE_NOT_SUBSCRIBED;\n        if (array_key_exists($otpInState, $this->otpInStateList)) {\n            $this->otpInState = $this->otpInStateList[$otpInState];\n        }\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getCountry(): string\n    {\n        return $this->country;\n    }\n\n    /**\n     * @param string $country\n     *\n     * @return NewsletterRecipient\n     */\n    public function setCountry(string $country): NewsletterRecipient\n    {\n        $this->country = $country;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getUserGroups(): string\n    {\n        return $this->userGroups;\n    }\n\n    /**\n     * @param string $userGroups\n     *\n     * @return NewsletterRecipient\n     */\n    public function setUserGroups(string $userGroups): NewsletterRecipient\n    {\n        $this->userGroups = $userGroups;\n\n        return $this;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Newsletter/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n    public: false\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\Dao\\NewsletterRecipientsDaoInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\Dao\\NewsletterRecipientsDao\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\Bridge\\NewsletterRecipientsDaoBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\Bridge\\NewsletterRecipientsDaoBridge\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\DataMapper\\NewsletterRecipientsDataMapperInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\DataMapper\\NewsletterRecipientsDataMapper\n    public: true"
  },
  {
    "path": "source/Internal/Domain/Product/Media/Dao/ProductMediaDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Dao;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse Doctrine\\DBAL\\Query\\QueryBuilder;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataMapper\\DataMapperInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaSorting;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRole;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\ConnectionFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface;\n\nuse function sprintf;\n\nreadonly class ProductMediaDao implements ProductMediaDaoInterface\n{\n    private const MEDIA_TABLE = 'oxmedia';\n    private const PRODUCT_MEDIA_TABLE = 'oxproduct_media';\n    private const PRODUCT_MEDIA_ROLES_TABLE = 'oxproduct_media_roles';\n\n    public function __construct(\n        private QueryBuilderFactoryInterface $queryBuilderFactory,\n        private ConnectionFactoryInterface $connectionFactory,\n        private DataMapperInterface $productMediaDataMapper\n    ) {\n    }\n\n    public function add(ProductMedia $productMedia): void\n    {\n        if (!$productMedia->hasPosition()) {\n            $productMedia->setPosition(\n                $this->getNextPosition($productMedia->getProductId())\n            );\n        }\n        $data = $this->productMediaDataMapper->toData($productMedia);\n\n        $this->queryBuilderFactory\n            ->create()\n            ->insert(self::PRODUCT_MEDIA_TABLE)\n            ->values([\n                'id' => ':id',\n                'product_id' => ':product_id',\n                'media_id' => ':media_id',\n                'position' => ':position',\n                'active' => ':active'\n            ])\n            ->setParameters(\n                $data\n            )\n            ->executeStatement();\n\n        $this->addRoles($productMedia->getId(), $data['roles']);\n    }\n\n    public function update(ProductMedia $productMedia): void\n    {\n        $this->get($productMedia->getId());\n        $data = $this->productMediaDataMapper->toData($productMedia);\n\n        $this->queryBuilderFactory\n            ->create()\n            ->update(self::PRODUCT_MEDIA_TABLE)\n            ->set(\n                'product_id',\n                ':product_id'\n            )\n            ->set(\n                'media_id',\n                ':media_id'\n            )\n            ->set(\n                'position',\n                ':position'\n            )\n            ->set(\n                'active',\n                ':active'\n            )\n            ->where('id = :id')\n            ->setParameters(\n                $data\n            )\n            ->executeStatement();\n\n        $this->replaceRoles($productMedia->getId(), $data['roles']);\n    }\n\n    public function delete(Id $id): void\n    {\n        $this->removeRoles($id);\n\n        $this->queryBuilderFactory\n            ->create()\n            ->delete(self::PRODUCT_MEDIA_TABLE)\n            ->where('id = :id')\n            ->setParameter(\n                'id',\n                $id\n            )\n            ->executeStatement();\n    }\n\n    public function sort(ProductMediaSorting $sorting): void\n    {\n        $caseClauses = '';\n        $parameters = [];\n        $inClausePlaceholders = [];\n\n        foreach ($sorting->getSorting() as $position => $id) {\n            $idParamName = 'id_' . $position;\n            $positionParamName = 'position_' . $position;\n\n            $caseClauses .= sprintf(\n                \" WHEN :%s THEN :%s \",\n                $idParamName,\n                $positionParamName\n            );\n\n            $parameters[$idParamName] = (string) $id;\n            $parameters[$positionParamName] = $position;\n            $inClausePlaceholders[] = ':' . $idParamName;\n        }\n\n        $query = sprintf(\n            'UPDATE `%s` SET `position` = CASE `id` %s END WHERE `id` IN (%s)',\n            self::PRODUCT_MEDIA_TABLE,\n            $caseClauses,\n            implode(', ', $inClausePlaceholders)\n        );\n\n        $this->connectionFactory\n            ->create()\n            ->executeStatement($query, $parameters);\n    }\n\n    public function get(Id $id): ProductMedia\n    {\n        $row = $this\n            ->prepareSelectWithJoin()\n            ->where('pm.id = :id')\n            ->setParameter(\n                'id',\n                $id\n            )\n            ->executeQuery()\n            ->fetchAssociative();\n        if (!isset($row['id'])) {\n            throw new EntryDoesNotExistDaoException(\n                sprintf(\n                    'Product media with ID %s was not found.',\n                    $id\n                )\n            );\n        }\n\n        return $this->productMediaDataMapper->fromData($row);\n    }\n\n    /** @return ArrayCollection<int, ProductMedia> */\n    public function getAll(Id $productId): ArrayCollection\n    {\n        return $this->getAllByActive($productId, false);\n    }\n\n    /** @return ArrayCollection<int, ProductMedia> */\n    public function getAllActive(Id $productId): ArrayCollection\n    {\n        return $this->getAllByActive($productId, true);\n    }\n\n    /** @return ArrayCollection<int, ProductMedia> */\n    public function getAllByRole(Id $productId, ProductMediaRole $role): ArrayCollection\n    {\n        return $this->getAllByRoleAndActive($productId, $role, false);\n    }\n\n    /** @return ArrayCollection<int, ProductMedia> */\n    public function getAllActiveByRole(Id $productId, ProductMediaRole $role): ArrayCollection\n    {\n        return $this->getAllByRoleAndActive($productId, $role, true);\n    }\n\n    public function getByRole(Id $productId, ProductMediaRole $role): ?ProductMedia\n    {\n        return $this->getByRoleAndActive($productId, $role, false);\n    }\n\n    public function getActiveByRole(Id $productId, ProductMediaRole $role): ?ProductMedia\n    {\n        return $this->getByRoleAndActive($productId, $role, true);\n    }\n\n    public function getActiveByPosition(Id $productId, int $position): ?ProductMedia\n    {\n        $row = $this->prepareSelectWithJoin()\n            ->where('pm.product_id = :productId')\n            ->andWhere('pm.position = :position')\n            ->andWhere('pm.active = 1')\n            ->setParameter('productId', $productId)\n            ->setParameter('position', $position)\n            ->setMaxResults(1)\n            ->executeQuery()\n            ->fetchAssociative();\n\n        return $row ? $this->productMediaDataMapper->fromData($row) : null;\n    }\n\n    public function getFirstActive(Id $productId): ?ProductMedia\n    {\n        $row = $this->prepareSelectWithJoin()\n            ->where('pm.product_id = :productId')\n            ->andWhere('pm.active = 1')\n            ->setParameter('productId', $productId)\n            ->orderBy('pm.position', 'ASC')\n            ->setMaxResults(1)\n            ->executeQuery()\n            ->fetchAssociative();\n\n        return $row ? $this->productMediaDataMapper->fromData($row) : null;\n    }\n\n    private function prepareSelectWithJoin(): QueryBuilder\n    {\n        return $this->queryBuilderFactory\n            ->create()\n            ->select(\n                'pm.id as id',\n                'pm.product_id as product_id',\n                'pm.position as position',\n                'pm.active as active',\n                'm.id as media_id',\n                'm.path as media_path',\n                'm.type as media_mime_type',\n                'GROUP_CONCAT(pmr.role) as roles',\n            )\n            ->from(\n                self::PRODUCT_MEDIA_TABLE,\n                'pm'\n            )\n            ->join(\n                'pm',\n                self::MEDIA_TABLE,\n                'm',\n                'pm.media_id = m.id'\n            )\n            ->leftJoin(\n                'pm',\n                self::PRODUCT_MEDIA_ROLES_TABLE,\n                'pmr',\n                'pm.id = pmr.product_media_id'\n            )\n            ->groupBy('pm.id');\n    }\n\n    /** @return ArrayCollection<int, ProductMedia> */\n    private function getAllByActive(Id $productId, bool $filterActive): ArrayCollection\n    {\n        $collection = new ArrayCollection();\n\n        $queryBuilder = $this\n            ->prepareSelectWithJoin()\n            ->where('pm.product_id = :productId')\n            ->setParameter('productId', $productId)\n            ->orderBy('pm.position', 'ASC');\n\n        if ($filterActive) {\n            $queryBuilder\n                ->andWhere('pm.active = :active')\n                ->setParameter('active', 1);\n        }\n\n        $rows = $queryBuilder\n            ->executeQuery()\n            ->fetchAllAssociative();\n\n        foreach ($rows as $row) {\n            $collection->add(\n                $this->productMediaDataMapper->fromData($row)\n            );\n        }\n\n        return $collection;\n    }\n\n    /** @return ArrayCollection<int, ProductMedia> */\n    private function getAllByRoleAndActive(Id $productId, ProductMediaRole $role, bool $onlyActive): ArrayCollection\n    {\n        $collection = new ArrayCollection();\n\n        $queryBuilder = $this\n            ->prepareSelectWithJoin()\n            ->where('pm.product_id = :productId')\n            ->andWhere('pmr.role = :role')\n            ->setParameter('productId', $productId)\n            ->setParameter('role', $role->value())\n            ->orderBy('pm.position', 'ASC');\n\n        if ($onlyActive) {\n            $queryBuilder\n                ->andWhere('pm.active = :active')\n                ->setParameter('active', 1);\n        }\n\n        $rows = $queryBuilder\n            ->executeQuery()\n            ->fetchAllAssociative();\n\n        foreach ($rows as $row) {\n            $collection->add(\n                $this->productMediaDataMapper->fromData($row)\n            );\n        }\n\n        return $collection;\n    }\n\n    private function getByRoleAndActive(Id $productId, ProductMediaRole $role, bool $onlyActive): ?ProductMedia\n    {\n        $queryBuilder = $this\n            ->prepareSelectWithJoin()\n            ->where('pm.product_id = :productId')\n            ->andWhere('pmr.role = :role')\n            ->setParameter('productId', $productId)\n            ->setParameter('role', $role->value())\n            ->orderBy('pm.position', 'ASC')\n            ->setMaxResults(1);\n\n        if ($onlyActive) {\n            $queryBuilder\n                ->andWhere('pm.active = :active')\n                ->setParameter('active', 1);\n        }\n\n        $row = $queryBuilder\n            ->executeQuery()\n            ->fetchAssociative();\n\n        return $row ? $this->productMediaDataMapper->fromData($row) : null;\n    }\n\n    private function getNextPosition(Id $productId): int\n    {\n        $maxPosition = $this->queryBuilderFactory\n            ->create()\n            ->select('MAX(pm.position) as maxPosition')\n            ->from(\n                self::PRODUCT_MEDIA_TABLE,\n                'pm'\n            )\n            ->where('pm.product_id = :productId')\n            ->setParameter(\n                'productId',\n                $productId\n            )\n            ->executeQuery()\n            ->fetchOne();\n\n        return $maxPosition === null ? 0 : ++$maxPosition;\n    }\n\n    private function removeRoles(Id $productMediaId): void\n    {\n        $this->queryBuilderFactory\n            ->create()\n            ->delete(self::PRODUCT_MEDIA_ROLES_TABLE)\n            ->where('product_media_id = :id')\n            ->setParameter('id', $productMediaId)\n            ->executeStatement();\n    }\n\n    private function addRoles(Id $productMediaId, array $roles): void\n    {\n        if (empty($roles)) {\n            return;\n        }\n\n        $insertQuery = $this->queryBuilderFactory\n            ->create()\n            ->insert(self::PRODUCT_MEDIA_ROLES_TABLE)\n            ->values([\n                'product_media_id' => ':product_media_id',\n                'role' => ':role'\n            ]);\n\n        foreach ($roles as $role) {\n            $insertQuery\n                ->setParameters([\n                    'product_media_id' => $productMediaId,\n                    'role' => $role\n                ])\n                ->executeStatement();\n        }\n    }\n\n    private function replaceRoles(Id $productMediaId, array $roles): void\n    {\n        $this->removeRoles($productMediaId);\n        $this->addRoles($productMediaId, $roles);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/Dao/ProductMediaDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Dao;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRole;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaSorting;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\n\ninterface ProductMediaDaoInterface\n{\n    public function add(ProductMedia $productMedia): void;\n\n    public function update(ProductMedia $productMedia): void;\n\n    public function delete(Id $id): void;\n\n    public function sort(ProductMediaSorting $sorting): void;\n\n    public function get(Id $id): ProductMedia;\n\n    /** @return ArrayCollection<int, ProductMedia> */\n    public function getAll(Id $productId): ArrayCollection;\n\n    /** @return ArrayCollection<int, ProductMedia> */\n    public function getAllActive(Id $productId): ArrayCollection;\n\n    /** @return ArrayCollection<int, ProductMedia> */\n    public function getAllByRole(Id $productId, ProductMediaRole $role): ArrayCollection;\n\n    /** @return ArrayCollection<int, ProductMedia> */\n    public function getAllActiveByRole(Id $productId, ProductMediaRole $role): ArrayCollection;\n\n    public function getByRole(Id $productId, ProductMediaRole $role): ?ProductMedia;\n\n    public function getActiveByRole(Id $productId, ProductMediaRole $role): ?ProductMedia;\n\n    public function getActiveByPosition(Id $productId, int $position): ?ProductMedia;\n\n    public function getFirstActive(Id $productId): ?ProductMedia;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/DataMapper/DataMapper.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataMapper;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataMapper\\DataMapperInterface as MediaDataMapperInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRole;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRoleSet;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\n\nreadonly class DataMapper implements DataMapperInterface\n{\n    public function __construct(private MediaDataMapperInterface $mediaDataMapper)\n    {\n    }\n\n    public function toData(ProductMedia $productMedia): array\n    {\n        return [\n            'id' => (string)$productMedia->getId(),\n            'product_id' => (string)$productMedia->getProductId(),\n            'media_id' => (string)$productMedia->getMedia()->getId(),\n            'position' => $productMedia->getPosition(),\n            'roles' => $this->getRolesAsValues($productMedia),\n            'active' => $productMedia->isActive(),\n\n        ];\n    }\n\n    public function fromData(array $data): ProductMedia\n    {\n        $roles = [];\n        if (!empty($data['roles'])) {\n            foreach (explode(',', $data['roles']) as $role) {\n                $roles[] = ProductMediaRole::from($role);\n            }\n        }\n        $productMedia = new ProductMedia(\n            Id::fromString($data['id']),\n            Id::fromString($data['product_id']),\n            $this->mediaDataMapper->fromData(\n                [\n                    'id' => $data['media_id'],\n                    'path' => $data['media_path'],\n                    'type' => $data['media_mime_type'],\n                ]\n            ),\n            new ProductMediaRoleSet(...$roles)\n        );\n        $productMedia->setPosition($data['position']);\n        if (!$data['active']) {\n            $productMedia->deactivate();\n        }\n\n        return $productMedia;\n    }\n\n    private function getRolesAsValues(ProductMedia $productMedia): array\n    {\n        return $productMedia\n            ->getRoleSet()\n            ->getRoles()\n            ->map(static fn(ProductMediaRole $role) => $role->value())\n            ->getValues();\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/DataMapper/DataMapperInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataMapper;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\n\ninterface DataMapperInterface\n{\n    public function toData(ProductMedia $productMedia): array;\n\n    public function fromData(array $data): ProductMedia;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/DataObject/ProductMedia.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\n\nclass ProductMedia\n{\n    private ?int $position = null;\n    private bool $active = true;\n\n    public function __construct(\n        private readonly Id $id,\n        private readonly Id $productId,\n        private readonly Media $media,\n        private ProductMediaRoleSet $roleSet,\n    ) {\n    }\n\n    public function getId(): Id\n    {\n        return $this->id;\n    }\n\n    public function getProductId(): Id\n    {\n        return $this->productId;\n    }\n\n    public function getMedia(): Media\n    {\n        return $this->media;\n    }\n\n    public function hasPosition(): bool\n    {\n        return $this->position !== null;\n    }\n\n    public function getPosition(): int\n    {\n        return $this->position;\n    }\n\n    public function setPosition(int $position): void\n    {\n        $this->position = $position;\n    }\n\n    public function getRoleSet(): ProductMediaRoleSet\n    {\n        return $this->roleSet;\n    }\n\n    public function isActive(): bool\n    {\n        return $this->active;\n    }\n\n    public function activate(): void\n    {\n        $this->active = true;\n    }\n\n    public function deactivate(): void\n    {\n        $this->active = false;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/DataObject/ProductMediaRole.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Exception\\EmptyProductMediaRoleException;\n\nclass ProductMediaRole\n{\n    public const ICON = 'icon';\n    public const THUMBNAIL = 'thumbnail';\n    public const DETAIL = 'detail';\n\n    public static function from(string $role): ProductMediaRole\n    {\n        if ($role === '') {\n            throw new EmptyProductMediaRoleException();\n        }\n\n        return new self($role);\n    }\n\n    public function value(): string\n    {\n        return $this->role;\n    }\n\n    private function __construct(private readonly string $role)\n    {\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/DataObject/ProductMediaRoleSet.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse Doctrine\\Common\\Collections\\Collection;\n\nclass ProductMediaRoleSet\n{\n    private Collection $roles;\n\n    public function __construct(ProductMediaRole ...$roles)\n    {\n        $this->roles = new ArrayCollection();\n        foreach ($roles as $r) {\n            $this->roles->set($r->value(), $r);\n        }\n    }\n\n    public function getRoles(): Collection\n    {\n        return $this->roles;\n    }\n\n    public function addRole(ProductMediaRole $role): void\n    {\n        $this->roles->set($role->value(), $role);\n    }\n\n    public function removeRole(ProductMediaRole $role): void\n    {\n        $this->roles->remove($role->value());\n    }\n\n    public function has(ProductMediaRole $role): bool\n    {\n        return $this->roles->containsKey($role->value());\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/DataObject/ProductMediaSorting.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject;\n\nuse ArrayIterator;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\n\nreadonly class ProductMediaSorting\n{\n    private ArrayIterator $sorting;\n\n    public function __construct(\n        array $sortedIds\n    ) {\n        $this->sorting = new ArrayIterator([]);\n        foreach ($sortedIds as $id) {\n            $this->sorting->append(\n                Id::fromString($id)\n            );\n        }\n    }\n\n    public function getSorting(): ArrayIterator\n    {\n        return $this->sorting;\n    }\n\n    public function __toString(): string\n    {\n        $ids = '';\n        foreach ($this->sorting as $id) {\n            $ids .= \"'$id',\";\n        }\n        return rtrim(\n            $ids,\n            ','\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/DataObject/ProductMediaView.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject;\n\nreadonly class ProductMediaView\n{\n    public function __construct(\n        private string $detailUrl,\n        private string $iconUrl,\n        private string $zoomUrl,\n        private string $thumbnailUrl,\n        private bool $isFallback = false\n    ) {\n    }\n\n    public function getDetailUrl(): string\n    {\n        return $this->detailUrl;\n    }\n\n    public function getIconUrl(): string\n    {\n        return $this->iconUrl;\n    }\n\n    public function getZoomUrl(): string\n    {\n        return $this->zoomUrl;\n    }\n\n    public function getThumbnailUrl(): string\n    {\n        return $this->thumbnailUrl;\n    }\n\n    public function isFallback(): bool\n    {\n        return $this->isFallback;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/Exception/EmptyProductMediaRoleException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Exception;\n\nclass EmptyProductMediaRoleException extends \\InvalidArgumentException\n{\n    public function __construct()\n    {\n        parent::__construct('ProductMediaRole must not be empty');\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/Factory/ProductMediaFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Factory;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaType;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRoleSet;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\n\nreadonly class ProductMediaFactory implements ProductMediaFactoryInterface\n{\n    public function create(Id $productId, MediaPath $path, MediaType $mimeType): ProductMedia\n    {\n        return new ProductMedia(\n            Id::generate(),\n            $productId,\n            new Media(\n                Id::generate(),\n                $path,\n                $mimeType\n            ),\n            new ProductMediaRoleSet(),\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/Factory/ProductMediaFactoryInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Factory;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaType;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\n\ninterface ProductMediaFactoryInterface\n{\n    public function create(Id $productId, MediaPath $path, MediaType $mimeType): ProductMedia;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/Service/ProductMediaPathResolver.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse Symfony\\Component\\Filesystem\\Path;\n\nreadonly class ProductMediaPathResolver implements ProductMediaPathResolverInterface\n{\n    public function __construct(\n        private ContextInterface $context\n    ) {\n    }\n\n    public function resolve(string $productId, string $filename): MediaPath\n    {\n        return new MediaPath(\n            Path::join(\n                Path::makeRelative(\n                    $this->context->getOutPath(),\n                    $this->context->getSourcePath()\n                ),\n                'pictures',\n                'media',\n                'products',\n                $productId,\n                $filename\n            )\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/Service/ProductMediaPathResolverInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\n\ninterface ProductMediaPathResolverInterface\n{\n    public function resolve(string $productId, string $filename): MediaPath;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/Service/ProductMediaService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Dao\\MediaDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Dao\\ProductMediaDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaSorting;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\n\nreadonly class ProductMediaService implements ProductMediaServiceInterface\n{\n    public function __construct(\n        private ProductMediaDaoInterface $productMediaDao,\n        private MediaDaoInterface $mediaDao,\n    ) {\n    }\n\n    public function add(ProductMedia $productMedia): void\n    {\n        $this->mediaDao\n            ->add(\n                $productMedia->getMedia()\n            );\n        $this->productMediaDao\n            ->add($productMedia);\n    }\n\n    public function get(Id $mediaId): ProductMedia\n    {\n        return $this->productMediaDao->get($mediaId);\n    }\n\n    public function remove(Id $mediaId): void\n    {\n        $this->productMediaDao->delete($mediaId);\n    }\n\n    public function update(ProductMedia $productMedia): void\n    {\n        $this->productMediaDao->update($productMedia);\n    }\n\n    public function sort(array $idsSorted): void\n    {\n        $this->productMediaDao->sort(\n            new ProductMediaSorting($idsSorted)\n        );\n    }\n\n    public function activate(ProductMedia $productMedia): void\n    {\n        if (!$productMedia->isActive()) {\n            $productMedia->activate();\n            $this->productMediaDao->update(\n                $productMedia\n            );\n        }\n    }\n\n    public function deactivate(ProductMedia $productMedia): void\n    {\n        if ($productMedia->isActive()) {\n            $productMedia->deactivate();\n            $this->productMediaDao->update(\n                $productMedia\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/Service/ProductMediaServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\n\ninterface ProductMediaServiceInterface\n{\n    public function add(ProductMedia $productMedia): void;\n\n    public function get(Id $mediaId): ProductMedia;\n\n    public function remove(Id $mediaId): void;\n\n    public function update(ProductMedia $productMedia): void;\n\n    public function sort(array $idsSorted): void;\n\n    public function activate(ProductMedia $productMedia): void;\n\n    public function deactivate(ProductMedia $productMedia): void;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/Service/ProductMediaUploadProcessor.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaType;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\MediaUploaderInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\MediaConstraintValidatorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Factory\\ProductMediaFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse Symfony\\Component\\HttpFoundation\\File\\UploadedFile;\n\nreadonly class ProductMediaUploadProcessor implements ProductMediaUploadProcessorInterface\n{\n    public function __construct(\n        private MediaConstraintValidatorInterface $mediaConstraintValidator,\n        private MediaUploaderInterface $mediaUploader,\n        private ProductMediaFactoryInterface $productMediaFactory,\n        private ProductMediaPathResolverInterface $productMediaPathResolver,\n    ) {\n    }\n\n    public function process(Id $productId, UploadedFile $uploadedFile): ProductMedia\n    {\n        $this->mediaConstraintValidator->validate($uploadedFile);\n\n        $targetPath = $this->productMediaPathResolver->resolve(\n            (string) $productId,\n            $uploadedFile->getClientOriginalName()\n        );\n\n        $this->mediaUploader->uploadTo($uploadedFile, $targetPath);\n\n        return $this->productMediaFactory->create(\n            $productId,\n            $targetPath,\n            new MediaType($uploadedFile->getClientMimeType())\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/Service/ProductMediaUploadProcessorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse Symfony\\Component\\HttpFoundation\\File\\UploadedFile;\n\ninterface ProductMediaUploadProcessorInterface\n{\n    public function process(Id $productId, UploadedFile $uploadedFile): ProductMedia;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/Service/ProductMediaViewService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaType;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\MediaUrlGeneratorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Dao\\ProductMediaDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRole;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaView;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Dao\\ShopConfigurationSettingDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Config\\Dao\\ThemeSettingDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\ShopAdapterInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\nuse Symfony\\Component\\Filesystem\\Path;\n\nreadonly class ProductMediaViewService implements ProductMediaViewServiceInterface\n{\n    public function __construct(\n        private ProductMediaDaoInterface $productMediaDao,\n        private MediaUrlGeneratorInterface $mediaUrlGenerator,\n        private ShopConfigurationSettingDaoInterface $shopConfigurationSettingDao,\n        private ThemeSettingDaoInterface $themeSettingDao,\n        private ShopAdapterInterface $shopAdapter,\n        private ContextInterface $context\n    ) {\n    }\n\n    public function getByRole(Id $productId, ProductMediaRole $role): ProductMediaView\n    {\n        $productMedia = $this->getMediaWithFallback($productId, $role);\n        return $productMedia ? $this->createMediaViewWithAllSizes($productMedia) : $this->createFallbackMediaView();\n    }\n\n    public function getByPosition(Id $productId, int $position): ProductMediaView\n    {\n        $productMedia = $this->productMediaDao->getActiveByPosition($productId, $position);\n\n        if (!$productMedia) {\n            return $this->createFallbackMediaView();\n        }\n\n        return $this->createMediaViewWithAllSizes($productMedia);\n    }\n\n    /** @return array<string, ProductMediaView> */\n    public function getAllByRole(Id $productId, ProductMediaRole $role): array\n    {\n        $productMediaCollection = $this->productMediaDao->getAllActiveByRole(\n            $productId,\n            $role\n        );\n        $mediaViews = [];\n\n        foreach ($productMediaCollection as $productMedia) {\n            $mediaViews[(string) $productMedia->getMedia()->getId()] =\n                $this->createMediaViewWithAllSizes($productMedia);\n        }\n\n        return $mediaViews;\n    }\n\n    private function getMediaWithFallback(Id $productId, ProductMediaRole $role): ?ProductMedia\n    {\n        $productMedia = $this->productMediaDao->getActiveByRole($productId, $role);\n        return $productMedia ?: $this->productMediaDao->getFirstActive($productId);\n    }\n\n    private function createMediaViewWithAllSizes(ProductMedia $productMedia): ProductMediaView\n    {\n        return $this->createMediaView($productMedia->getMedia(), false);\n    }\n\n    private function createFallbackMediaView(): ProductMediaView\n    {\n        return $this->createMediaView($this->createFallbackMedia(), true);\n    }\n\n    private function createMediaView(Media $media, bool $isFallback): ProductMediaView\n    {\n        $detailSize = $this->getConfiguredSize('sDetailImageSize');\n        $iconSize = $this->getConfiguredSize('sIconsize');\n        $zoomSize = $this->getConfiguredSize('sZoomImageSize');\n        $thumbnailSize = $this->getConfiguredSize('sThumbnailsize');\n\n        return new ProductMediaView(\n            detailUrl: $this->mediaUrlGenerator->generateSizedImageUrl($media, $detailSize),\n            iconUrl: $this->mediaUrlGenerator->generateSizedImageUrl($media, $iconSize),\n            zoomUrl: $this->mediaUrlGenerator->generateSizedImageUrl($media, $zoomSize),\n            thumbnailUrl: $this->mediaUrlGenerator->generateSizedImageUrl($media, $thumbnailSize),\n            isFallback: $isFallback\n        );\n    }\n\n    private function createFallbackMedia(): Media\n    {\n        $fallbackFilename = $this->getFallbackFilename();\n        $mimeType = str_ends_with($fallbackFilename, '.webp') ? 'image/webp' : 'image/jpeg';\n\n        return new Media(\n            Id::generate(),\n            new MediaPath(Path::join('out', 'pictures', 'media', $fallbackFilename)),\n            new MediaType($mimeType)\n        );\n    }\n\n    private function getFallbackFilename(): string\n    {\n        $convertToWebP = $this->shopConfigurationSettingDao->get(\n            'blConvertImagesToWebP',\n            $this->context->getCurrentShopId()\n        );\n\n        return $convertToWebP->getValue() ? 'nopic.webp' : 'nopic.jpg';\n    }\n\n    private function getConfiguredSize(string $sizeConfigKey): string\n    {\n        try {\n            $setting = $this->themeSettingDao->get(\n                $sizeConfigKey,\n                $this->context->getCurrentShopId(),\n                $this->shopAdapter->getActiveThemeId()\n            );\n        } catch (EntryDoesNotExistDaoException $e) {\n            $setting = $this->shopConfigurationSettingDao->get(\n                $sizeConfigKey,\n                $this->context->getCurrentShopId()\n            );\n        }\n\n        return (string) $setting->getValue();\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/Service/ProductMediaViewServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRole;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaView;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\n\ninterface ProductMediaViewServiceInterface\n{\n    public function getByRole(Id $productId, ProductMediaRole $role): ProductMediaView;\n\n    /** @return array<string, ProductMediaView> */\n    public function getAllByRole(Id $productId, ProductMediaRole $role): array;\n\n    public function getByPosition(Id $productId, int $position): ProductMediaView;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/Service/ProductVariantMediaService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Dao\\ProductMediaDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRoleSet;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\n\nreadonly class ProductVariantMediaService implements ProductVariantMediaServiceInterface\n{\n    public function __construct(\n        private ProductMediaDaoInterface $productMediaDao,\n    ) {\n    }\n\n    public function assignFromParentToVariant(Id $parentProductId, Id $variantProductId): void\n    {\n        $parentMediaCollection = $this->productMediaDao->getAll($parentProductId);\n\n        foreach ($parentMediaCollection as $parentMedia) {\n            $variantMedia = $this->createVariantMedia($parentMedia, $variantProductId);\n            $this->productMediaDao->add($variantMedia);\n        }\n    }\n\n    private function createVariantMedia(ProductMedia $parentMedia, Id $variantProductId): ProductMedia\n    {\n        $variantMedia = new ProductMedia(\n            Id::generate(),\n            $variantProductId,\n            $parentMedia->getMedia(),\n            new ProductMediaRoleSet(...$parentMedia->getRoleSet()->getRoles()->getValues()),\n        );\n\n        if ($parentMedia->hasPosition()) {\n            $variantMedia->setPosition($parentMedia->getPosition());\n        }\n\n        if (!$parentMedia->isActive()) {\n            $variantMedia->deactivate();\n        }\n\n        return $variantMedia;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/Service/ProductVariantMediaServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\n\ninterface ProductVariantMediaServiceInterface\n{\n    public function assignFromParentToVariant(Id $parentProductId, Id $variantProductId): void;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/Validator/ProductMediaValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\MediaConstraintValidatorInterface;\nuse Symfony\\Component\\HttpFoundation\\File\\UploadedFile;\n\nclass ProductMediaValidator implements MediaConstraintValidatorInterface\n{\n    public function __construct(\n        private readonly iterable $validators\n    ) {\n    }\n\n    public function validate(UploadedFile $uploadedFile): void\n    {\n        foreach ($this->validators as $validator) {\n            $validator->validate($uploadedFile);\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/parameters.yaml",
    "content": "parameters:\n  oxid_esales.product.media.file.min_size_kb: 1\n  oxid_esales.product.media.file.max_size_kb: 102400\n"
  },
  {
    "path": "source/Internal/Domain/Product/Media/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Dao\\ProductMediaDaoInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Dao\\ProductMediaDao\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Factory\\ProductMediaFactoryInterface:\n      class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Factory\\ProductMediaFactory\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaService\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaViewServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaViewService\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductVariantMediaServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductVariantMediaService\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaUploadProcessorInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaUploadProcessor\n    arguments:\n      $mediaConstraintValidator: '@OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Validator\\ProductMediaValidator'\n      $mediaUploader: '@oxid_esales.product.media.media_uploader'\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaPathResolverInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaPathResolver\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataMapper\\DataMapperInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataMapper\\DataMapper\n\n  oxid_esales.product.media.media_uploader:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\MediaUploader\n\n  oxid_esales.product.media.constraint_validator.upload_validity:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\UploadValidityConstraintValidator\n    tags:\n      - { name: oxid.product_media.constraint_validator }\n\n  oxid_esales.product.media.constraint_validator.size:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\FileSizeConstraintValidator\n    arguments:\n      $minSizeKb: '%oxid_esales.product.media.file.min_size_kb%'\n      $maxSizeKb: '%oxid_esales.product.media.file.max_size_kb%'\n    tags:\n      - { name: oxid.product_media.constraint_validator }\n\n  oxid_esales.product.media.constraint_validator.mime_type:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\MimeTypeConstraintValidator\n    arguments:\n      $baseTypePrefix: 'image/'\n      $mimeTypeGuesser: \"@oxid_esales.symfony.mime_types\"\n    tags:\n      - { name: oxid.product_media.constraint_validator }\n\n  oxid_esales.product.media.constraint_validator.file_extension:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\FileExtensionConstraintValidator\n    arguments:\n      $mimeTypeGuesser: \"@oxid_esales.symfony.mime_types\"\n    tags:\n      - { name: oxid.product_media.constraint_validator }\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Validator\\ProductMediaValidator:\n    arguments:\n      $validators: !tagged_iterator oxid.product_media.constraint_validator\n"
  },
  {
    "path": "source/Internal/Domain/Product/Search/Event/AfterProductSearchEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search\\Event;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search\\ProductSearchCriteria;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search\\ProductSearchResult;\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\nclass AfterProductSearchEvent extends Event\n{\n    public function __construct(\n        private readonly ProductSearchCriteria $searchCriteria,\n        private readonly array $context,\n        private ProductSearchResult $searchResult,\n    ) {\n    }\n\n    public function getSearchCriteria(): ProductSearchCriteria\n    {\n        return $this->searchCriteria;\n    }\n\n    public function getContext(): array\n    {\n        return $this->context;\n    }\n\n    public function getSearchResult(): ProductSearchResult\n    {\n        return $this->searchResult;\n    }\n\n    public function setSearchResult(ProductSearchResult $searchResult): void\n    {\n        $this->searchResult = $searchResult;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Search/Event/BeforeProductSearchEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search\\Event;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search\\ProductSearchCriteria;\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\nclass BeforeProductSearchEvent extends Event\n{\n    public function __construct(\n        private ProductSearchCriteria $searchCriteria,\n        private array $context = [],\n    ) {\n    }\n\n    public function getSearchCriteria(): ProductSearchCriteria\n    {\n        return $this->searchCriteria;\n    }\n\n    public function setSearchCriteria(ProductSearchCriteria $searchCriteria): void\n    {\n        $this->searchCriteria = $searchCriteria;\n    }\n\n    public function getContext(): array\n    {\n        return $this->context;\n    }\n\n    public function setContext(array $context): void\n    {\n        $this->context = $context;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Search/ProductSearchCriteria.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Search\\FilterInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Search\\Pagination;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Search\\SearchTerm;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Search\\Sorting;\n\nreadonly class ProductSearchCriteria\n{\n    /** @var list<FilterInterface> */\n    private array $filters;\n\n    /** @var list<Sorting> */\n    private array $sorting;\n\n    /**\n     * @param list<FilterInterface> $filters\n     * @param list<Sorting> $sorting\n     */\n    public function __construct(\n        private Pagination $pagination,\n        private SearchTerm $term,\n        array $filters = [],\n        array $sorting = [],\n    ) {\n        $this->filters = array_values($filters);\n        $this->sorting = array_values($sorting);\n    }\n\n    public function getTerm(): SearchTerm\n    {\n        return $this->term;\n    }\n\n    public function getPagination(): Pagination\n    {\n        return $this->pagination;\n    }\n\n    /** @return list<FilterInterface> */\n    public function getFilters(): array\n    {\n        return $this->filters;\n    }\n\n    /** @return list<Sorting> */\n    public function getSorting(): array\n    {\n        return $this->sorting;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Search/ProductSearchException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search;\n\nuse Exception;\n\nclass ProductSearchException extends Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Search/ProductSearchResult.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\n\nreadonly class ProductSearchResult\n{\n    /** @var list<Id> */\n    private array $productIds;\n\n    /** @param list<Id> $productIds */\n    public function __construct(\n        array $productIds,\n        private int $total,\n    ) {\n        $this->productIds = array_values($productIds);\n    }\n\n    /** @return list<Id> */\n    public function getProductIds(): array\n    {\n        return $this->productIds;\n    }\n\n    public function getTotal(): int\n    {\n        return $this->total;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Search/ProductSearchServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search;\n\ninterface ProductSearchServiceInterface\n{\n    /** @throws ProductSearchException */\n    public function search(ProductSearchCriteria $criteria, array $context = []): ProductSearchResult;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Product/Search/parameters.yaml",
    "content": "parameters:\n  oxid_esales.product_search_enabled: false\n"
  },
  {
    "path": "source/Internal/Domain/Product/Search/services.yaml",
    "content": "imports:\n  - { resource: parameters.yaml }\n\nservices:\n  _defaults:\n    autowire: true\n"
  },
  {
    "path": "source/Internal/Domain/Review/Bridge/ProductRatingBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\ProductRatingServiceInterface;\n\nclass ProductRatingBridge implements ProductRatingBridgeInterface\n{\n    public function __construct(private ProductRatingServiceInterface $productRatingService)\n    {\n    }\n\n    /**\n     * @param string $productId\n     */\n    public function updateProductRating($productId)\n    {\n        $this->productRatingService->updateProductRating($productId);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Bridge/ProductRatingBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\ninterface ProductRatingBridgeInterface\n{\n    /**\n     * @param string $productId\n     */\n    public function updateProductRating($productId);\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Bridge/UserRatingBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Exception\\RatingPermissionException;\nuse OxidEsales\\Eshop\\Application\\Model\\Rating;\n\nclass UserRatingBridge implements UserRatingBridgeInterface\n{\n    /**\n     * Delete a Rating.\n     *\n     * @param string $userId\n     * @param string $ratingId\n     *\n     * @throws RatingPermissionException\n     * @throws EntryDoesNotExistDaoException\n     */\n    public function deleteRating($userId, $ratingId)\n    {\n        $rating = $this->getRatingById($ratingId);\n\n        $this->validateUserPermissionsToManageRating($rating, $userId);\n\n        $rating = $this->disableSubShopDeleteProtectionForRating($rating);\n        $rating->delete();\n    }\n\n    /**\n     * @param Rating $rating\n     *\n     * @return Rating\n     */\n    private function disableSubShopDeleteProtectionForRating(Rating $rating)\n    {\n        $rating->setIsDerived(false);\n\n        return $rating;\n    }\n\n    /**\n     * @param Rating $rating\n     * @param string $userId\n     *\n     * @throws RatingPermissionException\n     */\n    private function validateUserPermissionsToManageRating(Rating $rating, $userId)\n    {\n        if ($rating->oxratings__oxuserid->value !== $userId) {\n            throw new RatingPermissionException();\n        }\n    }\n\n    /**\n     * @param string $ratingId\n     *\n     * @return Rating\n     * @throws EntryDoesNotExistDaoException\n     */\n    private function getRatingById($ratingId)\n    {\n        $rating = oxNew(Rating::class);\n        $doesRatingExist = $rating->load($ratingId);\n\n        if (!$doesRatingExist) {\n            throw new EntryDoesNotExistDaoException();\n        }\n\n        return $rating;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Bridge/UserRatingBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Exception\\RatingPermissionException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\ninterface UserRatingBridgeInterface\n{\n    /**\n     * Delete a Rating.\n     *\n     * @param string $userId\n     * @param string $ratingId\n     *\n     * @throws RatingPermissionException\n     * @throws EntryDoesNotExistDaoException\n     */\n    public function deleteRating($userId, $ratingId);\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Bridge/UserReviewAndRatingBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Application\\Model\\RecommendationList;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\ViewDataObject\\ReviewAndRating;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\UserReviewAndRatingServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Exception\\ReviewAndRatingObjectTypeException;\n\nclass UserReviewAndRatingBridge implements UserReviewAndRatingBridgeInterface\n{\n    public function __construct(private UserReviewAndRatingServiceInterface $userReviewAndRatingService)\n    {\n    }\n\n    /**\n     * Get number of reviews by given user.\n     *\n     * @param string $userId\n     *\n     * @return int\n     */\n    public function getReviewAndRatingListCount($userId)\n    {\n        return $this\n            ->userReviewAndRatingService\n            ->getReviewAndRatingListCount($userId);\n    }\n\n    /**\n     * Returns Collection of User Ratings and Reviews.\n     *\n     * @param string $userId\n     *\n     * @return array\n     */\n    public function getReviewAndRatingList($userId)\n    {\n        $reviewAndRatingList = $this\n            ->userReviewAndRatingService\n            ->getReviewAndRatingList($userId);\n\n        $this->prepareRatingAndReviewPropertiesData($reviewAndRatingList);\n\n        return $reviewAndRatingList->toArray();\n    }\n\n    /**\n     * Prepare RatingAndReview properties data.\n     *\n     * @param ArrayCollection $reviewAndRatingList\n     */\n    private function prepareRatingAndReviewPropertiesData($reviewAndRatingList)\n    {\n        foreach ($reviewAndRatingList as $reviewAndRating) {\n            $this->setObjectTitleToReviewAndRating($reviewAndRating);\n            $this->formatReviewText($reviewAndRating);\n            $this->formatReviewAndRatingDate($reviewAndRating);\n        }\n    }\n\n    /**\n     * Formats Review text.\n     *\n     * @param ReviewAndRating $reviewAndRating\n     */\n    private function formatReviewText(ReviewAndRating $reviewAndRating)\n    {\n        $preparedText = htmlspecialchars($reviewAndRating->getReviewText());\n\n        $reviewAndRating->setReviewText($preparedText);\n    }\n\n    /**\n     * Formats ReviewAndRating date.\n     *\n     * @param ReviewAndRating $reviewAndRating\n     */\n    private function formatReviewAndRatingDate(ReviewAndRating $reviewAndRating)\n    {\n        $formattedDate = Registry::getUtilsDate()->formatDBDate($reviewAndRating->getCreatedAt());\n\n        $reviewAndRating->setCreatedAt($formattedDate);\n    }\n\n    /**\n     * Sets object title to ReviewAndRating.\n     *\n     * @param ReviewAndRating $reviewAndRating\n     */\n    private function setObjectTitleToReviewAndRating(ReviewAndRating $reviewAndRating)\n    {\n        $title = $this->getObjectTitle(\n            $reviewAndRating->getObjectType(),\n            $reviewAndRating->getObjectId()\n        );\n\n        $reviewAndRating->setObjectTitle($title);\n    }\n\n    /**\n     * Returns object title.\n     *\n     * @param string $type\n     * @param string $objectId\n     *\n     * @return string\n     */\n    private function getObjectTitle($type, $objectId)\n    {\n        $objectModel = $this->getObjectModel($type);\n        $objectModel->load($objectId);\n\n        $fieldName = $this->getObjectTitleFieldName($type);\n        $field = $objectModel->$fieldName;\n\n        return $field ? $field->value : '';\n    }\n\n    /**\n     * Returns object model.\n     *\n     * @param string $type\n     *\n     * @return Article|RecommendationList\n     * @throws ReviewAndRatingObjectTypeException\n     */\n    private function getObjectModel($type): Article|RecommendationList\n    {\n        if ($type === 'oxarticle') {\n            $model = oxNew(Article::class);\n        }\n\n        if ($type === 'oxrecommlist') {\n            $model = oxNew(RecommendationList::class);\n        }\n\n        if (!isset($model)) {\n            throw new ReviewAndRatingObjectTypeException();\n        }\n\n        return $model;\n    }\n\n    /**\n     * Returns field name of the object title.\n     *\n     * @param string $type\n     *\n     * @return string\n     * @throws ReviewAndRatingObjectTypeException\n     */\n    private function getObjectTitleFieldName($type)\n    {\n        if ($type === 'oxarticle') {\n            $fieldName = 'oxarticles__oxtitle';\n        }\n\n        if ($type === 'oxrecommlist') {\n            $fieldName = 'oxrecommlists__oxtitle';\n        }\n\n        if (!isset($fieldName)) {\n            throw new ReviewAndRatingObjectTypeException();\n        }\n\n        return $fieldName;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Bridge/UserReviewAndRatingBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\ninterface UserReviewAndRatingBridgeInterface\n{\n    /**\n     * Get number of reviews by given user.\n     *\n     * @param string $userId\n     *\n     * @return int\n     */\n    public function getReviewAndRatingListCount($userId);\n\n    /**\n     * Returns Collection of User Ratings and Reviews.\n     *\n     * @param string $userId\n     *\n     * @return array\n     */\n    public function getReviewAndRatingList($userId);\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Bridge/UserReviewBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Exception\\ReviewPermissionException;\nuse OxidEsales\\Eshop\\Application\\Model\\Review;\n\nclass UserReviewBridge implements UserReviewBridgeInterface\n{\n    /**\n     * Delete a Review.\n     *\n     * @param string $userId\n     * @param string $reviewId\n     *\n     * @throws ReviewPermissionException\n     * @throws EntryDoesNotExistDaoException\n     */\n    public function deleteReview($userId, $reviewId)\n    {\n        $review = $this->getReviewById($reviewId);\n\n        $this->validateUserPermissionsToManageReview($review, $userId);\n\n        $review->delete();\n    }\n\n    /**\n     * @param Review $review\n     * @param string $userId\n     *\n     * @throws ReviewPermissionException\n     */\n    private function validateUserPermissionsToManageReview(Review $review, $userId)\n    {\n        if ($review->oxreviews__oxuserid->value !== $userId) {\n            throw new ReviewPermissionException();\n        }\n    }\n\n    /**\n     * @param string $reviewId\n     *\n     * @return Review\n     * @throws EntryDoesNotExistDaoException\n     */\n    private function getReviewById($reviewId)\n    {\n        $review = oxNew(Review::class);\n        $doesReviewExist = $review->load($reviewId);\n\n        if (!$doesReviewExist) {\n            throw new EntryDoesNotExistDaoException();\n        }\n\n        return $review;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Bridge/UserReviewBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Exception\\ReviewPermissionException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\ninterface UserReviewBridgeInterface\n{\n    /**\n     * Delete a Review.\n     *\n     * @param string $userId\n     * @param string $reviewId\n     *\n     * @throws ReviewPermissionException\n     * @throws EntryDoesNotExistDaoException\n     */\n    public function deleteReview($userId, $reviewId);\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Bridge/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge\\UserReviewBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge\\UserReviewBridge\n    public: true\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge\\UserRatingBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge\\UserRatingBridge\n    public: true\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge\\ProductRatingBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge\\ProductRatingBridge\n    public: true\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge\\UserReviewAndRatingBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge\\UserReviewAndRatingBridge\n    public: true\n"
  },
  {
    "path": "source/Internal/Domain/Review/Dao/ProductRatingDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\InvalidObjectIdDaoException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataMapper\\ProductRatingDataMapperInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\ProductRating;\n\nclass ProductRatingDao implements ProductRatingDaoInterface\n{\n    public function __construct(\n        private QueryBuilderFactoryInterface $queryBuilderFactory,\n        private ProductRatingDataMapperInterface $productRatingMapper\n    ) {\n    }\n\n    /**\n     * @param ProductRating $productRating\n     */\n    public function update(ProductRating $productRating)\n    {\n        $queryBuilder = $this->queryBuilderFactory->create();\n        $queryBuilder\n            ->update('oxarticles')\n            ->set('OXRATING', ':OXRATING')\n            ->set('OXRATINGCNT', ':OXRATINGCNT')\n            ->where('OXID = :OXID')\n            ->setParameters($this->productRatingMapper->getData($productRating));\n\n        $queryBuilder->executeStatement();\n    }\n\n    /**\n     * @param string $productId\n     *\n     * @return ProductRating\n     * @throws InvalidObjectIdDaoException\n     */\n    public function getProductRatingById($productId)\n    {\n        $this->validateProductId($productId);\n\n        $queryBuilder = $this->queryBuilderFactory->create();\n        $queryBuilder\n            ->select('OXID', 'OXRATING', 'OXRATINGCNT')\n            ->from('oxarticles')\n            ->where('oxid = :productId')\n            ->setMaxResults(1)\n            ->setParameter('productId', $productId);\n\n        return $this->productRatingMapper->map(\n            new ProductRating(),\n            $queryBuilder->fetchAssociative()\n        );\n    }\n\n    /**\n     * @param string $productId\n     *\n     * @throws InvalidObjectIdDaoException\n     */\n    private function validateProductId($productId)\n    {\n        if (empty($productId) || !is_string($productId)) {\n            throw new InvalidObjectIdDaoException();\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Dao/ProductRatingDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\ProductRating;\n\ninterface ProductRatingDaoInterface\n{\n    /**\n     * @param ProductRating $productRating\n     */\n    public function update(ProductRating $productRating);\n\n    /**\n     * @param string $productId\n     *\n     * @return ProductRating\n     */\n    public function getProductRatingById($productId);\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Dao/RatingDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataMapper\\RatingDataMapperInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\Rating;\n\nclass RatingDao implements RatingDaoInterface\n{\n    public function __construct(\n        private QueryBuilderFactoryInterface $queryBuilderFactory,\n        private RatingDataMapperInterface $ratingDataMapper\n    ) {\n    }\n\n    /**\n     * Returns User Ratings.\n     *\n     * @param string $userId\n     *\n     * @return ArrayCollection\n     */\n    public function getRatingsByUserId($userId)\n    {\n        $queryBuilder = $this->queryBuilderFactory->create();\n        $queryBuilder\n            ->select('r.*')\n            ->from('oxratings', 'r')\n            ->where('r.oxuserid = :userId')\n            ->orderBy('r.oxtimestamp', 'DESC')\n            ->setParameter('userId', $userId);\n\n        return $this->mapRatings($queryBuilder->fetchAllAssociative());\n    }\n\n    /**\n     * @param Rating $rating\n     */\n    public function delete(Rating $rating)\n    {\n        $queryBuilder = $this->queryBuilderFactory->create();\n        $queryBuilder\n            ->delete('oxratings')\n            ->where('oxid = :id')\n            ->setParameter('id', $rating->getId())\n            ->executeStatement();\n    }\n\n    /**\n     * Returns Ratings for a product.\n     *\n     * @param string $productId\n     *\n     * @return ArrayCollection\n     */\n    public function getRatingsByProductId($productId)\n    {\n        $queryBuilder = $this->queryBuilderFactory->create();\n        $queryBuilder\n            ->select('r.*')\n            ->from('oxratings', 'r')\n            ->where('r.oxobjectid = :productId')\n            ->andWhere('r.oxtype = :productType')\n            ->orderBy('r.oxtimestamp', 'DESC')\n            ->setParameters(\n                [\n                    'productId'     => $productId,\n                    'productType'   => 'oxarticle',\n                ]\n            );\n\n        return $this->mapRatings($queryBuilder->fetchAllAssociative());\n    }\n\n    /**\n     * Maps rating data from database to Ratings Collection.\n     *\n     * @param array $ratingsData\n     *\n     * @return ArrayCollection\n     */\n    private function mapRatings($ratingsData)\n    {\n        $ratings = new ArrayCollection();\n\n        foreach ($ratingsData as $ratingData) {\n            $rating = new Rating();\n            $ratings->add($this->ratingDataMapper->map($rating, $ratingData));\n        }\n\n        return $ratings;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Dao/RatingDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\Rating;\n\ninterface RatingDaoInterface\n{\n    /**\n     * Returns User Ratings.\n     *\n     * @param string $userId\n     *\n     * @return ArrayCollection\n     */\n    public function getRatingsByUserId($userId);\n\n    /**\n     * Returns Ratings for a product.\n     *\n     * @param string $productId\n     *\n     * @return ArrayCollection\n     */\n    public function getRatingsByProductId($productId);\n\n    /**\n     * @param Rating $rating\n     */\n    public function delete(Rating $rating);\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Dao/ReviewDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataMapper\\ReviewDataMapperInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\Review;\n\nclass ReviewDao implements ReviewDaoInterface\n{\n    public function __construct(\n        private QueryBuilderFactoryInterface $queryBuilderFactory,\n        private ReviewDataMapperInterface $reviewDataMapper\n    ) {\n    }\n\n    /**\n     * Returns User Reviews.\n     *\n     * @param string $userId\n     *\n     * @return ArrayCollection\n     */\n    public function getReviewsByUserId($userId)\n    {\n        $queryBuilder = $this->queryBuilderFactory->create();\n        $queryBuilder\n            ->select('r.*')\n            ->from('oxreviews', 'r')\n            ->where('r.oxuserid = :userId')\n            ->orderBy('r.oxcreate', 'DESC')\n            ->setParameter('userId', $userId);\n\n        return $this->mapReviews($queryBuilder->fetchAllAssociative());\n    }\n\n    /**\n     * @param Review $review\n     */\n    public function delete(Review $review)\n    {\n        $queryBuilder = $this->queryBuilderFactory->create();\n        $queryBuilder\n            ->delete('oxreviews')\n            ->where('oxid = :id')\n            ->setParameter('id', $review->getId())\n            ->executeStatement();\n    }\n\n    /**\n     * Maps rating data from database to Reviews Collection.\n     *\n     * @param array $reviewsData\n     *\n     * @return ArrayCollection\n     */\n    private function mapReviews($reviewsData)\n    {\n        $reviews = new ArrayCollection();\n\n        foreach ($reviewsData as $reviewData) {\n            $review = new Review();\n            $reviews[] = $this->reviewDataMapper->map($review, $reviewData);\n        }\n\n        return $reviews;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Dao/ReviewDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\Review;\n\ninterface ReviewDaoInterface\n{\n    /**\n     * Returns User Reviews.\n     *\n     * @param string $userId\n     *\n     * @return ArrayCollection\n     */\n    public function getReviewsByUserId($userId);\n\n    /**\n     * @param Review $review\n     */\n    public function delete(Review $review);\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Dao/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao\\ProductRatingDaoInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao\\ProductRatingDao\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao\\RatingDaoInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao\\RatingDao\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao\\ReviewDaoInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao\\ReviewDao\n"
  },
  {
    "path": "source/Internal/Domain/Review/DataMapper/ProductRatingDataMapper.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataMapper;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\ProductRating;\n\nclass ProductRatingDataMapper implements ProductRatingDataMapperInterface\n{\n    /**\n     * @param ProductRating $productRating\n     * @param array         $data\n     *\n     * @return ProductRating\n     */\n    public function map(ProductRating $productRating, array $data): ProductRating\n    {\n        $productRating\n            ->setProductId($data['OXID'])\n            ->setRatingAverage($data['OXRATING'])\n            ->setRatingCount($data['OXRATINGCNT']);\n\n        return $productRating;\n    }\n\n    /**\n     * @param ProductRating $productRating\n     *\n     * @return array\n     */\n    public function getData(ProductRating $productRating): array\n    {\n        return [\n            'OXID'        => $productRating->getProductId(),\n            'OXRATING'    => $productRating->getRatingAverage(),\n            'OXRATINGCNT' => $productRating->getRatingCount(),\n        ];\n    }\n\n    /**\n     * @param ProductRating $productRating\n     *\n     * @return array\n     */\n    public function getPrimaryKey(ProductRating $productRating): array\n    {\n        return [\n            'OXID' => $productRating->getProductId(),\n        ];\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/DataMapper/ProductRatingDataMapperInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataMapper;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\ProductRating;\n\ninterface ProductRatingDataMapperInterface\n{\n    /**\n     * @param ProductRating $productRating\n     * @param array         $data\n     *\n     * @return ProductRating\n     */\n    public function map(ProductRating $productRating, array $data): ProductRating;\n\n    /**\n     * @param ProductRating $productRating\n     *\n     * @return array\n     */\n    public function getData(ProductRating $productRating): array;\n\n    /**\n     * @param ProductRating $productRating\n     *\n     * @return array\n     */\n    public function getPrimaryKey(ProductRating $productRating): array;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/DataMapper/RatingDataMapper.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataMapper;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\Rating;\n\nclass RatingDataMapper implements RatingDataMapperInterface\n{\n    /**\n     * @param Rating $rating\n     * @param array  $data\n     *\n     * @return Rating\n     */\n    public function map(Rating $rating, array $data): Rating\n    {\n        $rating\n            ->setId($data['OXID'])\n            ->setRating($data['OXRATING'])\n            ->setObjectId($data['OXOBJECTID'])\n            ->setUserId($data['OXUSERID'])\n            ->setType($data['OXTYPE'])\n            ->setCreatedAt($data['OXTIMESTAMP']);\n\n        return $rating;\n    }\n\n    /**\n     * @param Rating $rating\n     *\n     * @return array\n     */\n    public function getData(Rating $rating): array\n    {\n        return [\n            'OXID'        => $rating->getId(),\n            'OXRATING'    => $rating->getRating(),\n            'OXOBJECTID'  => $rating->getObjectId(),\n            'OXUSERID'    => $rating->getUserId(),\n            'OXTYPE'      => $rating->getType(),\n            'OXTIMESTAMP' => $rating->getCreatedAt(),\n        ];\n    }\n\n    /**\n     * @param Rating $object\n     *\n     * @return array\n     */\n    public function getPrimaryKey(Rating $object): array\n    {\n        return [\n            'OXID' => $object->getId(),\n        ];\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/DataMapper/RatingDataMapperInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataMapper;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\Rating;\n\ninterface RatingDataMapperInterface\n{\n    /**\n     * @param Rating $rating\n     * @param array  $data\n     *\n     * @return Rating\n     */\n    public function map(Rating $rating, array $data): Rating;\n\n    /**\n     * @param Rating $rating\n     *\n     * @return array\n     */\n    public function getData(Rating $rating): array;\n\n    /**\n     * @param Rating $rating\n     *\n     * @return array\n     */\n    public function getPrimaryKey(Rating $rating): array;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/DataMapper/ReviewDataMapper.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataMapper;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\Review;\n\nclass ReviewDataMapper implements ReviewDataMapperInterface\n{\n    /**\n     * @param Review $review\n     * @param array  $data\n     *\n     * @return Review\n     */\n    public function map(Review $review, array $data): Review\n    {\n        $review\n            ->setId($data['OXID'])\n            ->setRating($data['OXRATING'])\n            ->setText($data['OXTEXT'])\n            ->setObjectId($data['OXOBJECTID'])\n            ->setUserId($data['OXUSERID'])\n            ->setType($data['OXTYPE'])\n            ->setCreatedAt($data['OXTIMESTAMP']);\n\n        return $review;\n    }\n\n    /**\n     * @param Review $review\n     *\n     * @return array\n     */\n    public function getData(Review $review): array\n    {\n        return [\n            'OXID'        => $review->getId(),\n            'OXRATING'    => $review->getRating(),\n            'OXTEXT'      => $review->getText(),\n            'OXOBJECTID'  => $review->getObjectId(),\n            'OXUSERID'    => $review->getUserId(),\n            'OXTYPE'      => $review->getType(),\n            'OXTIMESTAMP' => $review->getCreatedAt(),\n        ];\n    }\n\n    /**\n     * @param Review $review\n     *\n     * @return array\n     */\n    public function getPrimaryKey(Review $review): array\n    {\n        return [\n            'OXID' => $review->getId(),\n        ];\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/DataMapper/ReviewDataMapperInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataMapper;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\Review;\n\ninterface ReviewDataMapperInterface\n{\n    /**\n     * @param Review $review\n     * @param array  $data\n     *\n     * @return Review\n     */\n    public function map(Review $review, array $data): Review;\n\n    /**\n     * @param Review $review\n     *\n     * @return array\n     */\n    public function getData(Review $review): array;\n\n    /**\n     * @param Review $review\n     *\n     * @return array\n     */\n    public function getPrimaryKey(Review $review): array;\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/DataMapper/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataMapper\\ProductRatingDataMapperInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataMapper\\ProductRatingDataMapper\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataMapper\\RatingDataMapperInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataMapper\\RatingDataMapper\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataMapper\\ReviewDataMapperInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataMapper\\ReviewDataMapper\n"
  },
  {
    "path": "source/Internal/Domain/Review/DataObject/ProductRating.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject;\n\nclass ProductRating\n{\n    /**\n     * @var string\n     */\n    private $productId;\n\n    /**\n     * @var float\n     */\n    private $ratingAverage;\n\n    /**\n     * @var int\n     */\n    private $ratingCount;\n\n    /**\n     * @return string\n     */\n    public function getProductId()\n    {\n        return $this->productId;\n    }\n\n    /**\n     * @param string $productId\n     *\n     * @return $this\n     */\n    public function setProductId($productId)\n    {\n        $this->productId = $productId;\n\n        return $this;\n    }\n\n    /**\n     * @return float\n     */\n    public function getRatingAverage()\n    {\n        return $this->ratingAverage;\n    }\n\n    /**\n     * @param float $ratingAverage\n     *\n     * @return $this\n     */\n    public function setRatingAverage($ratingAverage)\n    {\n        $this->ratingAverage = $ratingAverage;\n\n        return $this;\n    }\n\n    /**\n     * @return int\n     */\n    public function getRatingCount()\n    {\n        return $this->ratingCount;\n    }\n\n    /**\n     * @param int $ratingCount\n     *\n     * @return $this\n     */\n    public function setRatingCount($ratingCount)\n    {\n        $this->ratingCount = $ratingCount;\n\n        return $this;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/DataObject/Rating.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject;\n\nclass Rating\n{\n    /**\n     * @var string\n     */\n    private $id;\n\n    /**\n     * @var int\n     */\n    private $rating;\n\n    /**\n     * @var string\n     */\n    private $objectId;\n\n    /**\n     * @var int\n     */\n    private $userId;\n\n    /**\n     * @var string\n     */\n    private $type;\n\n    /**\n     * @var string\n     */\n    private $createdAt;\n\n    /**\n     * @param string $id\n     *\n     * @return $this\n     */\n    public function setId($id)\n    {\n        $this->id = $id;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getId()\n    {\n        return $this->id;\n    }\n\n    /**\n     * @param int $rating\n     *\n     * @return $this\n     */\n    public function setRating($rating)\n    {\n        $this->rating = $rating;\n\n        return $this;\n    }\n\n    /**\n     * @return int\n     */\n    public function getRating()\n    {\n        return $this->rating;\n    }\n\n    /**\n     * @param string $objectId\n     *\n     * @return $this\n     */\n    public function setObjectId($objectId)\n    {\n        $this->objectId = $objectId;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getObjectId()\n    {\n        return $this->objectId;\n    }\n\n    /**\n     * @param int $userId\n     *\n     * @return $this\n     */\n    public function setUserId($userId)\n    {\n        $this->userId = $userId;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getUserId()\n    {\n        return $this->userId;\n    }\n\n    /**\n     * @param string $type\n     *\n     * @return $this\n     */\n    public function setType($type)\n    {\n        $this->type = $type;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getType()\n    {\n        return $this->type;\n    }\n\n    /**\n     * @param string $createdAt\n     *\n     * @return $this\n     */\n    public function setCreatedAt($createdAt)\n    {\n        $this->createdAt = $createdAt;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getCreatedAt()\n    {\n        return $this->createdAt;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/DataObject/Review.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject;\n\nclass Review\n{\n    /**\n     * @var string\n     */\n    private $id;\n\n    /**\n     * @var int\n     */\n    private $rating;\n\n    /**\n     * @var string\n     */\n    private $text;\n\n    /**\n     * @var string\n     */\n    private $objectId;\n\n    /**\n     * @var string\n     */\n    private $userId;\n\n    /**\n     * @var string\n     */\n    private $type;\n\n    /**\n     * @var string\n     */\n    private $createdAt;\n\n    /**\n     * @param string $id\n     *\n     * @return $this\n     */\n    public function setId($id)\n    {\n        $this->id = $id;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getId()\n    {\n        return $this->id;\n    }\n\n    /**\n     * @param int $rating\n     * @return $this\n     */\n    public function setRating($rating)\n    {\n        $this->rating = $rating;\n\n        return $this;\n    }\n\n    /**\n     * @return int\n     */\n    public function getRating()\n    {\n        return $this->rating;\n    }\n\n    /**\n     * @param string $text\n     * @return $this\n     */\n    public function setText($text)\n    {\n        $this->text = $text;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getText()\n    {\n        return $this->text;\n    }\n\n    /**\n     * @param string $objectId\n     * @return $this\n     */\n    public function setObjectId($objectId)\n    {\n        $this->objectId = $objectId;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getObjectId()\n    {\n        return $this->objectId;\n    }\n\n    /**\n     * @param string $userId\n     * @return $this\n     */\n    public function setUserId($userId)\n    {\n        $this->userId = $userId;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getUserId()\n    {\n        return $this->userId;\n    }\n\n    /**\n     * @param string $type\n     *\n     * @return $this\n     */\n    public function setType($type)\n    {\n        $this->type = $type;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getType()\n    {\n        return $this->type;\n    }\n\n    /**\n     * @param string $createdAt\n     *\n     * @return $this\n     */\n    public function setCreatedAt($createdAt)\n    {\n        $this->createdAt = $createdAt;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getCreatedAt()\n    {\n        return $this->createdAt;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Exception/RatingPermissionException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Exception;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\nclass RatingPermissionException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Exception/ReviewAndRatingObjectTypeException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Exception;\n\nclass ReviewAndRatingObjectTypeException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Exception/ReviewPermissionException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Exception;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\nclass ReviewPermissionException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Service/ProductRatingService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao\\RatingDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao\\ProductRatingDaoInterface;\n\nclass ProductRatingService implements ProductRatingServiceInterface\n{\n    public function __construct(\n        private RatingDaoInterface $ratingDao,\n        private ProductRatingDaoInterface $productRatingDao,\n        private RatingCalculatorServiceInterface $ratingCalculator\n    ) {\n    }\n\n    /**\n     * @param string $productId\n     */\n    public function updateProductRating($productId)\n    {\n        $ratings = $this\n            ->ratingDao\n            ->getRatingsByProductId($productId);\n\n        $ratingAverage = $this\n            ->ratingCalculator\n            ->getAverage($ratings);\n\n        $ratingCount = $ratings->count();\n\n        $productRating = $this->productRatingDao->getProductRatingById($productId);\n        $productRating\n            ->setRatingAverage($ratingAverage)\n            ->setRatingCount($ratingCount);\n\n        $this->productRatingDao->update($productRating);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Service/ProductRatingServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service;\n\ninterface ProductRatingServiceInterface\n{\n    /**\n     * @param string $productId\n     */\n    public function updateProductRating($productId);\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Service/RatingCalculatorService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\Rating;\n\nclass RatingCalculatorService implements RatingCalculatorServiceInterface\n{\n    /**\n     * @param ArrayCollection $ratings\n     *\n     * @return float\n     */\n    public function getAverage(ArrayCollection $ratings)\n    {\n        if ($ratings->count() === 0) {\n            $average = 0;\n        } else {\n            $average = $this->getSum($ratings) / $ratings->count();\n        }\n\n        return $average;\n    }\n\n    /**\n     * @param ArrayCollection $ratings\n     *\n     * @return int\n     */\n    private function getSum(ArrayCollection $ratings)\n    {\n        $sum = 0;\n\n        $ratings->forAll(function ($key, Rating $rating) use (&$sum) {\n            $sum += $rating->getRating();\n            return true;\n        });\n\n        return $sum;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Service/RatingCalculatorServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\n\ninterface RatingCalculatorServiceInterface\n{\n    /**\n     * @param ArrayCollection $ratings\n     *\n     * @return float\n     */\n    public function getAverage(ArrayCollection $ratings);\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Service/ReviewAndRatingMergingService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\Rating;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\Review;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\ViewDataObject\\ReviewAndRating;\n\nclass ReviewAndRatingMergingService implements ReviewAndRatingMergingServiceInterface\n{\n    /**\n     * Merges Reviews and Ratings to Collection of ReviewAndRating view objects.\n     *\n     * @param ArrayCollection $reviews\n     * @param ArrayCollection $ratings\n     *\n     * @return ArrayCollection\n     */\n    public function mergeReviewAndRating(ArrayCollection $reviews, ArrayCollection $ratings)\n    {\n        $ratingAndReviewList = array_merge(\n            $this->getReviewDataWithRating($reviews, $ratings),\n            $this->getRatingWithoutReviewData($reviews, $ratings)\n        );\n\n        return $this->mapReviewAndRatingList($ratingAndReviewList);\n    }\n\n    /**\n     * @param ArrayCollection $reviews\n     * @param ArrayCollection $ratings\n     *\n     * @return array\n     */\n    private function getReviewDataWithRating(ArrayCollection $reviews, ArrayCollection $ratings)\n    {\n        $reviewList = [];\n\n        foreach ($reviews as $review) {\n            $ratingAndReview = [\n                'reviewId'      => $review->getId(),\n                'text'          => $review->getText(),\n                'createdAt'     => $review->getCreatedAt(),\n                'objectId'      => $review->getObjectId(),\n                'objectType'    => $review->getType(),\n                'rating'        => false,\n                'ratingId'      => false,\n            ];\n\n            foreach ($ratings as $rating) {\n                if ($this->isReviewRating($review, $rating)) {\n                    $ratingAndReview['rating'] = $rating->getRating();\n                    $ratingAndReview['ratingId'] = $rating->getId();\n\n                    break;\n                }\n            }\n\n            $reviewList[] = $ratingAndReview;\n        }\n\n        return $reviewList;\n    }\n\n    /**\n     * @param ArrayCollection $reviews\n     * @param ArrayCollection $ratings\n     *\n     * @return array\n     */\n    private function getRatingWithoutReviewData(ArrayCollection $reviews, ArrayCollection $ratings)\n    {\n        $ratingList = [];\n\n        foreach ($ratings as $rating) {\n            if ($this->isRatingWithoutReview($rating, $reviews)) {\n                $ratingList[] = [\n                    'ratingId'      => $rating->getId(),\n                    'reviewId'      => false,\n                    'rating'        => $rating->getRating(),\n                    'text'          => '',\n                    'objectId'      => $rating->getObjectId(),\n                    'objectType'    => $rating->getType(),\n                    'createdAt'     => $rating->getCreatedAt(),\n                ];\n            }\n        }\n\n        return $ratingList;\n    }\n\n    /**\n     * Returns true if Rating doesn't belong to any review.\n     *\n     * @param Rating          $rating\n     * @param ArrayCollection $reviews\n     *\n     * @return bool\n     */\n    private function isRatingWithoutReview(Rating $rating, ArrayCollection $reviews)\n    {\n        $withoutReview = true;\n\n        foreach ($reviews as $review) {\n            if ($this->isReviewRating($review, $rating)) {\n                $withoutReview = false;\n                break;\n            }\n        }\n\n        return $withoutReview;\n    }\n\n    /**\n     * Returns true if Rating belongs to Review.\n     *\n     * @param Review $review\n     * @param Rating $rating\n     *\n     * @return bool\n     */\n    private function isReviewRating(Review $review, Rating $rating)\n    {\n        return $rating->getType() === $review->getType()\n            && $rating->getObjectId() === $review->getObjectId()\n            && $rating->getRating() === $review->getRating()\n            && $rating->getUserId() === $review->getUserId();\n    }\n\n    /**\n     * Maps Reviews and Ratings data to Collection of ReviewAndRating view objects.\n     *\n     * @param array $reviewAndRatingDataList\n     *\n     * @return ArrayCollection\n     */\n    private function mapReviewAndRatingList($reviewAndRatingDataList)\n    {\n        $mappedReviewAndRating = new ArrayCollection();\n\n        foreach ($reviewAndRatingDataList as $reviewAndRatingData) {\n            $mappedReviewAndRating[] = $this->mapReviewAndRating($reviewAndRatingData);\n        }\n\n        return $mappedReviewAndRating;\n    }\n\n    /**\n     * Maps Review and Rating data to ReviewAndRating view object.\n     *\n     * @param array $reviewAndRatingData\n     *\n     * @return ReviewAndRating\n     */\n    private function mapReviewAndRating($reviewAndRatingData)\n    {\n        $reviewAndRating = new ReviewAndRating();\n        $reviewAndRating\n            ->setReviewId($reviewAndRatingData['reviewId'])\n            ->setRatingId($reviewAndRatingData['ratingId'])\n            ->setRating($reviewAndRatingData['rating'])\n            ->setReviewText($reviewAndRatingData['text'])\n            ->setObjectId($reviewAndRatingData['objectId'])\n            ->setObjectType($reviewAndRatingData['objectType'])\n            ->setCreatedAt($reviewAndRatingData['createdAt']);\n\n        return $reviewAndRating;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Service/ReviewAndRatingMergingServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\n\ninterface ReviewAndRatingMergingServiceInterface\n{\n    /**\n     * Merges Reviews and Ratings to Collection of ReviewAndRating view objects.\n     *\n     * @param ArrayCollection $reviews\n     * @param ArrayCollection $ratings\n     *\n     * @return ArrayCollection\n     */\n    public function mergeReviewAndRating(ArrayCollection $reviews, ArrayCollection $ratings);\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Service/UserRatingService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao\\RatingDaoInterface;\n\nclass UserRatingService implements UserRatingServiceInterface\n{\n    public function __construct(private RatingDaoInterface $ratingDao)\n    {\n    }\n\n    /**\n     * Returns user ratings.\n     *\n     * @param string $userId\n     *\n     * @return ArrayCollection\n     */\n    public function getRatings($userId)\n    {\n        return $this->ratingDao->getRatingsByUserId($userId);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Service/UserRatingServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\n\ninterface UserRatingServiceInterface\n{\n    /**\n     * Returns Ratings of User.\n     *\n     * @param string $userId\n     *\n     * @return ArrayCollection\n     */\n    public function getRatings($userId);\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Service/UserReviewAndRatingService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\ViewDataObject\\ReviewAndRating;\n\nclass UserReviewAndRatingService implements UserReviewAndRatingServiceInterface\n{\n    public function __construct(\n        private UserReviewServiceInterface $userReviewService,\n        private UserRatingServiceInterface $userRatingService,\n        private ReviewAndRatingMergingServiceInterface $reviewAndRatingMergingService\n    ) {\n    }\n\n    /**\n     * Get number of reviews by given user.\n     *\n     * @param string $userId\n     *\n     * @return int\n     */\n    public function getReviewAndRatingListCount($userId)\n    {\n        return $this\n            ->getMergedReviewAndRatingList($userId)\n            ->count();\n    }\n\n    /**\n     * Returns Collection of User Ratings and Reviews.\n     *\n     * @param string $userId\n     *\n     * @return ArrayCollection\n     */\n    public function getReviewAndRatingList($userId)\n    {\n        $reviewAndRatingList = $this->getMergedReviewAndRatingList($userId);\n\n        return $this->sortReviewAndRatingList($reviewAndRatingList);\n    }\n\n    /**\n     * Returns merged Rating and Review.\n     *\n     * @param string $userId\n     *\n     * @return ArrayCollection\n     */\n    private function getMergedReviewAndRatingList($userId)\n    {\n        $reviews = $this->userReviewService->getReviews($userId);\n        $ratings = $this->userRatingService->getRatings($userId);\n\n        return $this\n            ->reviewAndRatingMergingService\n            ->mergeReviewAndRating($reviews, $ratings);\n    }\n\n    /**\n     * Sorts ReviewAndRating list.\n     *\n     * @param ArrayCollection $reviewAndRatingList\n     *\n     * @return ArrayCollection\n     */\n    private function sortReviewAndRatingList(ArrayCollection $reviewAndRatingList)\n    {\n        $reviewAndRatingListArray = $reviewAndRatingList->toArray();\n\n        usort($reviewAndRatingListArray, function (ReviewAndRating $first, ReviewAndRating $second) {\n            return $first->getCreatedAt() < $second->getCreatedAt() ? 1 : -1;\n        });\n\n        return new ArrayCollection($reviewAndRatingListArray);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Service/UserReviewAndRatingServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\n\ninterface UserReviewAndRatingServiceInterface\n{\n    /**\n     * Get number of reviews by given user.\n     *\n     * @param string $userId\n     *\n     * @return int\n     */\n    public function getReviewAndRatingListCount($userId);\n\n    /**\n     * Returns Collection of User Ratings and Reviews.\n     *\n     * @param string $userId\n     *\n     * @return ArrayCollection\n     */\n    public function getReviewAndRatingList($userId);\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Service/UserReviewService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao\\ReviewDaoInterface;\n\nclass UserReviewService implements UserReviewServiceInterface\n{\n    public function __construct(private ReviewDaoInterface $reviewDao)\n    {\n    }\n\n    /**\n     * Returns User Reviews.\n     *\n     * @param string $userId\n     *\n     * @return ArrayCollection\n     */\n    public function getReviews($userId)\n    {\n        return $this->reviewDao->getReviewsByUserId($userId);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Service/UserReviewServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\n\ninterface UserReviewServiceInterface\n{\n    /**\n     * Returns User Reviews.\n     *\n     * @param string $userId\n     *\n     * @return ArrayCollection\n     */\n    public function getReviews($userId);\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/Service/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\UserRatingServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\UserRatingService\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\UserReviewServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\UserReviewService\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\ReviewAndRatingMergingServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\ReviewAndRatingMergingService\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\UserReviewAndRatingServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\UserReviewAndRatingService\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\RatingCalculatorServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\RatingCalculatorService\n  OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\ProductRatingServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\ProductRatingService\n"
  },
  {
    "path": "source/Internal/Domain/Review/ViewDataObject/ReviewAndRating.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\ViewDataObject;\n\nclass ReviewAndRating\n{\n    /**\n     * @var string\n     */\n    private $reviewId;\n\n    /**\n     * @var string\n     */\n    private $ratingId;\n\n    /**\n     * @var int\n     */\n    private $rating;\n\n    /**\n     * @var string\n     */\n    private $reviewText;\n\n    /**\n     * @var string\n     */\n    private $objectId;\n\n    /**\n     * @var string\n     */\n    private $objectType;\n\n    /**\n     * @var string\n     */\n    private $objectTitle;\n\n    /**\n     * @var string\n     */\n    private $createdAt;\n\n    /**\n     * @param string $id\n     *\n     * @return $this\n     */\n    public function setReviewId($id)\n    {\n        $this->reviewId = $id;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getReviewId()\n    {\n        return $this->reviewId;\n    }\n\n    /**\n     * @param int $id\n     *\n     * @return $this\n     */\n    public function setRatingId($id)\n    {\n        $this->ratingId = $id;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getRatingId()\n    {\n        return $this->ratingId;\n    }\n\n    /**\n     * @param string $rating\n     *\n     * @return $this\n     */\n    public function setRating($rating)\n    {\n        $this->rating = $rating;\n\n        return $this;\n    }\n\n    /**\n     * @return int\n     */\n    public function getRating()\n    {\n        return $this->rating;\n    }\n\n    /**\n     * @param string $reviewText\n     *\n     * @return $this\n     */\n    public function setReviewText($reviewText)\n    {\n        $this->reviewText = $reviewText;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getReviewText()\n    {\n        return $this->reviewText;\n    }\n\n    /**\n     * @param string $objectId\n     *\n     * @return $this\n     */\n    public function setObjectId($objectId)\n    {\n        $this->objectId = $objectId;\n\n        return $this;\n    }\n\n    /**\n     * @return int\n     */\n    public function getObjectId()\n    {\n        return $this->objectId;\n    }\n\n    /**\n     * @param string $objectType\n     *\n     * @return $this\n     */\n    public function setObjectType($objectType)\n    {\n        $this->objectType = $objectType;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getObjectType()\n    {\n        return $this->objectType;\n    }\n\n    /**\n     * @param string $objectTitle\n     *\n     * @return $this\n     */\n    public function setObjectTitle($objectTitle)\n    {\n        $this->objectTitle = $objectTitle;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getObjectTitle()\n    {\n        return $this->objectTitle;\n    }\n\n    /**\n     * @param string $date\n     *\n     * @return $this\n     */\n    public function setCreatedAt($date)\n    {\n        $this->createdAt = $date;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getCreatedAt()\n    {\n        return $this->createdAt;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Domain/Review/services.yaml",
    "content": "imports:\n    - { resource: DataMapper/services.yaml }\n    - { resource: Dao/services.yaml }\n    - { resource: Service/services.yaml }\n    - { resource: Bridge/services.yaml }\n"
  },
  {
    "path": "source/Internal/Domain/parameter.yaml",
    "content": "parameters:\n  oxid_esales.shop_logo:\n  oxid_esales.basket_reservation_cleanup_rate: 200\n  oxid_esales.seo_mode: true\n  oxid_esales.shop_credit_rating: 1000\n"
  },
  {
    "path": "source/Internal/Domain/services.yaml",
    "content": "imports:\n    - { resource: ./parameter.yaml }\n    - { resource: Admin/services.yaml }\n    - { resource: Authentication/services.yaml }\n    - { resource: Contact/services.yaml }\n    - { resource: Media/services.yaml }\n    - { resource: Newsletter/services.yaml }\n    - { resource: Product/Media/parameters.yaml }\n    - { resource: Product/Media/services.yaml }\n    - { resource: Product/Search/services.yaml }\n    - { resource: Review/services.yaml }\n"
  },
  {
    "path": "source/Internal/Framework/Api/Api.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Api;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\ContainerFactory;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\nuse Symfony\\Component\\HttpFoundation\\Request;\nuse Symfony\\Component\\HttpFoundation\\RequestStack;\nuse Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver;\nuse Symfony\\Component\\HttpKernel\\Controller\\ContainerControllerResolver;\nuse Symfony\\Component\\HttpKernel\\EventListener\\RouterListener;\nuse Symfony\\Component\\HttpKernel\\HttpKernel;\nuse Symfony\\Component\\Routing\\Matcher\\CompiledUrlMatcher;\nuse Symfony\\Component\\Routing\\RequestContext;\n\nclass Api\n{\n    public function run(): void\n    {\n        $container = ContainerFactory::getInstance()->getContainer();\n        $request = Request::createFromGlobals();\n\n        $context = new RequestContext();\n        $context->fromRequest($request);\n        $matcher = new CompiledUrlMatcher($container->getParameter('oxid.routes'), $context);\n\n        $requestStack = new RequestStack();\n        $dispatcher = $container->get(EventDispatcherInterface::class);\n        $dispatcher->addSubscriber(new RouterListener($matcher, $requestStack));\n\n        $kernel = new HttpKernel(\n            $dispatcher,\n            new ContainerControllerResolver($container),\n            $requestStack,\n            new ArgumentResolver(namedResolvers: $container)\n        );\n\n        $response = $kernel->handle($request);\n        $response->send();\n\n        $kernel->terminate($request, $response);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Api/AttributeRouteControllerLoader.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Api;\n\nuse Symfony\\Component\\Routing\\Loader\\AttributeClassLoader;\nuse Symfony\\Component\\Routing\\Route;\n\nclass AttributeRouteControllerLoader extends AttributeClassLoader\n{\n    protected function configureRoute(\n        Route $route,\n        \\ReflectionClass $class,\n        \\ReflectionMethod $method,\n        object $attr\n    ): void {\n        $route->setDefault('_controller', [$class->getName(), $method->getName()]);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Api/ExceptionHandler.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Api;\n\nuse OxidEsales\\Eshop\\Core\\ShopIdCalculator;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Logger\\LoggerServiceFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\Context;\nuse Symfony\\Component\\HttpFoundation\\JsonResponse;\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Throwable;\n\nclass ExceptionHandler\n{\n    public function handle(Throwable $throwable): void\n    {\n        new LoggerServiceFactory(new Context(ShopIdCalculator::BASE_SHOP_ID))\n            ->getLogger()\n            ->error($throwable);\n\n        $error = filter_var(getenv('OXID_DEBUG_MODE'), FILTER_VALIDATE_BOOLEAN)\n            ? $throwable->getMessage()\n            : 'An error occurred';\n\n        new JsonResponse(['error' => $error], Response::HTTP_INTERNAL_SERVER_ERROR)->send();\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Api/HttpExceptionListener.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Api;\n\nuse Symfony\\Component\\EventDispatcher\\EventSubscriberInterface;\nuse Symfony\\Component\\HttpFoundation\\JsonResponse;\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent;\nuse Symfony\\Component\\HttpKernel\\Exception\\HttpExceptionInterface;\nuse Symfony\\Component\\HttpKernel\\KernelEvents;\n\nclass HttpExceptionListener implements EventSubscriberInterface\n{\n    public static function getSubscribedEvents(): array\n    {\n        return [\n            KernelEvents::EXCEPTION => ['onKernelException', -10],\n        ];\n    }\n\n    public function onKernelException(ExceptionEvent $event): void\n    {\n        $exception = $event->getThrowable();\n\n        if ($exception instanceof HttpExceptionInterface) {\n            $response = new JsonResponse(\n                ['error' => $this->getErrorMessage($exception)],\n                $exception->getStatusCode(),\n                $exception->getHeaders()\n            );\n            $event->setResponse($response);\n        }\n    }\n\n    private function getErrorMessage(HttpExceptionInterface $exception): string\n    {\n        if (filter_var(getenv('OXID_DEBUG_MODE'), FILTER_VALIDATE_BOOLEAN)) {\n            return $exception->getMessage();\n        }\n\n        return Response::$statusTexts[$exception->getStatusCode()] ?? 'An error occurred';\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Api/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Api\\HttpExceptionListener:\n    tags:\n      - { name: kernel.event_subscriber }\n"
  },
  {
    "path": "source/Internal/Framework/Cache/Adapter/FilesystemTagAwareAdapterFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\Adapter;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse Symfony\\Component\\Cache\\Adapter\\FilesystemTagAwareAdapter;\nuse Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass FilesystemTagAwareAdapterFactory implements TagAwareAdapterFactoryInterface\n{\n    public function __construct(private readonly ContextInterface $context)\n    {\n    }\n\n    public function create(int $shopId): TagAwareAdapterInterface\n    {\n        return new FilesystemTagAwareAdapter(\n            namespace: \"cache_items_shop_$shopId\",\n            directory: Path::join($this->context->getCacheDirectory(), 'pool')\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Cache/Adapter/TagAwareAdapterFactoryInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\Adapter;\n\nuse Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface;\n\ninterface TagAwareAdapterFactoryInterface\n{\n    public function create(int $shopId): TagAwareAdapterInterface;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Cache/Adapter/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\Adapter\\TagAwareAdapterFactoryInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\Adapter\\FilesystemTagAwareAdapterFactory\n\n  Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface:\n    factory: ['@OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\Adapter\\TagAwareAdapterFactoryInterface', 'create']\n    arguments: ['@=service(\"OxidEsales\\\\EshopCommunity\\\\Internal\\\\Transition\\\\Utility\\\\ContextInterface\").getCurrentShopId()']\n\n  Psr\\Cache\\CacheItemPoolInterface:\n    alias: Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface\n    public: true\n\n  Symfony\\Contracts\\Cache\\TagAwareCacheInterface:\n    alias: Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface\n    public: true"
  },
  {
    "path": "source/Internal/Framework/Cache/Command/ClearCacheCommand.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\Command;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\ShopCacheCleanerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Service\\ContainerCacheInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\nclass ClearCacheCommand extends Command\n{\n    public function __construct(\n        private readonly ContainerCacheInterface $containerCache,\n        private readonly ContextInterface $context,\n        private readonly ShopCacheCleanerInterface $shopCacheCleaner,\n    ) {\n        parent::__construct();\n    }\n\n    protected function configure(): void\n    {\n        $this->setDescription('Clears shop cache');\n    }\n\n    protected function execute(InputInterface $input, OutputInterface $output): int\n    {\n        $this->shopCacheCleaner->clearAll();\n        foreach ($this->context->getAllShopIds() as $shopId) {\n            $this->containerCache->invalidate($shopId);\n        }\n        $output->writeln('<info>Cleared cache files</info>');\n\n        return 0;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Cache/Command/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n    public: false\n\n  oxid_esales.command.clearcache_command:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\Command\\ClearCacheCommand\n    tags:\n      - { name: 'console.command', command: 'oe:cache:clear' }"
  },
  {
    "path": "source/Internal/Framework/Cache/Event/ClearShopCacheEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\Event;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\nclass ClearShopCacheEvent extends Event\n{\n    public function __construct(\n        private readonly int $shopId\n    ) {\n    }\n\n    public function getShopId(): int\n    {\n        return $this->shopId;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Cache/ShopCacheCleanerInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache;\n\ninterface ShopCacheCleanerInterface\n{\n    public function clear(int $shopId): void;\n\n    public function clearAll(): void;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Cache/ShopCacheFacade.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\Adapter\\TagAwareAdapterFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\Event\\ClearShopCacheEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Cache\\ShopTemplateCacheServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\ShopAdapterInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\n\nreadonly class ShopCacheFacade implements ShopCacheCleanerInterface\n{\n    public function __construct(\n        private ContextInterface $context,\n        private TagAwareAdapterFactoryInterface $tagAwareAdapterFactory,\n        private ShopAdapterInterface $shopAdapter,\n        private ShopTemplateCacheServiceInterface $templateCacheService,\n        private EventDispatcherInterface $eventDispatcher\n    ) {\n    }\n\n    public function clear(int $shopId): void\n    {\n        $this->shopAdapter->invalidateModulesCache();\n        $this->templateCacheService->invalidateCache($shopId);\n        $this->tagAwareAdapterFactory->create($shopId)->clear();\n\n        $this->eventDispatcher->dispatch(new ClearShopCacheEvent($shopId));\n    }\n\n    public function clearAll(): void\n    {\n        $this->shopAdapter->invalidateModulesCache();\n        $this->templateCacheService->invalidateAllShopsCache();\n        foreach ($this->context->getAllShopIds() as $shopId) {\n            $this->tagAwareAdapterFactory->create($shopId)->clear();\n            $this->eventDispatcher->dispatch(new ClearShopCacheEvent($shopId));\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Cache/services.yaml",
    "content": "imports:\n  - { resource: Adapter/services.yaml }\n  - { resource: Command/services.yaml }\n\nservices:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\ShopCacheCleanerInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\ShopCacheFacade\n    public: true\n"
  },
  {
    "path": "source/Internal/Framework/Config/Dao/ShopConfigurationSettingDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\DataObject\\ShopConfigurationSetting;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Utility\\ShopSettingEncoderInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Event\\ShopConfigurationChangedEvent;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\n\nclass ShopConfigurationSettingDao implements ShopConfigurationSettingDaoInterface\n{\n    public function __construct(\n        private QueryBuilderFactoryInterface $queryBuilderFactory,\n        private ShopSettingEncoderInterface $shopSettingEncoder,\n        private EventDispatcherInterface $eventDispatcher\n    ) {\n    }\n\n    private array $cache = [];\n\n    /**\n     * @param ShopConfigurationSetting $shopConfigurationSetting\n     */\n    public function save(ShopConfigurationSetting $shopConfigurationSetting)\n    {\n        $this->delete($shopConfigurationSetting);\n\n        $queryBuilder = $this->queryBuilderFactory->create();\n        $queryBuilder\n            ->insert('oxconfig')\n            ->values([\n                'oxid'          => ':id',\n                'oxshopid'      => ':shopId',\n                'oxvarname'     => ':name',\n                'oxvartype'     => ':type',\n                'oxvarvalue'    => ':value',\n            ])\n            ->setParameters([\n                'id'        => Id::generate(),\n                'shopId'    => $shopConfigurationSetting->getShopId(),\n                'name'      => $shopConfigurationSetting->getName(),\n                'type'      => $shopConfigurationSetting->getType(),\n                'value'     => $this->shopSettingEncoder->encode(\n                    $shopConfigurationSetting->getType(),\n                    $shopConfigurationSetting->getValue()\n                ),\n            ]);\n\n        $queryBuilder->executeStatement();\n\n        $this->eventDispatcher->dispatch(\n            new ShopConfigurationChangedEvent(\n                $shopConfigurationSetting->getName(),\n                $shopConfigurationSetting->getShopId()\n            )\n        );\n    }\n\n    /**\n     * @param string $name\n     * @param int    $shopId\n     * @return ShopConfigurationSetting\n     * @throws EntryDoesNotExistDaoException\n     */\n    public function get(string $name, int $shopId): ShopConfigurationSetting\n    {\n        if (!isset($this->cache[$shopId][$name])) {\n            $queryBuilder = $this->queryBuilderFactory->create();\n            $queryBuilder\n                ->select('oxvarvalue as value, oxvartype as type, oxvarname as name')\n                ->from('oxconfig')\n                ->where('oxshopid = :shopId')\n                ->andWhere('oxvarname = :name')\n                ->andWhere('oxmodule = \"\"')\n                ->setParameters([\n                    'shopId'    => $shopId,\n                    'name'      => $name,\n                ]);\n\n            $result = $queryBuilder->fetchAssociative();\n\n            if (false === $result) {\n                throw new EntryDoesNotExistDaoException(\n                    'Setting ' . $name . ' doesn\\'t exist in the shop with id ' . $shopId\n                );\n            }\n\n            $setting = new ShopConfigurationSetting();\n            $setting\n                ->setName($name)\n                ->setValue($this->shopSettingEncoder->decode($result['type'], $result['value']))\n                ->setShopId($shopId)\n                ->setType($result['type']);\n\n            $this->cache[$shopId][$name] = $setting;\n        }\n\n        return clone $this->cache[$shopId][$name];\n    }\n\n    /**\n     * @param ShopConfigurationSetting $setting\n     */\n    public function delete(ShopConfigurationSetting $setting)\n    {\n        $queryBuilder = $this->queryBuilderFactory->create();\n        $queryBuilder\n            ->delete('oxconfig')\n            ->where('oxshopid = :shopId')\n            ->andWhere('oxvarname = :name')\n            ->andWhere('oxmodule = \"\"')\n            ->setParameters([\n                'shopId'    => $setting->getShopId(),\n                'name'      => $setting->getName(),\n            ]);\n\n        $queryBuilder->executeStatement();\n\n        unset($this->cache[$setting->getShopId()][$setting->getName()]);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Config/Dao/ShopConfigurationSettingDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\DataObject\\ShopConfigurationSetting;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\n\ninterface ShopConfigurationSettingDaoInterface\n{\n    /**\n     * @param ShopConfigurationSetting $shopConfigurationSetting\n     */\n    public function save(ShopConfigurationSetting $shopConfigurationSetting);\n\n    /**\n     * @param string $name\n     * @param int    $shopId\n     * @return ShopConfigurationSetting\n     *\n     * @throws EntryDoesNotExistDaoException\n     */\n    public function get(string $name, int $shopId): ShopConfigurationSetting;\n\n    /**\n     * @param ShopConfigurationSetting $setting\n     */\n    public function delete(ShopConfigurationSetting $setting);\n}\n"
  },
  {
    "path": "source/Internal/Framework/Config/DataObject/ShopConfigurationSetting.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\DataObject;\n\nclass ShopConfigurationSetting\n{\n    /**\n     * @var int\n     */\n    private $shopId;\n\n    /**\n     * @var string\n     */\n    private $name;\n\n    /**\n     * @var string\n     */\n    private $type;\n\n    /**\n     * @var mixed\n     */\n    private $value;\n\n    /**\n     * @return int\n     */\n    public function getShopId(): int\n    {\n        return $this->shopId;\n    }\n\n    /**\n     * @param int $shopId\n     * @return ShopConfigurationSetting\n     */\n    public function setShopId(int $shopId): ShopConfigurationSetting\n    {\n        $this->shopId = $shopId;\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getName(): string\n    {\n        return $this->name;\n    }\n\n    /**\n     * @param string $name\n     * @return ShopConfigurationSetting\n     */\n    public function setName(string $name): ShopConfigurationSetting\n    {\n        $this->name = $name;\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getType(): string\n    {\n        return $this->type;\n    }\n\n    /**\n     * @param string $type\n     * @return ShopConfigurationSetting\n     */\n    public function setType(string $type): ShopConfigurationSetting\n    {\n        $this->type = $type;\n        return $this;\n    }\n\n    /**\n     * @return mixed\n     */\n    public function getValue()\n    {\n        return $this->value;\n    }\n\n    /**\n     * @param mixed $value\n     * @return ShopConfigurationSetting\n     */\n    public function setValue($value): ShopConfigurationSetting\n    {\n        $this->value = $value;\n        return $this;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Config/DataObject/ShopSettingType.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\DataObject;\n\nclass ShopSettingType\n{\n    public const BOOLEAN = 'bool';\n    public const ARRAY = 'arr';\n    public const ASSOCIATIVE_ARRAY = 'aarr';\n    public const STRING = 'str';\n}\n"
  },
  {
    "path": "source/Internal/Framework/Config/Event/ShopConfigurationChangedEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Event;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\nclass ShopConfigurationChangedEvent extends Event\n{\n    public function __construct(\n        private string $configurationVariable,\n        private int $shopId\n    ) {\n    }\n\n    /**\n     * Getter for configuration variable name.\n     *\n     * @return string\n     */\n    public function getConfigurationVariable(): string\n    {\n        return $this->configurationVariable;\n    }\n\n    /**\n     * Getter for shop id.\n     *\n     * @return integer\n     */\n    public function getShopId(): int\n    {\n        return $this->shopId;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Config/Exception/InvalidShopSettingValueException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Exception;\n\nclass InvalidShopSettingValueException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Config/Utility/ShopSettingEncoder.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Utility;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\DataObject\\ShopSettingType;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Exception\\InvalidShopSettingValueException;\n\nuse function unserialize;\nuse function serialize;\n\nclass ShopSettingEncoder implements ShopSettingEncoderInterface\n{\n    /**\n     * @param string $encodingType\n     * @param mixed  $value\n     * @return mixed\n     */\n    public function encode(string $encodingType, $value)\n    {\n        $this->validateSettingValue($value);\n\n        return match ($encodingType) {\n            ShopSettingType::ARRAY, ShopSettingType::ASSOCIATIVE_ARRAY => serialize($value),\n            ShopSettingType::BOOLEAN => $value === true ? '1' : '',\n            default => $value,\n        };\n    }\n\n    /**\n     * @param string $encodingType\n     * @param mixed  $value\n     * @return mixed\n     */\n    public function decode(string $encodingType, $value)\n    {\n        // phpcs:disable\n        return match ($encodingType) {\n            ShopSettingType::ARRAY, ShopSettingType::ASSOCIATIVE_ARRAY => unserialize($value, ['allowed_classes' => false]),\n            ShopSettingType::BOOLEAN => $value === 'true' || $value === '1',\n            default => $value,\n        };\n        // phpcs:enable\n    }\n\n    /**\n     * @param mixed $value\n     * @throws InvalidShopSettingValueException\n     */\n    private function validateSettingValue($value)\n    {\n        if (is_object($value)) {\n            throw new InvalidShopSettingValueException(\n                'Shop setting value must not be an object.'\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Config/Utility/ShopSettingEncoderInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Utility;\n\ninterface ShopSettingEncoderInterface\n{\n    /**\n     * @param string $encodingType\n     * @param mixed  $value\n     * @return mixed\n     */\n    public function encode(string $encodingType, $value);\n\n    /**\n     * @param string $encodingType\n     * @param mixed  $value\n     * @return mixed\n     */\n    public function decode(string $encodingType, $value);\n}\n"
  },
  {
    "path": "source/Internal/Framework/Console/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  oxid_esales.console.symfony.component.console.application:\n    class: Symfony\\Component\\Console\\Application\n    public: true\n"
  },
  {
    "path": "source/Internal/Framework/Controller/AbstractControllerDecorator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Controller;\n\nabstract class AbstractControllerDecorator implements ViewControllerInterface\n{\n    public function __construct(\n        protected readonly ViewControllerInterface $controller,\n    ) {\n    }\n\n    public function init()\n    {\n        $this->controller->init();\n    }\n\n    public function render()\n    {\n        return $this->controller->render();\n    }\n\n    public function getFncName()\n    {\n        return $this->controller->getFncName();\n    }\n\n    public function executeFunction($function)\n    {\n        $this->controller->executeFunction($function);\n    }\n\n    public function getIsCallForCache()\n    {\n        return $this->controller->getIsCallForCache();\n    }\n\n    public function getClassKey()\n    {\n        return $this->controller->getClassKey();\n    }\n\n    public function getViewData()\n    {\n        return $this->controller->getViewData();\n    }\n\n    public function setViewData($viewData = null)\n    {\n        $this->controller->setViewData($viewData);\n    }\n\n    public function getViewId()\n    {\n        return $this->controller->getViewId();\n    }\n\n    public function getCharSet()\n    {\n        return $this->controller->getCharSet();\n    }\n\n    public function setClassKey($classKey)\n    {\n        $this->controller->setClassKey($classKey);\n    }\n\n    public function setFncName($fncName)\n    {\n        $this->controller->setFncName($fncName);\n    }\n\n    public function setViewParameters($params = null)\n    {\n        $this->controller->setViewParameters($params);\n    }\n\n    public function getViewParameter($key)\n    {\n        return $this->controller->getViewParameter($key);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Controller/ViewControllerInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Controller;\n\ninterface ViewControllerInterface\n{\n    public function init();\n\n    public function render();\n\n    public function executeFunction($function);\n\n    public function setClassKey($classKey);\n\n    public function getClassKey();\n\n    public function setFncName($fncName);\n\n    public function getFncName();\n\n    public function setViewParameters($params = null);\n\n    public function getViewParameter($key);\n\n    public function setViewData($viewData = null);\n\n    public function getViewData();\n\n    public function getViewId();\n\n    public function getIsCallForCache();\n\n    /*\n     * @deprecated\n     *\n     * Added only for BC and will be removed in the next major with 'charset' language string.\n     */\n    public function getCharSet();\n}\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/CompilerPass/RoutePass.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\CompilerPass;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Api\\AttributeRouteControllerLoader;\nuse Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface;\nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder;\nuse Symfony\\Component\\Routing\\Matcher\\Dumper\\CompiledUrlMatcherDumper;\nuse Symfony\\Component\\Routing\\RouteCollection;\n\nclass RoutePass implements CompilerPassInterface\n{\n    public function process(ContainerBuilder $container): void\n    {\n        $loader = new AttributeRouteControllerLoader();\n        $routes = new RouteCollection();\n\n        foreach ($container->getDefinitions() as $definition) {\n            $class = $definition->getClass();\n            if ($definition->isPublic() && !$definition->isAbstract() && $class !== null && class_exists($class)) {\n                $routes->addCollection($loader->load($class));\n            }\n        }\n\n        $compiledRoutes = (new CompiledUrlMatcherDumper($routes))->getCompiledRoutes();\n\n        $container->setParameter('oxid.routes', $compiledRoutes);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/CompilerPass/ViewControllerPass.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\CompilerPass;\n\nuse Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface;\nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder;\n\nclass ViewControllerPass implements CompilerPassInterface\n{\n    public function process(ContainerBuilder $container): void\n    {\n        $taggedServices = $container->findTaggedServiceIds('oxid.view_controller');\n        $controllersMap = [];\n\n        foreach ($taggedServices as $id => $tags) {\n            foreach ($tags as $attributes) {\n                $controllersMap[$attributes['controller_key']] = $id;\n            }\n        }\n\n        $container->setParameter('oxid.view_controllers_map', $controllersMap);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/ContainerBuilder.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\CompilerPass\\ViewControllerPass;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\CompilerPass\\RoutePass;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition\\Edition;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Env\\EnvUrlFormatter;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Logger\\LoggerServiceFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\Context;\nuse Symfony\\Component\\Config\\Exception\\FileLocatorFileNotFoundException;\nuse Symfony\\Component\\Config\\Exception\\LoaderLoadException;\nuse Symfony\\Component\\Config\\FileLocator;\nuse Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass;\nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder as SymfonyContainerBuilder;\nuse Symfony\\Component\\DependencyInjection\\Loader\\YamlFileLoader;\nuse Symfony\\Component\\EventDispatcher\\DependencyInjection\\RegisterListenersPass;\nuse Symfony\\Component\\Filesystem\\Path;\n\n/**\n * @internal\n */\nclass ContainerBuilder\n{\n    private SymfonyContainerBuilder $containerBuilder;\n\n    public function __construct(\n        private readonly BasicContextInterface $basicContext,\n        private readonly int $shopId = 1\n    ) {\n    }\n\n    public function getContainer(): SymfonyContainerBuilder\n    {\n        $this->containerBuilder = new SymfonyContainerBuilder();\n\n        $this->containerBuilder->setParameter('oxid_esales.current_shop_id', $this->shopId);\n        $this->containerBuilder->setParameter(\n            'oxid_esales.shop_source_directory',\n            $this->basicContext->getSourcePath()\n        );\n\n        $this->containerBuilder->addCompilerPass(new RegisterListenersPass());\n        $this->containerBuilder->addCompilerPass(new AddConsoleCommandPass());\n        $this->containerBuilder->addCompilerPass(new ViewControllerPass());\n        $this->containerBuilder->addCompilerPass(new RoutePass());\n\n        $this->loadEditionServices();\n        $this->loadComponentServices();\n        $this->loadModuleServices();\n        $this->loadProjectServices();\n        $this->loadProjectSubshopServices();\n        $this->loadEnvironmentServices();\n        $this->loadSubshopEnvironmentServices();\n\n        return $this->containerBuilder;\n    }\n\n    private function loadEditionServices(): void\n    {\n        foreach ($this->getEditionsRootPaths() as $editionPath) {\n            $this->getYamlLoader([$editionPath])->load('Internal/services.yaml');\n        }\n    }\n\n    private function getEditionsRootPaths(): array\n    {\n        return match ($this->basicContext->getEdition()) {\n            Edition::Community => [\n                $this->basicContext->getEditionSourcePath(Edition::Community),\n            ],\n            Edition::Professional => [\n                $this->basicContext->getEditionSourcePath(Edition::Community),\n                $this->basicContext->getEditionSourcePath(Edition::Professional),\n            ],\n            Edition::Enterprise => [\n                $this->basicContext->getEditionSourcePath(Edition::Community),\n                $this->basicContext->getEditionSourcePath(Edition::Professional),\n                $this->basicContext->getEditionSourcePath(Edition::Enterprise),\n            ],\n        };\n    }\n\n    private function loadComponentServices(): void\n    {\n        $this->loadYamlIfExists($this->getYamlLoader([]), $this->basicContext->getGeneratedServicesFilePath());\n    }\n\n    private function loadModuleServices(): void\n    {\n        $moduleServicesFilePath = $this->basicContext->getActiveModuleServicesFilePath($this->shopId);\n        try {\n            $this->loadYamlIfExists($this->getYamlLoader([]), $moduleServicesFilePath);\n        } catch (LoaderLoadException $exception) {\n            (new LoggerServiceFactory(new Context($this->shopId)))\n                ->getLogger()\n                ->error(\n                    \"Can't load module services file path $moduleServicesFilePath. \"\n                    . 'Please check if all imports in the file are correct.',\n                    [$exception]\n                );\n        }\n    }\n\n    private function loadProjectServices(): void\n    {\n        $this->loadProjectExtensionFiles(\n            $this->basicContext->getProjectConfigurationDirectory()\n        );\n    }\n\n    private function loadProjectSubshopServices(): void\n    {\n        $this->loadProjectExtensionFiles(\n            $this->basicContext->getShopConfigurationDirectory($this->shopId)\n        );\n    }\n\n    private function loadSubshopEnvironmentServices(): void\n    {\n        $this->loadProjectExtensionFiles(\n            $this->getShopConfigurationPathForSpecificEnvironment()\n        );\n    }\n\n    private function getShopConfigurationPathForSpecificEnvironment(): string\n    {\n        return Path::join(\n            EnvUrlFormatter::toEnvUrl(\n                $this->basicContext->getProjectConfigurationDirectory()\n            ),\n            Path::makeRelative(\n                $this->basicContext->getShopConfigurationDirectory($this->shopId),\n                $this->basicContext->getProjectConfigurationDirectory()\n            )\n        );\n    }\n\n    private function loadEnvironmentServices(): void\n    {\n        $this->loadProjectExtensionFiles(\n            EnvUrlFormatter::toEnvUrl(\n                $this->basicContext->getProjectConfigurationDirectory()\n            )\n        );\n    }\n\n    private function loadProjectExtensionFiles(string $configurationUrl): void\n    {\n        foreach (['services.yaml', 'parameters.yaml'] as $file) {\n            $this->loadYamlIfExists(\n                $this->getYamlLoader([]),\n                Path::join($configurationUrl, $file)\n            );\n        }\n    }\n\n    private function getYamlLoader(array $paths): YamlFileLoader\n    {\n        return new YamlFileLoader($this->containerBuilder, new FileLocator($paths));\n    }\n\n    private function loadYamlIfExists(YamlFileLoader $loader, string $yamlFile): void\n    {\n        try {\n            $loader->load($yamlFile);\n        } catch (FileLocatorFileNotFoundException) {\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/Dao/ContainerAwareProjectYamlDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\DataObject\\DIConfigWrapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Event\\ProjectYamlChangedEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\nuse Symfony\\Component\\Filesystem\\Filesystem;\n\nclass ContainerAwareProjectYamlDao extends ProjectYamlDao\n{\n    public function __construct(\n        BasicContextInterface $context,\n        private EventDispatcherInterface $eventDispatcher,\n        Filesystem $filesystem\n    ) {\n        parent::__construct($context, $filesystem);\n    }\n\n    /**\n     * @param DIConfigWrapper $config\n     */\n    public function saveProjectConfigFile(DIConfigWrapper $config)\n    {\n        parent::saveProjectConfigFile($config);\n        $this->eventDispatcher->dispatch(\n            new ProjectYamlChangedEvent()\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/Dao/ParameterDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Event\\ProjectYamlChangedEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\nuse Symfony\\Component\\Yaml\\Yaml;\nuse UnitEnum;\n\nuse function array_key_exists;\n\nreadonly class ParameterDao implements ParameterDaoInterface\n{\n    public function __construct(\n        private BasicContextInterface $context,\n        private Filesystem $filesystem,\n        private EventDispatcherInterface $eventDispatcher\n    ) {\n    }\n\n    public function add(string $name, UnitEnum|float|int|bool|array|string|null $value, int $shopId): void\n    {\n        $this->saveParameterIntoFile($name, $value, $this->getShopParameterFilePath($shopId));\n    }\n\n    public function remove(string $name, int $shopId): void\n    {\n        $this->removeParameterFromFile($name, $this->getShopParameterFilePath($shopId));\n    }\n\n    public function has(string $name, int $shopId): bool\n    {\n        return array_key_exists(\n            $name,\n            $this->getParameters($this->getShopParameterFilePath($shopId))\n        );\n    }\n\n    private function getParameters(string $filePath): array\n    {\n        if (file_exists($filePath)) {\n            return Yaml::parse(file_get_contents($filePath), Yaml::PARSE_CUSTOM_TAGS)['parameters'] ?? [];\n        }\n\n        return [];\n    }\n\n    private function saveParameters(array $parameters, string $filePath): void\n    {\n        $this->filesystem->dumpFile(\n            $filePath,\n            Yaml::dump(['parameters' => $parameters], 3, 2)\n        );\n\n        $this->eventDispatcher->dispatch(new ProjectYamlChangedEvent());\n    }\n\n    private function saveParameterIntoFile(string $name, mixed $value, string $filePath): void\n    {\n        $parameters = $this->getParameters($filePath);\n        $parameters[$name] = $value;\n        $this->saveParameters($parameters, $filePath);\n    }\n\n    private function removeParameterFromFile(string $name, string $filePath): void\n    {\n        $parameters = $this->getParameters($filePath);\n        unset($parameters[$name]);\n        $this->saveParameters($parameters, $filePath);\n    }\n\n    private function getShopParameterFilePath(int $shopId): string\n    {\n        return Path::join($this->context->getShopConfigurationDirectory($shopId), 'parameters.yaml');\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/Dao/ParameterDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Dao;\n\ninterface ParameterDaoInterface\n{\n    public function add(string $name, array|bool|string|int|float|\\UnitEnum|null $value, int $shopId): void;\n\n    public function remove(string $name, int $shopId): void;\n\n    public function has(string $name, int $shopId): bool;\n}\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/Dao/ProjectYamlDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\DataObject\\DIConfigWrapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Yaml\\Yaml;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass ProjectYamlDao implements ProjectYamlDaoInterface\n{\n    public function __construct(\n        private BasicContextInterface $context,\n        private Filesystem $filesystem\n    ) {\n    }\n\n    /**\n     * @return DIConfigWrapper\n     */\n    public function loadProjectConfigFile(): DIConfigWrapper\n    {\n        return $this->loadDIConfigFile($this->context->getGeneratedServicesFilePath());\n    }\n\n    /**\n     * @param DIConfigWrapper $config\n     */\n    public function saveProjectConfigFile(DIConfigWrapper $config)\n    {\n        $config = $this->convertAbsolutePathsToRelative($config);\n\n        if (!$this->filesystem->exists($this->getGeneratedServicesFileDirectory())) {\n            $this->filesystem->mkdir($this->getGeneratedServicesFileDirectory());\n        }\n\n        file_put_contents(\n            $this->context->getGeneratedServicesFilePath(),\n            Yaml::dump($config->getConfigAsArray(), 3, 2)\n        );\n    }\n\n    /**\n     * @param string $path\n     *\n     * @return DIConfigWrapper\n     */\n    public function loadDIConfigFile(string $path): DIConfigWrapper\n    {\n        $yamlArray = [];\n\n        if (file_exists($path)) {\n            $yamlArray = Yaml::parse(file_get_contents($path), Yaml::PARSE_CUSTOM_TAGS) ?? [];\n        }\n\n        return new DIConfigWrapper($yamlArray);\n    }\n\n    /**\n     * @return string\n     */\n    private function getGeneratedServicesFileDirectory(): string\n    {\n        return \\dirname($this->context->getGeneratedServicesFilePath());\n    }\n\n    /**\n     * @param DIConfigWrapper $configWrapper\n     * @return DIConfigWrapper\n     */\n    private function convertAbsolutePathsToRelative(DIConfigWrapper $configWrapper): DIConfigWrapper\n    {\n        foreach ($configWrapper->getImportFileNames() as $fileName) {\n            if (Path::isAbsolute($fileName)) {\n                $relativePath = Path::makeRelative(\n                    $fileName,\n                    Path::getDirectory($this->context->getGeneratedServicesFilePath())\n                );\n                $configWrapper->addImport($relativePath);\n                $configWrapper->removeImport($fileName);\n            }\n        }\n\n        return $configWrapper;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/Dao/ProjectYamlDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\DataObject\\DIConfigWrapper;\n\ninterface ProjectYamlDaoInterface\n{\n    /**\n     * @param string $path\n     *\n     * @return DIConfigWrapper\n     */\n    public function loadDIConfigFile(string $path): DIConfigWrapper;\n\n    /**\n     * @return DIConfigWrapper\n     */\n    public function loadProjectConfigFile(): DIConfigWrapper;\n\n    /**\n     * @param DIConfigWrapper $config\n     */\n    public function saveProjectConfigFile(DIConfigWrapper $config);\n}\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/Dao/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Dao\\ProjectYamlDaoInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Dao\\ContainerAwareProjectYamlDao\n    arguments:\n      Symfony\\Component\\Filesystem\\Filesystem: '@oxid_esales.symfony.file_system'\n      $context: '@OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface'\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Dao\\ParameterDaoInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Dao\\ParameterDao\n    public: true\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/DataObject/DIConfigWrapper.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\DataObject;\n\nuse function array_key_exists;\n\nclass DIConfigWrapper\n{\n    private const SERVICE_SECTION = 'services';\n    private const RESOURCE_KEY = 'resource';\n    private const IMPORTS_SECTION = 'imports';\n\n    private array $sectionDefaults = [self::SERVICE_SECTION => ['_defaults' => ['autowire' => true]]];\n\n    public function __construct(private array $configArray)\n    {\n    }\n\n    /**\n     * @param string $importFilePath\n     * @return void\n     */\n    public function addImport(string $importFilePath): void\n    {\n        $this->addSectionIfMissing(static::IMPORTS_SECTION);\n        foreach ($this->getImports() as $import) {\n            if ($import[static::RESOURCE_KEY] === $importFilePath) {\n                return;\n            }\n        }\n        $this->configArray[static::IMPORTS_SECTION][] = [static::RESOURCE_KEY => $importFilePath];\n    }\n\n    /**\n     * @return array\n     */\n    public function getImportFileNames(): array\n    {\n        $importFileNames = [];\n        foreach ($this->getImports() as $import) {\n            $importFileNames[] = $import[static::RESOURCE_KEY];\n        }\n        return $importFileNames;\n    }\n\n    /**\n     * @param string $importFilePath\n     */\n    public function removeImport(string $importFilePath)\n    {\n        $imports = [];\n        foreach ($this->getImports() as $import) {\n            if ($import[static::RESOURCE_KEY] !== $importFilePath) {\n                $imports[] = $import;\n            }\n        }\n        $this->configArray[static::IMPORTS_SECTION] = $imports;\n    }\n\n    /**\n     * @return array\n     */\n    public function getConfigAsArray(): array\n    {\n        $this->cleanUpConfig();\n\n        return $this->configArray;\n    }\n\n    /**\n     * @return array\n     */\n    private function getImports(): array\n    {\n        if (!array_key_exists(static::IMPORTS_SECTION, $this->configArray)) {\n            return [];\n        }\n\n        return $this->configArray[static::IMPORTS_SECTION];\n    }\n\n    /**\n     * Removes not activated services and\n     * empty import or service sections from the array\n     */\n    private function cleanUpConfig()\n    {\n        $this->removeEmptySections();\n    }\n\n    /**\n     * Removes section entries when they are empty\n     */\n    private function removeEmptySections()\n    {\n        $sections = [static::IMPORTS_SECTION];\n        foreach ($sections as $section) {\n            if (\n                array_key_exists($section, $this->configArray) &&\n                (!$this->configArray[$section] || !count($this->configArray[$section]))\n            ) {\n                unset($this->configArray[$section]);\n            }\n        }\n    }\n\n    /**\n     * @param string $section\n     */\n    private function addSectionIfMissing($section)\n    {\n        if (!array_key_exists($section, $this->configArray)) {\n            if (array_key_exists($section, $this->sectionDefaults)) {\n                $this->configArray[$section] = $this->sectionDefaults[$section];\n            } else {\n                $this->configArray[$section] = [];\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/Event/ProjectYamlChangedEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Event;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\nclass ProjectYamlChangedEvent extends Event\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/Exception/MissingServiceException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Exception;\n\nclass MissingServiceException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/Exception/MissingUpdateCallException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Exception;\n\nclass MissingUpdateCallException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/Exception/NoServiceYamlException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Exception;\n\nclass NoServiceYamlException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/Exception/SystemServiceOverwriteException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Exception;\n\nclass SystemServiceOverwriteException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/Service/ContainerCacheInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Service;\n\nuse Psr\\Container\\ContainerInterface;\nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder;\n\ninterface ContainerCacheInterface\n{\n    public function put(ContainerBuilder $container, int $shopId): void;\n\n    public function get(int $shopId): ContainerInterface;\n\n    public function exists(int $shopId): bool;\n\n    public function invalidate(int $shopId): void;\n}\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/Service/FilesystemContainerCache.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse Psr\\Container\\ContainerInterface;\nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder;\nuse Symfony\\Component\\DependencyInjection\\Dumper\\PhpDumper;\nuse Symfony\\Component\\Filesystem\\Filesystem;\n\nclass FilesystemContainerCache implements ContainerCacheInterface\n{\n    public function __construct(private BasicContextInterface $context, private Filesystem $filesystem)\n    {\n    }\n\n    public function put(ContainerBuilder $container, int $shopId): void\n    {\n        $dumper = new PhpDumper($container);\n        $this->filesystem->dumpFile($this->context->getContainerCacheFilePath($shopId), $dumper->dump());\n    }\n\n    /**\n     * @psalm-suppress UndefinedClass\n     */\n    public function get(int $shopId): ContainerInterface\n    {\n        include_once $this->context->getContainerCacheFilePath($shopId);\n        return new \\ProjectServiceContainer();\n    }\n\n    public function exists(int $shopId): bool\n    {\n        $path = $this->context->getContainerCacheFilePath($shopId);\n        return $this->filesystem->exists($path);\n    }\n\n    public function invalidate(int $shopId): void\n    {\n        if ($this->filesystem->exists($this->context->getContainerCacheFilePath($shopId))) {\n            $this->filesystem->remove($this->context->getContainerCacheFilePath($shopId));\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/Service/ProjectYamlImportService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Dao\\ProjectYamlDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Exception\\NoServiceYamlException;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse Symfony\\Component\\Filesystem\\Path;\n\n/**\n * @internal\n */\nclass ProjectYamlImportService implements ProjectYamlImportServiceInterface\n{\n    private const SERVICE_FILE_NAME = 'services.yaml';\n\n    public function __construct(\n        private ProjectYamlDaoInterface $projectYamlDao,\n        private BasicContextInterface $context\n    ) {\n    }\n\n    /**\n     * @param string $serviceDir\n     */\n    public function addImport(string $serviceDir)\n    {\n        if (!realpath($serviceDir)) {\n            throw new NoServiceYamlException();\n        }\n        $projectConfig = $this->projectYamlDao->loadProjectConfigFile();\n        $projectConfig->addImport($this->getServiceRelativeFilePath($serviceDir));\n\n        $this->projectYamlDao->saveProjectConfigFile($projectConfig);\n    }\n\n    /**\n     * @param string $serviceDir\n     */\n    public function removeImport(string $serviceDir)\n    {\n        $projectConfig = $this->projectYamlDao->loadProjectConfigFile();\n\n        $projectConfig->removeImport($this->getServiceRelativeFilePath($serviceDir));\n\n        $this->projectYamlDao->saveProjectConfigFile($projectConfig);\n    }\n\n    /**\n     * Checks if the import files exist and if not removes them\n     */\n    public function removeNonExistingImports()\n    {\n        $projectConfig = $this->projectYamlDao->loadProjectConfigFile();\n\n        $configChanged = false;\n        foreach ($projectConfig->getImportFileNames() as $fileName) {\n            if (file_exists($this->getAbsolutePath($fileName))) {\n                continue;\n            }\n            $projectConfig->removeImport($fileName);\n            $configChanged = true;\n        }\n\n        if ($configChanged) {\n            $this->projectYamlDao->saveProjectConfigFile($projectConfig);\n        }\n    }\n\n    /**\n     * @param $fileName\n     * @return string\n     */\n    private function getAbsolutePath($fileName): string\n    {\n        return Path::makeAbsolute(\n            $fileName,\n            Path::getDirectory($this->context->getGeneratedServicesFilePath())\n        );\n    }\n\n    /**\n     * @param string $serviceDir\n     * @return string\n     */\n    private function getServiceRelativeFilePath(string $serviceDir): string\n    {\n        return Path::makeRelative(\n            $serviceDir . DIRECTORY_SEPARATOR . static::SERVICE_FILE_NAME,\n            Path::getDirectory($this->context->getGeneratedServicesFilePath())\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/Service/ProjectYamlImportServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Service;\n\n/**\n * @internal\n */\ninterface ProjectYamlImportServiceInterface\n{\n    /**\n     * @param string $serviceDir\n     *\n     * @return void\n     */\n    public function addImport(string $serviceDir);\n\n    /**\n     * @param string $serviceDir\n     */\n    public function removeImport(string $serviceDir);\n\n    /**\n     * Checks if the import files exist and if not removes them\n     *\n     * @return void\n     */\n    public function removeNonExistingImports();\n}\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/Service/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Service\\ProjectYamlImportServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Service\\ProjectYamlImportService\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Service\\ContainerCacheInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Service\\FilesystemContainerCache\n"
  },
  {
    "path": "source/Internal/Framework/DIContainer/services.yaml",
    "content": "imports:\n    - { resource: Dao/services.yaml }\n    - { resource: Service/services.yaml }\n"
  },
  {
    "path": "source/Internal/Framework/Dao/EntryDoesNotExistDaoException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\nclass EntryDoesNotExistDaoException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Dao/InvalidObjectIdDaoException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao;\n\nclass InvalidObjectIdDaoException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Database/Configuration/DataObject/DatabaseConfiguration.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\DataObject;\n\nuse Doctrine\\DBAL\\Tools\\DsnParser;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\InvalidDatabaseConfigurationException;\n\nclass DatabaseConfiguration\n{\n    private array $urlComponents;\n\n    /**\n     * List of URL schemes from a database URL and their mappings to driver.\n     * @see \\Doctrine\\DBAL\\DriverManager::$driverSchemeAliases\n     */\n    private static array $driverSchemeAliases = [\n        'db2' => 'ibm_db2',\n        'mssql' => 'pdo_sqlsrv',\n        'mysql' => 'pdo_mysql',\n        'mysql2' => 'pdo_mysql', // Amazon RDS, for some weird reason\n        'postgres' => 'pdo_pgsql',\n        'postgresql' => 'pdo_pgsql',\n        'pgsql' => 'pdo_pgsql',\n        'sqlite' => 'pdo_sqlite',\n        'sqlite3' => 'pdo_sqlite',\n    ];\n\n    public function __construct(private readonly string $databaseUrl)\n    {\n        $this->urlComponents = (new DsnParser(self::$driverSchemeAliases))->parse($databaseUrl);\n        $this->validateRequiredUrlComponents();\n    }\n\n    public function getDriver(): string\n    {\n        return $this->urlComponents['driver'];\n    }\n\n    public function getDatabaseUrl(): string\n    {\n        return $this->databaseUrl;\n    }\n\n    public function getUser(): string\n    {\n        return $this->urlComponents['user'] ?? '';\n    }\n\n    public function getPass(): string\n    {\n        return $this->urlComponents['password'] ?? '';\n    }\n\n    public function getHost(): string\n    {\n        return $this->urlComponents['host'];\n    }\n\n    public function getPort(): int\n    {\n        return $this->urlComponents['port'] ?? 3306;\n    }\n\n    public function getName(): string\n    {\n        return $this->urlComponents['dbname'] ?? '';\n    }\n\n    public function getOptions(): array\n    {\n        return $this->urlComponents['driverOptions'] ?? [];\n    }\n\n    public function getCharset(): ?string\n    {\n        return $this->urlComponents['charset'] ?? null;\n    }\n\n    public function isSocketConnection(): bool\n    {\n        return isset($this->urlComponents['socket']);\n    }\n\n    public function getSocket(): string\n    {\n        return trim($this->urlComponents['socket'], '()');\n    }\n\n    public function getConnectionParameters(): array\n    {\n        return $this->urlComponents;\n    }\n\n    private function validateRequiredUrlComponents(): void\n    {\n        if (\n            empty($this->urlComponents['host']) ||\n            !isset($this->urlComponents['driver']) ||\n            !in_array($this->urlComponents['driver'], self::$driverSchemeAliases, true)\n        ) {\n            throw new InvalidDatabaseConfigurationException('Provided database URL is not valid');\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Database/Configuration/InvalidDatabaseConfigurationException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration;\n\nclass InvalidDatabaseConfigurationException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Database/ConnectionFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Database;\n\nuse Doctrine\\DBAL\\Configuration as DbalConfiguration;\nuse Doctrine\\DBAL\\Connection;\nuse Doctrine\\DBAL\\DriverManager;\n\nreadonly class ConnectionFactory implements ConnectionFactoryInterface\n{\n    private Connection $connection;\n\n    public function __construct(\n        private ConnectionParameterProviderInterface $connectionParameterProvider,\n        private iterable $middlewares,\n    ) {\n    }\n\n    public function create(): Connection\n    {\n        if (!isset($this->connection)) {\n            $dbalConfiguration = new DbalConfiguration();\n            $dbalConfiguration->setMiddlewares(\n                iterator_to_array($this->middlewares)\n            );\n            $this->connection = DriverManager::getConnection(\n                $this->connectionParameterProvider->getParameters(),\n                $dbalConfiguration,\n            );\n        }\n\n        return $this->connection;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Database/ConnectionFactoryInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Database;\n\nuse Doctrine\\DBAL\\Connection;\n\ninterface ConnectionFactoryInterface\n{\n    public function create(): Connection;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Database/ConnectionParameterProvider.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Database;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\DataObject\\DatabaseConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\n\nreadonly class ConnectionParameterProvider implements ConnectionParameterProviderInterface\n{\n    public function __construct(\n        private BasicContextInterface $basicContext,\n    ) {\n    }\n\n    public function getParameters(): array\n    {\n        return (new DatabaseConfiguration($this->basicContext->getDatabaseUrl()))->getConnectionParameters();\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Database/ConnectionParameterProviderInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Database;\n\ninterface ConnectionParameterProviderInterface\n{\n    public function getParameters(): array;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Database/Id.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Database;\n\nreadonly class Id\n{\n    private string $uid;\n\n    private function __construct(?string $uid = null)\n    {\n        $this->uid = $uid ?? $this->generateUid();\n    }\n\n    public static function generate(): self\n    {\n        return new self();\n    }\n\n    public static function fromString(string $string): self\n    {\n        return new self($string);\n    }\n\n    public function __toString(): string\n    {\n        return $this->uid;\n    }\n\n    private function generateUid(): string\n    {\n        return md5(uniqid('', true) . '|' . microtime());\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Database/Logger/QueryLogContextExtender.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Logger;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\Exception\\AdminUserNotFoundException;\n\nreadonly class QueryLogContextExtender implements QueryLogContextExtenderInterface\n{\n    public function __construct(private ContextInterface $shopContext)\n    {\n    }\n\n    public function extend(array $queryContext): array\n    {\n        $extendedContext = [\n            'adminUserId' => $this->getAdminUserIdIfExists(),\n            'shopId' => $this->shopContext->getCurrentShopId(),\n            'trace' => debug_backtrace(),\n        ];\n\n        return array_merge($queryContext, $extendedContext);\n    }\n\n    private function getAdminUserIdIfExists(): string\n    {\n        try {\n            $adminId = $this->shopContext->getAdminUserId();\n        } catch (AdminUserNotFoundException) {\n            $adminId = '';\n        }\n\n        return $adminId;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Database/Logger/QueryLogContextExtenderInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Logger;\n\ninterface QueryLogContextExtenderInterface\n{\n    public function extend(array $queryContext): array;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Database/Logger/QueryLogFilter.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Logger;\n\nuse function implode;\nuse function sprintf;\n\nreadonly class QueryLogFilter implements QueryLogFilterInterface\n{\n    private const SHOULD_LOG_IF_CONTAINS_PATTERN = '(.?)(insert into|update |delete )';\n    private const SHOULD_NOT_LOG_IF_CONTAINS_PATTERN = '(?!.*oxsession)(?!.*oxcache)';\n\n    public function __construct(private array $skipLogTags)\n    {\n    }\n\n    public function shouldLogQuery(string $query): bool\n    {\n        $additionalPatternToSkipLogging = !empty($this->skipLogTags)\n            ? sprintf(\n                '(?!.*%s)',\n                implode(')(?!.*', $this->skipLogTags)\n            )\n            : '';\n\n        return (bool)preg_match(\n            sprintf(\n                '/%s%s%s/i',\n                self::SHOULD_LOG_IF_CONTAINS_PATTERN,\n                self::SHOULD_NOT_LOG_IF_CONTAINS_PATTERN,\n                $additionalPatternToSkipLogging\n            ),\n            $query\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Database/Logger/QueryLogFilterInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Logger;\n\ninterface QueryLogFilterInterface\n{\n    public function shouldLogQuery(string $query): bool;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Database/Logger/QueryLogger.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Logger;\n\nuse Monolog\\Logger;\n\nclass QueryLogger extends Logger\n{\n    public function __construct(\n        string $loggerName,\n        private readonly QueryLogFilterInterface $queryLogFilter,\n        private readonly QueryLogContextExtenderInterface $queryLogContextExtender,\n    ) {\n        parent::__construct($loggerName);\n    }\n\n    public function addRecord($level, $message, array $context = array()): bool\n    {\n        return isset($context['sql']) &&\n            $this->queryLogFilter->shouldLogQuery($context['sql'])\n            && parent::addRecord(\n                $level,\n                $message,\n                $this->queryLogContextExtender->extend($context)\n            );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Database/Logger/QueryLoggerFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Logger;\n\nuse Monolog\\Handler\\StreamHandler;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Logger\\Factory\\LoggerFactoryInterface;\nuse Psr\\Log\\LoggerInterface;\n\nreadonly class QueryLoggerFactory implements LoggerFactoryInterface\n{\n    public function __construct(\n        private QueryLogFilterInterface $queryLogFilter,\n        private QueryLogContextExtenderInterface $queryLogContextExtender,\n        private StreamHandler $streamHandler,\n        private string $loggerName,\n    ) {\n    }\n\n    public function create(): LoggerInterface\n    {\n        return (new QueryLogger(\n            $this->loggerName,\n            $this->queryLogFilter,\n            $this->queryLogContextExtender,\n        ))\n            ->pushHandler($this->streamHandler);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Database/Logger/services.yaml",
    "content": "parameters:\n  oxid_esales.log_admin_queries: false\n\nservices:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Logger\\QueryLogFilterInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Logger\\QueryLogFilter\n    arguments:\n      $skipLogTags: '@=service(\"OxidEsales\\\\EshopCommunity\\\\Internal\\\\Transition\\\\Utility\\\\ContextInterface\").getSkipLogTags()'\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Logger\\QueryLogContextExtenderInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Logger\\QueryLogContextExtender\n\n  Psr\\Log\\NullLogger:\n    public: true\n\n  Doctrine\\DBAL\\Logging\\Middleware:\n    arguments:\n      $logger: '@=(parameter(\"oxid_esales.log_admin_queries\") & service(\"OxidEsales\\\\EshopCommunity\\\\Internal\\\\Transition\\\\Utility\\\\ContextInterface\").isAdmin()) ? service(\"database.psr_logger\") : service(\"Psr\\\\Log\\\\NullLogger\")'\n    tags:\n      - { name: doctrine.middleware }\n\n  database.monolog_logger_factory:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Logger\\QueryLoggerFactory\n    arguments:\n      $loggerName: 'OXID Admin Logger'\n      $streamHandler: '@database.monolog.stream_handler'\n\n  database.psr_logger:\n    class: Monolog\\Logger\n    factory: ['@database.monolog_logger_factory', 'create']\n\n  database.monolog.stream_handler:\n    class: Monolog\\Handler\\StreamHandler\n    arguments:\n      $stream: '@=service(\"OxidEsales\\\\EshopCommunity\\\\Internal\\\\Transition\\\\Utility\\\\ContextInterface\").getAdminLogFilePath()'\n      $level: !php/const Psr\\Log\\LogLevel::DEBUG\n    calls:\n      - setFormatter: ['@Monolog\\Formatter\\LineFormatter']\n\n  Monolog\\Formatter\\LineFormatter:\n    calls:\n      - includeStacktraces: []\n"
  },
  {
    "path": "source/Internal/Framework/Database/QueryBuilderFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Database;\n\nuse Doctrine\\DBAL\\Query\\QueryBuilder;\n\nclass QueryBuilderFactory implements QueryBuilderFactoryInterface\n{\n    public function __construct(\n        private readonly ConnectionFactoryInterface $connectionFactory\n    ) {\n    }\n\n    public function create(): QueryBuilder\n    {\n        $connection = $this->connectionFactory->create();\n\n        return new QueryBuilder($connection);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Database/QueryBuilderFactoryInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Database;\n\nuse Doctrine\\DBAL\\Query\\QueryBuilder;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\ninterface QueryBuilderFactoryInterface\n{\n    public function create(): QueryBuilder;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Database/services.yaml",
    "content": "imports:\n  - { resource: Logger/services.yaml }\n\nparameters:\n  oxid_esales.multilingual_tables: []\n  oxid_esales.skip_database_views_usage: false\n  oxid_esales.show_update_views_button: true\n  oxid_esales.multi_shop_tables: []\n\nservices:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactory\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\ConnectionFactoryInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\ConnectionFactory\n    arguments:\n      $middlewares: !tagged doctrine.middleware\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\ConnectionParameterProviderInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\ConnectionParameterProvider\n"
  },
  {
    "path": "source/Internal/Framework/Edition/Edition.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition;\n\nenum Edition: string\n{\n    case Community = 'CE';\n    case Professional = 'PE';\n    case Enterprise = 'EE';\n\n    public function isCommunityEdition(): bool\n    {\n        return match ($this) {\n            self::Community => true,\n            default => false,\n        };\n    }\n\n    public function getFullEditionName(): string\n    {\n        return match ($this) {\n            self::Community => 'Community Edition',\n            self::Professional => 'Professional Edition',\n            self::Enterprise => 'Enterprise Edition',\n        };\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Edition/EditionDirectoriesLocator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\DirectoryNotExistentException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\ProjectDirectoriesLocator;\nuse Symfony\\Component\\Filesystem\\Path;\n\nreadonly class EditionDirectoriesLocator\n{\n    public function getEditionRootPath(Edition $edition): string\n    {\n        $projectDirectoriesLocator = new ProjectDirectoriesLocator();\n        $path = Path::join(\n            $projectDirectoriesLocator->getVendorPath(),\n            EditionPaths::from($edition->value)->getVendorFolderName(),\n            EditionPaths::from($edition->value)->getProjectFolderName(),\n        );\n        if (!is_dir($path)) {\n            if ($edition->isCommunityEdition()) {\n                return $projectDirectoriesLocator->getRootPath();\n            }\n            throw new DirectoryNotExistentException(\"Root directory for {$edition->name} does not exist!\");\n        }\n        return $path;\n    }\n\n    public function getEditionSourcePath(Edition $edition): string\n    {\n        return Path::join(\n            $this->getEditionRootPath($edition),\n            EditionPaths::from($edition->value)->getSourceFolderName(),\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Edition/EditionPaths.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition;\n\nenum EditionPaths: string\n{\n    case Community = Edition::Community->value;\n    case Professional = Edition::Professional->value;\n    case Enterprise = Edition::Enterprise->value;\n\n    public function getVendorFolderName(): string\n    {\n        return 'oxid-esales';\n    }\n\n    public function getProjectFolderName(): string\n    {\n        return match ($this) {\n            self::Community => 'oxideshop-ce',\n            self::Professional => 'oxideshop-pe',\n            self::Enterprise => 'oxideshop-ee',\n        };\n    }\n\n    public function getSourceFolderName(): string\n    {\n        return match ($this) {\n            self::Community => 'source',\n            default => '',\n        };\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Edition/EditionResolver.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\DirectoryNotExistentException;\n\nreadonly class EditionResolver\n{\n    public function getEdition(): Edition\n    {\n        $editionDirectoriesLocator = new EditionDirectoriesLocator();\n        try {\n            $editionDirectoriesLocator->getEditionRootPath(Edition::Enterprise);\n            return Edition::Enterprise;\n        } catch (DirectoryNotExistentException) {\n            try {\n                $editionDirectoriesLocator->getEditionRootPath(Edition::Professional);\n                return Edition::Professional;\n            } catch (DirectoryNotExistentException) {\n                return Edition::Community;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Env/DotenvLoader.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Env;\n\nuse Symfony\\Component\\Dotenv\\Dotenv;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass DotenvLoader\n{\n    private string $envKey = 'OXID_ENV';\n    private string $debugKey = 'OXID_DEBUG';\n    private string $envFile = '.env';\n\n    public function __construct(private readonly string $pathToEnvFiles)\n    {\n    }\n\n    public function loadEnvironmentVariables(): void\n    {\n        $dotEnv = new Dotenv($this->envKey, $this->debugKey);\n        $dotEnv\n            ->usePutenv()\n            ->loadEnv(\n                Path::join($this->pathToEnvFiles, $this->envFile)\n            );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Env/EnvUrlFormatter.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Env;\n\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass EnvUrlFormatter\n{\n    public static function toEnvUrl(string $url): string\n    {\n        return sprintf(\n            '%s.%s',\n            Path::canonicalize($url),\n            getenv('OXID_ENV')\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Event/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  Symfony\\Component\\EventDispatcher\\EventDispatcherInterface:\n    class: Symfony\\Component\\EventDispatcher\\EventDispatcher\n    public: true\n\n  event_dispatcher:\n    alias: Symfony\\Component\\EventDispatcher\\EventDispatcherInterface\n"
  },
  {
    "path": "source/Internal/Framework/FileSystem/Bridge/MasterImageHandlerBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\ImageHandlerInterface;\n\nclass MasterImageHandlerBridge implements MasterImageHandlerBridgeInterface\n{\n    public function __construct(private ImageHandlerInterface $masterImageHandler)\n    {\n    }\n\n    /** @inheritdoc */\n    public function copy(string $source, string $destination): void\n    {\n        $this->masterImageHandler->copy($source, $destination);\n    }\n\n    /** @inheritdoc */\n    public function upload(string $source, string $destination): void\n    {\n        $this->masterImageHandler->upload($source, $destination);\n    }\n\n    /** @inheritdoc */\n    public function remove(string $path): void\n    {\n        $this->masterImageHandler->remove($path);\n    }\n\n    /** @inheritdoc */\n    public function exists(string $path): bool\n    {\n        return $this->masterImageHandler->exists($path);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/FileSystem/Bridge/MasterImageHandlerBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\Bridge;\n\ninterface MasterImageHandlerBridgeInterface\n{\n    /**\n     * @param string $source\n     * @param string $destination\n     */\n    public function copy(string $source, string $destination): void;\n\n    /**\n     * @param string $source\n     * @param string $destination\n     */\n    public function upload(string $source, string $destination): void;\n\n    /** @param string $path */\n    public function remove(string $path): void;\n\n    /**\n     * @param string $path\n     * @return bool\n     */\n    public function exists(string $path): bool;\n}\n"
  },
  {
    "path": "source/Internal/Framework/FileSystem/DirectoryNotExistentException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem;\n\nclass DirectoryNotExistentException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/FileSystem/DirectoryNotReadableException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem;\n\nclass DirectoryNotReadableException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/FileSystem/FileGenerator/Bridge/CsvFileGeneratorBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\FileGenerator\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\FileGenerator\\FileGeneratorInterface;\n\nclass CsvFileGeneratorBridge implements FileGeneratorBridgeInterface\n{\n    public function __construct(private FileGeneratorInterface $fileGenerator)\n    {\n    }\n\n    /**\n     * @param string $filename\n     * @param array  $data\n     */\n    public function generate(string $filename, array $data): void\n    {\n        $this->fileGenerator->generate($filename, $data);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/FileSystem/FileGenerator/Bridge/FileGeneratorBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\FileGenerator\\Bridge;\n\ninterface FileGeneratorBridgeInterface\n{\n    /**\n     * @param string $filename\n     * @param array  $data\n     */\n    public function generate(string $filename, array $data): void;\n}\n"
  },
  {
    "path": "source/Internal/Framework/FileSystem/FileGenerator/CsvFileGenerator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\FileGenerator;\n\nclass CsvFileGenerator implements FileGeneratorInterface\n{\n    /**\n     * @param string $filename\n     * @param array  $data\n     */\n    public function generate(string $filename, array $data): void\n    {\n        $file = fopen($filename, 'wb');\n\n        foreach ($data as $value) {\n            fputcsv(stream: $file, fields: $value, escape: '');\n        }\n        fclose($file);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/FileSystem/FileGenerator/FileGeneratorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\FileGenerator;\n\ninterface FileGeneratorInterface\n{\n    /**\n     * @param string $filename\n     * @param array  $data\n     */\n    public function generate(string $filename, array $data): void;\n}\n"
  },
  {
    "path": "source/Internal/Framework/FileSystem/ImageHandlerInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem;\n\ninterface ImageHandlerInterface\n{\n    /**\n     * @param string $source\n     * @param string $destination\n     */\n    public function copy(string $source, string $destination): void;\n\n    /**\n     * @param string $source\n     * @param string $destination\n     */\n    public function upload(string $source, string $destination): void;\n\n    /**\n     * @param string $path\n     */\n    public function remove(string $path): void;\n\n    /**\n     * @param string $path\n     * @return bool\n     */\n    public function exists(string $path): bool;\n}\n"
  },
  {
    "path": "source/Internal/Framework/FileSystem/MasterImageHandler.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse Symfony\\Component\\Filesystem\\Exception\\IOException;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass MasterImageHandler implements ImageHandlerInterface\n{\n    public function __construct(\n        private Filesystem $filesystem,\n        private ContextInterface $context\n    ) {\n    }\n\n    /** @inheritDoc */\n    public function copy(string $source, string $destination): void\n    {\n        $destinationPath = $this->getAbsolutePath($destination);\n        $destinationDirectory = \\dirname($destinationPath);\n        $this->filesystem->mkdir($destinationDirectory, 0744);\n        $this->filesystem->copy($source, $destinationPath, true);\n        $this->filesystem->chmod($destinationPath, 0644);\n    }\n\n    /** @inheritDoc */\n    public function upload(string $source, string $destination): void\n    {\n        $destinationPath = $this->getAbsolutePath($destination);\n        $destinationDirectory = \\dirname($destinationPath);\n        $this->filesystem->mkdir($destinationDirectory, 0744);\n        if ($source !== $destinationPath && !move_uploaded_file($source, $destinationPath)) {\n            throw new IOException('Can not move uploaded file');\n        }\n        $this->filesystem->chmod($destinationPath, 0644);\n    }\n\n    /** @inheritDoc */\n    public function remove(string $path): void\n    {\n        $this->filesystem->remove($this->getAbsolutePath($path));\n    }\n\n    /** @inheritDoc */\n    public function exists(string $path): bool\n    {\n        return $this->filesystem->exists($this->getAbsolutePath($path));\n    }\n\n    /**\n     * @param string $path\n     * @return string\n     */\n    private function getAbsolutePath(string $path): string\n    {\n        return Path::join($this->context->getSourcePath(), $path);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/FileSystem/ProjectDirectoriesLocator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem;\n\nuse Symfony\\Component\\Filesystem\\Path;\n\nreadonly class ProjectDirectoriesLocator\n{\n    private string $projectRoot;\n\n    public function __construct()\n    {\n        $this->projectRoot = (new ProjectRootLocator())->getProjectRoot();\n    }\n\n    public function getRootPath(): string\n    {\n        return $this->projectRoot;\n    }\n\n    public function getSourcePath(): string\n    {\n        return Path::join($this->projectRoot, 'source');\n    }\n\n    public function getVendorPath(): string\n    {\n        return Path::join($this->projectRoot, 'vendor');\n    }\n\n    public function getOutPath(): string\n    {\n        return Path::join($this->getSourcePath(), 'out');\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/FileSystem/ProjectRootLocator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem;\n\nuse RuntimeException;\nuse Symfony\\Component\\Filesystem\\Path;\nuse function dirname;\nuse function is_dir;\n\nclass ProjectRootLocator\n{\n    public function getProjectRoot(): string\n    {\n        $path = __DIR__;\n        while (!is_dir(Path::join($path, 'vendor'))) {\n            if ($this->isFilesystemRootDir($path)) {\n                throw new RuntimeException('Can not determine project root directory!');\n            }\n            $path = $this->getParentDir($path);\n        }\n\n        return $path;\n    }\n\n    private function isFilesystemRootDir(string $path): bool\n    {\n        return $path === $this->getParentDir($path);\n    }\n\n    private function getParentDir(string $path): string\n    {\n        return dirname($path);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/FileSystem/Validator/FileValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\Validator;\n\nuse Symfony\\Component\\Mime\\MimeTypesInterface;\n\nclass FileValidator implements FileValidatorInterface\n{\n    public function __construct(private MimeTypesInterface $mimeTypesService)\n    {\n    }\n\n    public function validateImage(string $filePath): bool\n    {\n        try {\n            if (\n                !empty($filePath)\n                && !str_starts_with(strtoupper($this->mimeTypesService->guessMimeType($filePath)), 'IMAGE/')\n            ) {\n                return false;\n            }\n        } catch (\\Exception $e) {\n            throw new ImageValidationException(\"Unable to get MimeType of file\");\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/FileSystem/Validator/FileValidatorBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\Validator;\n\nclass FileValidatorBridge implements FileValidatorBridgeInterface\n{\n    public function __construct(private FileValidatorInterface $fileValidator)\n    {\n    }\n\n    public function validateImage(string $filePath): bool\n    {\n        return $this->fileValidator->validateImage($filePath);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/FileSystem/Validator/FileValidatorBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\Validator;\n\ninterface FileValidatorBridgeInterface\n{\n    /**\n     * @throws ImageValidationException\n     */\n    public function validateImage(string $filePath): bool;\n}\n"
  },
  {
    "path": "source/Internal/Framework/FileSystem/Validator/FileValidatorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\Validator;\n\ninterface FileValidatorInterface\n{\n    /**\n     * @throws ImageValidationException\n     */\n    public function validateImage(string $filePath): bool;\n}\n"
  },
  {
    "path": "source/Internal/Framework/FileSystem/Validator/ImageValidationException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\Validator;\n\nuse Exception;\n\nclass ImageValidationException extends Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/FileSystem/bootstrap-services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  oxid_esales.common.storage.flock_store_lock_factory:\n    class: Symfony\\Component\\Lock\\LockFactory\n    arguments:\n      - '@oxid_esales.common.storage.flock_store'\n\n  oxid_esales.common.storage.flock_store:\n    class: Symfony\\Component\\Lock\\Store\\FlockStore\n\n  oxid_esales.common.file_locator:\n    class: Symfony\\Component\\Config\\FileLocator\n\n  oxid_esales.symfony.file_system:\n    class: Symfony\\Component\\Filesystem\\Filesystem\n    public: true\n\n  Symfony\\Component\\Filesystem\\Filesystem:\n    alias: oxid_esales.symfony.file_system\n\n  oxid_esales.symfony.mime_types:\n    class: Symfony\\Component\\Mime\\MimeTypes\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage\\FileStorageFactoryInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage\\YamlFileStorageFactory\n    arguments:\n      - '@oxid_esales.common.file_locator'\n      - '@oxid_esales.common.storage.flock_store_lock_factory'\n      - '@oxid_esales.symfony.file_system'\n"
  },
  {
    "path": "source/Internal/Framework/FileSystem/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\ImageHandlerInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\MasterImageHandler\n    arguments:\n      - \"@oxid_esales.symfony.file_system\"\n      - '@OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface'\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\Bridge\\MasterImageHandlerBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\Bridge\\MasterImageHandlerBridge\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\FileGenerator\\FileGeneratorInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\FileGenerator\\CsvFileGenerator\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\FileGenerator\\Bridge\\FileGeneratorBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\FileGenerator\\Bridge\\CsvFileGeneratorBridge\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\Validator\\FileValidatorInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\Validator\\FileValidator\n    arguments:\n      - \"@oxid_esales.symfony.mime_types\"\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\Validator\\FileValidatorBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\Validator\\FileValidatorBridge\n    public: true\n"
  },
  {
    "path": "source/Internal/Framework/Form/Form.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Form;\n\nclass Form implements FormInterface\n{\n    /**\n     * @var array\n     */\n    private $fields = [];\n\n    /**\n     * @var array\n     */\n    private $errors = [];\n\n    /**\n     * @var array\n     */\n    private $validators = [];\n\n    /**\n     * @param FormFieldInterface $field\n     */\n    public function add(FormFieldInterface $field)\n    {\n        $this->fields[$field->getName()] = $field;\n    }\n\n    /**\n     * @param string $name\n     * @return FormField\n     */\n    public function __get($name)\n    {\n        return $this->fields[$name];\n    }\n\n    /**\n     * @return array\n     */\n    public function getFields()\n    {\n        return $this->fields;\n    }\n\n    /**\n     * @param array $request\n     */\n    public function handleRequest($request)\n    {\n        foreach ($request as $fieldName => $value) {\n            $this->$fieldName->setValue($value);\n        }\n    }\n\n    public function addValidator(FormValidatorInterface $validator)\n    {\n        $this->validators[] = $validator;\n    }\n\n    /**\n     * @return bool\n     */\n    public function isValid()\n    {\n        $isValid = true;\n\n        foreach ($this->validators as $validator) {\n            if ($validator->isValid($this) !== true) {\n                $isValid = false;\n\n                $this->errors = array_merge(\n                    $this->errors,\n                    $validator->getErrors()\n                );\n            }\n        }\n\n        return $isValid;\n    }\n\n    /**\n     * @return array\n     */\n    public function getErrors()\n    {\n        return $this->errors;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Form/FormFactoryInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Form;\n\ninterface FormFactoryInterface\n{\n    /**\n     * @return FormInterface\n     */\n    public function getForm();\n}\n"
  },
  {
    "path": "source/Internal/Framework/Form/FormField.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Form;\n\nclass FormField implements FormFieldInterface\n{\n    /**\n     * @var string\n     */\n    private $name;\n\n    /**\n     * @var string\n     */\n    private $value;\n\n    /**\n     * @var string\n     */\n    private $label;\n\n    /**\n     * @var bool\n     */\n    private $isRequired;\n\n    /**\n     * @return string\n     */\n    public function getName()\n    {\n        return $this->name;\n    }\n\n    /**\n     * @param string $name\n     * @return FormField\n     */\n    public function setName($name)\n    {\n        $this->name = $name;\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getValue()\n    {\n        return $this->value;\n    }\n\n    /**\n     * @param string $value\n     * @return FormField\n     */\n    public function setValue($value)\n    {\n        $this->value = $value;\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getLabel()\n    {\n        return $this->label;\n    }\n\n    /**\n     * @param string $label\n     * @return FormField\n     */\n    public function setLabel($label)\n    {\n        $this->label = $label;\n        return $this;\n    }\n\n    /**\n     * @return bool\n     */\n    public function isRequired()\n    {\n        return $this->isRequired;\n    }\n\n    /**\n     * @param bool $isRequired\n     * @return FormField\n     */\n    public function setIsRequired($isRequired)\n    {\n        $this->isRequired = $isRequired;\n        return $this;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Form/FormFieldInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Form;\n\ninterface FormFieldInterface\n{\n    /**\n     * @return string\n     */\n    public function getName();\n\n    /**\n     * @param string $name\n     * @return FormFieldInterface\n     */\n    public function setName($name);\n\n    /**\n     * @return string\n     */\n    public function getValue();\n\n    /**\n     * @param string $value\n     * @return FormFieldInterface\n     */\n    public function setValue($value);\n\n    /**\n     * @return string\n     */\n    public function getLabel();\n\n    /**\n     * @param string $label\n     * @return FormFieldInterface\n     */\n    public function setLabel($label);\n    /**\n     * @return bool\n     */\n    public function isRequired();\n\n    /**\n     * @param bool $isRequired\n     * @return FormFieldInterface\n     */\n    public function setIsRequired($isRequired);\n}\n"
  },
  {
    "path": "source/Internal/Framework/Form/FormInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Form;\n\ninterface FormInterface\n{\n    /**\n     * @param FormFieldInterface $field\n     */\n    public function add(FormFieldInterface $field);\n\n    /**\n     * @return array\n     */\n    public function getFields();\n\n    /**\n     * @param array $request\n     */\n    public function handleRequest($request);\n\n    /**\n     * @return bool\n     */\n    public function isValid();\n\n    /**\n     * @return array\n     */\n    public function getErrors();\n}\n"
  },
  {
    "path": "source/Internal/Framework/Form/FormValidatorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Form;\n\ninterface FormValidatorInterface\n{\n    /**\n     * @param FormInterface $form\n     * @return bool\n     */\n    public function isValid(FormInterface $form);\n\n    /**\n     * @return array\n     */\n    public function getErrors();\n}\n"
  },
  {
    "path": "source/Internal/Framework/Form/RequiredFieldsValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Form;\n\nclass RequiredFieldsValidator implements FormValidatorInterface\n{\n    /**\n     * @var array\n     */\n    private $errors = [];\n\n    /**\n     * @param FormInterface $form\n     * @return bool\n     */\n    public function isValid(FormInterface $form)\n    {\n        $isValid = true;\n\n        foreach ($form->getFields() as $field) {\n            if ($field->isRequired() === true && !$field->getValue()) {\n                $this->addError();\n                $isValid = false;\n\n                break;\n            }\n        }\n\n        return $isValid;\n    }\n\n    /**\n     * @return array\n     */\n    public function getErrors()\n    {\n        return $this->errors;\n    }\n\n    /**\n     * Add error.\n     */\n    private function addError()\n    {\n        $this->errors[] = 'ERROR_MESSAGE_INPUT_NOTALLFIELDS';\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Form/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  common.form.required_fields_validator:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\RequiredFieldsValidator\n"
  },
  {
    "path": "source/Internal/Framework/FormConfiguration/FieldConfiguration.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration;\n\nclass FieldConfiguration implements FieldConfigurationInterface\n{\n    /**\n     * @var string\n     */\n    private $name;\n\n    /**\n     * @var string\n     */\n    private $label;\n\n    /**\n     * @var bool\n     */\n    private $isRequired;\n\n    /**\n     * @return string\n     */\n    public function getName()\n    {\n        return $this->name;\n    }\n\n    /**\n     * @param string $name\n     * @return FieldConfiguration\n     */\n    public function setName($name)\n    {\n        $this->name = $name;\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getLabel()\n    {\n        return $this->label;\n    }\n\n    /**\n     * @param string $label\n     * @return FieldConfiguration\n     */\n    public function setLabel($label)\n    {\n        $this->label = $label;\n        return $this;\n    }\n\n    /**\n     * @return bool\n     */\n    public function isRequired()\n    {\n        return $this->isRequired;\n    }\n\n    /**\n     * @param bool $isRequired\n     * @return FieldConfiguration\n     */\n    public function setIsRequired($isRequired)\n    {\n        $this->isRequired = $isRequired;\n        return $this;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/FormConfiguration/FieldConfigurationInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration;\n\ninterface FieldConfigurationInterface\n{\n    /**\n     * @return string\n     */\n    public function getName();\n\n    /**\n     * @param string $name\n     * @return FieldConfigurationInterface\n     */\n    public function setName($name);\n\n    /**\n     * @return string\n     */\n    public function getLabel();\n\n    /**\n     * @param string $label\n     * @return FieldConfigurationInterface\n     */\n    public function setLabel($label);\n\n    /**\n     * @return bool\n     */\n    public function isRequired();\n\n    /**\n     * @param bool $isRequired\n     * @return FieldConfigurationInterface\n     */\n    public function setIsRequired($isRequired);\n}\n"
  },
  {
    "path": "source/Internal/Framework/FormConfiguration/FormConfiguration.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration;\n\nclass FormConfiguration implements FormConfigurationInterface\n{\n    /**\n     * @var array\n     */\n    private $fieldConfigurations = [];\n\n    /**\n     * @param FieldConfigurationInterface $fieldConfiguration\n     * @return self\n     */\n    public function addFieldConfiguration(FieldConfigurationInterface $fieldConfiguration)\n    {\n        $this->fieldConfigurations[] = $fieldConfiguration;\n        return $this;\n    }\n\n    /**\n     * @return array\n     */\n    public function getFieldConfigurations()\n    {\n        return $this->fieldConfigurations;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/FormConfiguration/FormConfigurationFactoryInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration;\n\ninterface FormConfigurationFactoryInterface\n{\n    /**\n     * @return FormConfigurationInterface\n     */\n    public function getFormConfiguration();\n}\n"
  },
  {
    "path": "source/Internal/Framework/FormConfiguration/FormConfigurationInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration;\n\ninterface FormConfigurationInterface\n{\n    /**\n     * @param FieldConfigurationInterface $fieldConfiguration\n     * @return self\n     */\n    public function addFieldConfiguration(FieldConfigurationInterface $fieldConfiguration);\n\n    /**\n     * @return array\n     */\n    public function getFieldConfigurations();\n}\n"
  },
  {
    "path": "source/Internal/Framework/FormConfiguration/FormFieldsConfigurationDataProviderInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration;\n\ninterface FormFieldsConfigurationDataProviderInterface\n{\n    /**\n     * @return array\n     */\n    public function getFormFieldsConfiguration();\n}\n"
  },
  {
    "path": "source/Internal/Framework/Html/AllowAllHtmlSanitizer.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Html;\n\nreadonly class AllowAllHtmlSanitizer implements HtmlSanitizerInterface\n{\n    public function sanitize(string $html): string\n    {\n        return $html;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Html/HtmlSanitizer.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Html;\n\nuse Symfony\\Component\\HtmlSanitizer\\HtmlSanitizerInterface as SymfonyHtmlSanitizerInterface;\n\nreadonly class HtmlSanitizer implements HtmlSanitizerInterface\n{\n    public function __construct(private SymfonyHtmlSanitizerInterface $sanitizer)\n    {\n    }\n\n    public function sanitize(string $html): string\n    {\n        return $this->sanitizer->sanitize($html);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Html/HtmlSanitizerConfigFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Html;\n\nuse Symfony\\Component\\HtmlSanitizer\\HtmlSanitizerConfig;\n\nreadonly class HtmlSanitizerConfigFactory implements HtmlSanitizerConfigFactoryInterface\n{\n    public function create(): HtmlSanitizerConfig\n    {\n        return (new HtmlSanitizerConfig())\n            ->allowStaticElements()\n            ->allowRelativeMedias()\n            ->allowRelativeLinks()\n            ->allowMediaSchemes(['http', 'https']);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Html/HtmlSanitizerConfigFactoryInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Html;\n\nuse Symfony\\Component\\HtmlSanitizer\\HtmlSanitizerConfig;\n\ninterface HtmlSanitizerConfigFactoryInterface\n{\n    public function create(): HtmlSanitizerConfig;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Html/HtmlSanitizerFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Html;\n\nuse RuntimeException;\n\nreadonly class HtmlSanitizerFactory\n{\n    public function __construct(\n        private bool $enabled,\n        private HtmlSanitizerInterface $sanitizer,\n        private HtmlSanitizerInterface $allowAllSanitizer,\n    ) {\n    }\n\n    public function create(): HtmlSanitizerInterface\n    {\n        if ($this->enabled) {\n            return $this->sanitizer;\n        }\n\n        return $this->allowAllSanitizer;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Html/HtmlSanitizerInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Html;\n\ninterface HtmlSanitizerInterface\n{\n    public function sanitize(string $html): string;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Html/parameters.yaml",
    "content": "parameters:\n  oxid_esales.html_sanitizer_enabled: false"
  },
  {
    "path": "source/Internal/Framework/Html/services.yaml",
    "content": "imports:\n  - { resource: parameters.yaml }\n\nservices:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Html\\HtmlSanitizerConfigFactoryInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Html\\HtmlSanitizerConfigFactory\n\n  oxid_esales.html_sanitizer.config:\n    class: Symfony\\Component\\HtmlSanitizer\\HtmlSanitizerConfig\n    factory: ['@OxidEsales\\EshopCommunity\\Internal\\Framework\\Html\\HtmlSanitizerConfigFactoryInterface', 'create']\n\n  Symfony\\Component\\HtmlSanitizer\\HtmlSanitizerInterface:\n    class: Symfony\\Component\\HtmlSanitizer\\HtmlSanitizer\n    arguments:\n      - '@oxid_esales.html_sanitizer.config'\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Html\\HtmlSanitizer:\n    arguments:\n      - '@Symfony\\Component\\HtmlSanitizer\\HtmlSanitizerInterface'\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Html\\AllowAllHtmlSanitizer:\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Html\\HtmlSanitizerFactory:\n    arguments:\n      - '%oxid_esales.html_sanitizer_enabled%'\n      - '@OxidEsales\\EshopCommunity\\Internal\\Framework\\Html\\HtmlSanitizer'\n      - '@OxidEsales\\EshopCommunity\\Internal\\Framework\\Html\\AllowAllHtmlSanitizer'\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Html\\HtmlSanitizerInterface:\n    factory: [ '@OxidEsales\\EshopCommunity\\Internal\\Framework\\Html\\HtmlSanitizerFactory', 'create' ]\n    public: true"
  },
  {
    "path": "source/Internal/Framework/Logger/Factory/LoggerFactoryInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Logger\\Factory;\n\nuse Psr\\Log\\LoggerInterface;\n\ninterface LoggerFactoryInterface\n{\n    public function create(): LoggerInterface;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Logger/Factory/MonologLoggerFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Logger\\Factory;\n\nuse Monolog\\Formatter\\LineFormatter;\nuse Monolog\\Handler\\StreamHandler;\nuse Monolog\\Logger;\nuse Psr\\Log\\LoggerInterface;\n\nreadonly class MonologLoggerFactory implements LoggerFactoryInterface\n{\n    public function __construct(\n        private string $loggerName,\n        private string $logFilePath,\n        private string $logLevel,\n    ) {\n    }\n\n    public function create(): LoggerInterface\n    {\n        $handler = $this->getHandler();\n\n        $logger = new Logger($this->loggerName);\n        $logger->pushHandler($handler);\n\n        return $logger;\n    }\n\n    private function getHandler(): StreamHandler\n    {\n        $handler = new StreamHandler(\n            $this->logFilePath,\n            $this->logLevel\n        );\n\n        $formatter = new LineFormatter();\n        $formatter->includeStacktraces();\n        $handler->setFormatter($formatter);\n\n        return $handler;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Logger/LoggerServiceFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Logger;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Logger\\Factory\\MonologLoggerFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse Psr\\Log\\LoggerInterface;\n\nreadonly class LoggerServiceFactory\n{\n    public function __construct(private ContextInterface $context)\n    {\n    }\n\n    public function getLogger(): LoggerInterface\n    {\n        return (new MonologLoggerFactory(\n            'OXID Logger',\n            $this->context->getLogFilePath(),\n            $this->context->getLogLevel()\n        ))\n            ->create();\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Logger/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Logger\\Factory\\LoggerFactoryInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Logger\\Factory\\MonologLoggerFactory\n    arguments:\n      $loggerName: 'OXID Logger'\n      $logFilePath: '@=service(\"OxidEsales\\\\EshopCommunity\\\\Internal\\\\Transition\\\\Utility\\\\ContextInterface\").getLogFilePath()'\n      $logLevel: '@=service(\"OxidEsales\\\\EshopCommunity\\\\Internal\\\\Transition\\\\Utility\\\\ContextInterface\").getLogLevel()'\n\n  oxid_esales.monolog.logger:\n    class: Monolog\\Logger\n    factory: ['@OxidEsales\\EshopCommunity\\Internal\\Framework\\Logger\\Factory\\LoggerFactoryInterface', 'create']\n\n  Psr\\Log\\LoggerInterface:\n    alias: 'oxid_esales.monolog.logger'\n    public: true\n\n"
  },
  {
    "path": "source/Internal/Framework/Mailing/Factory/TransportFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Mailing\\Factory;\n\nuse Symfony\\Component\\Mailer\\Transport\\TransportInterface;\nuse Symfony\\Component\\Mailer\\Transport;\n\nreadonly class TransportFactory implements TransportFactoryInterface\n{\n    public function __construct(private string $dsn)\n    {\n    }\n\n    public function create(): TransportInterface\n    {\n        return Transport::fromDsn($this->dsn);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Mailing/Factory/TransportFactoryInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Mailing\\Factory;\n\nuse Symfony\\Component\\Mailer\\Transport\\TransportInterface;\n\ninterface TransportFactoryInterface\n{\n    public function create(): TransportInterface;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Mailing/parameters.yaml",
    "content": "parameters:\n  oxid_esales.mailing.native_transport_dsn: 'native://default'\n  oxid_esales.mailing.use_symfony_mailer: '%env(default::bool:OXID_MAILING_SYMFONY_MAILER)%'\n  oxid_esales.mailing.dsn: '%env(default:oxid_esales.mailing.native_transport_dsn:OXID_MAILING_DSN)%'\n"
  },
  {
    "path": "source/Internal/Framework/Mailing/services.yaml",
    "content": "imports:\n  - { resource: parameters.yaml }\n\nservices:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Mailing\\Factory\\TransportFactoryInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Mailing\\Factory\\TransportFactory\n    arguments:\n      $dsn: '%oxid_esales.mailing.dsn%'\n\n  Symfony\\Component\\Mailer\\Transport\\TransportInterface:\n    factory: ['@OxidEsales\\EshopCommunity\\Internal\\Framework\\Mailing\\Factory\\TransportFactoryInterface', 'create']\n\n  Symfony\\Component\\Mailer\\MailerInterface:\n    class: Symfony\\Component\\Mailer\\Mailer\n    arguments:\n      $transport: '@Symfony\\Component\\Mailer\\Transport\\TransportInterface'\n      $bus: null\n      $dispatcher: '@Symfony\\Component\\EventDispatcher\\EventDispatcherInterface'\n    public: true\n"
  },
  {
    "path": "source/Internal/Framework/Migration/MigrationExecutor.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Migration;\n\nuse OxidEsales\\DoctrineMigrationWrapper\\Migrations;\nuse OxidEsales\\DoctrineMigrationWrapper\\MigrationsBuilder;\n\nclass MigrationExecutor implements MigrationExecutorInterface\n{\n    public function execute(): void\n    {\n        (new MigrationsBuilder())\n            ->build()\n            ->execute(Migrations::MIGRATE_COMMAND);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Migration/MigrationExecutorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Migration;\n\ninterface MigrationExecutorInterface\n{\n    public function execute(): void;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Migration/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Migration\\MigrationExecutorInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Migration\\MigrationExecutor\n"
  },
  {
    "path": "source/Internal/Framework/Module/Cache/CacheNotFoundException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Cache;\n\nclass CacheNotFoundException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Cache/InvalidateModuleCacheEventSubscriber.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Cache;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\ShopCacheCleanerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Event\\ModuleConfigurationChangedEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Event\\FinalizingModuleActivationEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Event\\FinalizingModuleDeactivationEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Event\\ModuleSetupEvent;\nuse Symfony\\Component\\EventDispatcher\\EventSubscriberInterface;\n\nclass InvalidateModuleCacheEventSubscriber implements EventSubscriberInterface\n{\n    public function __construct(private readonly ShopCacheCleanerInterface $shopCacheCleaner)\n    {\n    }\n\n    public function invalidateModuleCache(ModuleSetupEvent|ModuleConfigurationChangedEvent $event): void\n    {\n        $this->shopCacheCleaner->clear($event->getShopId());\n    }\n\n    public static function getSubscribedEvents(): array\n    {\n        return [\n            FinalizingModuleActivationEvent::class   => 'invalidateModuleCache',\n            FinalizingModuleDeactivationEvent::class => 'invalidateModuleCache',\n            ModuleConfigurationChangedEvent::class   => 'invalidateModuleCache',\n        ];\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Cache/ModuleCache.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Cache;\n\nuse Psr\\Cache\\CacheItemPoolInterface;\n\nclass ModuleCache implements ModuleCacheInterface\n{\n    public function __construct(private readonly CacheItemPoolInterface $cache)\n    {\n    }\n\n    public function deleteItem(string $key): void\n    {\n        $this->cache->deleteItem($key);\n    }\n\n    public function put(string $key, array $data): void\n    {\n        $cacheItem = $this->cache->getItem($key);\n        $cacheItem->set($data);\n        $this->cache->save($cacheItem);\n    }\n\n    /**\n     * @throws CacheNotFoundException\n     */\n    public function get(string $key): array\n    {\n        $cacheItem = $this->cache->getItem($key);\n\n        if (!$cacheItem->isHit()) {\n            throw new CacheNotFoundException(\"Cache with key '$key' not found.\");\n        }\n\n        return $cacheItem->get();\n    }\n\n    public function exists(string $key): bool\n    {\n        return $this->cache->getItem($key)->isHit();\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Cache/ModuleCacheInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Cache;\n\ninterface ModuleCacheInterface\n{\n    public function deleteItem(string $key): void;\n\n    public function put(string $key, array $data): void;\n\n    public function get(string $key): array;\n\n    public function exists(string $key): bool;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Cache/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Cache\\ModuleCacheInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Cache\\ModuleCache\n    public: true\n\n  oxid_esales.module.cache.invalidate_module_cache_event_subscriber:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Cache\\InvalidateModuleCacheEventSubscriber\n    tags:\n      - { name: kernel.event_subscriber }"
  },
  {
    "path": "source/Internal/Framework/Module/Command/InstallModuleAssetsCommand.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Command;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleFilesInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse Symfony\\Component\\Console\\Style\\SymfonyStyle;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass InstallModuleAssetsCommand extends Command\n{\n    public function __construct(\n        private ShopConfigurationDaoInterface $shopConfigurationDao,\n        private BasicContextInterface $context,\n        private ModuleFilesInstallerInterface $moduleFilesInstaller\n    ) {\n        parent::__construct();\n    }\n\n\n    protected function configure()\n    {\n        $this->setDescription(\n            'Install assets for all modules (symlink or copy to the shop out directory depending on the platform).'\n        );\n    }\n\n    protected function execute(InputInterface $input, OutputInterface $output): int\n    {\n        $shopConfiguration = $this->shopConfigurationDao->get($this->context->getDefaultShopId());\n\n        foreach ($shopConfiguration->getModuleConfigurations() as $moduleConfiguration) {\n            $this->installModuleAsserts($moduleConfiguration);\n        }\n\n        $style = new SymfonyStyle($input, $output);\n        $style->success('Module assets have been installed.');\n\n        return Command::SUCCESS;\n    }\n\n    private function installModuleAsserts(ModuleConfiguration $moduleConfiguration): void\n    {\n        $this->moduleFilesInstaller->install(\n            new OxidEshopPackage(\n                Path::join($this->context->getShopRootPath(), $moduleConfiguration->getModuleSource())\n            )\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Command/ModuleActivateCommand.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Command;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ModuleActivationServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\n/**\n * Command activates module by module id.\n */\nclass ModuleActivateCommand extends Command\n{\n    public const MESSAGE_MODULE_ACTIVATED = 'Module - \"%s\" was activated.';\n    public const MESSAGE_MODULE_NOT_FOUND = 'Module - \"%s\" not found.';\n\n    public function __construct(\n        private ModuleConfigurationDaoInterface $moduleConfigurationDao,\n        private ContextInterface $context,\n        private ModuleActivationServiceInterface $moduleActivationService\n    ) {\n        parent::__construct(null);\n    }\n\n    /**\n     * @inheritdoc\n     */\n    protected function configure()\n    {\n        $this->setDescription('Activates a module.')\n            ->addArgument('module-id', InputArgument::REQUIRED, 'Module ID')\n            ->setHelp('Command activates module by defined module ID.');\n    }\n\n    /**\n     * @param InputInterface $input\n     * @param OutputInterface $output\n     */\n    protected function execute(InputInterface $input, OutputInterface $output): int\n    {\n        $moduleId = $input->getArgument('module-id');\n\n        if ($this->isInstalled($moduleId)) {\n            $this->activateModule($output, $moduleId);\n        } else {\n            $output->writeLn('<error>' . sprintf(static::MESSAGE_MODULE_NOT_FOUND, $moduleId) . '</error>');\n        }\n\n        return 0;\n    }\n\n    /**\n     * @param OutputInterface $output\n     * @param string          $moduleId\n     */\n    protected function activateModule(OutputInterface $output, string $moduleId)\n    {\n        $this->moduleActivationService->activate($moduleId, $this->context->getCurrentShopId());\n        $output->writeLn('<info>' . sprintf(static::MESSAGE_MODULE_ACTIVATED, $moduleId) . '</info>');\n    }\n\n    /**\n     * @param string $moduleId\n     * @return bool\n     */\n    private function isInstalled(string $moduleId): bool\n    {\n        return $this->moduleConfigurationDao->exists($moduleId, $this->context->getCurrentShopId());\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Command/ModuleDeactivateCommand.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Command;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ModuleActivationServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\nclass ModuleDeactivateCommand extends Command\n{\n    public const MESSAGE_MODULE_DEACTIVATED = 'Module - \"%s\" has been deactivated.';\n    public const MESSAGE_MODULE_NOT_FOUND = 'Module - \"%s\" not found.';\n    private const ARGUMENT_MODULE_ID = 'module-id';\n\n    public function __construct(\n        private ModuleConfigurationDaoInterface $moduleConfigurationDao,\n        private ContextInterface $context,\n        private ModuleActivationServiceInterface $moduleActivationService\n    ) {\n        parent::__construct(null);\n    }\n\n    /**\n     * @inheritdoc\n     */\n    protected function configure()\n    {\n        $this->setDescription('Deactivates a module.')\n            ->addArgument(static::ARGUMENT_MODULE_ID, InputArgument::REQUIRED, 'Module ID')\n            ->setHelp('Command deactivates module by defined module ID.');\n    }\n\n    /**\n     * @param InputInterface  $input\n     * @param OutputInterface $output\n     */\n    protected function execute(InputInterface $input, OutputInterface $output): int\n    {\n        $moduleId = $input->getArgument('module-id');\n\n        if ($this->isInstalled($moduleId)) {\n            $this->deactivateModule($output, $moduleId);\n        } else {\n            $output->writeLn('<error>' . sprintf(static::MESSAGE_MODULE_NOT_FOUND, $moduleId) . '</error>');\n        }\n\n        return 0;\n    }\n\n    /**\n     * @param OutputInterface $output\n     * @param string          $moduleId\n     */\n    protected function deactivateModule(OutputInterface $output, string $moduleId)\n    {\n        $this->moduleActivationService->deactivate($moduleId, $this->context->getCurrentShopId());\n        $output->writeLn(\n            '<info>' . sprintf(static::MESSAGE_MODULE_DEACTIVATED, $moduleId) . '</info>'\n        );\n    }\n\n    /**\n     * @param string $moduleId\n     * @return bool\n     */\n    private function isInstalled(string $moduleId): bool\n    {\n        return $this->moduleConfigurationDao->exists($moduleId, $this->context->getCurrentShopId());\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Command/ModuleInstallCommand.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Command;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse Symfony\\Component\\Console\\Style\\SymfonyStyle;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass ModuleInstallCommand extends Command\n{\n    private const SUCCESS_MESSAGE = 'Module installed successfully';\n    private const ERROR_MESSAGE = 'Error installing module: ';\n\n    public function __construct(\n        private ModuleInstallerInterface $moduleInstaller\n    ) {\n        parent::__construct();\n    }\n\n    /** @inheritdoc */\n    protected function configure(): void\n    {\n        $this->setDescription('Install module assets and configuration')\n            ->addArgument(\n                'module-path',\n                InputArgument::REQUIRED,\n                'Absolute or relative path to module files'\n            );\n    }\n\n    /**\n     * @param InputInterface $input\n     * @param OutputInterface $output\n     * @return int\n     * @throws \\Throwable\n     */\n    protected function execute(InputInterface $input, OutputInterface $output): int\n    {\n        $style = new SymfonyStyle($input, $output);\n        try {\n            $modulePath = $this->getModulePath($input->getArgument('module-path'));\n            $this->moduleInstaller->install($this->getPackage($modulePath));\n            $style->success(self::SUCCESS_MESSAGE);\n            return Command::SUCCESS;\n        } catch (\\InvalidArgumentException $exception) {\n            $style->error(self::ERROR_MESSAGE . $exception->getMessage());\n        } catch (\\Throwable $throwable) {\n            $style->error(self::ERROR_MESSAGE . $throwable->getMessage());\n            $style->text($throwable->getTraceAsString());\n        }\n        return Command::FAILURE;\n    }\n\n    /**\n     * @param string $path\n     * @return string\n     */\n    private function getModulePath(string $path): string\n    {\n        return Path::isRelative($path) ? Path::makeAbsolute($path, getcwd()) : $path;\n    }\n\n    /**\n     * @param string $modulePath\n     * @return OxidEshopPackage\n     */\n    private function getPackage(string $modulePath): OxidEshopPackage\n    {\n        return new OxidEshopPackage($modulePath);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Command/ModuleUninstallCommand.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Command;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\ModuleConfigurationNotFoundException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModulePathResolverInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse Symfony\\Component\\Console\\Style\\SymfonyStyle;\n\nclass ModuleUninstallCommand extends Command\n{\n    private const SUCCESS_MESSAGE = 'Module uninstalled successfully';\n    private const ERROR_MESSAGE = 'Error uninstalling module: ';\n\n    public function __construct(\n        private ModuleInstallerInterface $moduleInstaller,\n        private ModulePathResolverInterface $modulePathResolver,\n        private ContextInterface $context\n    ) {\n        parent::__construct();\n    }\n\n    /** @inheritdoc */\n    protected function configure(): void\n    {\n        $this->setDescription('Uninstall module assets and configuration')\n            ->addArgument(\n                'module-id',\n                InputArgument::REQUIRED,\n                'Module ID (see metadata.php)'\n            );\n    }\n\n    /**\n     * @param InputInterface $input\n     * @param OutputInterface $output\n     * @return int\n     * @throws \\Throwable\n     */\n    protected function execute(InputInterface $input, OutputInterface $output): int\n    {\n        $style = new SymfonyStyle($input, $output);\n        try {\n            $modulePath = $this->getModulePath($input->getArgument('module-id'));\n            $this->moduleInstaller->uninstall($this->getPackage($modulePath));\n            $style->success(self::SUCCESS_MESSAGE);\n            return Command::SUCCESS;\n        } catch (ModuleConfigurationNotFoundException $exception) {\n            $style->error(self::ERROR_MESSAGE . $exception->getMessage());\n        } catch (\\Throwable $throwable) {\n            $style->error(self::ERROR_MESSAGE . $throwable->getMessage());\n            $style->text($throwable->getTraceAsString());\n        }\n        return Command::FAILURE;\n    }\n\n    private function getModulePath(string $moduleId): string\n    {\n        return $this->modulePathResolver\n            ->getFullModulePathFromConfiguration($moduleId, $this->context->getDefaultShopId());\n    }\n\n    /**\n     * @param string $modulePath\n     * @return OxidEshopPackage\n     */\n    private function getPackage(string $modulePath): OxidEshopPackage\n    {\n        return new OxidEshopPackage($modulePath);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Command/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  oxid_esales.command.module_activate_command:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Command\\ModuleActivateCommand\n    tags:\n      - { name: 'console.command', command: 'oe:module:activate' }\n  oxid_esales.command.module_deactivate_command:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Command\\ModuleDeactivateCommand\n    tags:\n      - { name: 'console.command', command: 'oe:module:deactivate' }\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Command\\ModuleInstallCommand:\n    tags:\n      - { name: 'console.command', command: 'oe:module:install' }\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Command\\ModuleUninstallCommand:\n    tags:\n      - { name: 'console.command', command: 'oe:module:uninstall' }\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Command\\InstallModuleAssetsCommand:\n    tags:\n      - { name: 'console.command', command: 'oe:module:install-assets' }\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Bridge/ModuleConfigurationDaoBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleEnvironmentConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\n\nclass ModuleConfigurationDaoBridge implements ModuleConfigurationDaoBridgeInterface\n{\n    public function __construct(\n        private ContextInterface $context,\n        private ModuleConfigurationDaoInterface $moduleConfigurationDao,\n        private ModuleEnvironmentConfigurationDaoInterface $moduleEnvironmentConfigurationDao\n    ) {\n    }\n\n    /**\n     * @param string $moduleId\n     * @return ModuleConfiguration\n     */\n    public function get(string $moduleId): ModuleConfiguration\n    {\n        return $this->moduleConfigurationDao->get($moduleId, $this->context->getCurrentShopId());\n    }\n\n    /**\n     * @param ModuleConfiguration $moduleConfiguration\n     */\n    public function save(ModuleConfiguration $moduleConfiguration)\n    {\n        $this->moduleConfigurationDao->save($moduleConfiguration, $this->context->getCurrentShopId());\n        $this->moduleEnvironmentConfigurationDao->remove(\n            $moduleConfiguration->getId(),\n            $this->context->getCurrentShopId()\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Bridge/ModuleConfigurationDaoBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\ninterface ModuleConfigurationDaoBridgeInterface\n{\n    /**\n     * @param string $moduleId\n     * @return ModuleConfiguration\n     */\n    public function get(string $moduleId): ModuleConfiguration;\n\n    /**\n     * @param ModuleConfiguration $moduleConfiguration\n     */\n    public function save(ModuleConfiguration $moduleConfiguration);\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Bridge/ModuleConfigurationDataMapperBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\{\n    ModuleConfigurationExportDataMapperInterface\n};\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\n\nreadonly class ModuleConfigurationDataMapperBridge implements ModuleConfigurationDataMapperBridgeInterface\n{\n    public function __construct(private ModuleConfigurationExportDataMapperInterface $moduleConfigurationDataMapper)\n    {\n    }\n\n    public function toData(ModuleConfiguration $configuration): array\n    {\n        return $this->moduleConfigurationDataMapper->toData($configuration);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Bridge/ModuleConfigurationDataMapperBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\n\ninterface ModuleConfigurationDataMapperBridgeInterface\n{\n    public function toData(ModuleConfiguration $configuration): array;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Bridge/ModuleSettingBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setting\\Event\\SettingChangedEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\n\n/**\n * @deprecated use OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ModuleSettingServiceInterface\n */\nclass ModuleSettingBridge implements ModuleSettingBridgeInterface\n{\n    public function __construct(\n        private ContextInterface $context,\n        private ModuleConfigurationDaoInterface $moduleConfigurationDao,\n        private EventDispatcherInterface $eventDispatcher\n    ) {\n    }\n\n    /**\n     * @param string $name\n     * @param mixed $value\n     * @param string $moduleId\n     */\n    public function save(string $name, $value, string $moduleId): void\n    {\n        $moduleConfiguration = $this->moduleConfigurationDao->get($moduleId, $this->context->getCurrentShopId());\n        $setting = $moduleConfiguration->getModuleSetting($name);\n        $setting->setValue($value);\n        $this->moduleConfigurationDao->save($moduleConfiguration, $this->context->getCurrentShopId());\n\n        $this->eventDispatcher->dispatch(new SettingChangedEvent($name, $this->context->getCurrentShopId(), $moduleId));\n    }\n\n    /**\n     * @param string $name\n     * @param string $moduleId\n     * @return mixed\n     */\n    public function get(string $name, string $moduleId)\n    {\n        $configuration = $this->moduleConfigurationDao->get($moduleId, $this->context->getCurrentShopId());\n        return $configuration->getModuleSetting($name)->getValue();\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Bridge/ModuleSettingBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge;\n\n/**\n * @deprecated use OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ModuleSettingServiceInterface\n */\ninterface ModuleSettingBridgeInterface\n{\n    /**\n     * @param string $name\n     * @param bool|int|string|array $value\n     * @param string $moduleId\n     */\n    public function save(string $name, $value, string $moduleId): void;\n\n    public function get(string $name, string $moduleId);\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Bridge/ShopConfigurationDaoBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\n\nclass ShopConfigurationDaoBridge implements ShopConfigurationDaoBridgeInterface\n{\n    public function __construct(\n        private ContextInterface $context,\n        private ShopConfigurationDaoInterface $shopConfigurationDao\n    ) {\n    }\n\n    public function get(): ShopConfiguration\n    {\n        return $this->shopConfigurationDao->get(\n            $this->context->getCurrentShopId()\n        );\n    }\n\n    public function save(ShopConfiguration $shopConfiguration)\n    {\n        $this->shopConfigurationDao->save(\n            $shopConfiguration,\n            $this->context->getCurrentShopId()\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Bridge/ShopConfigurationDaoBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\ninterface ShopConfigurationDaoBridgeInterface\n{\n    public function get(): ShopConfiguration;\n\n    /**\n     * @deprecated use ModuleConfigurationDaoInterface::save() and ClassExtensionsChainDaoInterface::saveChain() instead\n     */\n    public function save(ShopConfiguration $shopConfiguration);\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Cache/ClassPropertyModuleConfigurationCache.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Cache;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\n\nclass ClassPropertyModuleConfigurationCache implements ModuleConfigurationCacheInterface\n{\n    /**\n     * @var ModuleConfiguration[][]\n     */\n    private array $cache = [];\n\n    public function put(int $shopId, ModuleConfiguration $configuration): void\n    {\n        $this->cache[$shopId][$configuration->getId()] = $configuration;\n    }\n\n    public function get(string $moduleId, int $shopId): ModuleConfiguration\n    {\n        return $this->cache[$shopId][$moduleId];\n    }\n\n    public function exists(string $moduleId, int $shopId): bool\n    {\n        return isset($this->cache[$shopId][$moduleId]);\n    }\n\n    public function evict(string $moduleId, int $shopId): void\n    {\n        if ($this->exists($moduleId, $shopId)) {\n            unset($this->cache[$shopId][$moduleId]);\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Cache/ModuleConfigurationCacheInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Cache;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\n\ninterface ModuleConfigurationCacheInterface\n{\n    public function put(int $shopId, ModuleConfiguration $configuration): void;\n\n    public function get(string $moduleId, int $shopId): ModuleConfiguration;\n\n    public function exists(string $moduleId, int $shopId): bool;\n\n    public function evict(string $moduleId, int $shopId): void;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Dao/Chain/ClassExtensionsChainDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\Chain;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ClassExtensionsChain;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Event\\ModuleClassExtensionChainChangedEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage\\ArrayStorageInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage\\FileStorageFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass ClassExtensionsChainDao implements ClassExtensionsChainDaoInterface\n{\n    public function __construct(\n        private BasicContextInterface $context,\n        private FileStorageFactoryInterface $fileStorageFactory,\n        private EventDispatcherInterface $eventDispatcher\n    ) {\n    }\n\n    public function getChain(int $shopId): ClassExtensionsChain\n    {\n        return new ClassExtensionsChain(\n            $this->storageExists($shopId) ? $this->getStorage($shopId)->get() : []\n        );\n    }\n\n    public function saveChain(int $shopId, ClassExtensionsChain $chain): void\n    {\n        $this->getStorage($shopId)->save($chain->getChain());\n\n        $this->eventDispatcher->dispatch(new ModuleClassExtensionChainChangedEvent());\n    }\n\n    private function storageExists(int $shopId): bool\n    {\n        return file_exists($this->getStorageFilePath($shopId));\n    }\n\n    private function getStorage(int $shopId): ArrayStorageInterface\n    {\n        return $this->fileStorageFactory->create($this->getStorageFilePath($shopId));\n    }\n\n    private function getStorageFilePath(int $shopId): string\n    {\n        return Path::join($this->context->getShopConfigurationDirectory($shopId), 'class_extension_chain.yaml');\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Dao/Chain/ClassExtensionsChainDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\Chain;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ClassExtensionsChain;\n\ninterface ClassExtensionsChainDaoInterface\n{\n    public function getChain(int $shopId): ClassExtensionsChain;\n    public function saveChain(int $shopId, ClassExtensionsChain $chain): void;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Dao/Chain/TemplateExtensionChainDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\Chain;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleTemplateExtensionChain;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage\\ArrayStorageInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage\\FileStorageFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass TemplateExtensionChainDao implements TemplateExtensionChainDaoInterface\n{\n    public function __construct(\n        private BasicContextInterface $context,\n        private FileStorageFactoryInterface $fileStorageFactory,\n    ) {\n    }\n\n    public function getChain(int $shopId): ModuleTemplateExtensionChain\n    {\n        return new ModuleTemplateExtensionChain(\n            $this->storageExists($shopId) ? $this->getStorage($shopId)->get() : []\n        );\n    }\n\n    private function storageExists(int $shopId): bool\n    {\n        return file_exists($this->getStorageFilePath($shopId));\n    }\n\n    private function getStorage(int $shopId): ArrayStorageInterface\n    {\n        return $this->fileStorageFactory->create($this->getStorageFilePath($shopId));\n    }\n\n    private function getStorageFilePath(int $shopId): string\n    {\n        return Path::join($this->context->getShopConfigurationDirectory($shopId), 'template_extension_chain.yaml');\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Dao/Chain/TemplateExtensionChainDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\Chain;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleTemplateExtensionChain;\n\ninterface TemplateExtensionChainDaoInterface\n{\n    public function getChain(int $shopId): ModuleTemplateExtensionChain;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Dao/ModuleConfigurationDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Cache\\ModuleConfigurationCacheInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfigurationDataMapperInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Event\\ModuleConfigurationChangedEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\ModuleConfigurationNotFoundException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage\\ArrayStorageInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage\\FileStorageFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException;\nuse Symfony\\Component\\Config\\Definition\\NodeInterface;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass ModuleConfigurationDao implements ModuleConfigurationDaoInterface\n{\n    public function __construct(\n        private BasicContextInterface $context,\n        private ModuleConfigurationDataMapperInterface $moduleConfigurationDataMapper,\n        private FileStorageFactoryInterface $fileStorageFactory,\n        private ModuleConfigurationCacheInterface $cache,\n        private ModuleConfigurationExtenderInterface $moduleConfigurationExtender,\n        private NodeInterface $node,\n        private Filesystem $filesystem,\n        private EventDispatcherInterface $eventDispatcher\n    ) {\n    }\n\n    /**\n     * @param string $moduleId\n     * @param int    $shopId\n     *\n     * @return ModuleConfiguration\n     * @throws ModuleConfigurationNotFoundException\n     */\n    public function get(string $moduleId, int $shopId): ModuleConfiguration\n    {\n        if (!$this->cache->exists($moduleId, $shopId)) {\n            if (!file_exists($this->getModuleConfigurationFilePath($shopId, $moduleId))) {\n                throw new ModuleConfigurationNotFoundException('There is no module configuration with id ' . $moduleId);\n            }\n\n            $moduleConfiguration = $this->moduleConfigurationDataMapper\n                ->fromData(new ModuleConfiguration(), $this->getNormalizedData($shopId, $moduleId));\n            $moduleConfiguration = $this->moduleConfigurationExtender->extend($moduleConfiguration, $shopId);\n\n            $this->cache->put($shopId, $moduleConfiguration);\n        }\n\n        return $this->cache->get($moduleId, $shopId);\n    }\n\n    /**\n     * @param ModuleConfiguration $moduleConfiguration\n     * @param int                 $shopId\n     */\n    public function save(ModuleConfiguration $moduleConfiguration, int $shopId): void\n    {\n        $this->cache->evict($moduleConfiguration->getId(), $shopId);\n\n        $this->getStorage($shopId, $moduleConfiguration->getId())->save(\n            $this->moduleConfigurationDataMapper->toData($moduleConfiguration)\n        );\n\n        $this->eventDispatcher->dispatch(new ModuleConfigurationChangedEvent($moduleConfiguration, $shopId));\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function getAll(int $shopId): array\n    {\n        $moduleConfigurations = [];\n\n        foreach ($this->getModuleIds($shopId) as $id) {\n            $moduleConfigurations[$id] = $this->get($id, $shopId);\n        }\n\n        return $moduleConfigurations;\n    }\n\n    /**\n     * @deprecated will be completely removed\n     */\n    public function deleteAll(int $shopId): void\n    {\n        $this->filesystem->remove($this->getModulesConfigurationDirectory($shopId));\n    }\n\n    public function delete(string $moduleId, int $shopId): void\n    {\n        $this->filesystem->remove($this->getModuleConfigurationFilePath($shopId, $moduleId));\n    }\n\n    public function exists(string $moduleId, int $shopId): bool\n    {\n        return in_array($moduleId, $this->getModuleIds($shopId), true);\n    }\n\n    private function getStorage(int $shopId, string $moduleId): ArrayStorageInterface\n    {\n        return $this->fileStorageFactory->create(\n            $this->getModuleConfigurationFilePath($shopId, $moduleId)\n        );\n    }\n\n    private function getModulesConfigurationDirectory(int $shopId): string\n    {\n        return Path::join($this->context->getShopConfigurationDirectory($shopId), 'modules');\n    }\n\n    private function getModuleIds(int $shopId): array\n    {\n        $moduleIds = [];\n\n        if (file_exists($this->getModulesConfigurationDirectory($shopId))) {\n            $dir = new \\DirectoryIterator($this->getModulesConfigurationDirectory($shopId));\n\n            foreach ($dir as $fileInfo) {\n                if ($fileInfo->isFile()) {\n                    $moduleIds[] = $fileInfo->getBasename('.' . $fileInfo->getExtension());\n                }\n            }\n        }\n\n        sort($moduleIds);\n\n        return $moduleIds;\n    }\n\n    private function getModuleConfigurationFilePath(int $shopId, string $moduleId): string\n    {\n        return Path::join($this->getModulesConfigurationDirectory($shopId), $moduleId . '.yaml');\n    }\n\n    private function getNormalizedData(int $shopId, string $moduleId): mixed\n    {\n        try {\n            $data = $this->node->normalize($this->getStorage($shopId, $moduleId)->get());\n        } catch (InvalidConfigurationException $exception) {\n            throw new InvalidConfigurationException(\n                'File '\n                        . $this->getModuleConfigurationFilePath($shopId, $moduleId)\n                        . ' is broken: '\n                        . $exception->getMessage(),\n                $exception->getCode(),\n                $exception\n            );\n        }\n        return $data;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Dao/ModuleConfigurationDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\n\ninterface ModuleConfigurationDaoInterface\n{\n    public function get(string $moduleId, int $shopId): ModuleConfiguration;\n\n    public function save(ModuleConfiguration $moduleConfiguration, int $shopId);\n\n    public function delete(string $moduleId, int $shopId): void;\n\n    /**\n     * @param int $shopId\n     * @return ModuleConfiguration[]\n     */\n    public function getAll(int $shopId): array;\n\n    /**\n     * @deprecated will be completely removed\n     */\n    public function deleteAll(int $shopId): void;\n\n    public function exists(string $moduleId, int $shopId): bool;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Dao/ModuleConfigurationExtenderInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\n\ninterface ModuleConfigurationExtenderInterface\n{\n    public function extend(ModuleConfiguration $moduleConfiguration, int $shopId): ModuleConfiguration;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Dao/ModuleDependencyDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleDependencies;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModulePathResolverInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage\\ArrayStorageInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage\\FileStorageFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass ModuleDependencyDao implements ModuleDependencyDaoInterface\n{\n    public function __construct(\n        private readonly FileStorageFactoryInterface $fileStorageFactory,\n        private readonly ModulePathResolverInterface $modulePathResolver,\n        private readonly ContextInterface $context\n    ) {\n    }\n\n    public function get(string $moduleId): ModuleDependencies\n    {\n        return new ModuleDependencies(\n            $this->storageExists($moduleId) ? $this->getStorage($moduleId)->get() : []\n        );\n    }\n\n    private function storageExists(string $moduleId): bool\n    {\n        return file_exists($this->getStorageFilePath($moduleId));\n    }\n\n    private function getStorage(string $moduleId): ArrayStorageInterface\n    {\n        return $this->fileStorageFactory->create($this->getStorageFilePath($moduleId));\n    }\n\n    private function getStorageFilePath(string $moduleId): string\n    {\n        $modulePath = $this->modulePathResolver->getFullModulePathFromConfiguration(\n            $moduleId,\n            $this->context->getCurrentShopId()\n        );\n\n        return Path::join($modulePath, 'dependencies.yaml');\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Dao/ModuleDependencyDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleDependencies;\n\ninterface ModuleDependencyDaoInterface\n{\n    public function get(string $moduleId): ModuleDependencies;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Dao/ModuleEnvironmentConfigurationDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Env\\EnvUrlFormatter;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage\\FileStorageFactoryInterface;\nuse Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException;\nuse Symfony\\Component\\Config\\Definition\\NodeInterface;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass ModuleEnvironmentConfigurationDao implements ModuleEnvironmentConfigurationDaoInterface\n{\n    public function __construct(\n        private FileStorageFactoryInterface $fileStorageFactory,\n        private Filesystem $fileSystem,\n        private NodeInterface $node,\n        private BasicContextInterface $context\n    ) {\n    }\n\n    public function get(string $moduleId, int $shopId): array\n    {\n        $data = [];\n\n        $configurationFilePath = $this->getEnvironmentConfigurationFilePath($moduleId, $shopId);\n\n        if ($this->fileSystem->exists($configurationFilePath)) {\n            $storage = $this->fileStorageFactory->create(\n                $this->getEnvironmentConfigurationFilePath($moduleId, $shopId)\n            );\n\n            try {\n                $data = $this->node->normalize($storage->get());\n            } catch (InvalidConfigurationException $exception) {\n                throw new InvalidConfigurationException(\n                    'File ' .\n                    $this->getEnvironmentConfigurationFilePath($moduleId, $shopId) .\n                    ' is broken: ' . $exception->getMessage()\n                );\n            }\n        }\n\n        return $data;\n    }\n\n    public function remove(string $moduleId, int $shopId): void\n    {\n        $path = $this->getEnvironmentConfigurationFilePath($moduleId, $shopId);\n\n        if ($this->fileSystem->exists($path)) {\n            $this->fileSystem->rename($path, $path . '.bak', true);\n        }\n    }\n\n    private function getEnvironmentConfigurationFilePath(string $moduleId, int $shopId): string\n    {\n        return Path::join(\n            EnvUrlFormatter::toEnvUrl($this->context->getProjectConfigurationDirectory()),\n            'shops',\n            (string)$shopId,\n            'modules',\n            $moduleId . '.yaml'\n        );\n    }\n}"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Dao/ModuleEnvironmentConfigurationDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao;\n\ninterface ModuleEnvironmentConfigurationDaoInterface\n{\n    public function get(string $moduleId, int $shopId): array;\n    public function remove(string $moduleId, int $shopId): void;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Dao/ModuleEnvironmentConfigurationExtender.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setting\\Setting;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\n\nclass ModuleEnvironmentConfigurationExtender implements ModuleConfigurationExtenderInterface\n{\n    public function __construct(\n        private ModuleEnvironmentConfigurationDaoInterface $moduleEnvironmentConfigurationDao,\n        private EventDispatcherInterface $eventDispatcher\n    ) {\n    }\n\n    public function extend(ModuleConfiguration $moduleConfiguration, int $shopId): ModuleConfiguration\n    {\n        $environmentData = $this->moduleEnvironmentConfigurationDao->get($moduleConfiguration->getId(), $shopId);\n\n        if (isset($environmentData['moduleSettings'])) {\n            foreach ($environmentData['moduleSettings'] as $settingId => $environmentSetting) {\n                if (!$moduleConfiguration->hasModuleSetting($settingId)) {\n                    $this->processOrphanSetting($shopId, $moduleConfiguration->getId(), $settingId);\n                    continue;\n                }\n                $this->mergeEnvironmentSetting($moduleConfiguration->getModuleSetting($settingId), $environmentSetting);\n            }\n        }\n\n        return $moduleConfiguration;\n    }\n\n    public function mergeEnvironmentSetting(Setting $originalSetting, array $environmentSetting): void\n    {\n        if (isset($environmentSetting['value'])) {\n            $originalSetting->setValue($environmentSetting['value']);\n        }\n\n        if (isset($environmentSetting['group'])) {\n            $originalSetting->setGroupName($environmentSetting['group']);\n        }\n\n        if (isset($environmentSetting['position'])) {\n            $originalSetting->setPositionInGroup($environmentSetting['position']);\n        }\n\n        if (isset($environmentSetting['constraints'])) {\n            $originalSetting->setConstraints($environmentSetting['constraints']);\n        }\n    }\n\n    private function processOrphanSetting(int $shopId, string $moduleId, string $orphanSettingId): void\n    {\n        $this->eventDispatcher->dispatch(\n            new ShopEnvironmentWithOrphanSettingEvent($shopId, $moduleId, $orphanSettingId)\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Dao/ShopConfigurationDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao;\n\nuse DirectoryIterator;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\Chain\\ClassExtensionsChainDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\Chain\\TemplateExtensionChainDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\ShopConfigurationNotFoundException;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse function dirname;\nuse function in_array;\n\nclass ShopConfigurationDao implements ShopConfigurationDaoInterface\n{\n    public function __construct(\n        private BasicContextInterface $context,\n        private Filesystem $fileSystem,\n        private ModuleConfigurationDaoInterface $moduleConfigurationDao,\n        private ClassExtensionsChainDaoInterface $classExtensionsChainDao,\n        private TemplateExtensionChainDaoInterface $templateExtensionChainDao\n    ) {\n    }\n\n    /**\n     * @param int $shopId\n     *\n     * @return ShopConfiguration\n     * @throws ShopConfigurationNotFoundException\n     */\n    public function get(int $shopId): ShopConfiguration\n    {\n        if (!$this->isShopIdExists($shopId)) {\n            throw new ShopConfigurationNotFoundException(\n                'Configuration for ShopID ' . $shopId . ' not found'\n            );\n        }\n\n        $configuration = new ShopConfiguration();\n        $configuration->setClassExtensionsChain($this->classExtensionsChainDao->getChain($shopId));\n        $configuration->setModuleTemplateExtensionChain($this->templateExtensionChainDao->getChain($shopId));\n\n        foreach ($this->moduleConfigurationDao->getAll($shopId) as $moduleConfiguration) {\n            $configuration->addModuleConfiguration($moduleConfiguration);\n        }\n\n        return $configuration;\n    }\n\n    /**\n     * @deprecated use ModuleConfigurationDaoInterface::save() and ClassExtensionsChainDaoInterface::saveChain() instead\n     *\n     * @param ShopConfiguration $shopConfiguration\n     * @param int $shopId\n     */\n    public function save(ShopConfiguration $shopConfiguration, int $shopId): void\n    {\n        $this->moduleConfigurationDao->deleteAll($shopId);\n\n        foreach ($shopConfiguration->getModuleConfigurations() as $moduleConfiguration) {\n            $this->moduleConfigurationDao->save($moduleConfiguration, $shopId);\n        }\n\n        $this->classExtensionsChainDao->saveChain($shopId, $shopConfiguration->getClassExtensionsChain());\n    }\n\n    /**\n     * @return ShopConfiguration[]\n     * @throws ShopConfigurationNotFoundException\n     */\n    public function getAll(): array\n    {\n        $configurations = [];\n\n        foreach ($this->getShopIds() as $shopId) {\n            $configurations[$shopId] = $this->get($shopId);\n        }\n\n        return $configurations;\n    }\n\n    /**\n     * @deprecated will be completely removed\n     */\n    public function deleteAll(): void\n    {\n        if ($this->fileSystem->exists($this->getShopsConfigurationDirectory())) {\n            $this->fileSystem->remove(\n                $this->getShopsConfigurationDirectory()\n            );\n        }\n    }\n\n    /**\n     * @return int[]\n     */\n    private function getShopIds(): array\n    {\n        $shopIds = [];\n\n        if (file_exists($this->getShopsConfigurationDirectory())) {\n            $dir = new DirectoryIterator($this->getShopsConfigurationDirectory());\n\n            foreach ($dir as $fileInfo) {\n                if ($fileInfo->isDir() && is_numeric($fileInfo->getFilename())) {\n                    $shopIds[] = (int)$fileInfo->getFilename();\n                }\n            }\n        }\n\n        return $shopIds;\n    }\n\n    private function getShopsConfigurationDirectory(): string\n    {\n        return dirname(\n            $this->context->getShopConfigurationDirectory(\n                $this->context->getDefaultShopId()\n            )\n        );\n    }\n\n    /**\n     * @param int $shopId\n     *\n     * @return bool\n     */\n    private function isShopIdExists(int $shopId): bool\n    {\n        return in_array($shopId, $this->getShopIds(), true);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Dao/ShopConfigurationDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\n\ninterface ShopConfigurationDaoInterface\n{\n    /**\n     * @param int $shopId\n     *\n     * @return ShopConfiguration\n     */\n    public function get(int $shopId): ShopConfiguration;\n\n    /**\n     * @deprecated use ModuleConfigurationDaoInterface::save() and ClassExtensionsChainDaoInterface::saveChain() instead\n     */\n    public function save(ShopConfiguration $shopConfiguration, int $shopId): void;\n\n    /**\n     * @return ShopConfiguration[]\n     */\n    public function getAll(): array;\n\n    /**\n     * @deprecated will be completely removed\n     */\n    public function deleteAll(): void;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Dao/ShopEnvironmentMisconfigurationEventSubscriber.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao;\n\nuse Psr\\Log\\LoggerInterface;\nuse Symfony\\Component\\EventDispatcher\\EventSubscriberInterface;\n\nclass ShopEnvironmentMisconfigurationEventSubscriber implements EventSubscriberInterface\n{\n    public function __construct(private LoggerInterface $logger)\n    {\n    }\n\n    /** @param ShopEnvironmentWithOrphanSettingEvent $event */\n    public function logOrphanSetting(ShopEnvironmentWithOrphanSettingEvent $event): void\n    {\n        $this->logger->warning(\n            'Environment configuration tries to change non-existing module setting. Environment value will be ignored',\n            [\n                'shopId' => $event->getShopId(),\n                'moduleId' => $event->getModuleId(),\n                'settingId' => $event->getSettingId(),\n            ]\n        );\n    }\n\n    /** @return string[] */\n    public static function getSubscribedEvents(): array\n    {\n        return [ShopEnvironmentWithOrphanSettingEvent::class => 'logOrphanSetting'];\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Dao/ShopEnvironmentWithOrphanSettingEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\nclass ShopEnvironmentWithOrphanSettingEvent extends Event\n{\n    public function __construct(\n        /** @var int */\n        private $shopId,\n        /** @var  string */\n        private $moduleId,\n        /** @var string */\n        private $settingId\n    ) {\n    }\n\n    /** @return int */\n    public function getShopId(): int\n    {\n        return $this->shopId;\n    }\n\n    /** @return string */\n    public function getModuleId(): string\n    {\n        return $this->moduleId;\n    }\n\n    /** @return string */\n    public function getSettingId(): string\n    {\n        return $this->settingId;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/DataMapper/ModuleConfiguration/ClassExtensionsDataMapper.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfigurationDataMapperInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\ClassExtension;\n\nclass ClassExtensionsDataMapper implements ModuleConfigurationDataMapperInterface\n{\n    public const MAPPING_KEY = 'classExtensions';\n\n    public function toData(ModuleConfiguration $configuration): array\n    {\n        $data = [];\n\n        if ($configuration->hasClassExtensions()) {\n            $data[self::MAPPING_KEY] = $this->getClassExtensions($configuration);\n        }\n\n        return $data;\n    }\n\n    public function fromData(ModuleConfiguration $moduleConfiguration, array $data): ModuleConfiguration\n    {\n        if (isset($data[self::MAPPING_KEY])) {\n            $this->setClassExtensions($moduleConfiguration, $data[self::MAPPING_KEY]);\n        }\n        return $moduleConfiguration;\n    }\n\n    private function getClassExtensions(ModuleConfiguration $configuration): array\n    {\n        $extensions = [];\n\n        foreach ($configuration->getClassExtensions() as $extension) {\n            $extensions[$extension->getShopClassName()] = $extension->getModuleExtensionClassName();\n        }\n\n        return $extensions;\n    }\n\n    private function setClassExtensions(ModuleConfiguration $moduleConfiguration, array $extensions): void\n    {\n        foreach ($extensions as $shopClass => $moduleClass) {\n            $moduleConfiguration->addClassExtension(new ClassExtension(\n                $shopClass,\n                $moduleClass\n            ));\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/DataMapper/ModuleConfiguration/ClassExtensionsExportDataMapper.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\{\n    DataMapper\\ModuleConfigurationExportDataMapperInterface, DataObject\\ModuleConfiguration\n};\n\nclass ClassExtensionsExportDataMapper implements ModuleConfigurationExportDataMapperInterface\n{\n    public function toData(ModuleConfiguration $configuration): array\n    {\n        return ['classExtensions' => $this->getClassExtensions($configuration)];\n    }\n\n    private function getClassExtensions(ModuleConfiguration $configuration): array\n    {\n        $extensions = [];\n\n        if ($configuration->hasClassExtensions()) {\n            $extensions['classExtension'] = [];\n            foreach ($configuration->getClassExtensions() as $extension) {\n                $extensions['classExtension'][] = [\n                    'shopClass' => $extension->getShopClassName(),\n                    'moduleClass' => $extension->getModuleExtensionClassName(),\n                ];\n            }\n        }\n\n        return $extensions;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/DataMapper/ModuleConfiguration/ControllersDataMapper.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfigurationDataMapperInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\Controller;\n\nclass ControllersDataMapper implements ModuleConfigurationDataMapperInterface\n{\n    public const MAPPING_KEY = 'controllers';\n\n    public function toData(ModuleConfiguration $configuration): array\n    {\n        $data = [];\n\n        if ($configuration->hasControllers()) {\n            $data[self::MAPPING_KEY] = $this->getControllers($configuration);\n        }\n\n        return $data;\n    }\n\n    public function fromData(ModuleConfiguration $moduleConfiguration, array $data): ModuleConfiguration\n    {\n        if (isset($data[self::MAPPING_KEY])) {\n            $this->setControllers($moduleConfiguration, $data[self::MAPPING_KEY]);\n        }\n\n        return $moduleConfiguration;\n    }\n\n    /**\n     * @param ModuleConfiguration $moduleConfiguration\n     * @param array               $controllers\n     */\n    private function setControllers(ModuleConfiguration $moduleConfiguration, array $controllers): void\n    {\n        foreach ($controllers as $id => $controllerClassNamespace) {\n            $moduleConfiguration->addController(new Controller(\n                $id,\n                $controllerClassNamespace\n            ));\n        }\n    }\n\n    /**\n     * @param ModuleConfiguration $configuration\n     *\n     * @return array\n     */\n    private function getControllers(ModuleConfiguration $configuration): array\n    {\n        $controllers = [];\n\n        if ($configuration->hasControllers()) {\n            foreach ($configuration->getControllers() as $controller) {\n                $controllers[$controller->getId()] = $controller->getControllerClassNameSpace();\n            }\n        }\n\n        return $controllers;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/DataMapper/ModuleConfiguration/ControllersExportDataMapper.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\{\n    DataMapper\\ModuleConfigurationExportDataMapperInterface, DataObject\\ModuleConfiguration\n};\n\nclass ControllersExportDataMapper implements ModuleConfigurationExportDataMapperInterface\n{\n    public function toData(ModuleConfiguration $configuration): array\n    {\n        return ['controllers' => $this->getControllers($configuration)];\n    }\n    private function getControllers(ModuleConfiguration $configuration): array\n    {\n        $controllers = [];\n\n        if ($configuration->hasControllers()) {\n            $controllers['controller'] = [];\n            foreach ($configuration->getControllers() as $controller) {\n                $controllers['controller'][] = [\n                    'id' => $controller->getId(),\n                    'controllerClassNameSpace' => $controller->getControllerClassNameSpace(),\n                ];\n            }\n        }\n\n        return $controllers;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/DataMapper/ModuleConfiguration/EventsDataMapper.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfigurationDataMapperInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\Event;\n\nclass EventsDataMapper implements ModuleConfigurationDataMapperInterface\n{\n    public const MAPPING_KEY = 'events';\n\n    public function toData(ModuleConfiguration $configuration): array\n    {\n        $data = [];\n\n        if ($configuration->hasEvents()) {\n            $data[self::MAPPING_KEY] = $this->getEvents($configuration);\n        }\n\n        return $data;\n    }\n\n    public function fromData(ModuleConfiguration $moduleConfiguration, array $data): ModuleConfiguration\n    {\n        if (isset($data[self::MAPPING_KEY])) {\n            $this->setEvents($moduleConfiguration, $data[self::MAPPING_KEY]);\n        }\n        return $moduleConfiguration;\n    }\n\n    /**\n     * @param ModuleConfiguration $moduleConfiguration\n     * @param array               $event\n     */\n    private function setEvents(ModuleConfiguration $moduleConfiguration, array $event): void\n    {\n        foreach ($event as $action => $method) {\n            $moduleConfiguration->addEvent(new Event(\n                $action,\n                $method\n            ));\n        }\n    }\n\n    /**\n     * @param ModuleConfiguration $configuration\n     *\n     * @return array\n     */\n    private function getEvents(ModuleConfiguration $configuration): array\n    {\n        $events = [];\n\n        if ($configuration->hasEvents()) {\n            foreach ($configuration->getEvents() as $event) {\n                $events[$event->getAction()] = $event->getMethod();\n            }\n        }\n\n        return $events;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/DataMapper/ModuleConfiguration/ModuleSettingsDataMapper.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfigurationDataMapperInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setting\\Setting;\n\nclass ModuleSettingsDataMapper implements ModuleConfigurationDataMapperInterface\n{\n    public const MAPPING_KEY = 'moduleSettings';\n\n    public function toData(ModuleConfiguration $configuration): array\n    {\n        $data = [];\n\n        if ($configuration->hasModuleSettings()) {\n            $data[self::MAPPING_KEY] = $this->mapSettingsToData($configuration);\n        }\n\n        return $data;\n    }\n\n    public function fromData(ModuleConfiguration $moduleConfiguration, array $data): ModuleConfiguration\n    {\n        if (isset($data[self::MAPPING_KEY])) {\n            $this->mapSettingsFromData($moduleConfiguration, $data);\n        }\n\n        return $moduleConfiguration;\n    }\n\n    /**\n     * @param ModuleConfiguration $configuration\n     * @return array\n     */\n    private function mapSettingsToData(ModuleConfiguration $configuration): array\n    {\n        $data = [];\n\n        foreach ($configuration->getModuleSettings() as $setting) {\n            if ($setting->getGroupName()) {\n                $data[$setting->getName()]['group'] = $setting->getGroupName();\n            }\n\n            if ($setting->getType()) {\n                $data[$setting->getName()]['type'] = $setting->getType();\n            }\n\n            $data[$setting->getName()]['value'] = $setting->getValue();\n\n            if (!empty($setting->getConstraints())) {\n                $data[$setting->getName()]['constraints'] = $setting->getConstraints();\n            }\n\n            if ($setting->getPositionInGroup() > 0) {\n                $data[$setting->getName()]['position'] = $setting->getPositionInGroup();\n            }\n        }\n\n        return $data;\n    }\n\n    /**\n     * @param ModuleConfiguration $configuration\n     * @param array $data\n     * @return ModuleConfiguration\n     */\n    private function mapSettingsFromData(ModuleConfiguration $configuration, array $data): ModuleConfiguration\n    {\n        if (isset($data[self::MAPPING_KEY])) {\n            foreach ($data[self::MAPPING_KEY] as $name => $settingData) {\n                $setting = new Setting();\n                $setting->setName($name);\n                $setting->setType($settingData['type']);\n\n                if (isset($settingData['value'])) {\n                    $setting->setValue($settingData['value']);\n                }\n\n                if (!isset($settingData['value'])) {\n                    $setting->setValue('');\n                }\n\n                if (isset($settingData['group'])) {\n                    $setting->setGroupName($settingData['group']);\n                }\n\n                if (isset($settingData['position'])) {\n                    $setting->setPositionInGroup($settingData['position']);\n                }\n\n                if (isset($settingData['constraints'])) {\n                    $setting->setConstraints($settingData['constraints']);\n                }\n\n                $configuration->addModuleSetting($setting);\n            }\n        }\n\n        return $configuration;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/DataMapper/ModuleConfigurationDataMapper.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\n\nclass ModuleConfigurationDataMapper implements ModuleConfigurationDataMapperInterface\n{\n    /** @var ModuleConfigurationDataMapperInterface[] */\n    private $dataMappers = [];\n\n    public function __construct(ModuleConfigurationDataMapperInterface ...$dataMappers)\n    {\n        $this->dataMappers = $dataMappers;\n    }\n\n    /**\n     * @param ModuleConfiguration $configuration\n     *\n     * @return array\n     */\n    public function toData(ModuleConfiguration $configuration): array\n    {\n        $data = [\n            'id' => $configuration->getId(),\n            'moduleSource' => $configuration->getModuleSource(),\n            'version' => $configuration->getVersion(),\n            'activated' => $configuration->isActivated(),\n            'title' => $configuration->getTitle(),\n            'description' => $configuration->getDescription(),\n            'lang' => $configuration->getLang(),\n            'thumbnail' => $configuration->getThumbnail(),\n            'author' => $configuration->getAuthor(),\n            'url' => $configuration->getUrl(),\n            'email' => $configuration->getEmail(),\n        ];\n\n        foreach ($this->dataMappers as $dataMapper) {\n            $data = array_merge($data, $dataMapper->toData($configuration));\n        }\n\n        return $data;\n    }\n\n    /**\n     * @param ModuleConfiguration $moduleConfiguration\n     * @param array               $data\n     *\n     * @return ModuleConfiguration\n     */\n    public function fromData(ModuleConfiguration $moduleConfiguration, array $data): ModuleConfiguration\n    {\n        $moduleConfiguration\n            ->setId($data['id'])\n            ->setModuleSource($data['moduleSource'])\n            ->setVersion($data['version'])\n            ->setActivated($data['activated'])\n            ->setTitle($data['title']);\n\n        if (isset($data['description'])) {\n            $moduleConfiguration->setDescription($data['description']);\n        }\n\n        if (isset($data['lang'])) {\n            $moduleConfiguration->setLang($data['lang']);\n        }\n\n        if (isset($data['thumbnail'])) {\n            $moduleConfiguration->setThumbnail($data['thumbnail']);\n        }\n\n        if (isset($data['author'])) {\n            $moduleConfiguration->setAuthor($data['author']);\n        }\n\n        if (isset($data['url'])) {\n            $moduleConfiguration->setUrl($data['url']);\n        }\n\n        if (isset($data['email'])) {\n            $moduleConfiguration->setEmail($data['email']);\n        }\n\n        foreach ($this->dataMappers as $dataMapper) {\n            $moduleConfiguration = $dataMapper->fromData($moduleConfiguration, $data);\n        }\n\n        return $moduleConfiguration;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/DataMapper/ModuleConfigurationDataMapperInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\n\ninterface ModuleConfigurationDataMapperInterface extends ModuleConfigurationExportDataMapperInterface\n{\n    public function fromData(ModuleConfiguration $moduleConfiguration, array $data): ModuleConfiguration;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/DataMapper/ModuleConfigurationExportDataMapper.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper;\n\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\State\\ModuleStateServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\n\nclass ModuleConfigurationExportDataMapper implements ModuleConfigurationExportDataMapperInterface\n{\n    private array $dataMappers;\n\n    public function __construct(\n        private ModuleStateServiceInterface $moduleStateService,\n        private ContextInterface $context,\n        ModuleConfigurationExportDataMapperInterface ...$dataMappers\n    ) {\n        $this->dataMappers = $dataMappers;\n    }\n\n    public function toData(ModuleConfiguration $configuration): array\n    {\n        $data = [\n            'id' => $configuration->getId(),\n            'title' => $this->getPreferredTranslation($configuration->getTitle()),\n            'description' => $this->getPreferredTranslation($configuration->getDescription()),\n            'version' => $configuration->getVersion(),\n            'author' => $configuration->getAuthor(),\n            'url' => $configuration->getUrl(),\n            'email' => $configuration->getEmail(),\n        ];\n\n        if ($this->moduleStateService->isActive($configuration->getId(), $this->context->getCurrentShopId())) {\n            $data['activeInShops'] = ['activeInShop' => [ContainerFacade::getParameter('oxid_esales.shop_url')]];\n        } else {\n            $data['activeInShops'] = ['activeInShop' => []];\n        }\n\n        foreach ($this->dataMappers as $dataMapper) {\n            $data = array_merge($data, $dataMapper->toData($configuration));\n        }\n\n        return $data;\n    }\n\n    private function getPreferredTranslation(array $title): string\n    {\n        if (empty($title)) {\n            return '';\n        }\n\n        return $title['en'] ?? array_values($title)[0];\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/DataMapper/ModuleConfigurationExportDataMapperInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\n\ninterface ModuleConfigurationExportDataMapperInterface\n{\n    public function toData(ModuleConfiguration $configuration): array;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/DataObject/ClassExtensionsChain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject;\n\nuse ArrayIterator;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\ExtensionNotInChainException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\ClassExtension;\nuse Traversable;\n\nclass ClassExtensionsChain implements \\IteratorAggregate\n{\n    public const NAME = 'classExtensions';\n\n    /**\n     * @var array\n     */\n    private $chain = [];\n\n    public function __construct(array $chain = [])\n    {\n        $this->chain = $chain;\n    }\n\n    /**\n     * @return string\n     */\n    public function getName(): string\n    {\n        return self::NAME;\n    }\n\n    /**\n     * @return array\n     */\n    public function getChain(): array\n    {\n        return $this->chain;\n    }\n\n    /**\n     * @param array $chain\n     * @return ClassExtensionsChain\n     */\n    public function setChain(array $chain): ClassExtensionsChain\n    {\n        $this->chain = $chain;\n        return $this;\n    }\n\n    /**\n     * @param ClassExtension[] $extensions\n     *\n     * @return void\n     */\n    public function addExtensions(array $extensions): void\n    {\n        foreach ($extensions as $extension) {\n            $this->addExtension($extension);\n        }\n    }\n\n    /**\n     * @param ClassExtension $classExtension\n     *\n     * @throws ExtensionNotInChainException\n     */\n    public function removeExtension(ClassExtension $classExtension): void\n    {\n        $extended = $classExtension->getShopClassName();\n        $extension = $classExtension->getModuleExtensionClassName();\n\n        if (\n            false === \\array_key_exists($extended, $this->chain) ||\n            false === \\array_search($extension, $this->chain[$extended], true)\n        ) {\n            throw new ExtensionNotInChainException(\n                'There is no class ' . $extended . ' extended by class ' .\n                $extension . ' in the current chain'\n            );\n        }\n\n        $resultOffset = \\array_search($extension, $this->chain[$extended], true);\n        unset($this->chain[$extended][$resultOffset]);\n        $this->chain[$extended] = \\array_values($this->chain[$extended]);\n\n        if (empty($this->chain[$extended])) {\n            unset($this->chain[$extended]);\n        }\n    }\n\n    /**\n     * @param ClassExtension $extension\n     */\n    public function addExtension(ClassExtension $extension): void\n    {\n        if (\\array_key_exists($extension->getShopClassName(), $this->chain)) {\n            if (!$this->isModuleExtensionClassNameInChain($extension)) {\n                array_push(\n                    $this->chain[$extension->getShopClassName()],\n                    $extension->getModuleExtensionClassName()\n                );\n            }\n        } else {\n            $this->chain[$extension->getShopClassName()] = [$extension->getModuleExtensionClassName()];\n        }\n    }\n\n    /**\n     * @param ClassExtension $extension\n     *\n     * @return bool\n     */\n    private function isModuleExtensionClassNameInChain(ClassExtension $extension): bool\n    {\n        if (\\in_array($extension->getModuleExtensionClassName(), $this->chain[$extension->getShopClassName()])) {\n            return true;\n        }\n\n        return false;\n    }\n\n    public function getIterator(): Traversable\n    {\n        return new ArrayIterator($this->chain);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/DataObject/ModuleConfiguration/ClassExtension.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\n\nclass ClassExtension\n{\n    public function __construct(\n        private string $ShopClassName,\n        private string $moduleExtensionClassName\n    ) {\n    }\n\n    /**\n     * @return string\n     */\n    public function getShopClassName(): string\n    {\n        return $this->ShopClassName;\n    }\n\n    /**\n     * @return string\n     */\n    public function getModuleExtensionClassName(): string\n    {\n        return $this->moduleExtensionClassName;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/DataObject/ModuleConfiguration/Controller.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\n\nclass Controller\n{\n    public function __construct(\n        private string $id,\n        private string $controllerClassNameSpace\n    ) {\n    }\n\n    /**\n     * @return string\n     */\n    public function getId(): string\n    {\n        return $this->id;\n    }\n\n    /**\n     * @return string\n     */\n    public function getControllerClassNameSpace(): string\n    {\n        return $this->controllerClassNameSpace;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/DataObject/ModuleConfiguration/Event.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\n\nclass Event\n{\n    public function __construct(\n        private string $action,\n        private string $method\n    ) {\n    }\n\n    /**\n     * @return string\n     */\n    public function getAction(): string\n    {\n        return $this->action;\n    }\n\n    /**\n     * @return string\n     */\n    public function getMethod(): string\n    {\n        return $this->method;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/DataObject/ModuleConfiguration.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\ClassExtension;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\Controller;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\Event;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\InvalidModuleIdException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\ModuleSettingNotFountException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setting\\Setting;\n\nuse function Symfony\\Component\\String\\u;\n\nclass ModuleConfiguration\n{\n    /**\n     * @var string\n     */\n    private $id;\n\n    /**\n     * @var string\n     */\n    private $moduleSource;\n\n    /**\n     * @var string\n     */\n    private $version = '';\n\n    /**\n     * @var bool\n     */\n    private $activated = false;\n\n    /**\n     * @var array\n     */\n    private $title = [];\n    /**\n     * @var array\n     */\n    private $description = [];\n    /**\n     * @var string\n     */\n    private $lang = '';\n    /**\n     * @var string\n     */\n    private $thumbnail = '';\n    /**\n     * @var string\n     */\n    private $author = '';\n    /**\n     * @var string\n     */\n    private $url = '';\n    /**\n     * @var string\n     */\n    private $email = '';\n\n    /**\n     * @var ClassExtension[]\n     */\n    private $classExtensions = [];\n\n    /**\n     * @var Controller[]\n     */\n    private $controllers = [];\n\n    /**\n     * @var Event[]\n     */\n    private $events = [];\n\n    /**\n     * @var Setting[]\n     */\n    private $moduleSettings = [];\n\n    /**\n     * @return string\n     */\n    public function getId(): string\n    {\n        return $this->id;\n    }\n\n    /**\n     * @param string $id\n     *\n     * @return ModuleConfiguration\n     * @throws InvalidModuleIdException\n     */\n    public function setId(string $id): ModuleConfiguration\n    {\n        if (u($id)->containsAny('/')) {\n            throw new InvalidModuleIdException('Module ID (' . $id . ') must not contain \"/\".');\n        }\n\n        $this->id = $id;\n\n        return $this;\n    }\n\n    /** @return string */\n    public function getModuleSource(): string\n    {\n        return $this->moduleSource;\n    }\n\n    /**\n     * @param string $moduleSource\n     * @return ModuleConfiguration\n     */\n    public function setModuleSource(string $moduleSource): ModuleConfiguration\n    {\n        $this->moduleSource = $moduleSource;\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getVersion(): string\n    {\n        return $this->version;\n    }\n\n    /**\n     * @param string $version\n     *\n     * @return ModuleConfiguration\n     */\n    public function setVersion(string $version): ModuleConfiguration\n    {\n        $this->version = $version;\n\n        return $this;\n    }\n\n    /**\n     * @return array\n     */\n    public function getTitle(): array\n    {\n        return $this->title;\n    }\n\n    /**\n     * @param array $title\n     *\n     * @return ModuleConfiguration\n     */\n    public function setTitle(array $title): ModuleConfiguration\n    {\n        $this->title = $title;\n\n        return $this;\n    }\n\n    /**\n     * @return array\n     */\n    public function getDescription(): array\n    {\n        return $this->description;\n    }\n\n    /**\n     * @param array $description\n     *\n     * @return ModuleConfiguration\n     */\n    public function setDescription(array $description): ModuleConfiguration\n    {\n        $this->description = $description;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getLang(): string\n    {\n        return $this->lang;\n    }\n\n    /**\n     * @param string $lang\n     *\n     * @return ModuleConfiguration\n     */\n    public function setLang(string $lang): ModuleConfiguration\n    {\n        $this->lang = $lang;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getThumbnail(): string\n    {\n        return $this->thumbnail;\n    }\n\n    /**\n     * @param string $thumbnail\n     *\n     * @return ModuleConfiguration\n     */\n    public function setThumbnail(string $thumbnail): ModuleConfiguration\n    {\n        $this->thumbnail = $thumbnail;\n\n        return $this;\n    }\n\n    /**\n     * @return bool\n     */\n    public function isActivated(): bool\n    {\n        return $this->activated;\n    }\n\n    /**\n     * @param bool $activated\n     * @return ModuleConfiguration\n     */\n    public function setActivated(bool $activated): ModuleConfiguration\n    {\n        $this->activated = $activated;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getAuthor(): string\n    {\n        return $this->author;\n    }\n\n    /**\n     * @param string $author\n     *\n     * @return ModuleConfiguration\n     */\n    public function setAuthor(string $author): ModuleConfiguration\n    {\n        $this->author = $author;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getUrl(): string\n    {\n        return $this->url;\n    }\n\n    /**\n     * @param string $url\n     *\n     * @return ModuleConfiguration\n     */\n    public function setUrl(string $url): ModuleConfiguration\n    {\n        $this->url = $url;\n\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getEmail(): string\n    {\n        return $this->email;\n    }\n\n    /**\n     * @param string $email\n     *\n     * @return ModuleConfiguration\n     */\n    public function setEmail(string $email): ModuleConfiguration\n    {\n        $this->email = $email;\n\n        return $this;\n    }\n\n    /**\n     * @return ClassExtension[]\n     */\n    public function getClassExtensions(): array\n    {\n        return $this->classExtensions;\n    }\n\n    /**\n     * @param ClassExtension $extension\n     *\n     * @return $this\n     */\n    public function addClassExtension(ClassExtension $extension)\n    {\n        $this->classExtensions[] = $extension;\n\n        return $this;\n    }\n\n    /**\n     * @return bool\n     */\n    public function hasClassExtensions(): bool\n    {\n        return !empty($this->classExtensions);\n    }\n\n    /**\n     * @param string $namespace\n     *\n     * @return bool\n     */\n    public function hasClassExtension(string $namespace): bool\n    {\n        foreach ($this->getClassExtensions() as $classExtension) {\n            if ($classExtension->getModuleExtensionClassName() === $namespace) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * @param string $shopClassNamespace\n     *\n     * @return bool\n     */\n    public function isExtendingShopClass(string $shopClassNamespace): bool\n    {\n        foreach ($this->getClassExtensions() as $classExtension) {\n            if ($classExtension->getShopClassName() === $shopClassNamespace) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * @param Controller $controller\n     *\n     * @return $this\n     */\n    public function addController(Controller $controller)\n    {\n        $this->controllers[] = $controller;\n\n        return $this;\n    }\n\n    /**\n     * @return Controller[]\n     */\n    public function getControllers(): array\n    {\n        return $this->controllers;\n    }\n\n    /**\n     * @return bool\n     */\n    public function hasControllers(): bool\n    {\n        return !empty($this->controllers);\n    }\n\n    /**\n     * @param Event $event\n     *\n     * @return $this\n     */\n    public function addEvent(Event $event)\n    {\n        $this->events[] = $event;\n\n        return $this;\n    }\n\n    /**\n     * @return Event[]\n     */\n    public function getEvents(): array\n    {\n        return $this->events;\n    }\n\n    /**\n     * @return bool\n     */\n    public function hasEvents(): bool\n    {\n        return !empty($this->events);\n    }\n\n    /**\n     * @return Setting[]\n     */\n    public function getModuleSettings(): array\n    {\n        return $this->moduleSettings;\n    }\n\n    /**\n     * @param string $settingName\n     *\n     * @return bool\n     */\n    public function hasModuleSetting(string $settingName): bool\n    {\n        foreach ($this->getModuleSettings() as $setting) {\n            if ($setting->getName() === $settingName) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * @return bool\n     */\n    public function hasModuleSettings(): bool\n    {\n        return !empty($this->moduleSettings);\n    }\n\n    /**\n     * @param string $settingName\n     * @return Setting\n     * @throws ModuleSettingNotFountException\n     */\n    public function getModuleSetting(string $settingName): Setting\n    {\n        foreach ($this->getModuleSettings() as $setting) {\n            if ($setting->getName() === $settingName) {\n                return $setting;\n            }\n        }\n        throw new ModuleSettingNotFountException(\"Module setting \\\"$settingName\\\" was not found in configuration.\");\n    }\n\n    /**\n     * @param Setting $moduleSettings\n     * @return ModuleConfiguration\n     */\n    public function addModuleSetting(Setting $moduleSettings): ModuleConfiguration\n    {\n        $this->moduleSettings[] = $moduleSettings;\n        return $this;\n    }\n\n    /**\n     * @param Setting[] $moduleSettings\n     * @return ModuleConfiguration\n     */\n    public function setModuleSettings(array $moduleSettings): ModuleConfiguration\n    {\n        $this->moduleSettings = $moduleSettings;\n        return $this;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/DataObject/ModuleDependencies.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject;\n\nuse RecursiveArrayIterator;\n\nclass ModuleDependencies extends RecursiveArrayIterator\n{\n    public function getRequiredModuleIds(): array\n    {\n        return $this->offsetExists('modules') ? $this->offsetGet('modules') : [];\n    }\n\n    public function isRequiredModule(string $moduleId): bool\n    {\n        return in_array($moduleId, $this->getRequiredModuleIds(), true);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/DataObject/ModuleIdChain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject;\n\nuse ArrayIterator;\nuse IteratorAggregate;\n\nclass ModuleIdChain implements IteratorAggregate\n{\n    public function __construct(private array $moduleIds)\n    {\n    }\n\n    public function getIterator(): ArrayIterator\n    {\n        return new ArrayIterator($this->moduleIds);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/DataObject/ModuleTemplateExtensionChain.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject;\n\nuse RecursiveArrayIterator;\n\nclass ModuleTemplateExtensionChain extends RecursiveArrayIterator\n{\n    public function getTemplateLoadingPriority(string $templateName): ModuleIdChain\n    {\n        return new ModuleIdChain(\n            $this->offsetExists($templateName) ? $this->offsetGet($templateName) : []\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/DataObject/ShopConfiguration.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\ModuleConfigurationNotFoundException;\n\nclass ShopConfiguration\n{\n    /** @var ModuleConfiguration[] */\n    private $moduleConfigurations = [];\n\n    /**\n     * @var ClassExtensionsChain\n     */\n    private $classExtensionsChain;\n\n    /**\n     * @var ModuleTemplateExtensionChain\n     */\n    private $moduleTemplateExtensionsChain;\n\n    public function __construct()\n    {\n        $this->setClassExtensionsChain(new ClassExtensionsChain());\n        $this->setModuleTemplateExtensionChain(new ModuleTemplateExtensionChain());\n    }\n\n    /**\n     * @param string $moduleId\n     *\n     * @return ModuleConfiguration\n     * @throws ModuleConfigurationNotFoundException\n     */\n    public function getModuleConfiguration(string $moduleId): ModuleConfiguration\n    {\n        if (\\array_key_exists($moduleId, $this->moduleConfigurations)) {\n            return $this->moduleConfigurations[$moduleId];\n        }\n        throw new ModuleConfigurationNotFoundException('There is no module configuration with id ' . $moduleId);\n    }\n\n    /**\n     * @return ModuleConfiguration[]\n     */\n    public function getModuleConfigurations(): array\n    {\n        return $this->moduleConfigurations;\n    }\n\n    /**\n     * @param ModuleConfiguration $moduleConfiguration\n     *\n     * @return $this\n     */\n    public function addModuleConfiguration(ModuleConfiguration $moduleConfiguration)\n    {\n        $this->moduleConfigurations[$moduleConfiguration->getId()] = $moduleConfiguration;\n\n        return $this;\n    }\n\n    /**\n     * @deprecated use ModuleConfigurationDaoInterface::delete() instead\n     *\n     * @param string $moduleId\n     *\n     * @throws ModuleConfigurationNotFoundException\n     */\n    public function deleteModuleConfiguration(string $moduleId)\n    {\n        if (\\array_key_exists($moduleId, $this->moduleConfigurations)) {\n            $this->removeModuleExtensionFromClassChain($moduleId);\n            unset($this->moduleConfigurations[$moduleId]);\n        } else {\n            throw new ModuleConfigurationNotFoundException('There is no module configuration with id ' . $moduleId);\n        }\n    }\n\n    /**\n     * @return array\n     */\n    public function getModuleIdsOfModuleConfigurations(): array\n    {\n        return array_keys($this->moduleConfigurations);\n    }\n\n    public function setClassExtensionsChain(ClassExtensionsChain $chain): self\n    {\n        $this->classExtensionsChain = $chain;\n\n        return $this;\n    }\n\n    /**\n     * @return ClassExtensionsChain\n     */\n    public function getClassExtensionsChain(): ClassExtensionsChain\n    {\n        return $this->classExtensionsChain;\n    }\n\n    public function setModuleTemplateExtensionChain(ModuleTemplateExtensionChain $moduleTemplateExtensionsChain): void\n    {\n        $this->moduleTemplateExtensionsChain = $moduleTemplateExtensionsChain;\n    }\n\n    public function getModuleTemplateExtensionChain(): ModuleTemplateExtensionChain\n    {\n        return $this->moduleTemplateExtensionsChain;\n    }\n\n    /**\n     * @param string $moduleId\n     *\n     * @return bool\n     */\n    public function hasModuleConfiguration(string $moduleId): bool\n    {\n        return isset($this->moduleConfigurations[$moduleId]);\n    }\n\n    /**\n     * @param string $moduleId\n     */\n    private function removeModuleExtensionFromClassChain(string $moduleId): void\n    {\n        $moduleConfiguration = $this->moduleConfigurations[$moduleId];\n        foreach ($moduleConfiguration->getClassExtensions() as $classExtension) {\n            $this->getClassExtensionsChain()->removeExtension($classExtension);\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/DataObject/UnresolvedModuleDependencies.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject;\n\nclass UnresolvedModuleDependencies\n{\n    private array $moduleDependencyIds = [];\n\n    public function getModuleIds(): array\n    {\n        return $this->moduleDependencyIds;\n    }\n\n    public function addModuleId(string $moduleId): static\n    {\n        $this->moduleDependencyIds[] = $moduleId;\n\n        return $this;\n    }\n\n    public function hasModuleDependencies(): bool\n    {\n        return !empty($this->moduleDependencyIds);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Definition/TreeBuilderFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Definition;\n\n// phpcs:disable\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\ClassExtensionsDataMapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\ControllersDataMapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\EventsDataMapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\ModuleSettingsDataMapper;\nuse Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder;\nuse Symfony\\Component\\Config\\Definition\\NodeInterface;\n// phpcs:enable\n\nclass TreeBuilderFactory implements TreeBuilderFactoryInterface\n{\n    /**\n     * @return NodeInterface\n     */\n    public function create(): NodeInterface\n    {\n        $treeBuilder = new TreeBuilder('moduleConfiguration');\n        $rootNode = $treeBuilder->getRootNode();\n\n        $rootNode\n            ->children()\n                ->scalarNode('id')\n                    ->isRequired()\n                    ->cannotBeEmpty()\n                ->end()\n                ->scalarNode('moduleSource')\n                    ->isRequired()\n                    ->cannotBeEmpty()\n                ->end()\n                ->scalarNode('version')\n                ->end()\n                ->scalarNode('activated')\n                ->end()\n                ->arrayNode('title')\n                    ->scalarPrototype()->end()\n                ->end()\n                ->arrayNode('description')\n                    ->scalarPrototype()->end()\n                ->end()\n                ->scalarNode('lang')\n                ->end()\n                ->scalarNode('thumbnail')\n                ->end()\n                ->scalarNode('author')\n                ->end()\n                ->scalarNode('url')\n                ->end()\n                ->scalarNode('email')\n                ->end()\n                ->arrayNode(ClassExtensionsDataMapper::MAPPING_KEY)\n                    ->normalizeKeys(false)->scalarPrototype()->end()\n                ->end()\n                ->arrayNode(ControllersDataMapper::MAPPING_KEY)\n                    ->normalizeKeys(false)->scalarPrototype()->end()\n                ->end()\n                ->arrayNode(EventsDataMapper::MAPPING_KEY)\n                    ->normalizeKeys(false)->scalarPrototype()->end()\n                ->end()\n                ->arrayNode(ModuleSettingsDataMapper::MAPPING_KEY)\n                    ->normalizeKeys(false)\n                    ->arrayPrototype()\n                        ->children()\n                            ->scalarNode('group')\n                            ->end()\n                            ->scalarNode('name')\n                            ->end()\n                            ->scalarNode('type')\n                            ->end()\n                            ->variableNode('value')\n                            ->end()\n                            ->scalarNode('position')\n                            ->end()\n                            ->arrayNode('constraints')\n                                ->scalarPrototype()->end()\n                            ->end()\n                        ->end()\n                    ->end()\n                ->end()\n            ->end()\n        ;\n\n        return $treeBuilder->buildTree();\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Definition/TreeBuilderFactoryInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Definition;\n\nuse Symfony\\Component\\Config\\Definition\\NodeInterface;\n\ninterface TreeBuilderFactoryInterface\n{\n    /**\n     * @return NodeInterface\n     */\n    public function create(): NodeInterface;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Event/ModuleClassExtensionChainChangedEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Event;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\nclass ModuleClassExtensionChainChangedEvent extends Event\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Event/ModuleConfigurationChangedEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Event;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\nclass ModuleConfigurationChangedEvent extends Event\n{\n    public function __construct(private ModuleConfiguration $moduleConfiguration, private int $shopId)\n    {\n    }\n\n    public function getModuleConfiguration(): ModuleConfiguration\n    {\n        return $this->moduleConfiguration;\n    }\n\n    public function getModuleId(): string\n    {\n        return $this->moduleConfiguration->getId();\n    }\n\n    public function getShopId(): int\n    {\n        return $this->shopId;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Exception/ExtensionNotInChainException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception;\n\nclass ExtensionNotInChainException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Exception/InvalidModuleIdException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception;\n\nclass InvalidModuleIdException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Exception/ModuleConfigurationNotFoundException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception;\n\nclass ModuleConfigurationNotFoundException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Exception/ModuleSettingNotFountException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception;\n\n/**\n * @internal\n */\nclass ModuleSettingNotFountException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Exception/ProjectConfigurationIsEmptyException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception;\n\nclass ProjectConfigurationIsEmptyException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Exception/ShopConfigurationNotFoundException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception;\n\nclass ShopConfigurationNotFoundException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Service/ModuleClassExtensionsMergingService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\ModuleConfigurationNotFoundException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ClassExtensionsChain;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\ClassExtension;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\ExtensionNotInChainException;\n\nclass ModuleClassExtensionsMergingService implements ModuleClassExtensionsMergingServiceInterface\n{\n    /**\n     * @param ShopConfiguration   $shopConfiguration\n     * @param ModuleConfiguration $moduleConfiguration\n     *\n     * @return ClassExtensionsChain\n     * @throws ExtensionNotInChainException\n     * @throws ModuleConfigurationNotFoundException\n     */\n    public function merge(\n        ShopConfiguration $shopConfiguration,\n        ModuleConfiguration $moduleConfiguration\n    ): ClassExtensionsChain {\n        $chain = $shopConfiguration->getClassExtensionsChain();\n\n        if (!$shopConfiguration->hasModuleConfiguration($moduleConfiguration->getId())) {\n            $chain->addExtensions($moduleConfiguration->getClassExtensions());\n        } else {\n            $chain = $this->addNewModuleExtensionsToChain($moduleConfiguration, $shopConfiguration, $chain);\n            $chain = $this->replaceExistingModuleExtensionsInChain($moduleConfiguration, $shopConfiguration, $chain);\n            $chain = $this->removeDeletedModuleExtensionsFromChain($moduleConfiguration, $shopConfiguration, $chain);\n        }\n\n        return $chain;\n    }\n\n    /**\n     * @param ModuleConfiguration  $moduleConfiguration\n     * @param ShopConfiguration    $shopConfiguration\n     * @param ClassExtensionsChain $classExtensionChain\n     *\n     * @return ClassExtensionsChain\n     * @throws ModuleConfigurationNotFoundException\n     * @throws ExtensionNotInChainException\n     */\n    private function removeDeletedModuleExtensionsFromChain(\n        ModuleConfiguration $moduleConfiguration,\n        ShopConfiguration $shopConfiguration,\n        ClassExtensionsChain $classExtensionChain\n    ): ClassExtensionsChain {\n        $existentModuleConfiguration = $shopConfiguration->getModuleConfiguration(\n            $moduleConfiguration->getId()\n        );\n\n        foreach ($existentModuleConfiguration->getClassExtensions() as $extension) {\n            if (!$this->isExtendingShopClass($extension, $moduleConfiguration->getClassExtensions())) {\n                $classExtensionChain->removeExtension($extension);\n            }\n        }\n\n        return $classExtensionChain;\n    }\n\n    /**\n     * @param ModuleConfiguration  $moduleConfiguration\n     * @param ShopConfiguration    $shopConfiguration\n     * @param ClassExtensionsChain $chain\n     *\n     * @return ClassExtensionsChain\n     * @throws ModuleConfigurationNotFoundException\n     */\n    private function replaceExistingModuleExtensionsInChain(\n        ModuleConfiguration $moduleConfiguration,\n        ShopConfiguration $shopConfiguration,\n        ClassExtensionsChain $chain\n    ): ClassExtensionsChain {\n        $existentModuleConfiguration = $shopConfiguration->getModuleConfiguration(\n            $moduleConfiguration->getId()\n        );\n\n        foreach ($existentModuleConfiguration->getClassExtensions() as $existingExtension) {\n            foreach ($moduleConfiguration->getClassExtensions() as $newExtension) {\n                if ($this->areExtensionsEqual($existingExtension, $newExtension)) {\n                    $this->replaceExistingExtension($chain, $existingExtension, $newExtension);\n                }\n            }\n        }\n\n        return $chain;\n    }\n\n    /**\n     * @param ModuleConfiguration  $moduleConfiguration\n     * @param ShopConfiguration    $shopConfiguration\n     * @param ClassExtensionsChain $chain\n     *\n     * @return ClassExtensionsChain\n     * @throws ModuleConfigurationNotFoundException\n     */\n    private function addNewModuleExtensionsToChain(\n        ModuleConfiguration $moduleConfiguration,\n        ShopConfiguration $shopConfiguration,\n        ClassExtensionsChain $chain\n    ): ClassExtensionsChain {\n        foreach ($moduleConfiguration->getClassExtensions() as $classExtension) {\n            $existentModuleConfiguration = $shopConfiguration->getModuleConfiguration(\n                $moduleConfiguration->getId()\n            );\n\n            if (!$existentModuleConfiguration->isExtendingShopClass($classExtension->getShopClassName())) {\n                $chain->addExtension($classExtension);\n            }\n        }\n\n        return $chain;\n    }\n\n    /**\n     * @param ClassExtension $existingClassExtension\n     * @param ClassExtension[]          $newClassExtensions\n     *\n     * @return bool\n     */\n    private function isExtendingShopClass(ClassExtension $existingClassExtension, array $newClassExtensions): bool\n    {\n        foreach ($newClassExtensions as $newExtension) {\n            if ($newExtension->getShopClassName() === $existingClassExtension->getShopClassName()) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * @param ClassExtension $existingExtension\n     * @param ClassExtension $newExtension\n     *\n     * @return bool\n     */\n    private function areExtensionsEqual(ClassExtension $existingExtension, ClassExtension $newExtension): bool\n    {\n        return $existingExtension->getShopClassName() === $newExtension->getShopClassName()\n               && $existingExtension->getModuleExtensionClassName() !==\n                  $newExtension->getModuleExtensionClassName();\n    }\n\n    /**\n     * Converts e.g. the chain [Class1, ClassOld, Class3] to [Class1, ClassNew, Class3]. Keeping the order is important\n     * as the order can be changed in OXID eShop admin.\n     *\n     * @param ClassExtensionsChain $chain\n     * @param ClassExtension       $existingExtension\n     * @param ClassExtension       $newExtension\n     */\n    private function replaceExistingExtension(\n        ClassExtensionsChain $chain,\n        ClassExtension $existingExtension,\n        ClassExtension $newExtension\n    ): void {\n        $classExtensionChain = $chain->getChain();\n        $shopClassNamespaceInChain = $classExtensionChain[$existingExtension->getShopClassName()];\n        foreach ($shopClassNamespaceInChain as $key => $existingExtensionInChain) {\n            if ($existingExtensionInChain === $existingExtension->getModuleExtensionClassName()) {\n                $classExtensionChain[$existingExtension->getShopClassName()][$key] =\n                    $newExtension->getModuleExtensionClassName();\n            }\n        }\n\n        $chain->setChain($classExtensionChain);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Service/ModuleClassExtensionsMergingServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ClassExtensionsChain;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\n\ninterface ModuleClassExtensionsMergingServiceInterface\n{\n    /**\n     * @param ShopConfiguration   $shopConfiguration\n     * @param ModuleConfiguration $moduleConfiguration\n     *\n     * @return ClassExtensionsChain\n     */\n    public function merge(\n        ShopConfiguration $shopConfiguration,\n        ModuleConfiguration $moduleConfiguration\n    ): ClassExtensionsChain;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Service/ModuleConfigurationMergingService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\ExtensionNotInChainException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\ModuleConfigurationNotFoundException;\n\nclass ModuleConfigurationMergingService implements ModuleConfigurationMergingServiceInterface\n{\n    public function __construct(\n        private SettingsMergingServiceInterface $settingsMergingService,\n        private ModuleClassExtensionsMergingServiceInterface $classExtensionsMergingService\n    ) {\n    }\n\n    /**\n     * @inheritDoc\n     * @throws ModuleConfigurationNotFoundException\n     * @throws ExtensionNotInChainException\n     */\n    public function merge(\n        ShopConfiguration $shopConfiguration,\n        ModuleConfiguration $moduleConfiguration\n    ): ShopConfiguration {\n        $moduleConfigurationClone = $this->cloneModuleConfiguration($moduleConfiguration);\n\n        $mergedClassExtensionChain = $this->classExtensionsMergingService->merge(\n            $shopConfiguration,\n            $moduleConfigurationClone\n        );\n        $shopConfiguration->setClassExtensionsChain($mergedClassExtensionChain);\n\n        $mergedModuleConfiguration = $this->settingsMergingService->merge(\n            $shopConfiguration,\n            $moduleConfigurationClone\n        );\n\n        $this->setActivatedOptionToMergedConfiguration($shopConfiguration, $mergedModuleConfiguration);\n\n        $shopConfiguration->addModuleConfiguration($mergedModuleConfiguration);\n\n        return $shopConfiguration;\n    }\n\n    /**\n     * @param ModuleConfiguration $moduleConfiguration\n     * @return ModuleConfiguration\n     */\n    private function cloneModuleConfiguration(ModuleConfiguration $moduleConfiguration): ModuleConfiguration\n    {\n        $moduleSettingClones = [];\n        foreach ($moduleConfiguration->getModuleSettings() as $moduleSetting) {\n            $moduleSettingClones[] = clone $moduleSetting;\n        }\n        $moduleConfigurationClone = clone $moduleConfiguration;\n        $moduleConfigurationClone->setModuleSettings($moduleSettingClones);\n        return $moduleConfigurationClone;\n    }\n\n    /**\n     * @param ShopConfiguration $shopConfiguration\n     * @param ModuleConfiguration $mergedModuleConfiguration\n     * @throws ModuleConfigurationNotFoundException\n     */\n    private function setActivatedOptionToMergedConfiguration(\n        ShopConfiguration $shopConfiguration,\n        ModuleConfiguration $mergedModuleConfiguration\n    ): void {\n        if ($shopConfiguration->hasModuleConfiguration($mergedModuleConfiguration->getId())) {\n            $isConfigured = $shopConfiguration\n                ->getModuleConfiguration($mergedModuleConfiguration->getId())\n                ->isActivated();\n            $mergedModuleConfiguration->setActivated($isConfigured);\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Service/ModuleConfigurationMergingServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\n\ninterface ModuleConfigurationMergingServiceInterface\n{\n    /**\n     * @param ShopConfiguration   $shopConfiguration\n     * @param ModuleConfiguration $moduleConfigurationToMerge\n     *\n     * @return ShopConfiguration\n     */\n    public function merge(\n        ShopConfiguration $shopConfiguration,\n        ModuleConfiguration $moduleConfigurationToMerge\n    ): ShopConfiguration;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Service/SettingsMergingService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setting\\Setting;\n\nclass SettingsMergingService implements SettingsMergingServiceInterface\n{\n    /**\n     * @param ShopConfiguration   $shopConfiguration\n     * @param ModuleConfiguration $moduleConfigurationToMerge\n     *\n     * @return ModuleConfiguration\n     */\n    public function merge(\n        ShopConfiguration $shopConfiguration,\n        ModuleConfiguration $moduleConfigurationToMerge\n    ): ModuleConfiguration {\n        if ($shopConfiguration->hasModuleConfiguration($moduleConfigurationToMerge->getId())) {\n            $existingModuleConfiguration = $shopConfiguration->getModuleConfiguration(\n                $moduleConfigurationToMerge->getId()\n            );\n            if (\n                !empty($existingModuleConfiguration->getModuleSettings()) &&\n                !empty($moduleConfigurationToMerge->getModuleSettings())\n            ) {\n                $mergedModuleSettings = $this->mergeModuleSettings(\n                    $existingModuleConfiguration->getModuleSettings(),\n                    $moduleConfigurationToMerge->getModuleSettings()\n                );\n                $moduleConfigurationToMerge->setModuleSettings($mergedModuleSettings);\n            }\n        }\n        return $moduleConfigurationToMerge;\n    }\n\n    /**\n     * @param Setting[] $existingSettings\n     * @param Setting[] $settingsToMerge\n     *\n     * @return Setting[]\n     */\n    private function mergeModuleSettings(array $existingSettings, array $settingsToMerge): array\n    {\n        foreach ($settingsToMerge as &$settingToMerge) {\n            foreach ($existingSettings as $existingSetting) {\n                if ($this->shouldMerge($existingSetting, $settingToMerge)) {\n                    $settingToMerge->setValue($existingSetting->getValue());\n                }\n            }\n        }\n\n        return $settingsToMerge;\n    }\n\n    private function shouldMerge(Setting $existingSetting, Setting $settingToMerge): bool\n    {\n        $shouldMerge = $existingSetting->getValue() !== null &&\n            $existingSetting->getName() === $settingToMerge->getName() &&\n            $existingSetting->getType() === $settingToMerge->getType();\n\n        if (\n            $shouldMerge === true\n            && !empty($settingToMerge->getConstraints())\n            && ($settingToMerge->getType() === 'select')\n        ) {\n            $resultPosition = array_search($existingSetting->getValue(), $settingToMerge->getConstraints(), true);\n            $shouldMerge = $resultPosition !== false;\n        }\n\n        return $shouldMerge;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/Service/SettingsMergingServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\n\ninterface SettingsMergingServiceInterface\n{\n    /**\n     * @param ShopConfiguration   $shopConfiguration\n     * @param ModuleConfiguration $moduleConfigurationToMerge\n     *\n     * @return ModuleConfiguration\n     */\n    public function merge(\n        ShopConfiguration $shopConfiguration,\n        ModuleConfiguration $moduleConfigurationToMerge\n    ): ModuleConfiguration;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/bootstrap-services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleDependencyDaoInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleDependencyDao\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDao\n    arguments:\n      Symfony\\Component\\Config\\Definition\\NodeInterface: '@oxid_esales.module.configuration.node'\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\Chain\\ClassExtensionsChainDaoInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\Chain\\ClassExtensionsChainDao\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\Chain\\TemplateExtensionChainDaoInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\Chain\\TemplateExtensionChainDao\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Cache\\ModuleConfigurationCacheInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Cache\\ClassPropertyModuleConfigurationCache\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDao\n    arguments:\n      Symfony\\Component\\Filesystem\\Filesystem: '@oxid_esales.symfony.file_system'\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleEnvironmentConfigurationDaoInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleEnvironmentConfigurationDao\n    arguments:\n      Symfony\\Component\\Filesystem\\Filesystem: '@oxid_esales.symfony.file_system'\n      Symfony\\Component\\Config\\Definition\\NodeInterface: '@oxid_esales.module.configuration.node'\n      $context: '@OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface'\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationExtenderInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleEnvironmentConfigurationExtender\n\n  oxid_esales.module.configuration.node:\n    class: Symfony\\Component\\Config\\Definition\\NodeInterface\n    factory: [ '@OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Definition\\TreeBuilderFactoryInterface', 'create' ]\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Definition\\TreeBuilderFactoryInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Definition\\TreeBuilderFactory\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfigurationDataMapperInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfigurationDataMapper\n    arguments:\n      - '@oxid_esales.module.configuration.class_extensions_data_mapper'\n      - '@oxid_esales.module.configuration.controllers_data_mapper'\n      - '@oxid_esales.module.configuration.events_data_mapper'\n      - '@oxid_esales.module.configuration.module_settings_data_mapper'\n\n  oxid_esales.module.configuration.class_extensions_data_mapper:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\ClassExtensionsDataMapper\n\n  oxid_esales.module.configuration.controllers_data_mapper:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\ControllersDataMapper\n\n  oxid_esales.module.configuration.events_data_mapper:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\EventsDataMapper\n\n  oxid_esales.module.configuration.module_settings_data_mapper:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\ModuleSettingsDataMapper\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Service\\ModuleConfigurationMergingServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Service\\ModuleConfigurationMergingService\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Service\\SettingsMergingServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Service\\SettingsMergingService\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Service\\ModuleClassExtensionsMergingServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Service\\ModuleClassExtensionsMergingService\n"
  },
  {
    "path": "source/Internal/Framework/Module/Configuration/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ActiveModulesDataProviderInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ActiveModulesDataProvider\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ModuleConfigurationDaoBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ModuleConfigurationDaoBridge\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ShopConfigurationDaoBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ShopConfigurationDaoBridge\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ModuleSettingBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ModuleSettingBridge\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopEnvironmentMisconfigurationEventSubscriber:\n    tags:\n      - { name: kernel.event_subscriber }\n\n  oxid_esales.module.configuration.class_extensions_export_data_mapper:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\ClassExtensionsExportDataMapper\n\n  oxid_esales.module.configuration.controllers_export_data_mapper:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\ControllersExportDataMapper\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\ModuleConfigurationExportDataMapperInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfigurationExportDataMapper\n    arguments:\n      $moduleStateService: '@OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\State\\ModuleStateServiceInterface'\n      $context: '@OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface'\n      $dataMappers:\n        - '@oxid_esales.module.configuration.class_extensions_export_data_mapper'\n        - '@oxid_esales.module.configuration.controllers_export_data_mapper'\n\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ModuleConfigurationDataMapperBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ModuleConfigurationDataMapperBridge\n    arguments:\n      - '@OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\ModuleConfigurationExportDataMapperInterface'\n    public: true\n\n"
  },
  {
    "path": "source/Internal/Framework/Module/Facade/ActiveModulesDataProvider.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Cache\\ModuleCacheInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModulePathResolverInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ActiveClassExtensionChainResolverInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\n\nclass ActiveModulesDataProvider implements ActiveModulesDataProviderInterface\n{\n    public function __construct(\n        private readonly ModuleConfigurationDaoInterface $moduleConfigurationDao,\n        private readonly ModulePathResolverInterface $modulePathResolver,\n        private readonly ContextInterface $context,\n        private readonly ModuleCacheInterface $moduleCache,\n        private readonly ActiveClassExtensionChainResolverInterface $activeClassExtensionChainResolver\n    ) {\n    }\n\n    /** @inheritDoc */\n    public function getModuleIds(): array\n    {\n        $cacheKey = 'active_module_ids';\n\n        if (!$this->moduleCache->exists($cacheKey)) {\n            $moduleIds = [];\n\n            foreach ($this->getActiveModuleConfigurations() as $moduleConfiguration) {\n                $moduleIds[] = $moduleConfiguration->getId();\n            }\n\n            $this->moduleCache->put($cacheKey, $moduleIds);\n        }\n\n        return $this->moduleCache->get($cacheKey);\n    }\n\n    /** @inheritDoc */\n    public function getModulePaths(): array\n    {\n        $cacheKey = 'absolute_module_paths';\n\n        if (!$this->moduleCache->exists($cacheKey)) {\n            $this->moduleCache->put(\n                $cacheKey,\n                $this->collectModulePathsForCaching()\n            );\n        }\n        return $this->moduleCache->get($cacheKey);\n    }\n\n    /** @inheritDoc */\n    public function getControllers(): array\n    {\n        $cacheKey = 'controllers';\n\n        if (!$this->moduleCache->exists($cacheKey)) {\n            $this->moduleCache->put($cacheKey, $this->collectControllersForCaching());\n        }\n\n        return $this->createControllersFromData(\n            $this->moduleCache->get($cacheKey)\n        );\n    }\n\n    /** @inheritDoc */\n    public function getClassExtensions(): array\n    {\n        $shopId = $this->context->getCurrentShopId();\n        $cacheKey = 'module_class_extensions';\n\n        if (!$this->moduleCache->exists($cacheKey)) {\n            $this->moduleCache->put(\n                $cacheKey,\n                $this->activeClassExtensionChainResolver->getActiveExtensionChain($shopId)->getChain()\n            );\n        }\n\n        return $this->moduleCache->get($cacheKey);\n    }\n\n    /** @return array */\n    private function collectModulePathsForCaching(): array\n    {\n        $modulePaths = [];\n        foreach ($this->getActiveModuleConfigurations() as $moduleConfiguration) {\n            $modulePaths[$moduleConfiguration->getId()] = $this\n                ->modulePathResolver\n                ->getFullModulePathFromConfiguration(\n                    $moduleConfiguration->getId(),\n                    $this->context->getCurrentShopId()\n                );\n        }\n        return $modulePaths;\n    }\n\n    /** @return array */\n    private function collectControllersForCaching(): array\n    {\n        $controllers = [];\n        foreach ($this->getActiveModuleConfigurations() as $moduleConfiguration) {\n            foreach ($moduleConfiguration->getControllers() as $controller) {\n                $controllers[$controller->getId()] = $controller->getControllerClassNameSpace();\n            }\n        }\n        return $controllers;\n    }\n\n    /** @return ModuleConfiguration[] */\n    private function getActiveModuleConfigurations(): array\n    {\n        $moduleConfigurations = [];\n        $shopId = $this->context->getCurrentShopId();\n\n        foreach ($this->moduleConfigurationDao->getAll($shopId) as $moduleConfiguration) {\n            if ($moduleConfiguration->isActivated()) {\n                $moduleConfigurations[] = $moduleConfiguration;\n            }\n        }\n        return $moduleConfigurations;\n    }\n\n    private function createControllersFromData(array $data): array\n    {\n        $controllers = [];\n        foreach ($data as $id => $namespace) {\n            $controllers[] = new ModuleConfiguration\\Controller($id, $namespace);\n        }\n\n        return $controllers;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Facade/ActiveModulesDataProviderBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\Controller;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\ShopConfigurationNotFoundException;\nuse Psr\\Log\\LoggerInterface;\n\nclass ActiveModulesDataProviderBridge implements ActiveModulesDataProviderBridgeInterface\n{\n    private array $chain;\n\n    public function __construct(\n        private readonly ActiveModulesDataProviderInterface $activeModulesDataProvider,\n        private readonly LoggerInterface $logger\n    ) {\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function getModuleIds(): array\n    {\n        return $this->activeModulesDataProvider->getModuleIds();\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function getModulePaths(): array\n    {\n        return $this->activeModulesDataProvider->getModulePaths();\n    }\n\n    /**\n     * @return Controller[]\n     */\n    public function getControllers(): array\n    {\n        return $this->activeModulesDataProvider->getControllers();\n    }\n\n    public function getClassExtensions(): array\n    {\n        if (isset($this->chain)) {\n            return $this->chain;\n        }\n\n        try {\n            $this->chain = $this->activeModulesDataProvider->getClassExtensions();\n        } catch (ShopConfigurationNotFoundException $exception) {\n            $this->chain = [];\n            $this->logger->error($exception->getMessage(), [$exception]);\n        }\n\n        return $this->chain;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Facade/ActiveModulesDataProviderBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\ClassExtension;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\Controller;\n\ninterface ActiveModulesDataProviderBridgeInterface\n{\n    /**\n     * @return string[]\n     */\n    public function getModuleIds(): array;\n\n    /**\n     * @return string[]\n     */\n    public function getModulePaths(): array;\n\n    /**\n     * @return Controller[]\n     */\n    public function getControllers(): array;\n\n    /**\n     * @return string[]\n     */\n    public function getClassExtensions(): array;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Facade/ActiveModulesDataProviderInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\Controller;\n\ninterface ActiveModulesDataProviderInterface\n{\n    /**\n     * @return string[]\n     */\n    public function getModuleIds(): array;\n\n    /**\n     * @return string[]\n     */\n    public function getModulePaths(): array;\n\n    /**\n     * @return Controller[]\n     */\n    public function getControllers(): array;\n\n    /**\n     * @return string[]\n     */\n    public function getClassExtensions(): array;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Facade/ModuleSettingService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Cache\\ModuleCacheInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\ModuleSettingNotFountException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setting\\Event\\SettingChangedEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\nuse Symfony\\Component\\String\\UnicodeString;\n\nclass ModuleSettingService implements ModuleSettingServiceInterface\n{\n    public function __construct(\n        private readonly ContextInterface $context,\n        private readonly ModuleConfigurationDaoInterface $moduleConfigurationDao,\n        private readonly ModuleCacheInterface $moduleCache,\n        private readonly EventDispatcherInterface $eventDispatcher\n    ) {\n    }\n\n    public function getInteger(string $name, string $moduleId): int\n    {\n        return $this->getValue($moduleId, $name);\n    }\n\n    public function getFloat(string $name, string $moduleId): float\n    {\n        return $this->getValue($moduleId, $name);\n    }\n\n    public function getString(string $name, string $moduleId): UnicodeString\n    {\n        return new UnicodeString($this->getValue($moduleId, $name));\n    }\n\n    public function getBoolean(string $name, string $moduleId): bool\n    {\n        return $this->getValue($moduleId, $name);\n    }\n\n    public function getCollection(string $name, string $moduleId): array\n    {\n        return $this->getValue($moduleId, $name);\n    }\n\n    public function saveInteger(string $name, int $value, string $moduleId): void\n    {\n        $this->saveSettingToModuleConfiguration($moduleId, $name, $value);\n    }\n\n    public function saveFloat(string $name, float $value, string $moduleId): void\n    {\n        $this->saveSettingToModuleConfiguration($moduleId, $name, $value);\n    }\n\n    public function saveString(string $name, string $value, string $moduleId): void\n    {\n        $this->saveSettingToModuleConfiguration($moduleId, $name, $value);\n    }\n\n    public function saveBoolean(string $name, bool $value, string $moduleId): void\n    {\n        $this->saveSettingToModuleConfiguration($moduleId, $name, $value);\n    }\n\n    public function saveCollection(string $name, array $value, string $moduleId): void\n    {\n        $this->saveSettingToModuleConfiguration($moduleId, $name, $value);\n    }\n\n    public function exists(string $name, string $moduleId): bool\n    {\n        try {\n            $this->getValue($moduleId, $name);\n        } catch (ModuleSettingNotFountException) {\n            return false;\n        }\n\n        return true;\n    }\n\n    private function saveSettingToModuleConfiguration(string $moduleId, string $name, mixed $value): void\n    {\n        $shopId = $this->context->getCurrentShopId();\n\n        $moduleConfiguration = $this->moduleConfigurationDao->get($moduleId, $shopId);\n        $setting = $moduleConfiguration->getModuleSetting($name);\n        $setting->setValue($value);\n        $this->moduleConfigurationDao->save($moduleConfiguration, $shopId);\n\n        $this->eventDispatcher->dispatch(new SettingChangedEvent($name, $shopId, $moduleId));\n    }\n\n    private function getValue(string $moduleId, string $name): mixed\n    {\n        $shopId = $this->context->getCurrentShopId();\n        $cacheKey = $this->getCacheKey($moduleId, $name);\n\n        if (!$this->moduleCache->exists($cacheKey)) {\n            $this->moduleCache->put(\n                $cacheKey,\n                ['value' => $this->moduleConfigurationDao->get($moduleId, $shopId)->getModuleSetting($name)->getValue()]\n            );\n        }\n\n        return $this->moduleCache->get($cacheKey, $shopId)['value'];\n    }\n\n    private function getCacheKey(string $moduleId, string $name): string\n    {\n        return $moduleId . '-setting-' . $name;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Facade/ModuleSettingServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade;\n\nuse Symfony\\Component\\String\\UnicodeString;\n\ninterface ModuleSettingServiceInterface\n{\n    public function getInteger(string $name, string $moduleId): int;\n    public function getFloat(string $name, string $moduleId): float;\n    public function getString(string $name, string $moduleId): UnicodeString;\n    public function getBoolean(string $name, string $moduleId): bool;\n    public function getCollection(string $name, string $moduleId): array;\n\n    public function saveInteger(string $name, int $value, string $moduleId): void;\n    public function saveFloat(string $name, float $value, string $moduleId): void;\n    public function saveString(string $name, string $value, string $moduleId): void;\n    public function saveBoolean(string $name, bool $value, string $moduleId): void;\n    public function saveCollection(string $name, array $value, string $moduleId): void;\n\n    public function exists(string $name, string $moduleId): bool;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Facade/ModulesDataProvider.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModulePathResolverInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\n\nclass ModulesDataProvider implements ModulesDataProviderInterface\n{\n    public function __construct(\n        private ShopConfigurationDaoInterface $shopConfigurationDao,\n        private ModulePathResolverInterface $modulePathResolver,\n        private ContextInterface $context\n    ) {\n    }\n\n    /** @inheritDoc */\n    public function getModuleIds(): array\n    {\n        $moduleIds = [];\n        $shopId = $this->context->getCurrentShopId();\n        $moduleConfigurations = $this->shopConfigurationDao->get($shopId)->getModuleConfigurations();\n\n        foreach ($moduleConfigurations as $moduleConfiguration) {\n            $moduleIds[] = $moduleConfiguration->getId();\n        }\n\n        return $moduleIds;\n    }\n\n    /** @inheritDoc */\n    public function getModulePaths(): array\n    {\n        $shopId = $this->context->getCurrentShopId();\n\n        $modulePaths = [];\n        $moduleConfigurations = $this->shopConfigurationDao->get($shopId)->getModuleConfigurations();\n\n        foreach ($moduleConfigurations as $moduleConfiguration) {\n            $modulePaths[] = $this->modulePathResolver->getFullModulePathFromConfiguration(\n                $moduleConfiguration->getId(),\n                $this->context->getCurrentShopId()\n            );\n        }\n\n        return $modulePaths;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Facade/ModulesDataProviderInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade;\n\ninterface ModulesDataProviderInterface\n{\n    /**\n     * @return string[]\n     */\n    public function getModuleIds(): array;\n\n    /**\n     * @return string[]\n     */\n    public function getModulePaths(): array;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Facade/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n    public: false\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ActiveModulesDataProviderBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ActiveModulesDataProviderBridge\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ActiveModulesDataProviderInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ActiveModulesDataProvider\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ModulesDataProviderInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ModulesDataProvider\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ModuleSettingServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ModuleSettingService\n    public: true\n"
  },
  {
    "path": "source/Internal/Framework/Module/Install/DataObject/OxidEshopPackage.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject;\n\nclass OxidEshopPackage\n{\n    public function __construct(private string $packagePath)\n    {\n    }\n\n    /**\n     * @return string\n     */\n    public function getPackagePath(): string\n    {\n        return $this->packagePath;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Install/Service/BootstrapModuleInstaller.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\n\nclass BootstrapModuleInstaller implements ModuleInstallerInterface\n{\n    public function __construct(\n        private ModuleFilesInstallerInterface $moduleFilesInstaller,\n        private ModuleConfigurationInstallerInterface $moduleConfigurationInstaller\n    ) {\n    }\n\n    /**\n     * @param OxidEshopPackage $package\n     */\n    public function install(OxidEshopPackage $package): void\n    {\n        $this->moduleFilesInstaller->install($package);\n        $this->moduleConfigurationInstaller->install($package->getPackagePath());\n    }\n\n    /**\n     * @param OxidEshopPackage $package\n     */\n    public function uninstall(OxidEshopPackage $package): void\n    {\n        $this->moduleConfigurationInstaller->uninstall($package->getPackagePath());\n        $this->moduleFilesInstaller->uninstall($package);\n    }\n\n    /**\n     * @param OxidEshopPackage $package\n     * @return bool\n     */\n    public function isInstalled(OxidEshopPackage $package): bool\n    {\n        return $this->moduleFilesInstaller->isInstalled($package)\n               && $this->moduleConfigurationInstaller->isInstalled($package->getPackagePath());\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Install/Service/ModuleConfigurationInstaller.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\Chain\\ClassExtensionsChainDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\ExtensionNotInChainException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\ModuleConfigurationDaoInterface as MetadataDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse Symfony\\Component\\Filesystem\\Path;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Service\\{\n    ModuleConfigurationMergingServiceInterface\n};\n\nclass ModuleConfigurationInstaller implements ModuleConfigurationInstallerInterface\n{\n    public function __construct(\n        private ShopConfigurationDaoInterface $shopConfigurationDao,\n        private BasicContextInterface $context,\n        private ModuleConfigurationMergingServiceInterface $moduleConfigurationMergingService,\n        private MetadataDaoInterface $metadataModuleConfigurationDao,\n        private ModuleConfigurationDaoInterface $moduleConfigurationDao,\n        private ClassExtensionsChainDaoInterface $classExtensionsChainDao\n    ) {\n    }\n\n    public function install(string $moduleSourcePath): void\n    {\n        $moduleConfiguration = $this->metadataModuleConfigurationDao->get($moduleSourcePath);\n        $moduleConfiguration->setModuleSource($this->getModuleSourceRelativePath($moduleSourcePath));\n\n        foreach ($this->shopConfigurationDao->getAll() as $shopId => $shopConfiguration) {\n            $mergedModuleConfiguration = $this\n                ->moduleConfigurationMergingService\n                ->merge($shopConfiguration, $moduleConfiguration)\n                ->getModuleConfiguration($moduleConfiguration->getId());\n\n            $this->moduleConfigurationDao->save($mergedModuleConfiguration, $shopId);\n            $this->classExtensionsChainDao->saveChain($shopId, $shopConfiguration->getClassExtensionsChain());\n        }\n    }\n\n    public function uninstall(string $moduleSourcePath): void\n    {\n        $moduleConfiguration = $this->metadataModuleConfigurationDao->get($moduleSourcePath);\n\n        foreach ($this->shopConfigurationDao->getAll() as $shopId => $shopConfiguration) {\n            if ($shopConfiguration->hasModuleConfiguration($moduleConfiguration->getId())) {\n                $this->removeModuleConfiguration($moduleConfiguration, $shopId);\n            }\n        }\n    }\n\n    public function uninstallById(string $moduleId): void\n    {\n        foreach ($this->shopConfigurationDao->getAll() as $shopId => $shopConfiguration) {\n            if ($shopConfiguration->hasModuleConfiguration($moduleId)) {\n                $this->removeModuleConfiguration($shopConfiguration->getModuleConfiguration($moduleId), $shopId);\n            }\n        }\n    }\n\n    public function isInstalled(string $moduleSourcePath): bool\n    {\n        $moduleConfiguration = $this->metadataModuleConfigurationDao->get($moduleSourcePath);\n\n        return $this->shopConfigurationDao->get($this->context->getDefaultShopId())\n            ->hasModuleConfiguration($moduleConfiguration->getId());\n    }\n\n    private function getModuleSourceRelativePath(string $moduleSourcePath): string\n    {\n        return Path::makeRelative($moduleSourcePath, $this->context->getShopRootPath());\n    }\n\n    private function removeModuleConfiguration(ModuleConfiguration $moduleConfiguration, int $shopId): void\n    {\n        $this->moduleConfigurationDao->delete($moduleConfiguration->getId(), $shopId);\n\n        $chain = $this->classExtensionsChainDao->getChain($shopId);\n        foreach ($moduleConfiguration->getClassExtensions() as $classExtension) {\n            try {\n                $chain->removeExtension($classExtension);\n            } catch (ExtensionNotInChainException) {\n            }\n        }\n        $this->classExtensionsChainDao->saveChain($shopId, $chain);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Install/Service/ModuleConfigurationInstallerInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service;\n\ninterface ModuleConfigurationInstallerInterface\n{\n    /**\n     * @param string $moduleSourcePath\n     */\n    public function install(string $moduleSourcePath): void;\n\n    /**\n     * @param string $moduleSourcePath\n     */\n    public function uninstall(string $moduleSourcePath): void;\n\n    /**\n     * @param string $moduleId\n     */\n    public function uninstallById(string $moduleId): void;\n\n    /**\n     * @param string $moduleSourcePath\n     *\n     * @return bool\n     */\n    public function isInstalled(string $moduleSourcePath): bool;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Install/Service/ModuleFilesInstaller.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModuleAssetsPathResolverInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass ModuleFilesInstaller implements ModuleFilesInstallerInterface\n{\n    public function __construct(\n        private Filesystem $fileSystemService,\n        private ModuleConfigurationDaoInterface $moduleConfigurationDao,\n        private ModuleAssetsPathResolverInterface $moduleAssetsPathResolver\n    ) {\n    }\n\n    /**\n     * @param OxidEshopPackage $package\n     */\n    public function install(OxidEshopPackage $package): void\n    {\n        $symlinkFile = $this->getModuleAssetsPath($package);\n        $modulesAssetsDirectory = Path::getDirectory($symlinkFile);\n        $relativePathToPackageAssets = Path::makeRelative(\n            Path::join($package->getPackagePath(), '/assets'),\n            $modulesAssetsDirectory\n        );\n        $this->fileSystemService->symlink(\n            $relativePathToPackageAssets,\n            $symlinkFile,\n            true\n        );\n    }\n\n    /**\n     * @param OxidEshopPackage $package\n     */\n    public function uninstall(OxidEshopPackage $package): void\n    {\n        $this->fileSystemService->remove($this->getModuleAssetsPath($package));\n    }\n\n    /**\n     * @param OxidEshopPackage $package\n     * @return bool\n     */\n    public function isInstalled(OxidEshopPackage $package): bool\n    {\n        return is_link($this->getModuleAssetsPath($package));\n    }\n\n    private function getModuleAssetsPath(OxidEshopPackage $package): string\n    {\n        return $this->moduleAssetsPathResolver->getAssetsPath($this->getModuleId($package));\n    }\n\n    private function getModuleId(OxidEshopPackage $package): string\n    {\n        return $this\n            ->moduleConfigurationDao\n            ->get($package->getPackagePath())\n            ->getId();\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Install/Service/ModuleFilesInstallerInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\n\ninterface ModuleFilesInstallerInterface\n{\n    /**\n     * @param OxidEshopPackage $package\n     */\n    public function install(OxidEshopPackage $package): void;\n\n    /**\n     * @param OxidEshopPackage $package\n     */\n    public function uninstall(OxidEshopPackage $package): void;\n\n    /**\n     * @param OxidEshopPackage $package\n     * @return bool\n     */\n    public function isInstalled(OxidEshopPackage $package): bool;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Install/Service/ModuleInstaller.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ModuleActivationServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\State\\ModuleStateServiceInterface;\n\nclass ModuleInstaller implements ModuleInstallerInterface\n{\n    public function __construct(\n        private ModuleInstallerInterface $bootstrapModuleInstaller,\n        private ModuleActivationServiceInterface $moduleActivationService,\n        private ModuleConfigurationDaoInterface $moduleConfigurationDao,\n        private ShopConfigurationDaoInterface $shopConfigurationDao,\n        private ModuleStateServiceInterface $moduleStateService\n    ) {\n    }\n\n    /**\n     * @param OxidEshopPackage $package\n     */\n    public function install(OxidEshopPackage $package): void\n    {\n        $this->bootstrapModuleInstaller->install($package);\n    }\n\n    /**\n     * @param OxidEshopPackage $package\n     */\n    public function uninstall(OxidEshopPackage $package): void\n    {\n        $moduleConfiguration = $this->moduleConfigurationDao->get($package->getPackagePath());\n        $this->deactivateModule($moduleConfiguration->getId());\n\n        $this->bootstrapModuleInstaller->uninstall($package);\n    }\n\n    /**\n     * @param OxidEshopPackage $package\n     *\n     * @return bool\n     */\n    public function isInstalled(OxidEshopPackage $package): bool\n    {\n        return $this->bootstrapModuleInstaller->isInstalled($package);\n    }\n\n    /**\n     * @param string $moduleId\n     */\n    private function deactivateModule(string $moduleId): void\n    {\n        foreach ($this->shopConfigurationDao->getAll() as $shopId => $shopConfiguration) {\n            if (\n                $shopConfiguration->hasModuleConfiguration($moduleId)\n                && $this->moduleStateService->isActive($moduleId, $shopId)\n            ) {\n                $this->moduleActivationService->deactivate($moduleId, $shopId);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Install/Service/ModuleInstallerInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\n\ninterface ModuleInstallerInterface\n{\n    /**\n     * @param OxidEshopPackage $package\n     */\n    public function install(OxidEshopPackage $package);\n\n    /**\n     * @param OxidEshopPackage $package\n     */\n    public function uninstall(OxidEshopPackage $package): void;\n\n    /**\n     * @param OxidEshopPackage $package\n     * @return bool\n     */\n    public function isInstalled(OxidEshopPackage $package): bool;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Install/Service/ProjectConfigurationGenerator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\n\nclass ProjectConfigurationGenerator implements ProjectConfigurationGeneratorInterface\n{\n    public function __construct(\n        private ShopConfigurationDaoInterface $shopConfigurationDao,\n        private BasicContextInterface $context\n    ) {\n    }\n\n    /**\n     * Generates default project configuration.\n     */\n    public function generate(): void\n    {\n        $allShopIds = $this->context->getAllShopIds();\n        $this->shopConfigurationDao->deleteAll();\n        foreach ($allShopIds as $shopId) {\n            $this->shopConfigurationDao->save(new ShopConfiguration(), $shopId);\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Install/Service/ProjectConfigurationGeneratorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service;\n\ninterface ProjectConfigurationGeneratorInterface\n{\n    /**\n     * Generates default project configuration.\n     */\n    public function generate(): void;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Install/bootstrap-services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  oxid_esales.module.install.service.bootstrap_module_installer:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\BootstrapModuleInstaller\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleFilesInstallerInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleFilesInstaller\n    arguments:\n      $fileSystemService: '@oxid_esales.symfony.file_system'\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleConfigurationInstallerInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleConfigurationInstaller\n    arguments:\n      $context: '@OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface'\n    public: true\n\n  oxid_esales.module.install.service.installed_shop_project_configuration_generator:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ProjectConfigurationGenerator\n    arguments:\n      $context: '@OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface'\n    public: true"
  },
  {
    "path": "source/Internal/Framework/Module/Install/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  oxid_esales.module.install.service.launched_shop_project_configuration_generator:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ProjectConfigurationGenerator\n    arguments:\n      $context: '@OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface'\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstaller\n    arguments:\n      - '@oxid_esales.module.install.service.bootstrap_module_installer'\n    public: true"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Converter/MetaDataConverterAggregate.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Converter;\n\nclass MetaDataConverterAggregate implements MetaDataConverterInterface\n{\n    /**\n     * @var MetaDataConverterInterface[]\n     */\n    private $converters;\n\n    public function __construct(MetaDataConverterInterface ...$converters)\n    {\n        $this->converters = $converters;\n    }\n\n    /**\n     * @param array $metaData\n     * @return array\n     */\n    public function convert(array $metaData): array\n    {\n        foreach ($this->converters as $converter) {\n            $metaData = $converter->convert($metaData);\n        }\n\n        return $metaData;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Converter/MetaDataConverterInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Converter;\n\ninterface MetaDataConverterInterface\n{\n    public function convert(array $metaData): array;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Converter/ModuleSettingsBooleanConverter.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Converter;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataProvider;\n\nclass ModuleSettingsBooleanConverter implements MetaDataConverterInterface\n{\n    private const CONVERSION_MAP = [\n        'true' => true,\n        '1' => true,\n        'false' => false,\n        '0' => false,\n    ];\n\n    public function convert(array $metaData): array\n    {\n        $convertedMetaData = $metaData;\n        if (isset($metaData[MetaDataProvider::METADATA_SETTINGS])) {\n            $settings = $metaData[MetaDataProvider::METADATA_SETTINGS];\n            foreach ($settings as $key => $setting) {\n                $convertedMetaData[MetaDataProvider::METADATA_SETTINGS][$key] = $this->updateValue($setting);\n            }\n        }\n\n        return $convertedMetaData;\n    }\n\n    /**\n     * @param $setting\n     * @return mixed\n     */\n    private function updateValue($setting)\n    {\n        if (isset($setting['type']) && $setting['type'] === 'bool') {\n            $value = is_string($setting['value']) ? strtolower($setting['value']) : $setting['value'];\n            $setting['value'] = self::CONVERSION_MAP[$value] ?? $setting['value'];\n        }\n        return $setting;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Dao/MetaDataNormalizer.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao;\n\nuse function is_string;\n\nclass MetaDataNormalizer implements MetaDataNormalizerInterface\n{\n    /**\n     * Normalize the array aModule in metadata.php\n     *\n     * @param array $data\n     *\n     * @return array\n     */\n    public function normalizeData(array $data): array\n    {\n        $normalizedMetaData = $data;\n\n        if (isset($normalizedMetaData[MetaDataProvider::METADATA_SETTINGS])) {\n            $normalizedMetaData[MetaDataProvider::METADATA_SETTINGS] = $this->convertModuleSettingConstraintsToArray(\n                $normalizedMetaData[MetaDataProvider::METADATA_SETTINGS]\n            );\n        }\n\n        if (isset($normalizedMetaData[MetaDataProvider::METADATA_TITLE])) {\n            $normalizedMetaData = $this->normalizeMultiLanguageField(\n                $normalizedMetaData,\n                MetaDataProvider::METADATA_TITLE\n            );\n        }\n\n        if (isset($normalizedMetaData[MetaDataProvider::METADATA_DESCRIPTION])) {\n            $normalizedMetaData = $this->normalizeMultiLanguageField(\n                $normalizedMetaData,\n                MetaDataProvider::METADATA_DESCRIPTION\n            );\n        }\n\n        return $normalizedMetaData;\n    }\n\n    /**\n     * @param array $metadataModuleSettings\n     * @return array\n     */\n    private function convertModuleSettingConstraintsToArray(array $metadataModuleSettings): array\n    {\n        foreach ($metadataModuleSettings as $key => $setting) {\n            if (isset($setting['constraints'])) {\n                $metadataModuleSettings[$key]['constraints'] = explode('|', $setting['constraints']);\n            }\n        }\n\n        return $metadataModuleSettings;\n    }\n\n    /**\n     * @param array  $normalizedMetaData\n     * @param string $fieldName\n     * @return array\n     */\n    private function normalizeMultiLanguageField(array $normalizedMetaData, string $fieldName): array\n    {\n        $title = $normalizedMetaData[$fieldName];\n\n        if (is_string($title)) {\n            $defaultLanguage = $normalizedMetaData[MetaDataProvider::METADATA_LANG] ?? 'en';\n            $normalizedTitle = [\n                $defaultLanguage => $title,\n            ];\n            $normalizedMetaData[$fieldName] = $normalizedTitle;\n        }\n\n        return $normalizedMetaData;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Dao/MetaDataNormalizerInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao;\n\ninterface MetaDataNormalizerInterface\n{\n    /**\n     * Normalize the array aModule in metadata.php\n     *\n     * @param array $data\n     *\n     * @return array\n     */\n    public function normalizeData(array $data): array;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Dao/MetaDataProvider.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Converter\\MetaDataConverterInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\InvalidMetaDataException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator\\MetaDataValidatorInterface;\n\nclass MetaDataProvider implements MetaDataProviderInterface\n{\n    public const METADATA_ID = 'id';\n    public const METADATA_METADATA_VERSION = 'metaDataVersion';\n    public const METADATA_MODULE_DATA = 'moduleData';\n    public const METADATA_TITLE = 'title';\n    public const METADATA_DESCRIPTION = 'description';\n    public const METADATA_LANG = 'lang';\n    public const METADATA_THUMBNAIL = 'thumbnail';\n    public const METADATA_AUTHOR = 'author';\n    public const METADATA_URL = 'url';\n    public const METADATA_EMAIL = 'email';\n    public const METADATA_VERSION = 'version';\n    public const METADATA_EXTEND = 'extend';\n    public const METADATA_CONTROLLERS = 'controllers';\n    public const METADATA_EVENTS = 'events';\n    public const METADATA_SETTINGS = 'settings';\n    /**\n     * @deprecated will be removed in v7.0\n     */\n    public const METADATA_FILEPATH = 'metaDataFilePath';\n\n    private string $filePath;\n\n    public function __construct(\n        private MetaDataNormalizerInterface $metaDataNormalizer,\n        private BasicContextInterface $context,\n        private MetaDataValidatorInterface $metaDataValidatorService,\n        private MetaDataConverterInterface $metaDataConverter\n    ) {\n    }\n\n    /**\n     * @param string $filePath\n     *\n     * @return array\n     * @throws InvalidMetaDataException\n     */\n    public function getData(string $filePath): array\n    {\n        if (!is_readable($filePath) || is_dir($filePath)) {\n            throw new \\InvalidArgumentException('File ' . $filePath . ' is not readable or not even a file.');\n        }\n        $this->filePath = $filePath;\n        $normalizedMetaData = $this->getNormalizedMetaDataFileContent();\n\n        return $this->addFilePathToData($normalizedMetaData);\n    }\n\n    /**\n     * @return array\n     * @throws InvalidMetaDataException\n     */\n    private function getNormalizedMetaDataFileContent(): array\n    {\n        /**\n         * The following variables will be overwritten when the metadata file is included.\n         */\n        $sMetadataVersion = null;\n        $aModule = null;\n        include $this->filePath;\n        $metadataVersion = $sMetadataVersion;\n        $moduleData = $aModule;\n\n        $this->validateMetaDataFileVariables($metadataVersion, $moduleData);\n        $this->metaDataValidatorService->validate($moduleData);\n        $moduleData = $this->metaDataConverter->convert($moduleData);\n        $normalizedMetaData = $this->metaDataNormalizer->normalizeData($moduleData);\n\n        if (isset($normalizedMetaData[static::METADATA_EXTEND])) {\n            $normalizedMetaData[static::METADATA_EXTEND] = $this->sanitizeExtendedClasses($normalizedMetaData);\n        }\n\n        return [\n            static::METADATA_METADATA_VERSION => $metadataVersion,\n            static::METADATA_MODULE_DATA      => $normalizedMetaData,\n        ];\n    }\n\n    /**\n     * @param array $normalizedMetaData\n     *\n     * @return array\n     */\n    private function addFilePathToData(array $normalizedMetaData): array\n    {\n        $normalizedMetaData[static::METADATA_FILEPATH] = $this->filePath;\n\n        return $normalizedMetaData;\n    }\n\n    /**\n     * @param mixed $metaDataVersion\n     * @param mixed $moduleData\n     *\n     * @throws InvalidMetaDataException\n     */\n    private function validateMetaDataFileVariables($metaDataVersion, $moduleData): void\n    {\n        if ($metaDataVersion === null || !is_scalar($metaDataVersion)) {\n            throw new InvalidMetaDataException(\n                'The variable $sMetadataVersion must be present in '\n                . $this->filePath . ' and it must be a scalar.'\n            );\n        }\n        if ($moduleData === null || !\\is_array($moduleData)) {\n            throw new InvalidMetaDataException(\n                'The variable $aModule must be present in '\n                . $this->filePath . ' and it must be an array'\n            );\n        }\n    }\n\n    /**\n     * @param array $normalizedMetaData\n     *\n     * @return array\n     */\n    private function sanitizeExtendedClasses(array $normalizedMetaData): array\n    {\n        $sanitizedExtendedClasses = [];\n        $extendedClasses = $normalizedMetaData[static::METADATA_EXTEND] ?? [];\n        foreach ($extendedClasses as $shopClass => $moduleClass) {\n            if ($this->isBackwardsCompatibleClass($shopClass)) {\n                $sanitizedShopClass = $this->getBackwardsCompatibilityClassMap()[strtolower($shopClass)];\n            } else {\n                $sanitizedShopClass = $shopClass;\n            }\n            $sanitizedExtendedClasses[$sanitizedShopClass] = $moduleClass;\n        }\n\n        return $sanitizedExtendedClasses;\n    }\n\n    /**\n     * @param string $className\n     *\n     * @return bool\n     */\n    private function isBackwardsCompatibleClass(string $className): bool\n    {\n        return \\array_key_exists(strtolower($className), $this->getBackwardsCompatibilityClassMap());\n    }\n\n    /**\n     * @return array\n     */\n    private function getBackwardsCompatibilityClassMap(): array\n    {\n        return $this->context->getBackwardsCompatibilityClassMap();\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Dao/MetaDataProviderInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\InvalidMetaDataException;\n\ninterface MetaDataProviderInterface\n{\n    /**\n     * @param string $filePath\n     *\n     * @return array\n     * @throws InvalidMetaDataException\n     */\n    public function getData(string $filePath): array;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Dao/MetaDataSchemataProvider.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\UnsupportedMetaDataVersionException;\n\n/**\n * @deprecated will be removed in v7.0\n */\nclass MetaDataSchemataProvider implements MetaDataSchemataProviderInterface\n{\n    public function __construct(private array $metaDataSchemata)\n    {\n    }\n\n    /**\n     * @return array\n     */\n    public function getMetaDataSchemata(): array\n    {\n        return $this->metaDataSchemata;\n    }\n\n    /**\n     * @param string $metaDataVersion\n     *\n     * @throws UnsupportedMetaDataVersionException\n     *\n     * @return array\n     */\n    public function getMetaDataSchemaForVersion(string $metaDataVersion): array\n    {\n        if (false === array_key_exists($metaDataVersion, $this->metaDataSchemata)) {\n            throw new UnsupportedMetaDataVersionException(\"Metadata version $metaDataVersion is not supported\");\n        }\n\n        return $this->metaDataSchemata[$metaDataVersion];\n    }\n\n    /**\n     * @param string $metaDataVersion\n     *\n     * @throws UnsupportedMetaDataVersionException\n     *\n     * @return array\n     */\n    public function getFlippedMetaDataSchemaForVersion(string $metaDataVersion): array\n    {\n        if (false === array_key_exists($metaDataVersion, $this->metaDataSchemata)) {\n            throw new UnsupportedMetaDataVersionException(\"Metadata version $metaDataVersion is not supported\");\n        }\n\n        return $this->arrayFlipRecursive($this->metaDataSchemata[$metaDataVersion]);\n    }\n\n    /**\n     * Recursively exchange keys and values for a given array\n     *\n     * @param array $metaDataVersion\n     *\n     * @return array\n     */\n    private function arrayFlipRecursive(array $metaDataVersion): array\n    {\n        $transposedArray = [];\n\n        foreach ($metaDataVersion as $key => $item) {\n            if (is_numeric($key) && \\is_string($item)) {\n                $transposedArray[$item] = $key;\n            } elseif (\\is_string($key) && \\is_array($item)) {\n                $transposedArray[$key] = $this->arrayFlipRecursive($item);\n            }\n        }\n\n        return $transposedArray;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Dao/MetaDataSchemataProviderInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao;\n\n/**\n * @deprecated will be removed in v7.0\n */\ninterface MetaDataSchemataProviderInterface\n{\n    /**\n     * @return array\n     */\n    public function getMetaDataSchemata(): array;\n\n    /**\n     * @param string $metaDataVersion\n     *\n     * @return array\n     */\n    public function getMetaDataSchemaForVersion(string $metaDataVersion): array;\n\n    /**\n     * @param string $metaDataVersion\n     *\n     * @return array\n     */\n    public function getFlippedMetaDataSchemaForVersion(string $metaDataVersion): array;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Dao/ModuleConfigurationDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao;\n\n// phpcs:disable\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\DataMapper\\MetaDataToModuleConfigurationDataMapperInterface;\n// phpcs:enable\n\nclass ModuleConfigurationDao implements ModuleConfigurationDaoInterface\n{\n    /**\n     * @var string\n     */\n    private $metadataFileName = 'metadata.php';\n\n    public function __construct(\n        private MetaDataProviderInterface $metadataProvider,\n        private MetaDataToModuleConfigurationDataMapperInterface $metadataMapper\n    ) {\n    }\n\n    /**\n     * @param string $modulePath\n     *\n     * @return ModuleConfiguration\n     * @throws \\OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\InvalidMetaDataException\n     */\n    public function get(string $modulePath): ModuleConfiguration\n    {\n        $metadata = $this->metadataProvider->getData($this->getMetadataFilePath($modulePath));\n        return $this->metadataMapper->fromData($metadata);\n    }\n\n    /**\n     * @param string $moduleFullPath\n     * @return string\n     */\n    private function getMetadataFilePath(string $moduleFullPath): string\n    {\n        return $moduleFullPath . DIRECTORY_SEPARATOR . $this->metadataFileName;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Dao/ModuleConfigurationDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\n\ninterface ModuleConfigurationDaoInterface\n{\n    public function get(string $modulePath): ModuleConfiguration;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/DataMapper/MetaDataMapper.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\DataMapper;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\ClassExtension;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\Controller;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\Event;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\UnsupportedMetaDataValueTypeException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataProvider;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator\\MetaDataSchemaValidatorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setting\\Setting;\n\nclass MetaDataMapper implements MetaDataToModuleConfigurationDataMapperInterface\n{\n    public function __construct(private MetaDataSchemaValidatorInterface $validator)\n    {\n    }\n\n    /**\n     * @param array $metaData\n     *\n     * @return ModuleConfiguration\n     */\n    public function fromData(array $metaData): ModuleConfiguration\n    {\n        $this->validateParameterFormat($metaData);\n\n        $this->validator->validate(\n            $metaData[MetaDataProvider::METADATA_FILEPATH],\n            $metaData[MetaDataProvider::METADATA_METADATA_VERSION],\n            $metaData[MetaDataProvider::METADATA_MODULE_DATA]\n        );\n\n        $moduleData = $metaData[MetaDataProvider::METADATA_MODULE_DATA];\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration\n            ->setId($moduleData[MetaDataProvider::METADATA_ID])\n            ->setVersion($moduleData[MetaDataProvider::METADATA_VERSION] ?? '')\n            ->setDescription($moduleData[MetaDataProvider::METADATA_DESCRIPTION] ?? [])\n            ->setLang($moduleData[MetaDataProvider::METADATA_LANG] ?? '')\n            ->setThumbnail($moduleData[MetaDataProvider::METADATA_THUMBNAIL] ?? '')\n            ->setAuthor($moduleData[MetaDataProvider::METADATA_AUTHOR] ?? '')\n            ->setUrl($moduleData[MetaDataProvider::METADATA_URL] ?? '')\n            ->setEmail($moduleData[MetaDataProvider::METADATA_EMAIL] ?? '');\n\n        if (isset($moduleData[MetaDataProvider::METADATA_TITLE])) {\n            $moduleConfiguration->setTitle($moduleData[MetaDataProvider::METADATA_TITLE]);\n        }\n\n        return $this->mapModuleConfigurationSettings($moduleConfiguration, $metaData);\n    }\n\n    /**\n     * @param ModuleConfiguration $moduleConfiguration\n     * @param array $metaData\n     * @return ModuleConfiguration\n     */\n    private function mapModuleConfigurationSettings(\n        ModuleConfiguration $moduleConfiguration,\n        array $metaData\n    ): ModuleConfiguration {\n        $moduleData = $metaData[MetaDataProvider::METADATA_MODULE_DATA];\n\n        if (isset($moduleData[MetaDataProvider::METADATA_EXTEND])) {\n            foreach ($moduleData[MetaDataProvider::METADATA_EXTEND] as $shopClass => $moduleClass) {\n                $moduleConfiguration->addClassExtension(\n                    new ClassExtension($shopClass, $moduleClass)\n                );\n            }\n        }\n\n        if (isset($moduleData[MetaDataProvider::METADATA_CONTROLLERS])) {\n            foreach ($moduleData[MetaDataProvider::METADATA_CONTROLLERS] as $id => $controllerClassNameSpace) {\n                $moduleConfiguration->addController(\n                    new Controller($id, $controllerClassNameSpace)\n                );\n            }\n        }\n\n        if (isset($moduleData[MetaDataProvider::METADATA_EVENTS])) {\n            foreach ($moduleData[MetaDataProvider::METADATA_EVENTS] as $action => $method) {\n                $moduleConfiguration->addEvent(\n                    new Event($action, $method)\n                );\n            }\n        }\n\n        return $this->mapSettings($moduleConfiguration, $moduleData);\n    }\n\n    /**\n     * @param array $data\n     */\n    private function validateParameterFormat(array $data)\n    {\n        $mandatoryKeys = [\n            MetaDataProvider::METADATA_METADATA_VERSION,\n            MetaDataProvider::METADATA_MODULE_DATA,\n        ];\n        foreach ($mandatoryKeys as $mandatoryKey) {\n            if (false === \\array_key_exists($mandatoryKey, $data)) {\n                throw new \\InvalidArgumentException(\n                    'The key \"' . $mandatoryKey . '\" must be present in the array passed in the parameter'\n                );\n            }\n        }\n    }\n\n    /**\n     * @param ModuleConfiguration $moduleConfiguration\n     * @param $moduleData\n     * @return ModuleConfiguration\n     */\n    private function mapSettings(ModuleConfiguration $moduleConfiguration, $moduleData): ModuleConfiguration\n    {\n        if (isset($moduleData[MetaDataProvider::METADATA_SETTINGS])) {\n            foreach ($moduleData[MetaDataProvider::METADATA_SETTINGS] as $data) {\n                $setting = new Setting();\n                $setting->setName($data['name']);\n                $setting->setType($data['type']);\n\n                if (isset($data['group'])) {\n                    $setting->setGroupName($data['group']);\n                }\n\n                if (isset($data['value'])) {\n                    $setting->setValue($data['value']);\n                }\n\n                if (isset($data['constraints'])) {\n                    $setting->setConstraints($data['constraints']);\n                }\n\n                if (isset($data['position'])) {\n                    $setting->setPositionInGroup((int)$data['position']);\n                }\n\n                $moduleConfiguration->addModuleSetting($setting);\n            }\n        }\n\n        return $moduleConfiguration;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/DataMapper/MetaDataToModuleConfigurationDataMapperInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\DataMapper;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\n\ninterface MetaDataToModuleConfigurationDataMapperInterface\n{\n    /**\n     * @param array $metaData\n     *\n     * @return ModuleConfiguration\n     */\n    public function fromData(array $metaData): ModuleConfiguration;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Exception/InvalidMetaDataException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception;\n\nclass InvalidMetaDataException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Exception/MetaDataVersionException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception;\n\nclass MetaDataVersionException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Exception/ModuleIdNotValidException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception;\n\nclass ModuleIdNotValidException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Exception/SettingNotValidException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception;\n\nclass SettingNotValidException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Exception/UnsupportedMetaDataKeyException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception;\n\nclass UnsupportedMetaDataKeyException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Exception/UnsupportedMetaDataValueTypeException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception;\n\nclass UnsupportedMetaDataValueTypeException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Exception/UnsupportedMetaDataVersionException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception;\n\nclass UnsupportedMetaDataVersionException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Validator/MetaDataSchemaValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\UnsupportedMetaDataKeyException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\UnsupportedMetaDataValueTypeException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataProvider;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataSchemataProviderInterface;\n\n/**\n * @deprecated will be removed in v7.0\n */\nclass MetaDataSchemaValidator implements MetaDataSchemaValidatorInterface\n{\n    private static array $sectionsExcludedFromItemValidation = [\n        MetaDataProvider::METADATA_EXTEND,\n        MetaDataProvider::METADATA_CONTROLLERS,\n        MetaDataProvider::METADATA_EVENTS\n    ];\n\n    private string $currentValidationMetaDataVersion;\n\n    public function __construct(private MetaDataSchemataProviderInterface $metaDataSchemataProvider)\n    {\n    }\n\n    /**\n     * Validate that a given metadata meets the specifications of a given metadata version\n     *\n     * @param string $metaDataFilePath\n     * @param string $metaDataVersion\n     * @param array  $metaData\n     *\n     * @throws UnsupportedMetaDataValueTypeException\n     * @throws UnsupportedMetaDataKeyException\n     */\n    public function validate(string $metaDataFilePath, string $metaDataVersion, array $metaData)\n    {\n        $this->currentValidationMetaDataVersion = $metaDataVersion;\n\n        $supportedMetaDataKeys = $this->metaDataSchemataProvider->getFlippedMetaDataSchemaForVersion(\n            $this->currentValidationMetaDataVersion\n        );\n        foreach ($metaData as $metaDataKey => $value) {\n            if (is_scalar($value)) {\n                $this->validateMetaDataKey($supportedMetaDataKeys, (string) $metaDataKey);\n            } elseif (true === \\is_array($value)) {\n                $this->validateMetaDataSection($supportedMetaDataKeys, $metaDataKey, $value);\n            } else {\n                throw new UnsupportedMetaDataValueTypeException(\n                    'The value type \"' . \\gettype($value) .\n                    '\" is not supported in metadata version ' . $this->currentValidationMetaDataVersion\n                );\n            }\n        }\n    }\n\n    /**\n     * @param array $supportedMetaDataKeys\n     * @param string $metaDataKey\n     *\n     * @throws UnsupportedMetaDataKeyException\n     */\n    private function validateMetaDataKey(array $supportedMetaDataKeys, string $metaDataKey): void\n    {\n        if (false === array_key_exists($metaDataKey, $supportedMetaDataKeys)) {\n            throw new UnsupportedMetaDataKeyException(\n                'The metadata key \"' . $metaDataKey . '\" is not supported in metadata version \"'\n                . $this->currentValidationMetaDataVersion . '\".'\n            );\n        }\n    }\n\n    /**\n     * Validate well defined section items\n     *\n     * @param array  $supportedMetaDataKeys\n     * @param string $sectionName\n     * @param array  $sectionData\n     *\n     * @throws UnsupportedMetaDataKeyException\n     */\n    private function validateMetaDataSectionItems(array $supportedMetaDataKeys, string $sectionName, array $sectionData)\n    {\n        foreach ($sectionData as $sectionItem) {\n            if (\\is_array($sectionItem)) {\n                $metaDataKeys = array_keys($sectionItem);\n                foreach ($metaDataKeys as $metaDataKey) {\n                    $this->validateMetaDataKey($supportedMetaDataKeys[$sectionName], $metaDataKey);\n                }\n            }\n        }\n    }\n\n    /**\n     * Validate a section of metadata like 'blocks' or 'settings', which are multidimensional arrays of well\n     * defined items. There are sections (e.g. extend), which are arrays or multidimensional arrays\n     * of not well defined items. In these cases the items cannot be validated.\n     *\n     * @param array  $supportedMetaDataKeys\n     * @param string $sectionName\n     * @param array  $sectionData\n     * @throws UnsupportedMetaDataKeyException\n     */\n    private function validateMetaDataSection(\n        array $supportedMetaDataKeys,\n        string $sectionName,\n        array $sectionData\n    ): void {\n        $this->validateMetaDataKey($supportedMetaDataKeys, $sectionName);\n        if (\\in_array($sectionName, static::$sectionsExcludedFromItemValidation, true)) {\n            return;\n        }\n        $this->validateMetaDataSectionItems($supportedMetaDataKeys, $sectionName, $sectionData);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Validator/MetaDataSchemaValidatorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator;\n\n/**\n * @deprecated will be removed in v7.0\n */\ninterface MetaDataSchemaValidatorInterface\n{\n    /**\n     * @param string $metaDataFilePath\n     * @param string $metaDataVersion\n     * @param array  $metaData\n     */\n    public function validate(string $metaDataFilePath, string $metaDataVersion, array $metaData);\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Validator/MetaDataValidatorAggregate.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator;\n\nclass MetaDataValidatorAggregate implements MetaDataValidatorInterface\n{\n    /**\n     * @var MetaDataValidatorInterface[]\n     */\n    private $metaDataValidators;\n\n    public function __construct(MetaDataValidatorInterface ...$metaDataValidators)\n    {\n        $this->metaDataValidators = $metaDataValidators;\n    }\n\n    /**\n     * @param array $metaData\n     */\n    public function validate(array $metaData)\n    {\n        foreach ($this->metaDataValidators as $metaDataValidator) {\n            $metaDataValidator->validate($metaData);\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Validator/MetaDataValidatorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator;\n\ninterface MetaDataValidatorInterface\n{\n    public function validate(array $metaData);\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Validator/ModuleIdValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\ModuleIdNotValidException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataProvider;\n\nclass ModuleIdValidator implements MetaDataValidatorInterface\n{\n    /**\n     * @param array $metaData\n     * @throws ModuleIdNotValidException\n     */\n    public function validate(array $metaData): void\n    {\n        $metaDataId = $metaData[MetaDataProvider::METADATA_ID] ?? '';\n        if ($metaDataId === '') {\n            throw new ModuleIdNotValidException('Module ID is not provided in metadata file.');\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Validator/ModuleSettingBooleanValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\SettingNotValidException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataProvider;\n\nclass ModuleSettingBooleanValidator implements MetaDataValidatorInterface\n{\n    private const ALLOWED_VALUES = [\n        0,\n        1,\n        '0',\n        '1',\n        'true',\n        'false',\n        true,\n        false,\n    ];\n\n    /**\n     * @param array $metaData\n     *\n     * @throws SettingNotValidException\n     */\n    public function validate(array $metaData): void\n    {\n        if (isset($metaData[MetaDataProvider::METADATA_SETTINGS])) {\n            $settings = $metaData[MetaDataProvider::METADATA_SETTINGS];\n            foreach ($settings as $setting) {\n                $this->validateSetting($metaData, $setting);\n            }\n        }\n    }\n\n    /**\n     * @param array $metaData\n     * @param array $setting\n     * @throws SettingNotValidException\n     */\n    private function validateSetting(array $metaData, array $setting): void\n    {\n        if (isset($setting['type']) && $setting['type'] === 'bool') {\n            $value = is_string($setting['value']) ? strtolower($setting['value']) : $setting['value'];\n            if (!in_array($value, self::ALLOWED_VALUES, true)) {\n                throw new SettingNotValidException(\n                    'Invalid boolean value- \"' . $setting['value'] . '\" was used for module setting. '\n                    . 'Please update setting value in module \"' . $metaData[MetaDataProvider::METADATA_ID] . '\".'\n                );\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/Validator/SettingValidatorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator;\n\n/**\n * @deprecated will be removed in v7.0\n */\ninterface SettingValidatorInterface\n{\n    /**\n     * @param string $metadataVersion\n     * @param array  $moduleSettings\n     */\n    public function validate(string $metadataVersion, array $moduleSettings);\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/MetaData/bootstrap-services.yaml",
    "content": "parameters:\n  oxid_esales.module.metadata.service.metadata_schemata:\n    '2.0':\n      0: id\n      1: version\n      2: title\n      3: description\n      4: lang\n      5: thumbnail\n      6: author\n      7: url\n      8: email\n      9: extend\n      10: controllers\n      11: templates\n      blocks: [theme, template, block, file, position]\n      settings: [group, name, type, value, constraints, position]\n      12: events\n    '2.1':\n      0: id\n      1: version\n      2: title\n      3: description\n      4: lang\n      5: thumbnail\n      6: author\n      7: url\n      8: email\n      9: extend\n      10: controllers\n      11: templates\n      blocks: [theme, template, block, file, position]\n      settings: [group, name, type, value, constraints, position]\n      12: events\n  deprecated:\n    package: 'oxid-esales/oxideshop-ce'\n    version: '6.5.0'\n    message: \"The attribute will be removed\"\n\nservices:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataProviderInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataProvider\n    arguments:\n      $context: '@OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface'\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\DataMapper\\MetaDataToModuleConfigurationDataMapperInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\DataMapper\\MetaDataMapper\n\n  oxid_esales.module.metadata.datamapper.metadatamapper:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\DataMapper\\MetaDataMapper\n    deprecated:\n      package: 'oxid-esales/oxideshop-ce'\n      version: '6.5.0'\n      message: 'The \"%service_id%\" service is deprecated.'\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator\\MetaDataSchemaValidatorInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator\\MetaDataSchemaValidator\n    deprecated:\n      package: 'oxid-esales/oxideshop-ce'\n      version: '6.5.0'\n      message: 'The \"%service_id%\" service is deprecated.'\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataSchemataProviderInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataSchemataProvider\n    arguments:\n      - '%oxid_esales.module.metadata.service.metadata_schemata%'\n    deprecated:\n      package: 'oxid-esales/oxideshop-ce'\n      version: '6.5.0'\n      message: 'The \"%service_id%\" service is deprecated.'\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataNormalizerInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataNormalizer\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator\\MetaDataValidatorInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator\\MetaDataValidatorAggregate\n    arguments:\n      - '@oxid_esales.module.meta_data.validator.module_id_validator'\n      - '@oxid_esales.module.meta_data.validator.shop_module_setting_boolean_validator'\n\n  oxid_esales.module.meta_data.validator.shop_module_setting_boolean_validator:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator\\ModuleSettingBooleanValidator\n\n  oxid_esales.module.meta_data.validator.module_id_validator:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator\\ModuleIdValidator\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Converter\\MetaDataConverterInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Converter\\MetaDataConverterAggregate\n    arguments:\n      - '@oxid_esales.module.meta_data.converter.shop_module_settings_boolean_converter'\n\n  oxid_esales.module.meta_data.converter.shop_module_settings_boolean_converter:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Converter\\ModuleSettingsBooleanConverter\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\ModuleConfigurationDaoInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\ModuleConfigurationDao\n"
  },
  {
    "path": "source/Internal/Framework/Module/Path/ModuleAssetsPathResolver.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass ModuleAssetsPathResolver implements ModuleAssetsPathResolverInterface\n{\n    public function __construct(private BasicContextInterface $context)\n    {\n    }\n\n    public function getAssetsPath(string $moduleId): string\n    {\n        return Path::join(\n            $this->context->getOutPath(),\n            'modules',\n            $moduleId\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Path/ModuleAssetsPathResolverBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path;\n\nclass ModuleAssetsPathResolverBridge implements ModuleAssetsPathResolverInterface\n{\n    public function __construct(private ModuleAssetsPathResolverInterface $moduleAssetsPathResolver)\n    {\n    }\n\n    public function getAssetsPath(string $moduleId): string\n    {\n        return $this->moduleAssetsPathResolver->getAssetsPath($moduleId);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Path/ModuleAssetsPathResolverBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path;\n\ninterface ModuleAssetsPathResolverBridgeInterface\n{\n    public function getAssetsPath(string $moduleId): string;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Path/ModuleAssetsPathResolverInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path;\n\ninterface ModuleAssetsPathResolverInterface\n{\n    public function getAssetsPath(string $moduleId): string;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Path/ModulePathResolver.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass ModulePathResolver implements ModulePathResolverInterface\n{\n    public function __construct(\n        private ModuleConfigurationDaoInterface $moduleConfigurationDao,\n        private BasicContextInterface $context\n    ) {\n    }\n\n    /**\n     * This method does not validate if the path returned exists. It returns more or less the value from the project\n     * configuration.\n     *\n     * @param string $moduleId\n     * @param int    $shopId\n     *\n     * @return string\n     */\n    public function getFullModulePathFromConfiguration(string $moduleId, int $shopId): string\n    {\n        $moduleConfiguration = $this->moduleConfigurationDao->get($moduleId, $shopId);\n\n        return Path::join($this->context->getShopRootPath(), $moduleConfiguration->getModuleSource());\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Path/ModulePathResolverInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path;\n\ninterface ModulePathResolverInterface\n{\n    /**\n     * @param string $moduleId\n     * @param int    $shopId\n     *\n     * @return string\n     */\n    public function getFullModulePathFromConfiguration(string $moduleId, int $shopId): string;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Path/bootstrap-services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n    public: false\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModulePathResolverInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModulePathResolver\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModuleAssetsPathResolverInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModuleAssetsPathResolver\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModuleAssetsPathResolverBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModuleAssetsPathResolverBridge\n    public: true"
  },
  {
    "path": "source/Internal/Framework/Module/Setting/Event/SettingChangedEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setting\\Event;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\nclass SettingChangedEvent extends Event\n{\n    public function __construct(\n        private string $settingName,\n        private int $shopId,\n        private string $moduleId\n    ) {\n    }\n\n    public function getSettingName(): string\n    {\n        return $this->settingName;\n    }\n\n    public function getShopId(): int\n    {\n        return $this->shopId;\n    }\n\n    public function getModuleId(): string\n    {\n        return $this->moduleId;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setting/Setting.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setting;\n\nclass Setting\n{\n    /**\n     * @var string\n     */\n    private $name;\n\n    /**\n     * @var string\n     */\n    private $type;\n\n    /**\n     * @var mixed\n     */\n    private $value;\n\n    /**\n     * @var array\n     */\n    private $constraints = [];\n\n    /**\n     * @var string\n     */\n    private $groupName = '';\n\n    /**\n     * @var int\n     */\n    private $positionInGroup = 0;\n\n    /**\n     * @return string\n     */\n    public function getName(): string\n    {\n        return $this->name;\n    }\n\n    /**\n     * @param string $name\n     * @return Setting\n     */\n    public function setName(string $name): Setting\n    {\n        $this->name = $name;\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getType(): string\n    {\n        if ($this->type === null) {\n            return gettype($this->value);\n        }\n\n        return $this->type;\n    }\n\n    /**\n     * @param string $type\n     * @return Setting\n     */\n    public function setType(string $type): Setting\n    {\n        $this->type = $type;\n        return $this;\n    }\n\n    /**\n     * @return mixed\n     */\n    public function getValue()\n    {\n        return $this->value;\n    }\n\n    /**\n     * @param mixed $value\n     * @return Setting\n     */\n    public function setValue($value): Setting\n    {\n        $this->value = $value;\n        return $this;\n    }\n\n    /**\n     * @return array\n     */\n    public function getConstraints(): array\n    {\n        return $this->constraints;\n    }\n\n    /**\n     * @param array $constraints\n     * @return Setting\n     */\n    public function setConstraints(array $constraints): Setting\n    {\n        $this->constraints = $constraints;\n        return $this;\n    }\n\n    /**\n     * @return string\n     */\n    public function getGroupName(): string\n    {\n        return $this->groupName;\n    }\n\n    /**\n     * @param string $groupName\n     * @return Setting\n     */\n    public function setGroupName(string $groupName): Setting\n    {\n        $this->groupName = $groupName;\n        return $this;\n    }\n\n    /**\n     * @return int\n     */\n    public function getPositionInGroup(): int\n    {\n        return $this->positionInGroup;\n    }\n\n    /**\n     * @param int $positionInGroup\n     * @return Setting\n     */\n    public function setPositionInGroup(int $positionInGroup): Setting\n    {\n        $this->positionInGroup = $positionInGroup;\n        return $this;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Bridge/ModuleActivationBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ModuleActivationServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\State\\ModuleStateServiceInterface;\n\nclass ModuleActivationBridge implements ModuleActivationBridgeInterface\n{\n    public function __construct(\n        private ModuleActivationServiceInterface $moduleActivationService,\n        private ModuleStateServiceInterface $moduleStateService\n    ) {\n    }\n\n    /**\n     * @param string $moduleId\n     * @param int    $shopId\n     */\n    public function activate(string $moduleId, int $shopId)\n    {\n        $this->moduleActivationService->activate($moduleId, $shopId);\n        Registry::getConfig()->reinitialize();\n    }\n\n    /**\n     * @param string $moduleId\n     * @param int    $shopId\n     */\n    public function deactivate(string $moduleId, int $shopId)\n    {\n        $this->moduleActivationService->deactivate($moduleId, $shopId);\n        Registry::getConfig()->reinitialize();\n    }\n\n    /**\n     * @param string $moduleId\n     * @param int    $shopId\n     * @return bool\n     */\n    public function isActive(string $moduleId, int $shopId): bool\n    {\n        return $this->moduleStateService->isActive($moduleId, $shopId);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Bridge/ModuleActivationBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\ninterface ModuleActivationBridgeInterface\n{\n    /**\n     * @param string $moduleId\n     * @param int    $shopId\n     */\n    public function activate(string $moduleId, int $shopId);\n\n    /**\n     * @param string $moduleId\n     * @param int    $shopId\n     */\n    public function deactivate(string $moduleId, int $shopId);\n\n    /**\n     * @param string $moduleId\n     * @param int    $shopId\n     * @return bool\n     */\n    public function isActive(string $moduleId, int $shopId): bool;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Event/BeforeModuleDeactivationEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Event;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\nclass BeforeModuleDeactivationEvent extends ModuleSetupEvent\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Event/FinalizingModuleActivationEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Event;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\nclass FinalizingModuleActivationEvent extends ModuleSetupEvent\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Event/FinalizingModuleDeactivationEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Event;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\nclass FinalizingModuleDeactivationEvent extends ModuleSetupEvent\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Event/ModuleSetupEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Event;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\nabstract class ModuleSetupEvent extends Event\n{\n    public function __construct(\n        private int $shopId,\n        private string $moduleId\n    ) {\n    }\n\n    /**\n     * @return string\n     */\n    public function getModuleId(): string\n    {\n        return $this->moduleId;\n    }\n\n    /**\n     * @return int\n     */\n    public function getShopId(): int\n    {\n        return $this->shopId;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Event/ServicesYamlConfigurationErrorEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Event;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\n/**\n * This event is dispatched when there are not loadable service classes\n * found in a services.yaml file.\n */\nclass ServicesYamlConfigurationErrorEvent extends Event\n{\n    public function __construct(\n        private string $errorMessage,\n        private string $configurationFilePath\n    ) {\n    }\n\n    /**\n     * Returns the file that is misconfigured\n     *\n     * @return string\n     */\n    public function getConfigurationFilePath(): string\n    {\n        return $this->configurationFilePath;\n    }\n\n    /**\n     * @return string\n     */\n    public function getErrorMessage(): string\n    {\n        return $this->errorMessage;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/EventSubscriber/DispatchLegacyEventsSubscriber.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\EventSubscriber;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Event\\BeforeModuleDeactivationEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Event\\FinalizingModuleActivationEvent;\nuse Symfony\\Component\\EventDispatcher\\EventSubscriberInterface;\n\nclass DispatchLegacyEventsSubscriber implements EventSubscriberInterface\n{\n    public function __construct(private ModuleConfigurationDaoInterface $moduleConfigurationDao)\n    {\n    }\n\n    /**\n     * @param FinalizingModuleActivationEvent $event\n     */\n    public function executeMetadataOnActivationEvent(FinalizingModuleActivationEvent $event)\n    {\n        $this->executeMetadataEvent(\n            'onActivate',\n            $event->getModuleId(),\n            $event->getShopId()\n        );\n    }\n\n    /**\n     * @param BeforeModuleDeactivationEvent $event\n     */\n    public function executeMetadataOnDeactivationEvent(BeforeModuleDeactivationEvent $event)\n    {\n        $this->executeMetadataEvent(\n            'onDeactivate',\n            $event->getModuleId(),\n            $event->getShopId()\n        );\n    }\n\n    /**\n     * @param string $eventName\n     * @param string $moduleId\n     * @param int    $shopId\n     */\n    private function executeMetadataEvent(string $eventName, string $moduleId, int $shopId)\n    {\n        $moduleConfiguration = $this->moduleConfigurationDao->get($moduleId, $shopId);\n\n        if ($moduleConfiguration->hasEvents()) {\n            $events = [];\n\n            foreach ($moduleConfiguration->getEvents() as $event) {\n                $events[$event->getAction()] = $event->getMethod();\n            }\n\n            if (\\is_array($events) && array_key_exists($eventName, $events)) {\n                \\call_user_func($events[$eventName]);\n            }\n        }\n    }\n\n    /**\n     * @return array\n     */\n    public static function getSubscribedEvents(): array\n    {\n        return [\n            FinalizingModuleActivationEvent::class   => 'executeMetadataOnActivationEvent',\n            BeforeModuleDeactivationEvent::class     => 'executeMetadataOnDeactivationEvent',\n        ];\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/EventSubscriber/EventLoggingSubscriber.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\EventSubscriber;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Event\\ServicesYamlConfigurationErrorEvent;\nuse Psr\\Log\\LoggerInterface;\nuse Psr\\Log\\LogLevel;\nuse Symfony\\Component\\EventDispatcher\\EventSubscriberInterface;\n\nclass EventLoggingSubscriber implements EventSubscriberInterface\n{\n    public function __construct(private LoggerInterface $logger)\n    {\n    }\n\n    public function logConfigurationError(ServicesYamlConfigurationErrorEvent $event): void\n    {\n        $this->logger->log(\n            LogLevel::ERROR,\n            $event->getErrorMessage() . ' (' . $event->getConfigurationFilePath() . ')'\n        );\n    }\n\n    /**\n     * @return array\n     */\n    public static function getSubscribedEvents()\n    {\n        return [\n            ServicesYamlConfigurationErrorEvent::class => 'logConfigurationError',\n        ];\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Exception/ControllersDuplicationModuleConfigurationException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Exception;\n\nclass ControllersDuplicationModuleConfigurationException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Exception/DependencyValidationException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Exception;\n\nuse Exception;\n\nclass DependencyValidationException extends Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Exception/InvalidClassExtensionNamespaceException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Exception;\n\nclass InvalidClassExtensionNamespaceException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Exception/InvalidModuleServicesException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Exception;\n\nclass InvalidModuleServicesException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Exception/ModuleSettingHandlerNotFoundException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Exception;\n\nclass ModuleSettingHandlerNotFoundException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Exception/ModuleSettingNotValidException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Exception;\n\nclass ModuleSettingNotValidException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Exception/ModuleSetupValidationException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Exception;\n\nclass ModuleSetupValidationException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Exception/ServicesYamlConfigurationError.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Exception;\n\nclass ServicesYamlConfigurationError extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Service/ActiveClassExtensionChainResolver.php",
    "content": "<?php\n\n/**Utility\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ClassExtensionsChain;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\State\\ModuleStateServiceInterface;\n\nclass ActiveClassExtensionChainResolver implements ActiveClassExtensionChainResolverInterface\n{\n    public function __construct(\n        private ShopConfigurationDaoInterface $shopConfigurationDao,\n        private ModuleStateServiceInterface $moduleStateService\n    ) {\n    }\n\n\n    /**\n     * @param int $shopId\n     *\n     * @return ClassExtensionsChain\n     */\n    public function getActiveExtensionChain(int $shopId): ClassExtensionsChain\n    {\n        $shopConfiguration = $this->shopConfigurationDao->get($shopId);\n        $classExtensionChain = $shopConfiguration->getClassExtensionsChain();\n\n        $activeExtensions = [];\n\n        foreach ($classExtensionChain as $shopClass => $moduleExtensionClasses) {\n            $activeModuleExtensionClasses = $this->getActiveModuleExtensionClasses(\n                $moduleExtensionClasses,\n                $shopId,\n                $shopConfiguration\n            );\n\n            if (!empty($activeModuleExtensionClasses)) {\n                $activeExtensions[$shopClass] = $activeModuleExtensionClasses;\n            }\n        }\n\n        $activeExtensionChain = new ClassExtensionsChain();\n        $activeExtensionChain->setChain($activeExtensions);\n\n        return $activeExtensionChain;\n    }\n\n    /**\n     * @param array             $moduleExtensionClasses\n     * @param int               $shopId\n     * @param ShopConfiguration $shopConfiguration\n     * @return array\n     */\n    private function getActiveModuleExtensionClasses(\n        array $moduleExtensionClasses,\n        int $shopId,\n        ShopConfiguration $shopConfiguration\n    ): array {\n        $activeClasses = [];\n\n        foreach ($moduleExtensionClasses as $extensionClass) {\n            if ($this->isActiveExtension($extensionClass, $shopId, $shopConfiguration)) {\n                $activeClasses[] = $extensionClass;\n            }\n        }\n\n        return $activeClasses;\n    }\n\n    /**\n     * @param string            $classExtension\n     * @param int               $shopId\n     * @param ShopConfiguration $shopConfiguration\n     *\n     * @return bool\n     */\n    private function isActiveExtension(\n        string $classExtension,\n        int $shopId,\n        ShopConfiguration $shopConfiguration\n    ): bool {\n        foreach ($shopConfiguration->getModuleConfigurations() as $moduleConfiguration) {\n            if (\n                $moduleConfiguration->hasClassExtension($classExtension)\n                && $this->moduleStateService->isActive($moduleConfiguration->getId(), $shopId)\n            ) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Service/ActiveClassExtensionChainResolverInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ClassExtensionsChain;\n\ninterface ActiveClassExtensionChainResolverInterface\n{\n    /**\n     * @param int $shopId\n     * @return ClassExtensionsChain\n     */\n    public function getActiveExtensionChain(int $shopId): ClassExtensionsChain;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Service/ModuleActivationService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Event\\ProjectYamlChangedEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Exception\\NoServiceYamlException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModulePathResolverInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Event\\BeforeModuleDeactivationEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Event\\FinalizingModuleActivationEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Event\\FinalizingModuleDeactivationEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator\\ModuleConfigurationValidatorInterface;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\n\nclass ModuleActivationService implements ModuleActivationServiceInterface\n{\n    public function __construct(\n        private readonly ModuleConfigurationDaoInterface $moduleConfigurationDao,\n        private readonly EventDispatcherInterface $eventDispatcher,\n        private readonly ModuleConfigurationValidatorInterface $moduleConfigurationValidator,\n        private readonly ModuleServicesImporterInterface $modulesYamlImportService,\n        private readonly ModulePathResolverInterface $modulePathResolver,\n        private readonly ModuleConfigurationValidatorInterface $deactivationDependencyValidator\n    ) {\n    }\n\n    public function activate(string $moduleId, int $shopId): void\n    {\n        $moduleConfiguration = $this->moduleConfigurationDao->get($moduleId, $shopId);\n\n        $this->moduleConfigurationValidator->validate($moduleConfiguration, $shopId);\n\n        $moduleConfiguration->setActivated(true);\n        $this->moduleConfigurationDao->save($moduleConfiguration, $shopId);\n\n        $this->addModuleServices($moduleId, $shopId);\n\n        $this->eventDispatcher->dispatch(\n            new FinalizingModuleActivationEvent($shopId, $moduleId)\n        );\n    }\n\n    public function deactivate(string $moduleId, int $shopId): void\n    {\n        $moduleConfiguration = $this->moduleConfigurationDao->get($moduleId, $shopId);\n\n        $this->deactivationDependencyValidator->validate($moduleConfiguration, $shopId);\n\n        $this->eventDispatcher->dispatch(new BeforeModuleDeactivationEvent($shopId, $moduleId));\n\n        $this->removeModuleServices($moduleId, $shopId);\n\n        $moduleConfiguration->setActivated(false);\n        $this->moduleConfigurationDao->save($moduleConfiguration, $shopId);\n\n        $this->eventDispatcher->dispatch(\n            new FinalizingModuleDeactivationEvent($shopId, $moduleId)\n        );\n    }\n\n    private function addModuleServices(string $moduleId, int $shopId): void\n    {\n        try {\n            $this->modulesYamlImportService->addImport(\n                $this->modulePathResolver->getFullModulePathFromConfiguration($moduleId, $shopId),\n                $shopId\n            );\n            $this->eventDispatcher->dispatch(new ProjectYamlChangedEvent());\n        } catch (NoServiceYamlException) {\n        }\n    }\n\n    private function removeModuleServices(string $moduleId, int $shopId): void\n    {\n        $this->modulesYamlImportService->removeImport(\n            $this->modulePathResolver->getFullModulePathFromConfiguration($moduleId, $shopId),\n            $shopId\n        );\n        $this->eventDispatcher->dispatch(new ProjectYamlChangedEvent());\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Service/ModuleActivationServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service;\n\ninterface ModuleActivationServiceInterface\n{\n    /**\n     * @param string $moduleId\n     * @param int    $shopId\n     */\n    public function activate(string $moduleId, int $shopId);\n\n    /**\n     * @param string $moduleId\n     * @param int    $shopId\n     */\n    public function deactivate(string $moduleId, int $shopId);\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Service/ModuleDependencyResolver.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleDependencyDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\UnresolvedModuleDependencies;\n\nuse function in_array;\n\nclass ModuleDependencyResolver implements ModuleDependencyResolverInterface\n{\n    public function __construct(\n        private readonly ModuleDependencyDaoInterface $moduleDependencyDao,\n        private readonly ModuleConfigurationDaoInterface $moduleConfigurationDao\n    ) {\n    }\n\n    public function getUnresolvedActivationDependencies(string $moduleId, int $shopId): UnresolvedModuleDependencies\n    {\n        $unresolvedDependencies = new UnresolvedModuleDependencies();\n        $requiredModules = $this->moduleDependencyDao->get($moduleId)->getRequiredModuleIds();\n        $activeModules = $this->getActiveModuleIds($shopId);\n\n        foreach ($requiredModules as $requiredModule) {\n            if (!in_array($requiredModule, $activeModules, true)) {\n                $unresolvedDependencies->addModuleId($requiredModule);\n            }\n        }\n\n        return $unresolvedDependencies;\n    }\n\n    public function getUnresolvedDeactivationDependencies(string $moduleId, int $shopId): UnresolvedModuleDependencies\n    {\n        $unresolvedDependencies = new UnresolvedModuleDependencies();\n        $activeModuleIds = $this->getActiveModuleIds($shopId);\n\n        foreach ($activeModuleIds as $activeModuleId) {\n            if ($this->isRequiredByActiveModule($moduleId, $activeModuleId)) {\n                $unresolvedDependencies->addModuleId($activeModuleId);\n            }\n        }\n\n        return $unresolvedDependencies;\n    }\n\n    private function getActiveModuleIds(int $shopId): array\n    {\n        $activeModuleIds = [];\n\n        foreach ($this->moduleConfigurationDao->getAll($shopId) as $moduleConfiguration) {\n            if ($moduleConfiguration->isActivated()) {\n                $activeModuleIds[] = $moduleConfiguration->getId();\n            }\n        }\n\n        return $activeModuleIds;\n    }\n\n    private function isRequiredByActiveModule(string $moduleId, string $activeModule): bool\n    {\n        return\n            $moduleId !== $activeModule &&\n            $this->moduleDependencyDao->get($activeModule)->isRequiredModule($moduleId);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Service/ModuleDependencyResolverInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\UnresolvedModuleDependencies;\n\ninterface ModuleDependencyResolverInterface\n{\n    public function getUnresolvedActivationDependencies(string $moduleId, int $shopId): UnresolvedModuleDependencies;\n\n    public function getUnresolvedDeactivationDependencies(string $moduleId, int $shopId): UnresolvedModuleDependencies;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Service/ModuleServicesImporter.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\DataObject\\DIConfigWrapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Exception\\NoServiceYamlException;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse Symfony\\Component\\Yaml\\Yaml;\nuse Symfony\\Component\\Filesystem\\Path;\n\n/**\n * @internal\n */\nclass ModuleServicesImporter implements ModuleServicesImporterInterface\n{\n    public function __construct(\n        private BasicContextInterface $context\n    ) {\n    }\n\n    public function addImport(string $serviceDir, int $shopId): void\n    {\n        if (!file_exists($this->getServiceFilePath($serviceDir))) {\n            throw new NoServiceYamlException();\n        }\n        $services = $this->loadDIConfigFile($this->context->getActiveModuleServicesFilePath($shopId));\n        $services->addImport($this->getServiceRelativeFilePath($serviceDir, $shopId));\n\n        $this->saveServicesFile($services, $shopId);\n    }\n\n    public function removeImport(string $serviceDir, int $shopId): void\n    {\n        $services = $this->loadDIConfigFile($this->context->getActiveModuleServicesFilePath($shopId));\n        $services->removeImport($this->getServiceRelativeFilePath($serviceDir, $shopId));\n\n        $this->saveServicesFile($services, $shopId);\n    }\n\n    private function getServiceRelativeFilePath(string $serviceDir, int $shopId): string\n    {\n        return Path::makeRelative(\n            $this->getServiceFilePath($serviceDir),\n            Path::getDirectory($this->context->getActiveModuleServicesFilePath($shopId))\n        );\n    }\n\n    private function loadDIConfigFile(string $path): DIConfigWrapper\n    {\n        $yamlArray = [];\n\n        if (file_exists($path)) {\n            $yamlArray = Yaml::parse(file_get_contents($path), Yaml::PARSE_CUSTOM_TAGS) ?? [];\n        }\n\n        return new DIConfigWrapper($yamlArray);\n    }\n\n    private function saveServicesFile(DIConfigWrapper $config, int $shopId): void\n    {\n        file_put_contents(\n            $this->context->getActiveModuleServicesFilePath($shopId),\n            Yaml::dump($config->getConfigAsArray(), 3, 2)\n        );\n    }\n\n    private function getServiceFilePath(string $serviceDir): string\n    {\n        return Path::join($serviceDir, 'services.yaml');\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Service/ModuleServicesImporterInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service;\n\n/**\n * @internal\n */\ninterface ModuleServicesImporterInterface\n{\n    public function addImport(string $serviceDir, int $shopId): void;\n    public function removeImport(string $serviceDir, int $shopId): void;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Validator/ActivationDependencyValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Exception\\DependencyValidationException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ModuleDependencyResolverInterface;\n\nclass ActivationDependencyValidator implements ModuleConfigurationValidatorInterface\n{\n    public function __construct(private readonly ModuleDependencyResolverInterface $moduleDependencyResolver)\n    {\n    }\n\n    /**\n     * @throws DependencyValidationException\n     */\n    public function validate(ModuleConfiguration $configuration, int $shopId): void\n    {\n        $unresolvedDependencies =\n            $this->moduleDependencyResolver->getUnresolvedActivationDependencies($configuration->getId(), $shopId);\n\n        if (!$unresolvedDependencies->hasModuleDependencies()) {\n            return;\n        }\n\n        throw new DependencyValidationException(\n            sprintf(\n                'Module \"%s\" has unfulfilled dependencies in shop \"%d\" and can not be activated. \n                \"%1$s\" requires the following modules to be activated: \"%s\"\n                Make sure all dependencies are resolved and try again.',\n                $configuration->getId(),\n                $shopId,\n                implode(', ', $unresolvedDependencies->getModuleIds())\n            )\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Validator/ClassExtensionsValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\ShopAdapterInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Exception\\InvalidClassExtensionNamespaceException;\n\nclass ClassExtensionsValidator implements ModuleConfigurationValidatorInterface\n{\n    public function __construct(private ShopAdapterInterface $shopAdapter)\n    {\n    }\n\n    /**\n     * @param ModuleConfiguration $configuration\n     * @param int                 $shopId\n     *\n     * @throws InvalidClassExtensionNamespaceException\n     */\n    public function validate(ModuleConfiguration $configuration, int $shopId): void\n    {\n        if ($configuration->hasClassExtensions()) {\n            foreach ($configuration->getClassExtensions() as $extension) {\n                if ($this->shopAdapter->isNamespace($extension->getShopClassName())) {\n                    $this->validateClassToBePatchedNamespace($extension->getShopClassName());\n                }\n            }\n        }\n    }\n\n    /**\n     * @param string $namespace\n     * @throws InvalidClassExtensionNamespaceException\n     */\n    private function validateClassToBePatchedNamespace(string $namespace)\n    {\n        if ($this->shopAdapter->isShopEditionNamespace($namespace)) {\n            throw new InvalidClassExtensionNamespaceException(\n                'Module should not extend shop edition class: ' . $namespace\n            );\n        }\n\n        if ($this->shopAdapter->isShopUnifiedNamespace($namespace) && !class_exists($namespace)) {\n            throw new InvalidClassExtensionNamespaceException(\n                'Module tries to extend non existent shop class: ' . $namespace\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Validator/ControllersValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator;\n\n// phpcs:disable\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\Controller;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Exception\\ControllersDuplicationModuleConfigurationException;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\ShopAdapterInterface;\nuse Psr\\Log\\LoggerInterface;\n// phpcs:enable\n\nuse function in_array;\nuse function array_key_exists;\n\nclass ControllersValidator implements ModuleConfigurationValidatorInterface\n{\n    public function __construct(\n        private ShopAdapterInterface $shopAdapter,\n        private ShopConfigurationDaoInterface $shopConfigurationDao,\n        private LoggerInterface $logger\n    ) {\n    }\n\n    /**\n     * @param ModuleConfiguration $configuration\n     * @param int                 $shopId\n     *\n     * @throws ControllersDuplicationModuleConfigurationException\n     */\n    public function validate(ModuleConfiguration $configuration, int $shopId): void\n    {\n        if ($configuration->hasControllers()) {\n            $controllerClassMap = $this->getControllersClassMap($shopId);\n\n            foreach ($configuration->getControllers() as $controller) {\n                if (!$this->controllerAlreadyExistsInMap($controller, $controllerClassMap)) {\n                    $this->validateKeyDuplication($controller, $controllerClassMap);\n                    $this->validateNamespaceDuplication($controller, $controllerClassMap);\n                } else {\n                    /**\n                     * @TODO this is a wrong place to check and log database discrepancy, not only controllers should be\n                     *       checked. It should be moved to separate module data discrepancy checker outside the module\n                     *       validation.\n                     */\n                    $this->logger->error(\n                        'Module data discrepancy error: module data (controller with id '\n                        . $controller->getId() . ' and namespace: '\n                        . $controller->getControllerClassNameSpace() . ' ) for module '\n                        . $configuration->getId() . ' was present in the database before the module activation'\n                    );\n                }\n            }\n        }\n    }\n\n    private function controllerAlreadyExistsInMap(Controller $controller, array $controllerClassMap): bool\n    {\n        return array_key_exists(strtolower($controller->getId()), $controllerClassMap)\n            && $controllerClassMap[strtolower($controller->getId())] === $controller->getControllerClassNameSpace();\n    }\n\n    /**\n     * @param int $shopId\n     * @return array\n     */\n    private function getModulesControllerClassMap(int $shopId): array\n    {\n        $moduleControllersClassMap = [];\n\n        foreach ($this->shopConfigurationDao->get($shopId)->getModuleConfigurations() as $moduleConfiguration) {\n            if ($moduleConfiguration->isActivated()) {\n                foreach ($moduleConfiguration->getControllers() as $controller) {\n                    $moduleControllersClassMap[$controller->getId()] = $controller->getControllerClassNameSpace();\n                }\n            }\n        }\n\n        return $moduleControllersClassMap;\n    }\n\n    /**\n     * @param Controller $controller\n     * @param array $controllerClassMap\n     * @throws ControllersDuplicationModuleConfigurationException\n     */\n    private function validateKeyDuplication(Controller $controller, array $controllerClassMap): void\n    {\n        if (array_key_exists(strtolower($controller->getId()), $controllerClassMap)) {\n            throw new ControllersDuplicationModuleConfigurationException(\n                'Controller key duplication: ' . $controller->getId()\n            );\n        }\n    }\n\n    /**\n     * @param Controller $controller\n     * @param array $controllerClassMap\n     * @throws ControllersDuplicationModuleConfigurationException\n     */\n    private function validateNamespaceDuplication(Controller $controller, array $controllerClassMap): void\n    {\n        if (in_array($controller->getControllerClassNameSpace(), $controllerClassMap, true)) {\n            throw new ControllersDuplicationModuleConfigurationException(\n                'Controller namespace duplication: ' . $controller->getControllerClassNameSpace()\n            );\n        }\n    }\n\n    /**\n     * @param int $shopId\n     * @return array\n     */\n    private function getControllersClassMap(int $shopId): array\n    {\n        return array_merge(\n            $this->shopAdapter->getShopControllerClassMap(),\n            $this->getModulesControllerClassMap($shopId)\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Validator/DeactivationDependencyValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Exception\\DependencyValidationException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ModuleDependencyResolverInterface;\n\nclass DeactivationDependencyValidator implements ModuleConfigurationValidatorInterface\n{\n    public function __construct(private readonly ModuleDependencyResolverInterface $moduleDependencyResolver)\n    {\n    }\n\n    /**\n     * @throws DependencyValidationException\n     */\n    public function validate(ModuleConfiguration $configuration, int $shopId): void\n    {\n        $unresolvedDependencies =\n            $this->moduleDependencyResolver->getUnresolvedDeactivationDependencies($configuration->getId(), $shopId);\n\n        if (!$unresolvedDependencies->hasModuleDependencies()) {\n            return;\n        }\n\n        throw new DependencyValidationException(\n            sprintf(\n                'Module \"%s\" has unfulfilled dependencies in shop \"%d\" and can not be deactivated. \n                \"%1$s\" requires the following modules to be deactivated: \"%s\"\n                Make sure all dependencies are resolved and try again.',\n                $configuration->getId(),\n                $shopId,\n                implode(', ', $unresolvedDependencies->getModuleIds())\n            )\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Validator/EventsValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Exception\\ModuleSettingNotValidException;\n\nuse function is_array;\n\nclass EventsValidator implements ModuleConfigurationValidatorInterface\n{\n    /** @var array $validEvents */\n    private $validEvents = ['onActivate', 'onDeactivate'];\n\n    /**\n     * There is another service for syntax validation and we won't validate syntax in this method.\n     *\n     * @param ModuleConfiguration $configuration\n     * @param int                 $shopId\n     *\n     * @throws ModuleSettingNotValidException\n     */\n    public function validate(ModuleConfiguration $configuration, int $shopId): void\n    {\n        if ($configuration->hasEvents()) {\n            $events = [];\n\n            foreach ($configuration->getEvents() as $event) {\n                $events[$event->getAction()] = $event->getMethod();\n            }\n            foreach ($this->validEvents as $validEventName) {\n                if (is_array($events) && \\array_key_exists($validEventName, $events)) {\n                    $this->checkIfMethodIsCallable($events[$validEventName]);\n                }\n            }\n        }\n    }\n\n    /**\n     * @param string $method\n     *\n     * @throws ModuleSettingNotValidException\n     */\n    private function checkIfMethodIsCallable(string $method): void\n    {\n        if (!\\is_callable($method)) {\n            throw new ModuleSettingNotValidException('The method ' . $method . ' is not callable.');\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Validator/ModuleConfigurationValidatorAggregate.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\n\nclass ModuleConfigurationValidatorAggregate implements ModuleConfigurationValidatorInterface\n{\n    private array $validators;\n\n    public function __construct(ModuleConfigurationValidatorInterface ...$validators)\n    {\n        $this->validators = $validators;\n    }\n\n    public function validate(ModuleConfiguration $configuration, int $shopId): void\n    {\n        foreach ($this->validators as $validator) {\n            $validator->validate($configuration, $shopId);\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Validator/ModuleConfigurationValidatorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\n\ninterface ModuleConfigurationValidatorInterface\n{\n    public function validate(ModuleConfiguration $configuration, int $shopId): void;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/Validator/ServicesYamlValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\ContainerBuilder;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Dao\\ProjectYamlDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\DataObject\\DIConfigWrapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Exception\\NoServiceYamlException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModulePathResolverInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Exception\\InvalidModuleServicesException;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder as SymfonyContainer;\nuse Symfony\\Component\\Filesystem\\Path;\nuse Throwable;\n\nclass ServicesYamlValidator implements ModuleConfigurationValidatorInterface\n{\n    private DIConfigWrapper $configFile;\n    private DIConfigWrapper $originalConfigFile;\n    private SymfonyContainer $fakeContainer;\n    private string $moduleId;\n    private int $shopId;\n\n    public function __construct(\n        private readonly ContextInterface $context,\n        private readonly ProjectYamlDaoInterface $projectYamlDao,\n        private readonly ModulePathResolverInterface $modulePathResolver\n    ) {\n    }\n\n    public function validate(ModuleConfiguration $configuration, int $shopId): void\n    {\n        $this->backupProjectConfigFile();\n        $this->moduleId = $configuration->getId();\n        $this->shopId = $shopId;\n        try {\n            $this->importValidatedModulesServicesIntoProjectConfigFile();\n            $this->buildFakeContainerWithModifiedProjectConfigFile();\n            $this->validateContainerDefinitions();\n        } catch (NoServiceYamlException) {\n            return;\n        } catch (Throwable $e) {\n            throw new InvalidModuleServicesException(\n                message: \"Service YAML for module [$this->moduleId] is invalid\",\n                previous: $e\n            );\n        } finally {\n            $this->restoreProjectConfigFile();\n        }\n    }\n\n    private function backupProjectConfigFile(): void\n    {\n        $this->configFile = $this->projectYamlDao->loadProjectConfigFile();\n        $this->originalConfigFile = clone $this->configFile;\n    }\n\n    /**\n     * We use project service file just to run validation, actual\n     * module's service.yaml will be imported into active_module_services.yaml.\n     * @throws NoServiceYamlException\n     */\n    private function importValidatedModulesServicesIntoProjectConfigFile(): void\n    {\n        $importFilePath = Path::join(\n            $this->modulePathResolver->getFullModulePathFromConfiguration($this->moduleId, $this->shopId),\n            'services.yaml'\n        );\n        if (!realpath($importFilePath)) {\n            throw new NoServiceYamlException();\n        }\n        $this->configFile->addImport($importFilePath);\n        $this->projectYamlDao->saveProjectConfigFile($this->configFile);\n    }\n\n    private function buildFakeContainerWithModifiedProjectConfigFile(): void\n    {\n        $this->fakeContainer = (new ContainerBuilder($this->context, $this->context->getCurrentShopId()))\n            ->getContainer();\n        foreach ($this->fakeContainer->getDefinitions() as $definition) {\n            $definition->setPublic(true);\n        }\n        $this->fakeContainer->compile(true);\n    }\n\n    private function validateContainerDefinitions(): void\n    {\n        foreach ($this->fakeContainer->getDefinitions() as $definitionKey => $definition) {\n            $this->fakeContainer->get($definitionKey);\n        }\n    }\n\n    private function restoreProjectConfigFile(): void\n    {\n        $this->projectYamlDao->saveProjectConfigFile($this->originalConfigFile);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Setup/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridge\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ModuleDependencyResolverInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ModuleDependencyResolver\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ModuleActivationServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ModuleActivationService\n    arguments:\n      $deactivationDependencyValidator: '@oxid_esales.module.setup.validator.deactivation_dependency_validator'\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ModuleServicesImporterInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ModuleServicesImporter\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator\\ModuleConfigurationValidatorInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator\\ModuleConfigurationValidatorAggregate\n    arguments:\n      - '@oxid_esales.module.setup.validator.activation_dependency_validator'\n      - '@oxid_esales.module.setup.validator.controllers_module_setting_validator'\n      - '@oxid_esales.module.setup.validator.class_extensions_module_setting_validator'\n      - '@oxid_esales.module.setup.validator.events_module_setting_validator'\n      - '@oxid_esales.module.setup.validator.services_yaml_validator'\n\n  oxid_esales.module.setup.validator.controllers_module_setting_validator:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator\\ControllersValidator\n\n  oxid_esales.module.setup.validator.class_extensions_module_setting_validator:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator\\ClassExtensionsValidator\n\n  oxid_esales.module.setup.validator.events_module_setting_validator:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator\\EventsValidator\n\n  oxid_esales.module.setup.validator.activation_dependency_validator:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator\\ActivationDependencyValidator\n\n  oxid_esales.module.setup.validator.deactivation_dependency_validator:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator\\DeactivationDependencyValidator\n\n  oxid_esales.module.setup.validator.services_yaml_validator:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator\\ServicesYamlValidator\n    arguments:\n      - '@OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface'\n\n  oxid_esales.module.setup.service.eventsubscriber.dispatch_legacy_events_subscriber:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\EventSubscriber\\DispatchLegacyEventsSubscriber\n    tags:\n      - { name: kernel.event_subscriber }\n\n  oxid_esales.module.setup.service.eventsubscriber.event_logging_subscriber:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\EventSubscriber\\EventLoggingSubscriber\n    tags:\n      - { name: kernel.event_subscriber }\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ActiveClassExtensionChainResolverInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ActiveClassExtensionChainResolver\n    public: true\n"
  },
  {
    "path": "source/Internal/Framework/Module/State/ModuleStateService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\State;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\n\nclass ModuleStateService implements ModuleStateServiceInterface\n{\n    public function __construct(private ModuleConfigurationDaoInterface $moduleConfigurationDao)\n    {\n    }\n\n    /**\n     * @param string $moduleId\n     * @param int    $shopId\n     * @return bool\n     */\n    public function isActive(string $moduleId, int $shopId): bool\n    {\n        return $this->moduleConfigurationDao->get($moduleId, $shopId)->isActivated();\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/State/ModuleStateServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\State;\n\ninterface ModuleStateServiceInterface\n{\n    /**\n     * @param string $moduleId\n     * @param int    $shopId\n     * @return bool\n     */\n    public function isActive(string $moduleId, int $shopId): bool;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/State/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\State\\ModuleStateServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\State\\ModuleStateService\n    public: true"
  },
  {
    "path": "source/Internal/Framework/Module/Template/Locator/ModulesMenuFileLocator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Template\\Locator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ActiveModulesDataProviderInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator\\NavigationFileLocatorInterface;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass ModulesMenuFileLocator implements NavigationFileLocatorInterface\n{\n    /** @var string */\n    private $fileName = 'menu.xml';\n\n    public function __construct(\n        private ActiveModulesDataProviderInterface $activeModulesDataProvider,\n        private Filesystem $filesystem\n    ) {\n    }\n\n    /** @inheritDoc */\n    public function locate(): array\n    {\n        $menuFiles = [];\n        foreach ($this->activeModulesDataProvider->getModulePaths() as $modulePath) {\n            $menuFilePath = Path::join($modulePath, $this->fileName);\n            if ($this->filesystem->exists($menuFilePath)) {\n                $menuFiles[] = $menuFilePath;\n            }\n        }\n        return $menuFiles;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Template/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n    public: false\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Template\\Locator\\ModulesMenuFileLocator:\n    tags:\n      - { name: 'oxid.menu.file.locator', priority: -255 }\n    arguments:\n      Symfony\\Component\\Filesystem\\Filesystem: '@oxid_esales.symfony.file_system'"
  },
  {
    "path": "source/Internal/Framework/Module/Translation/Bridge/AdminAreaModuleTranslationFileLocatorBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\{\n    Locator\\AdminAreaModuleTranslationFileLocatorInterface};\n\nclass AdminAreaModuleTranslationFileLocatorBridge implements AdminAreaModuleTranslationFileLocatorBridgeInterface\n{\n    public function __construct(private AdminAreaModuleTranslationFileLocatorInterface $moduleTranslationFileLocator)\n    {\n    }\n\n    /**\n     * @param string $lang\n     *\n     * @return array\n     */\n    public function locate(string $lang): array\n    {\n        return $this->moduleTranslationFileLocator->locate($lang);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Translation/Bridge/AdminAreaModuleTranslationFileLocatorBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Bridge;\n\ninterface AdminAreaModuleTranslationFileLocatorBridgeInterface\n{\n    /**\n     * @param string $lang\n     *\n     * @return array\n     */\n    public function locate(string $lang): array;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Translation/Bridge/FrontendModuleTranslationFileLocatorBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Locator\\{\n    FrontendModuleTranslationFileLocatorInterface};\n\nclass FrontendModuleTranslationFileLocatorBridge implements FrontendModuleTranslationFileLocatorBridgeInterface\n{\n    public function __construct(private FrontendModuleTranslationFileLocatorInterface $moduleTranslationFileLocator)\n    {\n    }\n\n    /**\n     * @param string $lang\n     *\n     * @return array\n     */\n    public function locate(string $lang): array\n    {\n        return $this->moduleTranslationFileLocator->locate($lang);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Translation/Bridge/FrontendModuleTranslationFileLocatorBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Bridge;\n\ninterface FrontendModuleTranslationFileLocatorBridgeInterface\n{\n    /**\n     * @param string $lang\n     *\n     * @return array\n     */\n    public function locate(string $lang): array;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Translation/Bridge/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n    public: false\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Bridge\\AdminAreaModuleTranslationFileLocatorBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Bridge\\AdminAreaModuleTranslationFileLocatorBridge\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Bridge\\FrontendModuleTranslationFileLocatorBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Bridge\\FrontendModuleTranslationFileLocatorBridge\n    public: true"
  },
  {
    "path": "source/Internal/Framework/Module/Translation/Locator/AdminAreaModuleTranslationFileLocator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Locator;\n\n// phpcs:disable\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Locator\\ModuleTranslationFileLocatorAbstract as LocatorAbstract;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Locator\\AdminAreaModuleTranslationFileLocatorInterface as LocatorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ModulesDataProviderInterface;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\n// phpcs:enable\n\nclass AdminAreaModuleTranslationFileLocator extends LocatorAbstract implements LocatorInterface\n{\n    public function __construct(\n        private ModulesDataProviderInterface $modulesDataProvider,\n        private Filesystem $filesystem,\n        private string $adminThemeName\n    ) {\n    }\n\n    /**\n     * @param string $lang\n     *\n     * @return array\n     */\n    public function locate(string $lang): array\n    {\n        $langFiles = [];\n\n        foreach ($this->modulesDataProvider->getModulePaths() as $moduleLangPath) {\n            $moduleLangPath = Path::join(\n                $this->checkAndAddApplicationFolder($this->filesystem, $moduleLangPath),\n                'views',\n                $this->adminThemeName,\n                $lang\n            );\n\n            $langFiles = $this->appendLangFiles($langFiles, $moduleLangPath);\n\n            $langFiles = $this->appendModuleOptionsFile($langFiles, $moduleLangPath);\n        }\n\n        return $langFiles;\n    }\n\n    private function appendModuleOptionsFile(array $langFiles, string $moduleLangPath): array\n    {\n        $langFilePath = Path::join($moduleLangPath, 'module_options.php');\n\n        if ($this->filesystem->exists($langFilePath)) {\n            $langFiles[] = $langFilePath;\n        }\n\n        return $langFiles;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Translation/Locator/AdminAreaModuleTranslationFileLocatorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Locator;\n\ninterface AdminAreaModuleTranslationFileLocatorInterface\n{\n    /**\n     * @param string $lang\n     *\n     * @return array\n     */\n    public function locate(string $lang): array;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Translation/Locator/FrontendModuleTranslationFileLocator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Locator;\n\n// phpcs:disable\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Locator\\ModuleTranslationFileLocatorAbstract as LocatorAbstract;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Locator\\FrontendModuleTranslationFileLocatorInterface as LocatorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ActiveModulesDataProviderInterface;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\n// phpcs:enable\n\nclass FrontendModuleTranslationFileLocator extends LocatorAbstract implements LocatorInterface\n{\n    public function __construct(\n        private ActiveModulesDataProviderInterface $activeModulesDataProvider,\n        private Filesystem $filesystem\n    ) {\n    }\n\n    /**\n     * @param string $lang\n     *\n     * @return array\n     */\n    public function locate(string $lang): array\n    {\n        $langFiles = [];\n\n        foreach ($this->activeModulesDataProvider->getModulePaths() as $moduleLangPath) {\n            $moduleLangPath = Path::join(\n                $this->checkAndAddApplicationFolder($this->filesystem, $moduleLangPath),\n                'translations',\n                $lang\n            );\n\n            $langFiles = $this->appendLangFiles($langFiles, $moduleLangPath);\n        }\n\n        return $langFiles;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Translation/Locator/FrontendModuleTranslationFileLocatorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Locator;\n\ninterface FrontendModuleTranslationFileLocatorInterface\n{\n    /**\n     * @param string $lang\n     *\n     * @return array\n     */\n    public function locate(string $lang): array;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Translation/Locator/ModuleTranslationFileLocatorAbstract.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Locator;\n\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\n\nabstract class ModuleTranslationFileLocatorAbstract\n{\n    protected function checkAndAddApplicationFolder(Filesystem $filesystem, string $moduleLangPath): string\n    {\n        $applicationFolder = Path::join($moduleLangPath, 'Application');\n\n        if ($filesystem->exists($applicationFolder)) {\n            return $applicationFolder;\n        }\n\n        return $moduleLangPath;\n    }\n\n    protected function appendLangFiles(array $langFiles, string $moduleLangPath): array\n    {\n        $files = glob(Path::join($moduleLangPath, '*_lang.php'));\n\n        if (\\is_array($files) && count($files)) {\n            foreach ($files as $file) {\n                if (!strpos($file, 'cust_lang.php')) {\n                    $langFiles[] = $file;\n                }\n            }\n        }\n\n        return $langFiles;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Module/Translation/Locator/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n    public: false\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Locator\\AdminAreaModuleTranslationFileLocatorInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Locator\\AdminAreaModuleTranslationFileLocator\n    arguments:\n      - '@OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ModulesDataProviderInterface'\n      - '@oxid_esales.symfony.file_system'\n      - '%oxid_esales.theme.admin.name%'\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Locator\\FrontendModuleTranslationFileLocatorInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Locator\\FrontendModuleTranslationFileLocator\n    arguments:\n      - '@OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ActiveModulesDataProviderInterface'\n      - '@oxid_esales.symfony.file_system'\n    public: true"
  },
  {
    "path": "source/Internal/Framework/Module/Translation/services.yaml",
    "content": "imports:\n  - { resource: Bridge/services.yaml }\n  - { resource: Locator/services.yaml }"
  },
  {
    "path": "source/Internal/Framework/Module/services.yaml",
    "content": "imports:\n   - { resource: Configuration/services.yaml }\n   - { resource: Facade/services.yaml }\n   - { resource: Setup/services.yaml }\n   - { resource: Cache/services.yaml }\n   - { resource: State/services.yaml }\n   - { resource: Install/services.yaml }\n   - { resource: Command/services.yaml }\n   - { resource: Template/services.yaml }\n   - { resource: Translation/services.yaml }\n"
  },
  {
    "path": "source/Internal/Framework/RateLimiter/ApiRateLimitListener.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\RateLimiter;\n\nuse Symfony\\Component\\EventDispatcher\\EventSubscriberInterface;\nuse Symfony\\Component\\HttpFoundation\\JsonResponse;\nuse Symfony\\Component\\HttpFoundation\\Request;\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Symfony\\Component\\HttpKernel\\Event\\RequestEvent;\nuse Symfony\\Component\\HttpKernel\\KernelEvents;\n\nreadonly class ApiRateLimitListener implements EventSubscriberInterface\n{\n    private const API_PATH_PREFIX = '/api/';\n\n    public function __construct(\n        private bool $enabled,\n        private array $excludedRoutes,\n        private ApiRateLimiterFactoryInterface $rateLimiterFactory,\n        private ClientIdentifierProviderInterface $clientIdentifierProvider\n    ) {\n    }\n\n    public static function getSubscribedEvents(): array\n    {\n        return [\n            KernelEvents::REQUEST => ['onKernelRequest', 10],\n        ];\n    }\n\n    public function onKernelRequest(RequestEvent $event): void\n    {\n        if (!$event->isMainRequest()) {\n            return;\n        }\n\n        $request = $event->getRequest();\n\n        if (!$this->isApiRequest($request)) {\n            return;\n        }\n\n        if (!$this->enabled) {\n            return;\n        }\n\n        if ($this->isRouteExcluded($request->getPathInfo())) {\n            return;\n        }\n\n        $limiter = $this->rateLimiterFactory->create($this->clientIdentifierProvider->getClientIdentifier($request));\n        $limit = $limiter->consume();\n\n        if (!$limit->isAccepted()) {\n            $event->setResponse($this->createRateLimitExceededResponse($limit->getRetryAfter()));\n            return;\n        }\n\n        $request->attributes->set('_rate_limit_info', [\n            'limit' => $limit->getLimit(),\n            'remaining' => $limit->getRemainingTokens(),\n            'reset' => $limit->getRetryAfter()->getTimestamp(),\n        ]);\n    }\n\n    private function isApiRequest(Request $request): bool\n    {\n        return str_starts_with($request->getPathInfo(), self::API_PATH_PREFIX);\n    }\n\n    private function isRouteExcluded(string $route): bool\n    {\n        foreach ($this->excludedRoutes as $pattern) {\n            if ($this->matchRoute($route, $pattern)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    private function matchRoute(string $route, string $pattern): bool\n    {\n        if ($pattern === $route) {\n            return true;\n        }\n\n        if (str_contains($pattern, '*')) {\n            $regex = '/^' . str_replace(['/', '*'], ['\\/', '.*'], $pattern) . '$/';\n            return (bool) preg_match($regex, $route);\n        }\n\n        return false;\n    }\n\n    private function createRateLimitExceededResponse(\\DateTimeImmutable $retryAfter): JsonResponse\n    {\n        $retryAfterSeconds = max(0, $retryAfter->getTimestamp() - time());\n\n        $response = new JsonResponse(\n            [\n                'error' => 'rate_limit_exceeded',\n                'message' => 'Too many requests. Please try again later.',\n                'retry_after' => $retryAfterSeconds,\n            ],\n            Response::HTTP_TOO_MANY_REQUESTS\n        );\n\n        $response->headers->set('Retry-After', (string) $retryAfterSeconds);\n        $response->headers->set('X-RateLimit-Reset', (string) $retryAfter->getTimestamp());\n\n        return $response;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/RateLimiter/ApiRateLimiterFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\RateLimiter;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse Symfony\\Component\\Cache\\Adapter\\FilesystemAdapter;\nuse Symfony\\Component\\Lock\\LockFactory;\nuse Symfony\\Component\\Lock\\Store\\FlockStore;\nuse Symfony\\Component\\RateLimiter\\LimiterInterface;\nuse Symfony\\Component\\RateLimiter\\RateLimiterFactory;\nuse Symfony\\Component\\RateLimiter\\Storage\\CacheStorage;\n\nclass ApiRateLimiterFactory implements ApiRateLimiterFactoryInterface\n{\n    private ?RateLimiterFactory $factory = null;\n\n    public function __construct(\n        private readonly int $limit,\n        private readonly int $interval,\n        private readonly string $policy,\n        private readonly BasicContextInterface $context\n    ) {\n    }\n\n    public function create(string $clientIdentifier): LimiterInterface\n    {\n        return $this->getFactory()->create($clientIdentifier);\n    }\n\n    private function getFactory(): RateLimiterFactory\n    {\n        if ($this->factory === null) {\n            $this->factory = $this->createFactory();\n        }\n\n        return $this->factory;\n    }\n\n    private function createFactory(): RateLimiterFactory\n    {\n        $config = [\n            'id' => 'api',\n            'policy' => $this->policy,\n            'limit' => $this->limit,\n        ];\n\n        if ($this->policy === 'token_bucket') {\n            $config['rate'] = [\n                'interval' => sprintf('%d seconds', $this->interval),\n                'amount' => $this->limit,\n            ];\n        } else {\n            $config['interval'] = sprintf('%d seconds', $this->interval);\n        }\n\n        return new RateLimiterFactory(\n            $config,\n            $this->createStorage(),\n            new LockFactory(new FlockStore($this->context->getCacheDirectory()))\n        );\n    }\n\n    private function createStorage(): CacheStorage\n    {\n        $cache = new FilesystemAdapter(\n            namespace: 'rate_limiter',\n            defaultLifetime: $this->interval * 2,\n            directory: $this->context->getCacheDirectory()\n        );\n\n        return new CacheStorage($cache);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/RateLimiter/ApiRateLimiterFactoryInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\RateLimiter;\n\nuse Symfony\\Component\\RateLimiter\\LimiterInterface;\n\ninterface ApiRateLimiterFactoryInterface\n{\n    public function create(string $clientIdentifier): LimiterInterface;\n}\n"
  },
  {
    "path": "source/Internal/Framework/RateLimiter/ClientIdentifierProvider.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\RateLimiter;\n\nuse Symfony\\Component\\HttpFoundation\\Request;\n\nreadonly class ClientIdentifierProvider implements ClientIdentifierProviderInterface\n{\n    public function getClientIdentifier(Request $request): string\n    {\n        return $request->getClientIp() ?? 'unknown';\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/RateLimiter/ClientIdentifierProviderInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\RateLimiter;\n\nuse Symfony\\Component\\HttpFoundation\\Request;\n\ninterface ClientIdentifierProviderInterface\n{\n    public function getClientIdentifier(Request $request): string;\n}\n"
  },
  {
    "path": "source/Internal/Framework/RateLimiter/RateLimitHeadersListener.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\RateLimiter;\n\nuse Symfony\\Component\\EventDispatcher\\EventSubscriberInterface;\nuse Symfony\\Component\\HttpKernel\\Event\\ResponseEvent;\nuse Symfony\\Component\\HttpKernel\\KernelEvents;\n\nreadonly class RateLimitHeadersListener implements EventSubscriberInterface\n{\n    public static function getSubscribedEvents(): array\n    {\n        return [\n            KernelEvents::RESPONSE => ['onKernelResponse', -10],\n        ];\n    }\n\n    public function onKernelResponse(ResponseEvent $event): void\n    {\n        if (!$event->isMainRequest()) {\n            return;\n        }\n\n        $rateLimitInfo = $event->getRequest()->attributes->get('_rate_limit_info');\n\n        if ($rateLimitInfo === null) {\n            return;\n        }\n\n        $response = $event->getResponse();\n        $response->headers->set('X-RateLimit-Limit', (string) $rateLimitInfo['limit']);\n        $response->headers->set('X-RateLimit-Remaining', (string) $rateLimitInfo['remaining']);\n        $response->headers->set('X-RateLimit-Reset', (string) $rateLimitInfo['reset']);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/RateLimiter/services.yaml",
    "content": "parameters:\n  oxid_esales.rate_limiter.enabled: true\n  oxid_esales.rate_limiter.limit: 100\n  oxid_esales.rate_limiter.interval: 60\n  oxid_esales.rate_limiter.policy: 'token_bucket'\n  oxid_esales.rate_limiter.excluded_routes: []\n\nservices:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\RateLimiter\\ApiRateLimiterFactoryInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\RateLimiter\\ApiRateLimiterFactory\n    arguments:\n      $limit: '%oxid_esales.rate_limiter.limit%'\n      $interval: '%oxid_esales.rate_limiter.interval%'\n      $policy: '%oxid_esales.rate_limiter.policy%'\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\RateLimiter\\ClientIdentifierProviderInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\RateLimiter\\ClientIdentifierProvider\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\RateLimiter\\ApiRateLimitListener:\n    arguments:\n      $enabled: '%oxid_esales.rate_limiter.enabled%'\n      $excludedRoutes: '%oxid_esales.rate_limiter.excluded_routes%'\n    tags:\n      - { name: kernel.event_subscriber }\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\RateLimiter\\RateLimitHeadersListener:\n    tags:\n      - { name: kernel.event_subscriber }\n"
  },
  {
    "path": "source/Internal/Framework/Request/RequestInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Request;\n\n/**\n * @deprecated since v8.0.0. Use Symfony\\Component\\HttpFoundation\\Request instead.\n */\ninterface RequestInterface\n{\n    public function get(string $key, mixed $default = null): mixed;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Request/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n    public: false\n\n  Symfony\\Component\\HttpFoundation\\Request:\n    factory: ['Symfony\\Component\\HttpFoundation\\Request', 'createFromGlobals']\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Request\\RequestInterface:\n    alias: Symfony\\Component\\HttpFoundation\\Request"
  },
  {
    "path": "source/Internal/Framework/Search/EqualsFilter.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Search;\n\nreadonly class EqualsFilter implements FilterInterface\n{\n    public function __construct(\n        private string $field,\n        private string $value,\n    ) {\n    }\n\n    public function getField(): string\n    {\n        return $this->field;\n    }\n\n    public function getValue(): string\n    {\n        return $this->value;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Search/FilterInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Search;\n\ninterface FilterInterface\n{\n    public function getField(): string;\n\n}\n"
  },
  {
    "path": "source/Internal/Framework/Search/InFilter.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Search;\n\nuse InvalidArgumentException;\n\nreadonly class InFilter implements FilterInterface\n{\n    /** @var list<string> */\n    private array $values;\n\n    /** @param list<string> $values */\n    public function __construct(\n        private string $field,\n        array $values,\n    ) {\n        if (empty($values)) {\n            throw new InvalidArgumentException('InFilter requires at least one value');\n        }\n        $this->values = array_values($values);\n    }\n\n    public function getField(): string\n    {\n        return $this->field;\n    }\n\n    /** @return list<string> */\n    public function getValues(): array\n    {\n        return $this->values;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Search/Pagination.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Search;\n\nuse InvalidArgumentException;\n\nreadonly class Pagination\n{\n    private int $limit;\n    private int $offset;\n\n    public function __construct(int $limit, int $offset)\n    {\n        if ($limit < 1) {\n            throw new InvalidArgumentException('Pagination limit must be >= 1');\n        }\n        if ($offset < 0) {\n            throw new InvalidArgumentException('Pagination offset must be >= 0');\n        }\n\n        $this->limit = $limit;\n        $this->offset = $offset;\n    }\n\n    public static function fromPage(int $page, int $limit): self\n    {\n        if ($page < 1 || $limit < 1) {\n            throw new InvalidArgumentException('Pagination page and limit must be >= 1');\n        }\n\n        return new self($limit, ($page - 1) * $limit);\n    }\n\n    public function getLimit(): int\n    {\n        return $this->limit;\n    }\n\n    public function getOffset(): int\n    {\n        return $this->offset;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Search/SearchTerm.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Search;\n\nuse InvalidArgumentException;\n\nreadonly class SearchTerm\n{\n    private string $value;\n\n    public function __construct(string $value)\n    {\n        $this->value = $value;\n    }\n\n    public static function empty(): self\n    {\n        return new self('');\n    }\n\n    public function getValue(): string\n    {\n        return $this->value;\n    }\n\n    public function isEmpty(): bool\n    {\n        return $this->value === '';\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Search/SortDirection.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Search;\n\nenum SortDirection: string\n{\n    case Asc = 'ASC';\n    case Desc = 'DESC';\n}\n"
  },
  {
    "path": "source/Internal/Framework/Search/Sorting.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Search;\n\nuse InvalidArgumentException;\n\nreadonly class Sorting\n{\n    public function __construct(\n        private string $field,\n        private SortDirection $direction = SortDirection::Asc,\n    ) {\n    }\n\n    public static function fromString(string $field, string $direction): self\n    {\n        $normalized = strtoupper(trim($direction));\n\n        return new self(\n            $field,\n            match ($normalized) {\n                SortDirection::Asc->value => SortDirection::Asc,\n                SortDirection::Desc->value => SortDirection::Desc,\n                default => throw new InvalidArgumentException(\n                    sprintf(\n                        'Invalid sort direction \"%s\", expected \"%s\" or \"%s\"',\n                        $direction,\n                        SortDirection::Asc->value,\n                        SortDirection::Desc->value\n                    )\n                ),\n            }\n        );\n    }\n\n    public function getField(): string\n    {\n        return $this->field;\n    }\n\n    public function getDirection(): SortDirection\n    {\n        return $this->direction;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Session/SessionInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Session;\n\ninterface SessionInterface\n{\n    public function has(string $name): bool;\n\n    public function get(string $name, mixed $default = null): mixed;\n\n    public function set(string $name, mixed $value): void;\n\n    public function remove(string $name): mixed;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Session/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n    public: false\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Session\\SessionInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Session\\SessionAdapter"
  },
  {
    "path": "source/Internal/Framework/Storage/ArrayStorageInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage;\n\ninterface ArrayStorageInterface\n{\n    /**\n     * @return array\n     */\n    public function get(): array;\n\n    /**\n     * @param array $data\n     */\n    public function save(array $data): void;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Storage/FileStorageFactoryInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage;\n\ninterface FileStorageFactoryInterface\n{\n    public function create(string $filePath): ArrayStorageInterface;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Storage/YamlFileStorage.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage;\n\nuse Symfony\\Component\\Config\\Exception\\FileLocatorFileNotFoundException;\nuse Symfony\\Component\\Config\\FileLocatorInterface;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Lock\\LockFactory;\nuse Symfony\\Component\\Yaml\\Yaml;\n\nclass YamlFileStorage implements ArrayStorageInterface\n{\n    public function __construct(\n        private FileLocatorInterface $fileLocator,\n        private string $filePath,\n        private LockFactory $lockFactory,\n        private Filesystem $filesystemService\n    ) {\n    }\n\n    /**\n     * @return array\n     */\n    public function get(): array\n    {\n        $fileContent = file_get_contents($this->getLocatedFilePath());\n\n        $yaml = Yaml::parse(\n            $fileContent\n        );\n\n        return $yaml ?? [];\n    }\n\n    /**\n     * @param array $data\n     */\n    public function save(array $data): void\n    {\n        $lock = $this->lockFactory->createLock($this->getLockId());\n\n        if ($lock->acquire(true)) {\n            try {\n                file_put_contents(\n                    $this->getLocatedFilePath(),\n                    Yaml::dump($data, 10, 2)\n                );\n            } finally {\n                $lock->release();\n            }\n        }\n    }\n\n    /**\n     * @return string\n     */\n    private function getLocatedFilePath(): string\n    {\n        try {\n            $filePath = $this->fileLocator->locate($this->filePath);\n        } catch (FileLocatorFileNotFoundException) {\n            $this->createFileDirectory();\n            $this->createFile();\n            $filePath = $this->fileLocator->locate($this->filePath);\n        }\n\n        return $filePath;\n    }\n\n    /**\n     * Creates file directory if it doesn't exist.\n     */\n    private function createFileDirectory(): void\n    {\n        if (!$this->filesystemService->exists(\\dirname($this->filePath))) {\n            $this->filesystemService->mkdir(\\dirname($this->filePath));\n        }\n    }\n\n    /**\n     * Creates file.\n     */\n    private function createFile(): void\n    {\n        $this->filesystemService->touch($this->filePath);\n    }\n\n    /**\n     * @return string\n     */\n    private function getLockId(): string\n    {\n        return md5($this->filePath);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Storage/YamlFileStorageFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage;\n\nuse Symfony\\Component\\Config\\FileLocatorInterface;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Lock\\LockFactory;\n\nclass YamlFileStorageFactory implements FileStorageFactoryInterface\n{\n    public function __construct(\n        private FileLocatorInterface $fileLocator,\n        private LockFactory $lockFactory,\n        private Filesystem $filesystemService\n    ) {\n    }\n\n\n    public function create(string $filePath): ArrayStorageInterface\n    {\n        return new YamlFileStorage(\n            $this->fileLocator,\n            $filePath,\n            $this->lockFactory,\n            $this->filesystemService\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/SystemRequirements/SystemSecurityChecker.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\SystemRequirements;\n\nuse Exception;\n\nuse function random_bytes;\n\nclass SystemSecurityChecker implements SystemSecurityCheckerInterface\n{\n    /** @inheritdoc */\n    public function isCryptographicallySecure(): bool\n    {\n        try {\n            random_bytes(1);\n            return true;\n        } catch (Exception) {\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/SystemRequirements/SystemSecurityCheckerInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\SystemRequirements;\n\ninterface SystemSecurityCheckerInterface\n{\n    /**\n     * Checks whether system is configured to access an appropriate source of randomness for\n     * Cryptographically-Secure PseudoRandom Number Generators.\n     * @return bool\n     */\n    public function isCryptographicallySecure(): bool;\n}\n"
  },
  {
    "path": "source/Internal/Framework/SystemRequirements/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\SystemRequirements\\SystemSecurityCheckerInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\SystemRequirements\\SystemSecurityChecker\n"
  },
  {
    "path": "source/Internal/Framework/Templating/Cache/ShopTemplateCacheService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Cache;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse Symfony\\Component\\Filesystem\\Path;\nuse Symfony\\Component\\Filesystem\\Filesystem;\n\nclass ShopTemplateCacheService implements ShopTemplateCacheServiceInterface\n{\n    public function __construct(\n        private readonly ContextInterface $context,\n        private readonly Filesystem $filesystem,\n        private readonly string $compilationDirectory\n    ) {\n    }\n\n    public function getCacheDirectory(int $shopId): string\n    {\n        return Path::join(\n            $this->compilationDirectory,\n            'template_cache',\n            'shops',\n            (string) $shopId\n        );\n    }\n\n    public function invalidateCache(int $shopId): void\n    {\n        if ($this->filesystem->exists($this->getCacheDirectory($shopId))) {\n            $this->filesystem->remove($this->getCacheDirectory($shopId));\n        }\n    }\n\n    public function invalidateAllShopsCache(): void\n    {\n        $shops = $this->context->getAllShopIds();\n\n        foreach ($shops as $shop) {\n            $this->invalidateCache($shop);\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Templating/Cache/ShopTemplateCacheServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Cache;\n\ninterface ShopTemplateCacheServiceInterface\n{\n    public function getCacheDirectory(int $shopId): string;\n\n    public function invalidateCache(int $shopId): void;\n\n    public function invalidateAllShopsCache(): void;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Templating/Exception/InvalidTemplateNameException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Exception;\n\nuse Exception;\n\nfinal class InvalidTemplateNameException extends Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Templating/Exception/InvalidThemeNameException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Exception;\n\nuse Exception;\n\nfinal class InvalidThemeNameException extends Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Framework/Templating/Locator/AdminNavigationFileLocator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator;\n\nclass AdminNavigationFileLocator implements NavigationFileLocatorInterface\n{\n    /**\n     * @param NavigationFileLocatorInterface[] $menuFileLocators\n     */\n    public function __construct(private iterable $menuFileLocators = [])\n    {\n    }\n\n    /**\n     * Returns a full path for a given file name.\n     *\n     * @return array An array of file paths\n     *\n     * @throws \\Exception\n     */\n    public function locate(): array\n    {\n        $menuFilePaths = [];\n        foreach ($this->menuFileLocators as $locator) {\n            $menuFilePaths[] = $locator->locate();\n        }\n        return array_merge([], ...$menuFilePaths);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Templating/Locator/AdminTemplateFileLocator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator;\n\nuse OxidEsales\\Eshop\\Core\\Config;\n\n/**\n * Class AdminTemplateFileLocator\n * @package OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator\n */\nclass AdminTemplateFileLocator implements FileLocatorInterface\n{\n    public function __construct(private Config $context)\n    {\n    }\n\n    /**\n     * Returns a full path for a given file name.\n     *\n     * @param string $name The file name to locate\n     *\n     * @return string The full path to the file\n     */\n    public function locate(string $name): string\n    {\n        return $this->context->getTemplatePath($name, true);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Templating/Locator/EditionMenuFileLocator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition\\Edition;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Bridge\\AdminThemeBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass EditionMenuFileLocator implements NavigationFileLocatorInterface\n{\n    private string $themeName;\n    private string $fileName = 'menu.xml';\n\n    public function __construct(\n        AdminThemeBridgeInterface $adminThemeBridge,\n        private readonly BasicContextInterface $context,\n        private readonly Filesystem $fileSystem\n    ) {\n        $this->themeName = $adminThemeBridge->getActiveTheme();\n    }\n\n    public function locate(): array\n    {\n        $path = $this->context->getEdition() === Edition::Community\n            ? $this->context->getSourcePath()\n            : $this->context->getEditionSourcePath($this->context->getEdition());\n\n        $filePath = Path::join(\n            $path,\n            'Application',\n            'views',\n            $this->themeName,\n            $this->fileName,\n        );\n\n        return $this->fileSystem->exists($filePath) ? [$filePath] : [];\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Templating/Locator/EditionUserFileLocator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Bridge\\AdminThemeBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass EditionUserFileLocator implements NavigationFileLocatorInterface\n{\n    private string $themeName;\n    private string $fileName = 'user.xml';\n\n    public function __construct(\n        AdminThemeBridgeInterface $adminThemeBridge,\n        private readonly BasicContextInterface $context,\n        private readonly Filesystem $fileSystem\n    ) {\n        $this->themeName = $adminThemeBridge->getActiveTheme();\n    }\n\n    public function locate(): array\n    {\n        $filePath = Path::join(\n            $this->context->getEditionSourcePath($this->context->getEdition()),\n            'Application',\n            'views',\n            $this->themeName,\n            $this->fileName,\n        );\n\n        return $this->fileSystem->exists($filePath) ? [$filePath] : [];\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Templating/Locator/FileLocatorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator;\n\n/**\n * Interface FileLocatorInterface\n * @package OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator\n */\ninterface FileLocatorInterface\n{\n    /**\n     * Returns a full path for a given file name.\n     *\n     * @param string $name The file name to locate\n     *\n     * @return string The full path to the file\n     */\n    public function locate(string $name): string;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Templating/Locator/NavigationFileLocatorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator;\n\n/**\n * Interface NavigationFileLocatorInterface\n * @package OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator\n */\ninterface NavigationFileLocatorInterface\n{\n    /**\n     * Returns a full path for a given file name.\n     *\n     * @return array An array of file paths\n     */\n    public function locate(): array;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Templating/Locator/TemplateFileLocator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator;\n\nuse OxidEsales\\Eshop\\Core\\Config;\n\n/**\n * Class TemplateFileLocator\n * @package OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator\n */\nclass TemplateFileLocator implements FileLocatorInterface\n{\n    public function __construct(private Config $context)\n    {\n    }\n\n    /**\n     * Returns a full path for a given file name.\n     *\n     * @param string $name The file name to locate\n     *\n     * @return string The full path to the file\n     */\n    public function locate(string $name): string\n    {\n        return $this->context->getTemplatePath($name, false);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Templating/TemplateEngine.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating;\n\nclass TemplateEngine implements TemplateEngineInterface\n{\n    private array $globals = [];\n\n    /**\n     * @param string $name\n     * @param mixed  $value\n     */\n    public function addGlobal(string $name, $value)\n    {\n        $this->globals[$name] = $value;\n    }\n\n    public function getGlobals(): array\n    {\n        return $this->globals;\n    }\n\n    /**\n     * Renders a template.\n     *\n     * @param string $name    A template name\n     * @param array  $context An array of parameters to pass to the template\n     *\n     * @return string The evaluated template as a string\n     */\n    public function render(string $name, array $context = []): string\n    {\n        return $name;\n    }\n\n    /**\n     * Renders a fragment of the template.\n     *\n     * @param string $fragment   The template fragment to render\n     * @param string $fragmentId The Id of the fragment\n     * @param array  $context    An array of parameters to pass to the template\n     *\n     * @return string\n     */\n    public function renderFragment(string $fragment, string $fragmentId, array $context = []): string\n    {\n        return $fragment;\n    }\n\n    /**\n     * Returns true if the template exists.\n     *\n     * @param string $name A template name\n     *\n     * @return bool true if the template exists, false otherwise\n     */\n    public function exists(string $name): bool\n    {\n        return true;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Templating/TemplateEngineFactoryInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating;\n\ninterface TemplateEngineFactoryInterface\n{\n    /**\n     * @return TemplateEngineInterface\n     */\n    public function getTemplateEngine(): TemplateEngineInterface;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Templating/TemplateEngineInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating;\n\ninterface TemplateEngineInterface\n{\n    /**\n     * @param string $name\n     * @param mixed  $value\n     */\n    public function addGlobal(string $name, $value);\n\n    /**\n     * Returns assigned globals.\n     *\n     * @return array\n     */\n    public function getGlobals(): array;\n\n    /**\n     * Renders a template.\n     *\n     * @param string $name    A template name\n     * @param array  $context An array of parameters to pass to the template\n     *\n     * @return string The evaluated template as a string\n     */\n    public function render(string $name, array $context = []): string;\n\n    /**\n     * Renders a fragment of the template.\n     *\n     * @param string $fragment   The template fragment to render\n     * @param string $fragmentId The Id of the fragment\n     * @param array  $context    An array of parameters to pass to the template\n     *\n     * @return string\n     */\n    public function renderFragment(string $fragment, string $fragmentId, array $context = []): string;\n\n    /**\n     * Returns true if the template exists.\n     *\n     * @param string $name A template name\n     *\n     * @return bool true if the template exists, false otherwise\n     */\n    public function exists(string $name): bool;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Templating/TemplateRenderer.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\n\nclass TemplateRenderer implements TemplateRendererInterface\n{\n    public function __construct(\n        private readonly TemplateEngineInterface $templateEngine,\n        private readonly ContextInterface $context,\n        private readonly string $filenameExtension\n    ) {\n    }\n\n    public function renderTemplate(string $template, array $context = []): string\n    {\n        return $this\n            ->getTemplateEngine()\n            ->render(\n                $this->appendDefaultFilenameExtension($template),\n                $context\n            );\n    }\n\n    public function renderFragment(string $fragment, string $fragmentId, array $context = []): string\n    {\n        if ($this->doNotRenderForDemoShop()) {\n            return $fragment;\n        }\n        return $this->getTemplateEngine()->renderFragment($fragment, $fragmentId, $context);\n    }\n\n    public function getTemplateEngine(): TemplateEngineInterface\n    {\n        return $this->templateEngine;\n    }\n\n    public function exists(string $name): bool\n    {\n        return $this\n            ->getTemplateEngine()\n            ->exists(\n                $this->appendDefaultFilenameExtension($name)\n            );\n    }\n\n    private function doNotRenderForDemoShop(): bool\n    {\n        return $this->context->isShopInDemoMode();\n    }\n\n    private function appendDefaultFilenameExtension(string $templateName): string\n    {\n        return str_ends_with($templateName, $this->filenameExtension) ?\n            $templateName :\n            \"$templateName.$this->filenameExtension\";\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Templating/TemplateRendererBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating;\n\nclass TemplateRendererBridge implements TemplateRendererBridgeInterface\n{\n    public function __construct(private TemplateRendererInterface $renderer)\n    {\n    }\n\n    public function getTemplateRenderer(): TemplateRendererInterface\n    {\n        return $this->renderer;\n    }\n\n    public function setEngine($engine)\n    {\n    }\n\n    public function getEngine()\n    {\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Templating/TemplateRendererBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating;\n\ninterface TemplateRendererBridgeInterface\n{\n    public function getTemplateRenderer(): TemplateRendererInterface;\n\n    /**\n     * @deprecated since 7.0.0 will be removed in next major\n     */\n    public function setEngine($engine);\n\n    /**\n     * @deprecated since 7.0.0 will be removed in next major\n     */\n    public function getEngine();\n}\n"
  },
  {
    "path": "source/Internal/Framework/Templating/TemplateRendererInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating;\n\ninterface TemplateRendererInterface\n{\n    /**\n     * @param string $template The template name\n     * @param array  $context  An array of parameters to pass to the template\n     *\n     * @return string\n     */\n    public function renderTemplate(string $template, array $context = []): string;\n\n    /**\n     * Renders a fragment of the template.\n     *\n     * @param string $fragment The template fragment to render\n     * @param string $fragmentId The id of the fragment\n     * @param array  $context    An array of parameters to pass to the template\n     *\n     * @return string\n     */\n    public function renderFragment(string $fragment, string $fragmentId, array $context = []): string;\n\n    /**\n     * @return TemplateEngineInterface\n     */\n    public function getTemplateEngine(): TemplateEngineInterface;\n\n    /**\n     * Returns true if the template exists.\n     *\n     * @param string $name A template name\n     *\n     * @return bool true if the template exists, false otherwise\n     */\n    public function exists(string $name): bool;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Templating/services.yaml",
    "content": "parameters:\n  oxid_esales.templating.engine_template_extension: 'html.twig'\n\nservices:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\Eshop\\Core\\Config:\n    factory: ['OxidEsales\\Eshop\\Core\\Registry', 'getConfig']\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRenderer\n    arguments:\n      $filenameExtension: '%oxid_esales.templating.engine_template_extension%'\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererBridge\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateEngineInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateEngine\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator\\TemplateFileLocator:\n    arguments:\n      - '@OxidEsales\\Eshop\\Core\\Config'\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator\\AdminTemplateFileLocator:\n    arguments:\n      - '@OxidEsales\\Eshop\\Core\\Config'\n\n  oxid_esales.templating.admin.navigation.file.locator:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator\\AdminNavigationFileLocator\n    arguments:\n      - !tagged oxid.menu.file.locator\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator\\EditionMenuFileLocator:\n    tags:\n      - { name: 'oxid.menu.file.locator', priority: 10 }\n    arguments:\n      Symfony\\Component\\Filesystem\\Filesystem: '@oxid_esales.symfony.file_system'\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator\\EditionUserFileLocator:\n    tags:\n      - { name: 'oxid.menu.file.locator', priority: 20 }\n    arguments:\n      Symfony\\Component\\Filesystem\\Filesystem: '@oxid_esales.symfony.file_system'\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Cache\\ShopTemplateCacheServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Cache\\ShopTemplateCacheService\n    arguments:\n      - '@OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface'\n      - '@oxid_esales.symfony.file_system'\n      - '%oxid_esales.build_directory%'\n"
  },
  {
    "path": "source/Internal/Framework/Theme/Bridge/AdminThemeBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Bridge;\n\nclass AdminThemeBridge implements AdminThemeBridgeInterface\n{\n    public function __construct(private string $activeThemeName)\n    {\n    }\n\n    /**\n     * @return string\n     */\n    public function getActiveTheme(): string\n    {\n        return $this->activeThemeName;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Theme/Bridge/AdminThemeBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Bridge;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\ninterface AdminThemeBridgeInterface\n{\n    /**\n     * @return string\n     */\n    public function getActiveTheme(): string;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Theme/Command/ThemeActivateCommand.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Command;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\ShopCacheCleanerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\ShopAdapterInterface;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\nclass ThemeActivateCommand extends Command\n{\n    private const MESSAGE_THEME_IS_ACTIVE = 'Theme - \"%s\" is already active.';\n    private const MESSAGE_THEME_ACTIVATED = 'Theme - \"%s\" was activated.';\n    private const MESSAGE_THEME_NOT_FOUND = 'Theme - \"%s\" not found.';\n\n    public function __construct(\n        private readonly ShopAdapterInterface $shopAdapter,\n        private readonly ShopCacheCleanerInterface $shopCacheCleaner,\n    ) {\n        parent::__construct();\n    }\n\n    protected function configure(): void\n    {\n        $this->setDescription('Activates a theme.')\n            ->addArgument('theme-id', InputArgument::REQUIRED, 'Theme ID')\n            ->setHelp('Command activates theme by defined theme ID.');\n    }\n\n    protected function execute(InputInterface $input, OutputInterface $output): int\n    {\n        $themeId = $input->getArgument('theme-id');\n\n        if (!$this->shopAdapter->themeExists($themeId)) {\n            $output->writeLn(\n                '<error>' . sprintf(self::MESSAGE_THEME_NOT_FOUND, $themeId) . '</error>'\n            );\n            return Command::INVALID;\n        }\n\n        if ($this->shopAdapter->getActiveThemeId() === $themeId) {\n            $output->writeln(\n                '<comment>' . sprintf(self::MESSAGE_THEME_IS_ACTIVE, $themeId) . '</comment>'\n            );\n            return Command::SUCCESS;\n        }\n\n        $this->shopAdapter->activateTheme($themeId);\n        $this->shopCacheCleaner->clearAll();\n        $output->writeLn('<info>' . sprintf(self::MESSAGE_THEME_ACTIVATED, $themeId) . '</info>');\n\n        return Command::SUCCESS;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Theme/Config/Dao/ThemeSettingDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Config\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Config\\DataObject\\ThemeSetting;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Utility\\ShopSettingEncoderInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Event\\ThemeSettingChangedEvent;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\n\nclass ThemeSettingDao implements ThemeSettingDaoInterface\n{\n    private const THEME_MODULE_PREFIX = 'theme:';\n\n    public function __construct(\n        private readonly QueryBuilderFactoryInterface $queryBuilderFactory,\n        private readonly ShopSettingEncoderInterface $shopSettingEncoder,\n        private readonly EventDispatcherInterface $eventDispatcher\n    ) {\n    }\n\n    private array $cache = [];\n\n    public function save(ThemeSetting $setting): void\n    {\n        $this->delete($setting);\n\n        $moduleIdentifier = $this->getModuleIdentifier($setting->getThemeId());\n        $queryBuilder = $this->queryBuilderFactory->create();\n        $queryBuilder\n            ->insert('oxconfig')\n            ->values([\n                'oxid' => ':id',\n                'oxshopid' => ':shopId',\n                'oxmodule' => ':module',\n                'oxvarname' => ':name',\n                'oxvartype' => ':type',\n                'oxvarvalue' => ':value',\n            ])\n            ->setParameters([\n                'id' => Id::generate(),\n                'shopId' => $setting->getShopId(),\n                'module' => $moduleIdentifier,\n                'name' => $setting->getName(),\n                'type' => $setting->getType(),\n                'value' => $this->shopSettingEncoder->encode(\n                    $setting->getType(),\n                    $setting->getValue()\n                ),\n            ]);\n\n        $queryBuilder->executeStatement();\n\n        $this->eventDispatcher->dispatch(\n            new ThemeSettingChangedEvent(\n                $setting->getName(),\n                $setting->getShopId(),\n                $moduleIdentifier\n            )\n        );\n    }\n\n    public function get(string $name, int $shopId, string $themeId): ThemeSetting\n    {\n        $moduleIdentifier = $this->getModuleIdentifier($themeId);\n\n        if (!isset($this->cache[$shopId][$themeId][$name])) {\n            $queryBuilder = $this->queryBuilderFactory->create();\n            $queryBuilder\n                ->select('oxvarvalue as value, oxvartype as type, oxvarname as name')\n                ->from('oxconfig')\n                ->where('oxshopid = :shopId')\n                ->andWhere('oxvarname = :name')\n                ->andWhere('oxmodule = :module')\n                ->setParameters([\n                    'shopId' => $shopId,\n                    'name' => $name,\n                    'module' => $moduleIdentifier,\n                ]);\n\n            $result = $queryBuilder->fetchAssociative();\n\n            if ($result === false) {\n                throw new EntryDoesNotExistDaoException(\n                    'Setting ' . $name . ' for theme ' . $themeId . ' does not exist in the shop with id ' . $shopId\n                );\n            }\n\n            $setting = new ThemeSetting();\n            $setting\n                ->setThemeId($themeId)\n                ->setName($name)\n                ->setShopId($shopId)\n                ->setType($result['type'])\n                ->setValue($this->shopSettingEncoder->decode($result['type'], $result['value']));\n\n            $this->cache[$shopId][$themeId][$name] = $setting;\n        }\n\n        return clone $this->cache[$shopId][$themeId][$name];\n    }\n\n    public function delete(ThemeSetting $setting): void\n    {\n        $queryBuilder = $this->queryBuilderFactory->create();\n        $queryBuilder\n            ->delete('oxconfig')\n            ->where('oxshopid = :shopId')\n            ->andWhere('oxvarname = :name')\n            ->andWhere('oxmodule = :module')\n            ->setParameters([\n                'shopId' => $setting->getShopId(),\n                'name' => $setting->getName(),\n                'module' => $this->getModuleIdentifier($setting->getThemeId()),\n            ]);\n\n        $queryBuilder->executeStatement();\n\n        unset($this->cache[$setting->getShopId()][$setting->getThemeId()][$setting->getName()]);\n    }\n\n    private function getModuleIdentifier(string $themeId): string\n    {\n        return self::THEME_MODULE_PREFIX . $themeId;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Theme/Config/Dao/ThemeSettingDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Config\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Config\\DataObject\\ThemeSetting;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\n\ninterface ThemeSettingDaoInterface\n{\n    public function save(ThemeSetting $setting): void;\n\n    /**\n     * @throws EntryDoesNotExistDaoException\n     */\n    public function get(string $name, int $shopId, string $themeId): ThemeSetting;\n\n    public function delete(ThemeSetting $setting): void;\n}\n"
  },
  {
    "path": "source/Internal/Framework/Theme/Config/DataObject/ThemeSetting.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Config\\DataObject;\n\nclass ThemeSetting\n{\n    private int $shopId;\n    private string $name;\n    private string $type;\n    private mixed $value;\n    private string $themeId;\n\n    public function getShopId(): int\n    {\n        return $this->shopId;\n    }\n\n    public function setShopId(int $shopId): self\n    {\n        $this->shopId = $shopId;\n        return $this;\n    }\n\n    public function getName(): string\n    {\n        return $this->name;\n    }\n\n    public function setName(string $name): self\n    {\n        $this->name = $name;\n        return $this;\n    }\n\n    public function getType(): string\n    {\n        return $this->type;\n    }\n\n    public function setType(string $type): self\n    {\n        $this->type = $type;\n        return $this;\n    }\n\n    public function getValue(): mixed\n    {\n        return $this->value;\n    }\n\n    public function setValue(mixed $value): self\n    {\n        $this->value = $value;\n        return $this;\n    }\n\n    public function getThemeId(): string\n    {\n        return $this->themeId;\n    }\n\n    public function setThemeId(string $themeId): self\n    {\n        $this->themeId = $themeId;\n        return $this;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Theme/Event/ThemeActivatedEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Event;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\nfinal class ThemeActivatedEvent extends Event\n{\n    public function __construct(private readonly int $shopId, private readonly string $themeId)\n    {\n    }\n\n    public function getShopId(): int\n    {\n        return $this->shopId;\n    }\n\n    public function getThemeId(): string\n    {\n        return $this->themeId;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Theme/Event/ThemeSettingChangedEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Event;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\nclass ThemeSettingChangedEvent extends Event\n{\n    /**\n     * @param string $theme Theme information as in oxconfig.oxmodule\n     */\n    public function __construct(\n        private string $configurationVariable,\n        private int $shopId,\n        private string $theme\n    ) {\n    }\n\n    /**\n     * Getter for configuration variable name.\n     *\n     * @return string\n     */\n    public function getConfigurationVariable(): string\n    {\n        return $this->configurationVariable;\n    }\n\n    /**\n     * Getter for shop id.\n     *\n     * @return integer\n     */\n    public function getShopId(): int\n    {\n        return $this->shopId;\n    }\n\n    /**\n     * Getter for theme information.\n     *\n     * @return string\n     */\n    public function getTheme(): string\n    {\n        return $this->theme;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Framework/Theme/services.yaml",
    "content": "parameters:\n  oxid_esales.theme.admin.name: 'admin_twig'\n  oxid_esales.theme.admin.media.image_grid_size: '400*400'\n  oxid_esales.theme.admin.media.image_zoom_size: '800*800'\n  oxid_esales.theme.media.allowed_image_sizes:\n    - '%oxid_esales.theme.admin.media.image_grid_size%'\n    - '%oxid_esales.theme.admin.media.image_zoom_size%'\n\nservices:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Bridge\\AdminThemeBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Bridge\\AdminThemeBridge\n    arguments:\n      - '%oxid_esales.theme.admin.name%'\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Command\\ThemeActivateCommand:\n    tags:\n      - { name: 'console.command', command: 'oe:theme:activate' }\n"
  },
  {
    "path": "source/Internal/Framework/parameters.yaml",
    "content": "parameters:\n  oxid_esales.shop_admin_url: null\n  oxid_esales.force_session_start: false\n  oxid_esales.session_init_params: [ ]\n  oxid_esales.disallow_force_session_id: false\n  oxid_esales.cookies_session: true\n  oxid_esales.cookie_domains: []\n  oxid_esales.cookie_paths: []\n  oxid_esales.trusted_ips: []\n  oxid_esales.log_not_seo_urls: false\n  oxid_esales.cacheable_user_components: []\n  oxid_esales.demo_shop_mode: false\n  oxid_esales.smtp_debug_mode: false\n  oxid_esales.cron_enabled: false\n  oxid_esales.alternative_image_url: ''\n  oxid_esales.max_product_picture_size: '10000000'\n  oxid_esales.allowed_uploaded_types:\n    - 'avi'\n    - 'doc'\n    - 'gif'\n    - 'jpg'\n    - 'mp3'\n    - 'mpeg'\n    - 'mpg'\n    - 'pdf'\n    - 'png'\n    - 'ppt'\n    - 'xls'\n    - 'webp'\n  oxid_esales.search_engine_list:\n    - 'ahrefs'\n    - 'ahrefsbot'\n    - 'applebot'\n    - 'archive.org'\n    - 'baidubot'\n    - 'bingbot'\n    - 'duckduckgo'\n    - 'facebookexternalhit'\n    - 'googlebot'\n    - 'lighthouse'\n    - 'linkedinbot'\n    - 'semrushbot'\n    - 'sistrixcrawler'\n    - 'sogouspider'\n    - 'twitterbot'\n    - 'yandexbot'\n    - 'yesco'\n"
  },
  {
    "path": "source/Internal/Framework/services.yaml",
    "content": "imports:\n  - { resource: parameters.yaml }\n  - { resource: Api/services.yaml }\n  - { resource: Cache/services.yaml }\n  - { resource: Console/services.yaml }\n  - { resource: Database/services.yaml }\n  - { resource: DIContainer/services.yaml }\n  - { resource: Event/services.yaml }\n  - { resource: FileSystem/services.yaml }\n  - { resource: Form/services.yaml }\n  - { resource: Html/services.yaml }\n  - { resource: Logger/services.yaml }\n  - { resource: Migration/services.yaml }\n  - { resource: Module/services.yaml }\n  - { resource: Request/services.yaml }\n  - { resource: Session/services.yaml }\n  - { resource: SystemRequirements/services.yaml }\n  - { resource: Templating/services.yaml }\n  - { resource: Theme/services.yaml }\n  - { resource: Mailing/services.yaml }\n  - { resource: RateLimiter/services.yaml }\n"
  },
  {
    "path": "source/Internal/README.md",
    "content": "Internal namespace\n===================\n\nThe purpose of the `Internal` namespace is to have a clearly defined public API. \nOne of the means to achieve this is the usage of the Symfony DI container to manage \nservices in the `Internal` namespace. The implementations themselves are shielded \nby interfaces, so the interfaces in the Internal namespace might be considered as \npart of the public API of the OXID eShop.\n\nYou may use the DI container in the traditional code by fetching the it via the \n`ContainerFactory` and then call the `get()`method on the container to obtain a \nservice. But be aware: this is only possible for public services, all other \nservices are protected from direct usage in the traditional code.\n\nBut when you write modules you can write your own services and inject every service \nfrom the internal namespace. In this case you need to be aware that the \nimplementation of these services might change between versions. And we also reserve \nthe right to change the interfaces, although this should not happen too often and \nwill be documented in the upgrade instructions.\n\nWe will follow our deprecation procedure only for interfaces that are explicitly \nmarked `@stable`. All other interfaces might change, even in minor versions \n(we will keep them stable for patch versions, because nobody should be afraid to \ninstall security fixes).\n\nThe `Internal`  namespace consists of four main directories:\n\n#### Container\n\nAll classes that enable traditional code to gain access `Internal` namespace via the `ContainerFactory`.        \n\n#### Domain\n\nAll packages that directly works with business logic.\n\n#### Framework\n\nAll Application infrastructure classes are not include business logic.\n\n#### Transition\n\nAll classes that enable `Internal` namespace to gain access traditional code.\n\nFor more information check the developer documentation.\n"
  },
  {
    "path": "source/Internal/Setup/Database/DatabaseNotEmptyException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Database;\n\nuse Exception;\n\nclass DatabaseNotEmptyException extends Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Setup/Database/SetupDbConnectionFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Database;\n\nuse Doctrine\\DBAL\\Connection;\nuse Doctrine\\DBAL\\DriverManager;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\DataObject\\DatabaseConfiguration;\n\nclass SetupDbConnectionFactory implements SetupDbConnectionFactoryInterface\n{\n    public function getServerConnection(DatabaseConfiguration $databaseConfiguration): Connection\n    {\n        $connectionParameters = $databaseConfiguration->getConnectionParameters();\n        unset($connectionParameters['dbname']);\n\n        return $this->getConnection($connectionParameters);\n    }\n\n    public function getDatabaseConnection(DatabaseConfiguration $databaseConfiguration): Connection\n    {\n        return $this->getConnection($databaseConfiguration->getConnectionParameters());\n    }\n\n    private function getConnection(array $parameters): Connection\n    {\n        $connection = DriverManager::getConnection($parameters);\n        $connection->getServerVersion();\n\n        return $connection;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Setup/Database/SetupDbConnectionFactoryInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Database;\n\nuse Doctrine\\DBAL\\Connection;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\DataObject\\DatabaseConfiguration;\n\ninterface SetupDbConnectionFactoryInterface\n{\n    public function getServerConnection(DatabaseConfiguration $databaseConfiguration): Connection;\n\n    public function getDatabaseConnection(DatabaseConfiguration $databaseConfiguration): Connection;\n}\n"
  },
  {
    "path": "source/Internal/Setup/Database/SetupDbConnectionValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Database;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\DataObject\\DatabaseConfiguration;\n\nreadonly class SetupDbConnectionValidator implements SetupDbConnectionValidatorInterface\n{\n    public function __construct(private SetupDbConnectionFactoryInterface $databaseConnectionFactory)\n    {\n    }\n\n    public function validate(DatabaseConfiguration $databaseConfiguration): void\n    {\n        if (\n            $databaseConfiguration->isSocketConnection() ||\n            !$databaseConfiguration->getUser() ||\n            !$databaseConfiguration->getPass() ||\n            !$databaseConfiguration->getName()\n        ) {\n            throw new UnsupportedDatabaseConfigurationException(\n                \"Invalid or unsupported database URL '{$databaseConfiguration->getDatabaseUrl()}'!\"\n            );\n        }\n        $this->canConnectToServer($databaseConfiguration);\n    }\n\n    private function canConnectToServer(DatabaseConfiguration $databaseConfiguration): void\n    {\n        $connection = $this->databaseConnectionFactory->getServerConnection($databaseConfiguration);\n        $connection->close();\n    }\n}\n"
  },
  {
    "path": "source/Internal/Setup/Database/SetupDbConnectionValidatorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Database;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\DataObject\\DatabaseConfiguration;\n\ninterface SetupDbConnectionValidatorInterface\n{\n    /** @throws UnsupportedDatabaseConfigurationException */\n    public function validate(DatabaseConfiguration $databaseConfiguration): void;\n}\n"
  },
  {
    "path": "source/Internal/Setup/Database/SetupDbValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Database;\n\nuse Doctrine\\DBAL\\Exception;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\DataObject\\DatabaseConfiguration;\n\nuse function sprintf;\n\nclass SetupDbValidator implements SetupDbValidatorInterface\n{\n    public function __construct(private SetupDbConnectionFactoryInterface $databaseConnectionFactory)\n    {\n    }\n\n    public function validate(DatabaseConfiguration $databaseConfiguration): void\n    {\n        $this->validateDbIsEmptyOrNotYetCreated($databaseConfiguration);\n    }\n\n    private function validateDbIsEmptyOrNotYetCreated(DatabaseConfiguration $databaseConfiguration): void\n    {\n        try {\n            $connection = $this->databaseConnectionFactory->getDatabaseConnection($databaseConfiguration);\n\n            if (count($connection->createSchemaManager()->listTables()) === 0) {\n                return;\n            }\n        } catch (Exception) {\n            return;\n        }\n        throw new DatabaseNotEmptyException(\n            sprintf('Database `%s` exists and is not empty.', $databaseConfiguration->getName())\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Setup/Database/SetupDbValidatorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Database;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\DataObject\\DatabaseConfiguration;\n\ninterface SetupDbValidatorInterface\n{\n    /** @throws DatabaseNotEmptyException */\n    public function validate(DatabaseConfiguration $databaseConfiguration): void;\n}\n"
  },
  {
    "path": "source/Internal/Setup/Database/ShopDbManager.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Database;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\DataObject\\DatabaseConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Migration\\MigrationExecutorInterface;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass ShopDbManager implements ShopDbManagerInterface\n{\n    private DatabaseConfiguration $databaseConfiguration;\n\n    public function __construct(\n        private readonly SetupDbConnectionFactoryInterface $databaseConnectionFactory,\n        private readonly MigrationExecutorInterface $migrationExecutor,\n        private readonly ViewsGeneratorFactoryInterface $databaseViewsGeneratorFactory,\n    ) {\n    }\n\n    public function create(DatabaseConfiguration $databaseConfiguration): void\n    {\n        $this->databaseConfiguration = $databaseConfiguration;\n\n        $this->createDatabase();\n        $this->loadSqlDumps();\n        $this->migrationExecutor->execute();\n        $this->databaseViewsGeneratorFactory\n            ->create()\n            ->generate();\n    }\n\n    private function createDatabase(): void\n    {\n        $connection = $this->databaseConnectionFactory->getServerConnection($this->databaseConfiguration);\n        $connection->executeStatement(\n            sprintf(\n                'CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET utf8 COLLATE utf8_general_ci;',\n                $this->databaseConfiguration->getName()\n            )\n        );\n        $connection->close();\n    }\n\n    private function loadSqlDumps(): void\n    {\n        $connection = $this->databaseConnectionFactory->getDatabaseConnection($this->databaseConfiguration);\n        $connection->executeStatement($this->readDumpFromFile('database_schema'));\n        $connection->executeStatement($this->readDumpFromFile('initial_data'));\n        $connection->close();\n    }\n\n    private function readDumpFromFile(string $file): string\n    {\n        return file_get_contents(\n            Path::join(\n                __DIR__,\n                'sql',\n                \"$file.sql\"\n            )\n        );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Setup/Database/ShopDbManagerInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Database;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\DataObject\\DatabaseConfiguration;\n\ninterface ShopDbManagerInterface\n{\n    public function create(DatabaseConfiguration $databaseConfiguration): void;\n}\n"
  },
  {
    "path": "source/Internal/Setup/Database/UnsupportedDatabaseConfigurationException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Database;\n\nuse Exception;\n\nclass UnsupportedDatabaseConfigurationException extends Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Setup/Database/ViewsGeneratorFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Database;\n\nuse OxidEsales\\DatabaseViewsGenerator\\ViewsGenerator;\n\nclass ViewsGeneratorFactory implements ViewsGeneratorFactoryInterface\n{\n    public function create(): ViewsGenerator\n    {\n        return new ViewsGenerator();\n    }\n}\n"
  },
  {
    "path": "source/Internal/Setup/Database/ViewsGeneratorFactoryInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Database;\n\nuse OxidEsales\\DatabaseViewsGenerator\\ViewsGenerator;\n\ninterface ViewsGeneratorFactoryInterface\n{\n    public function create(): ViewsGenerator;\n}\n"
  },
  {
    "path": "source/Internal/Setup/Database/services.yaml",
    "content": "services:\n    _defaults:\n        autowire: true\n\n    OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\SetupDbConnectionFactoryInterface:\n        class: OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\SetupDbConnectionFactory\n\n    OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\ViewsGeneratorFactoryInterface:\n        class: OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\ViewsGeneratorFactory\n\n    OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\ShopDbManagerInterface:\n        class: OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\ShopDbManager\n\n    OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\SetupDbConnectionValidatorInterface:\n        class: OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\SetupDbConnectionValidator\n\n    OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\SetupDbValidatorInterface:\n        class: OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\SetupDbValidator\n"
  },
  {
    "path": "source/Internal/Setup/Database/sql/database_schema.sql",
    "content": "ALTER DATABASE CHARACTER SET utf8 COLLATE utf8_general_ci;\nSET CHARACTER SET 'utf8';\nSET character_set_server = 'utf8';\nSET @@session.sql_mode = '';\n\n#\n# Table structure for table `oxacceptedterms`\n# for storing information user accepted terms version\n# created 2010-06-10\n#\n\nDROP TABLE IF EXISTS `oxacceptedterms`;\n\nCREATE TABLE `oxacceptedterms` (\n  `OXUSERID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'User id (oxuser)',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXTERMVERSION` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Terms version',\n  `OXACCEPTEDTIME` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Time, when terms were accepted',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY (`OXUSERID`, `OXSHOPID`)\n) ENGINE=InnoDB COMMENT='Shows which users has accepted shop terms';\n\n#\n# Table structure for table `oxaccessoire2article`\n#\n\nDROP TABLE IF EXISTS `oxaccessoire2article`;\n\nCREATE TABLE `oxaccessoire2article` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Record id',\n  `OXOBJECTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Accessory Article id (oxarticles)',\n  `OXARTICLENID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Article id (oxarticles)',\n  `OXSORT` int(5) NOT NULL default '0' COMMENT 'Sorting',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXOBJECTID` (`OXOBJECTID`),\n  KEY `OXARTICLENID` (`OXARTICLENID`)\n) ENGINE=InnoDB COMMENT 'Shows many-to-many relationship between article and its accessory articles';\n\n#\n# Table structure for table `oxactions`\n#\n\nDROP TABLE IF EXISTS `oxactions`;\n\nCREATE TABLE `oxactions` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Action id',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXTYPE` tinyint( 1 ) NOT NULL COMMENT 'Action type: 0 or 1 - action, 2 - promotion, 3 - banner',\n  `OXTITLE` varchar(128) NOT NULL default '' COMMENT 'Title (multilanguage)',\n  `OXTITLE_1` varchar(128) NOT NULL default '' COMMENT '',\n  `OXTITLE_2` varchar(128) NOT NULL default '' COMMENT '',\n  `OXTITLE_3` varchar(128) NOT NULL default '' COMMENT '',\n  `OXLONGDESC` text NOT NULL COMMENT 'Long description, used for promotion (multilanguage)',\n  `OXLONGDESC_1` text NOT NULL,\n  `OXLONGDESC_2` text NOT NULL,\n  `OXLONGDESC_3` text NOT NULL,\n  `OXACTIVE` tinyint(1) NOT NULL default '1' COMMENT 'Active',\n  `OXACTIVEFROM` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Active from specified date',\n  `OXACTIVETO` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Active to specified date',\n  `OXPIC`   VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'Picture filename, used for banner (multilanguage)',\n  `OXPIC_1` VARCHAR(128) NOT NULL DEFAULT '',\n  `OXPIC_2` VARCHAR(128) NOT NULL DEFAULT '',\n  `OXPIC_3` VARCHAR(128) NOT NULL DEFAULT '',\n  `OXLINK`   VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'Link, used on banner (multilanguage)',\n  `OXLINK_1` VARCHAR(128) NOT NULL DEFAULT '',\n  `OXLINK_2` VARCHAR(128) NOT NULL DEFAULT '',\n  `OXLINK_3` VARCHAR(128) NOT NULL DEFAULT '',\n  `OXSORT` int( 5 ) NOT NULL DEFAULT '0' COMMENT 'Sorting',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  index(`oxsort`),\n  index(`OXTYPE`, `OXACTIVE`, `OXACTIVETO`, `OXACTIVEFROM`)\n) ENGINE=InnoDB COMMENT 'Stores information about actions, promotions and banners';\n\n#\n# Table structure for table `oxactions2article`\n#\n\nDROP TABLE IF EXISTS `oxactions2article`;\n\nCREATE TABLE `oxactions2article` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Record id',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXACTIONID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Action id (oxactions)',\n  `OXARTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Article id (oxarticles)',\n  `OXSORT` int(11) NOT NULL default '0' COMMENT 'Sorting',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXMAINIDX` (`OXSHOPID`,`OXACTIONID`,`OXSORT`),\n  KEY `OXARTID` (`OXARTID`)\n) ENGINE=InnoDB COMMENT 'Shows many-to-many relationship between actions and articles';\n\n#\n# Table structure for table `oxaddress`\n#\n\nDROP TABLE IF EXISTS `oxaddress`;\n\nCREATE TABLE `oxaddress` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Address id',\n  `OXUSERID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'User id (oxuser)',\n  `OXADDRESSUSERID` VARCHAR(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'User id (oxuser)',\n  `OXCOMPANY` varchar(255) NOT NULL default '' COMMENT 'Company name',\n  `OXFNAME` varchar(255) NOT NULL default '' COMMENT 'First name',\n  `OXLNAME` varchar(255) NOT NULL default '' COMMENT 'Last name',\n  `OXSTREET` varchar(255) NOT NULL default '' COMMENT 'Street',\n  `OXSTREETNR` varchar(16) NOT NULL default '' COMMENT 'House number',\n  `OXADDINFO` varchar(255) NOT NULL default '' COMMENT 'Additional info',\n  `OXCITY` varchar(255) NOT NULL default '' COMMENT 'City',\n  `OXCOUNTRY` varchar(255) NOT NULL default '' COMMENT 'Country name',\n  `OXCOUNTRYID` char( 32 ) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Country id (oxcountry)',\n  `OXSTATEID` varchar(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'State id (oxstate)',\n  `OXZIP` varchar(50) NOT NULL default '' COMMENT 'Zip code',\n  `OXFON` varchar(128) NOT NULL default '' COMMENT 'Phone number',\n  `OXFAX` varchar(128) NOT NULL default '' COMMENT 'Fax number',\n  `OXSAL` varchar(128) NOT NULL default '' COMMENT 'User title prefix (Mr/Mrs)',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXUSERID` (`OXUSERID`)\n) ENGINE=InnoDB COMMENT 'Stores user shipping addresses';\n\n#\n# Table structure for table `oxadminlog`\n# @deprecated since v6.5.0 (2019-09-17). Table is not used. Admin logging will be reimplemented using log file.\n\nDROP TABLE IF EXISTS `oxadminlog`;\n\nCREATE TABLE `oxadminlog` (\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  `OXUSERID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'User id (oxuser)',\n  `OXSQL` text NOT NULL COMMENT 'Logged sql'\n) ENGINE=InnoDB COMMENT 'Logs admin actions';\n\n#\n# Table structure for table `oxarticles`\n#\n\nDROP TABLE IF EXISTS `oxarticles`;\n\nCREATE TABLE `oxarticles` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Article id',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXPARENTID` char(32) character set latin1 collate latin1_general_ci NOT NULL  default '' COMMENT 'Parent article id',\n  `OXACTIVE` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Active',\n  `OXHIDDEN` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Hidden',\n  `OXACTIVEFROM` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Active from specified date',\n  `OXACTIVETO` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT  'Active to specified date',\n  `OXARTNUM` varchar(255) NOT NULL default '' COMMENT 'Article number',\n  `OXEAN` varchar(128)  NOT NULL default '' COMMENT 'International Article Number (EAN)',\n  `OXDISTEAN` varchar(128)  NOT NULL default '' COMMENT 'Manufacture International Article Number (Man. EAN)',\n  `OXMPN` varchar(100) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Manufacture Part Number (MPN)',\n  `OXTITLE` varchar(255) NOT NULL default '' COMMENT 'Title (multilanguage)',\n  `OXSHORTDESC` varchar(255) NOT NULL default '' COMMENT 'Short description (multilanguage)',\n  `OXPRICE` double NOT NULL default '0' COMMENT 'Article Price',\n  `OXBLFIXEDPRICE` tinyint(1) NOT NULL default '0' COMMENT 'No Promotions (Price Alert) ',\n  `OXPRICEA` double NOT NULL default '0' COMMENT 'Price A',\n  `OXPRICEB` double NOT NULL default '0' COMMENT 'Price B',\n  `OXPRICEC` double NOT NULL default '0' COMMENT 'Price C',\n  `OXBPRICE` double NOT NULL default '0' COMMENT 'Purchase Price',\n  `OXTPRICE` double NOT NULL default '0' COMMENT 'Recommended Retail Price (RRP)',\n  `OXUNITNAME` varchar(32) NOT NULL default '' COMMENT 'Unit name (kg,g,l,cm etc), used in setting price per quantity unit calculation',\n  `OXUNITQUANTITY` double NOT NULL default '0' COMMENT 'Article quantity, used in setting price per quantity unit calculation',\n  `OXEXTURL` varchar(255) NOT NULL default '' COMMENT 'External URL to other information about the article',\n  `OXURLDESC` varchar(255) NOT NULL default '' COMMENT 'Text for external URL (multilanguage)',\n  `OXURLIMG` varchar(128) NOT NULL default '' COMMENT 'External URL image',\n  `OXVAT` float default NULL COMMENT 'Value added tax. If specified, used in all calculations instead of global vat',\n  `OXTHUMB` varchar(128) NOT NULL default '' COMMENT 'Thumbnail filename',\n  `OXICON` varchar(128) NOT NULL default '' COMMENT 'Icon filename',\n  `OXPIC1` varchar(128) NOT NULL default '' COMMENT '1# Picture filename',\n  `OXPIC2` varchar(128) NOT NULL default '' COMMENT '2# Picture filename',\n  `OXPIC3` varchar(128) NOT NULL default '' COMMENT '3# Picture filename',\n  `OXPIC4` varchar(128) NOT NULL default '' COMMENT '4# Picture filename',\n  `OXPIC5` varchar(128) NOT NULL default '' COMMENT '5# Picture filename',\n  `OXPIC6` varchar(128) NOT NULL default '' COMMENT '6# Picture filename',\n  `OXPIC7` varchar(128) NOT NULL default '' COMMENT '7# Picture filename',\n  `OXPIC8` varchar(128) NOT NULL default '' COMMENT '8# Picture filename',\n  `OXPIC9` varchar(128) NOT NULL default '' COMMENT '9# Picture filename',\n  `OXPIC10` varchar(128) NOT NULL default '' COMMENT '10# Picture filename',\n  `OXPIC11` varchar(128) NOT NULL default '' COMMENT '11# Picture filename',\n  `OXPIC12` varchar(128) NOT NULL default '' COMMENT '12# Picture filename',\n  `OXWEIGHT` double NOT NULL default '0' COMMENT 'Weight (kg)',\n  `OXSTOCK` double NOT NULL default '0' COMMENT 'Article quantity in stock',\n  `OXSTOCKFLAG` tinyint(1) NOT NULL default '1' COMMENT 'Delivery Status: 1 - Standard, 2 - If out of Stock, offline, 3 - If out of Stock, not orderable, 4 - External Storehouse',\n  `OXSTOCKTEXT` varchar(255) NOT NULL default '' COMMENT 'Message, which is shown if the article is in stock (multilanguage)',\n  `OXNOSTOCKTEXT` varchar(255) NOT NULL default '' COMMENT 'Message, which is shown if the article is off stock (multilanguage)',\n  `OXDELIVERY` date NOT NULL default '0000-00-00' COMMENT 'Date, when the product will be available again if it is sold out',\n  `OXINSERT` date NOT NULL default '0000-00-00' COMMENT 'Creation time',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  `OXLENGTH` double NOT NULL default '0' COMMENT 'Article dimensions: Length',\n  `OXWIDTH` double NOT NULL default '0' COMMENT 'Article dimensions: Width',\n  `OXHEIGHT` double NOT NULL default '0' COMMENT 'Article dimensions: Height',\n  `OXFILE` varchar(128) NOT NULL default '' COMMENT 'File, shown in article media list',\n  `OXSEARCHKEYS` varchar(255) NOT NULL default '' COMMENT 'Search terms (multilanguage)',\n  `OXTEMPLATE` varchar(128) NOT NULL default '' COMMENT 'Alternative template filename (if empty, default is used)',\n  `OXQUESTIONEMAIL` varchar(255) NOT NULL default '' COMMENT 'E-mail for question',\n  `OXISSEARCH` tinyint(1) NOT NULL default '1' COMMENT 'Should article be shown in search',\n  `OXISCONFIGURABLE` tinyint NOT NULL DEFAULT '0' COMMENT 'Can article be customized',\n  `OXVARNAME` varchar(255) NOT NULL default '' COMMENT 'Name of variants selection lists (different lists are separated by | ) (multilanguage)',\n  `OXVARSTOCK` int(5) NOT NULL default '0' COMMENT 'Sum of active article variants stock quantity',\n  `OXVARCOUNT` int(1) NOT NULL default '0' COMMENT 'Total number of variants that article has (active and inactive)',\n  `OXVARSELECT` varchar(255) NOT NULL default '' COMMENT 'Variant article selections (separated by | ) (multilanguage)',\n  `OXVARMINPRICE` double NOT NULL default '0' COMMENT 'Lowest price in active article variants',\n  `OXVARMAXPRICE` double NOT NULL default '0' COMMENT 'Highest price in active article variants',\n  `OXVARNAME_1` varchar(255) NOT NULL default '',\n  `OXVARSELECT_1` varchar(255) NOT NULL default '',\n  `OXVARNAME_2` varchar(255) NOT NULL default '',\n  `OXVARSELECT_2` varchar(255) NOT NULL default '',\n  `OXVARNAME_3` varchar(255) NOT NULL default '',\n  `OXVARSELECT_3` varchar(255) NOT NULL default '',\n  `OXTITLE_1` varchar(255) NOT NULL default '',\n  `OXSHORTDESC_1` varchar(255) NOT NULL default '',\n  `OXURLDESC_1` varchar(255) NOT NULL default '',\n  `OXSEARCHKEYS_1` varchar(255) NOT NULL default '',\n  `OXTITLE_2` varchar(255) NOT NULL default '',\n  `OXSHORTDESC_2` varchar(255) NOT NULL default '',\n  `OXURLDESC_2` varchar(255) NOT NULL default '',\n  `OXSEARCHKEYS_2` varchar(255) NOT NULL default '',\n  `OXTITLE_3` varchar(255) NOT NULL default '',\n  `OXSHORTDESC_3` varchar(255) NOT NULL default '',\n  `OXURLDESC_3` varchar(255) NOT NULL default '',\n  `OXSEARCHKEYS_3` varchar(255) NOT NULL default '',\n  `OXBUNDLEID` varchar(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Bundled article id',\n  `OXFOLDER` varchar(32) NOT NULL default '' COMMENT 'Folder',\n  `OXSUBCLASS` varchar(32) NOT NULL default '' COMMENT 'Subclass',\n  `OXSTOCKTEXT_1` varchar(255) NOT NULL default '',\n  `OXSTOCKTEXT_2` varchar(255) NOT NULL default '',\n  `OXSTOCKTEXT_3` varchar(255) NOT NULL default '',\n  `OXNOSTOCKTEXT_1` varchar(255) NOT NULL default '',\n  `OXNOSTOCKTEXT_2` varchar(255) NOT NULL default '',\n  `OXNOSTOCKTEXT_3` varchar(255) NOT NULL default '',\n  `OXSORT` int(5) NOT NULL default '0' COMMENT 'Sorting',\n  `OXSOLDAMOUNT` double NOT NULL default '0' COMMENT 'Amount of sold articles including variants (used only for parent articles)',\n  `OXNONMATERIAL` int(1) NOT NULL default '0' COMMENT 'Intangible article, free shipping is used (variants inherits parent setting)',\n  `OXFREESHIPPING` int(1) NOT NULL default '0' COMMENT 'Free shipping (variants inherits parent setting)',\n  `OXREMINDACTIVE` int(1) NOT NULL default '0' COMMENT 'Enables sending of notification email when oxstock field value falls below oxremindamount value',\n  `OXREMINDAMOUNT` double NOT NULL default '0' COMMENT 'Defines the amount, below which notification email will be sent if oxremindactive is set to 1',\n  `OXAMITEMID` varchar(32) character set latin1 collate latin1_general_ci NOT NULL default '',\n  `OXAMTASKID` varchar(16) character set latin1 collate latin1_general_ci NOT NULL default '0',\n  `OXVENDORID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Vendor id (oxvendor)',\n  `OXMANUFACTURERID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Manufacturer id (oxmanufacturers)',\n  `OXSKIPDISCOUNTS` tinyint(1) NOT NULL default '0' COMMENT 'Skips all negative Discounts (Discounts, Vouchers, Delivery ...)',\n  `OXRATING` double NOT NULL default '0' COMMENT 'Article rating',\n  `OXRATINGCNT` int(11) NOT NULL default '0' COMMENT 'Rating votes count',\n  `OXMINDELTIME` int(11) NOT NULL default '0' COMMENT 'Minimal delivery time (unit is set in oxdeltimeunit)',\n  `OXMAXDELTIME` int(11) NOT NULL default '0' COMMENT 'Maximum delivery time (unit is set in oxdeltimeunit)',\n  `OXDELTIMEUNIT` varchar(255) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Delivery time unit: DAY, WEEK, MONTH',\n  `OXUPDATEPRICE` DOUBLE NOT NULL default '0' COMMENT 'If not 0, oxprice will be updated to this value on oxupdatepricetime date',\n  `OXUPDATEPRICEA` DOUBLE NOT NULL default '0' COMMENT 'If not 0, oxpricea will be updated to this value on oxupdatepricetime date',\n  `OXUPDATEPRICEB` DOUBLE NOT NULL default '0' COMMENT 'If not 0, oxpriceb will be updated to this value on oxupdatepricetime date',\n  `OXUPDATEPRICEC` DOUBLE NOT NULL default '0' COMMENT 'If not 0, oxpricec will be updated to this value on oxupdatepricetime date',\n  `OXUPDATEPRICETIME` TIMESTAMP NOT NULL COMMENT 'Date, when oxprice[a,b,c] should be updated to oxupdateprice[a,b,c] values',\n  `OXISDOWNLOADABLE` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT 'Enable download of files for this product',\n  `OXSHOWCUSTOMAGREEMENT` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT 'Show custom agreement check in checkout',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXSORT` (`OXSORT`),\n  KEY `OXISSEARCH` (`OXISSEARCH`),\n  KEY `OXSTOCKFLAG` (`OXSTOCKFLAG`),\n  KEY `OXACTIVE` (`OXACTIVE`),\n  KEY `OXACTIVEFROM` (`OXACTIVEFROM`),\n  KEY `OXACTIVETO` (`OXACTIVETO`),\n  KEY `OXVENDORID` (`OXVENDORID`),\n  KEY `OXMANUFACTURERID` (`OXMANUFACTURERID`),\n  KEY `OXSOLDAMOUNT` ( `OXSOLDAMOUNT` ),\n  KEY `parentsort` ( `OXPARENTID` , `OXSORT` ),\n  KEY `OXUPDATEPRICETIME` ( `OXUPDATEPRICETIME` ),\n  KEY `OXISDOWNLOADABLE` ( `OXISDOWNLOADABLE` ),\n  KEY `OXPRICE` ( `OXPRICE` )\n)ENGINE=InnoDB COMMENT 'Articles information';\n\n#\n# Table structure for table `oxartextends`\n# created on 2008-05-23\n#\n\nDROP TABLE IF EXISTS `oxartextends`;\n\nCREATE TABLE `oxartextends` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Article id (extends oxarticles article with this id)',\n  `OXLONGDESC` text NOT NULL COMMENT 'Long description (multilanguage)',\n  `OXLONGDESC_1` text NOT NULL,\n  `OXLONGDESC_2` text NOT NULL,\n  `OXLONGDESC_3` text NOT NULL,\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`)\n) ENGINE=InnoDB COMMENT 'Additional information for articles';\n\n#\n# Table structure for table `oxattribute`\n#\n\nDROP TABLE IF EXISTS `oxattribute`;\n\nCREATE TABLE `oxattribute` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Attribute id',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXTITLE` varchar(128) NOT NULL default '' COMMENT 'Title (multilanguage)',\n  `OXTITLE_1` varchar(128) NOT NULL default '',\n  `OXTITLE_2` varchar(128) NOT NULL default '',\n  `OXTITLE_3` varchar(128) NOT NULL default '',\n  `OXPOS` int(11) NOT NULL default '9999' COMMENT 'Sorting',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  `OXDISPLAYINBASKET` tinyint(1) NOT NULL default '0' COMMENT 'Display attribute`s value for articles in checkout',\n  PRIMARY KEY  (`OXID`)\n) ENGINE=InnoDB COMMENT 'Article attributes';\n\n\n#\n# Table structure for table `oxcategories`\n#\n\nDROP TABLE IF EXISTS `oxcategories`;\n\nCREATE TABLE `oxcategories` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Category id',\n  `OXPARENTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default 'oxrootid' COMMENT 'Parent category id',\n  `OXLEFT` int(11) NOT NULL default '0' COMMENT 'Used for building category tree',\n  `OXRIGHT` int(11) NOT NULL default '0' COMMENT 'Used for building category tree',\n  `OXROOTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Root category id',\n  `OXSORT` int(11) NOT NULL default '9999' COMMENT 'Sorting',\n  `OXACTIVE` tinyint(1) NOT NULL default '1' COMMENT 'Active (multilanguage)',\n  `OXHIDDEN` tinyint(1) NOT NULL default '0' COMMENT 'Hidden (Can be accessed by direct link, but is not visible in lists and menu)',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXTITLE` varchar(254) NOT NULL default '' COMMENT 'Title (multilanguage)',\n  `OXDESC` varchar(255) NOT NULL default '' COMMENT 'Description (multilanguage)',\n  `OXLONGDESC` text NOT NULL COMMENT 'Long description (multilanguage)',\n  `OXTHUMB` varchar(128) NOT NULL default '' COMMENT 'Thumbnail filename (multilanguage)',\n  `OXTHUMB_1` VARCHAR(128) NOT NULL DEFAULT '',\n  `OXTHUMB_2` VARCHAR(128) NOT NULL DEFAULT '',\n  `OXTHUMB_3` VARCHAR(128) NOT NULL DEFAULT '',\n  `OXEXTLINK` varchar(255) NOT NULL default '' COMMENT 'External link, that if specified is opened instead of category content',\n  `OXTEMPLATE` varchar(128) NOT NULL default '' COMMENT 'Alternative template filename (if empty, default is used)',\n  `OXDEFSORT` varchar(64) NOT NULL default '' COMMENT 'Default field for sorting of articles in this category (most of oxarticles fields)',\n  `OXDEFSORTMODE` tinyint(1) NOT NULL default '0' COMMENT 'Default mode of sorting of articles in this category (0 - asc, 1 - desc)',\n  `OXPRICEFROM` double NOT NULL default '0' COMMENT 'If specified, all articles, with price higher than specified, will be shown in this category',\n  `OXPRICETO` double NOT NULL default '0' COMMENT 'If specified, all articles, with price lower than specified, will be shown in this category',\n  `OXACTIVE_1` tinyint(1) NOT NULL default '0',\n  `OXTITLE_1` varchar(255) NOT NULL default '',\n  `OXDESC_1` varchar(255) NOT NULL default '',\n  `OXLONGDESC_1` text NOT NULL,\n  `OXACTIVE_2` tinyint(1) NOT NULL default '0',\n  `OXTITLE_2` varchar(255) NOT NULL default '',\n  `OXDESC_2` varchar(255) NOT NULL default '',\n  `OXLONGDESC_2` text NOT NULL,\n  `OXACTIVE_3` tinyint(1) NOT NULL default '0',\n  `OXTITLE_3` varchar(255) NOT NULL default '',\n  `OXDESC_3` varchar(255) NOT NULL default '',\n  `OXLONGDESC_3` text NOT NULL,\n  `OXICON` varchar(128) NOT NULL default '' COMMENT 'Icon filename',\n  `OXPROMOICON` varchar(128) NOT NULL default '' COMMENT 'Promotion icon filename',\n  `OXVAT` FLOAT NULL DEFAULT NULL COMMENT 'VAT, used for articles in this category (only if oxarticles.oxvat is not set)',\n  `OXSKIPDISCOUNTS` tinyint(1) NOT NULL default '0' COMMENT 'Skip all negative Discounts for articles in this category (Discounts, Vouchers, Delivery ...) ',\n  `OXSHOWSUFFIX` tinyint(1) NOT NULL default '1' COMMENT 'Show SEO Suffix in Category',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n   PRIMARY KEY  (`OXID`),\n   KEY `OXROOTID` (`OXROOTID`, `OXLEFT`, `OXRIGHT`),\n   KEY `OXPARENTID` (`OXPARENTID`),\n   KEY `OXPRICEFROM` (`OXPRICEFROM`),\n   KEY `OXPRICETO` (`OXPRICETO`),\n   KEY `OXHIDDEN` (`OXHIDDEN`),\n   KEY `OXSHOPID` (`OXSHOPID`),\n   KEY `OXSORT` (`OXSORT`),\n   KEY `OXVAT` (`OXVAT`)\n) ENGINE=InnoDB COMMENT 'Article categories';\n\n#\n# Table structure for table `oxcategory2attribute`\n#\n\nDROP TABLE IF EXISTS `oxcategory2attribute`;\n\nCREATE TABLE `oxcategory2attribute` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Record id',\n  `OXOBJECTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Category id (oxcategories)',\n  `OXATTRID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Attribute id (oxattributes)',\n  `OXSORT` INT( 11 ) NOT NULL DEFAULT '9999' COMMENT 'Sorting',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Creation time',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXOBJECTID` (`OXOBJECTID`)\n) ENGINE=InnoDB COMMENT 'Shows many-to-many relationship between categories and attributes';\n\n\n#\n# Table structure for table `oxconfig`\n#\n\nDROP TABLE IF EXISTS `oxconfig`;\n\nCREATE TABLE `oxconfig` (\n  `OXID`            char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Config id',\n  `OXSHOPID`        int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXMODULE`        varchar(100) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Module or theme specific config (theme:themename, module:modulename)',\n  `OXVARNAME`       varchar(100) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Variable name',\n  `OXVARTYPE`       varchar(16) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Variable type',\n  `OXVARVALUE`      text NOT NULL COMMENT 'Variable value',\n  `OXTIMESTAMP`     timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXVARNAME` (`OXVARNAME`),\n  KEY `listall` (`OXSHOPID`, `OXMODULE`)\n) ENGINE=InnoDB COMMENT 'Shop configuration values';\n\n#\n# Table structure for table `oxconfigdisplay`\n# Created on 2010-11-11\n#\n\nDROP TABLE IF EXISTS `oxconfigdisplay`;\n\nCREATE TABLE `oxconfigdisplay` (\n  `OXID`            char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Config id (extends oxconfig record with this id)',\n  `OXCFGMODULE`     varchar(100) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Module or theme specific config (theme:themename, module:modulename)',\n  `OXCFGVARNAME`    varchar(100) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Variable name',\n  `OXGROUPING`      varchar(255) NOT NULL default '' COMMENT 'Grouping (groups config fields to array with specified value as key)',\n  `OXVARCONSTRAINT` varchar(255) NOT NULL default '' COMMENT 'Serialized constraints',\n  `OXPOS`           int NOT NULL default 0 COMMENT 'Sorting',\n  `OXTIMESTAMP`     timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `list` (`OXCFGMODULE`, `OXCFGVARNAME`)\n) ENGINE=InnoDB COMMENT 'Additional configuraion fields';\n\n#\n# Table structure for table `oxcontents`\n#\n\nDROP TABLE IF EXISTS `oxcontents`;\n\nCREATE TABLE `oxcontents` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Content id',\n  `OXLOADID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Id, specified by admin and can be used instead of oxid',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXSNIPPET` tinyint(1) NOT NULL default '1' COMMENT 'Snippet (can be included to other oxcontents records)',\n  `OXTYPE` tinyint(1) NOT NULL default '0' COMMENT 'Type: 0 - Snippet, 1 - Upper Menu, 2 - Category, 3 - Manual',\n  `OXACTIVE` tinyint(1) NOT NULL default '0' COMMENT 'Active (multilanguage)',\n  `OXACTIVE_1` tinyint(1) NOT NULL default '0' COMMENT '',\n  `OXPOSITION` varchar(32) NOT NULL default '' COMMENT 'Position',\n  `OXTITLE` varchar(255) NOT NULL default '' COMMENT 'Title (multilanguage)',\n  `OXCONTENT` MEDIUMTEXT NOT NULL COMMENT 'Content (multilanguage)',\n  `OXTITLE_1` varchar(255) NOT NULL default '' COMMENT '',\n  `OXCONTENT_1` MEDIUMTEXT NOT NULL,\n  `OXACTIVE_2` tinyint(1) NOT NULL default '0' ,\n  `OXTITLE_2` varchar(255) NOT NULL default '' ,\n  `OXCONTENT_2` MEDIUMTEXT NOT NULL ,\n  `OXACTIVE_3` tinyint(1) NOT NULL default '0' ,\n  `OXTITLE_3` varchar(255) NOT NULL default '' ,\n  `OXCONTENT_3` MEDIUMTEXT NOT NULL,\n  `OXCATID` varchar(32) character set latin1 collate latin1_general_ci default NULL COMMENT 'Category id (oxcategories), used only when type = 2',\n  `OXFOLDER` varchar(32) NOT NULL default '' COMMENT 'Content Folder (available options at oxconfig.OXVARNAME = aCMSfolder)',\n  `OXTERMVERSION` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Term and Conditions version (used only when OXLOADID = oxagb)',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  UNIQUE KEY `OXLOADID` (`OXLOADID`),\n  INDEX `cat_search` ( `OXTYPE` , `OXSHOPID` , `OXSNIPPET` , `OXCATID` )\n) ENGINE=InnoDB COMMENT 'Content pages (Snippets, Menu, Categories, Manual)';\n\n#\n# Table structure for table `oxcounters`\n#\n\nDROP TABLE IF EXISTS `oxcounters`;\n\nCREATE TABLE  `oxcounters` (\n  `OXIDENT` CHAR( 32 ) NOT NULL COMMENT 'Counter id',\n  `OXCOUNT` INT NOT NULL COMMENT 'Counted number',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY (  `OXIDENT` )\n) ENGINE = InnoDB COMMENT 'Shop counters';\n\n#\n# Table structure for table `oxcountry`\n#\n\nDROP TABLE IF EXISTS `oxcountry`;\n\nCREATE TABLE `oxcountry` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Country id',\n  `OXACTIVE` tinyint(1) NOT NULL default '0' COMMENT 'Active',\n  `OXTITLE` varchar(128) NOT NULL default '' COMMENT 'Title (multilanguage)',\n  `OXISOALPHA2` char(2) NOT NULL default '' COMMENT 'ISO 3166-1 alpha-2',\n  `OXISOALPHA3` char(3) NOT NULL default '' COMMENT 'ISO 3166-1 alpha-3',\n  `OXUNNUM3` char(3) NOT NULL default '' COMMENT 'ISO 3166-1 numeric',\n  `OXVATINPREFIX` char(2) NOT NULL default '' COMMENT 'VAT identification number prefix',\n  `OXORDER` int(11) NOT NULL default '9999' COMMENT 'Sorting',\n  `OXSHORTDESC` varchar(255) NOT NULL default '' COMMENT 'Short description (multilanguage)',\n  `OXLONGDESC` varchar(255) NOT NULL default '' COMMENT 'Long description (multilanguage)',\n  `OXTITLE_1` varchar(128) NOT NULL default '',\n  `OXTITLE_2` varchar(128) NOT NULL default '',\n  `OXTITLE_3` varchar(128) NOT NULL default '',\n  `OXSHORTDESC_1` varchar(255) NOT NULL default '',\n  `OXSHORTDESC_2` varchar(255) NOT NULL default '',\n  `OXSHORTDESC_3` varchar(255) NOT NULL default '',\n  `OXLONGDESC_1` varchar(255) NOT NULL,\n  `OXLONGDESC_2` varchar(255) NOT NULL,\n  `OXLONGDESC_3` varchar(255) NOT NULL,\n  `OXVATSTATUS` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Vat status: 0 - Do not bill VAT, 1 - Do not bill VAT only if provided valid VAT ID',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY (`OXACTIVE`)\n) ENGINE=InnoDB COMMENT 'Countries list';\n\n#\n# Table structure for table `oxdel2delset`\n#\n\nDROP TABLE IF EXISTS `oxdel2delset`;\n\nCREATE TABLE `oxdel2delset` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Record id',\n  `OXDELID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Shipping cost rule id (oxdelivery)',\n  `OXDELSETID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Delivery method id (oxdeliveryset)',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXDELID` (`OXDELID`)\n) ENGINE=InnoDB COMMENT 'Shows many-to-many relationship between Shipping cost rules (oxdelivery) and delivery methods (oxdeliveryset)';\n\n#\n# Table structure for table `oxdelivery`\n#\n\nDROP TABLE IF EXISTS `oxdelivery`;\n\nCREATE TABLE `oxdelivery` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Delivery shipping cost rule id',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXACTIVE` tinyint(1) NOT NULL default '0' COMMENT 'Active',\n  `OXACTIVEFROM` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Active from specified date',\n  `OXACTIVETO` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Active to specified date',\n  `OXTITLE` varchar(255) NOT NULL default '' COMMENT 'Title (multilanguage)',\n  `OXTITLE_1` varchar(255) NOT NULL default '',\n  `OXTITLE_2` varchar(255) NOT NULL default '',\n  `OXTITLE_3` varchar(255) NOT NULL default '',\n  `OXADDSUMTYPE` enum('%','abs') NOT NULL default 'abs' COMMENT 'Price Surcharge/Reduction type (abs|%)',\n  `OXADDSUM` double NOT NULL default '0' COMMENT 'Price Surcharge/Reduction amount',\n  `OXDELTYPE` enum('a','s','w','p') NOT NULL default 'a' COMMENT 'Condition type: a - Amount, s - Size, w - Weight, p - Price',\n  `OXPARAM` double NOT NULL default '0' COMMENT 'Condition param from (e.g. amount from 1)',\n  `OXPARAMEND` double NOT NULL default '0' COMMENT 'Condition param to (e.g. amount to 10)',\n  `OXFIXED` tinyint(1) NOT NULL default '0' COMMENT 'Calculation Rules: 0 - Once per Cart, 1 - Once for each different product, 2 - For each product',\n  `OXSORT` int(11) NOT NULL default '9999' COMMENT 'Order of Rules Processing',\n  `OXFINALIZE` tinyint(1) NOT NULL default '0' COMMENT 'Do not run further rules if this rule is valid and is being run',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXSHOPID` (`OXSHOPID`)\n)  ENGINE=InnoDB COMMENT 'Delivery shipping cost rules';\n\n#\n# Table structure for table `oxdeliveryset`\n#\n\nDROP TABLE IF EXISTS `oxdeliveryset`;\n\nCREATE TABLE `oxdeliveryset` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Delivery method id',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXACTIVE` tinyint(1) NOT NULL default '0' COMMENT 'Active',\n  `OXACTIVEFROM` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Active from specified date',\n  `OXACTIVETO` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Active to specified date',\n  `OXTITLE` varchar(255) NOT NULL default '' COMMENT 'Title (multilanguage)',\n  `OXTITLE_1` varchar(255) NOT NULL default '',\n  `OXTITLE_2` varchar(255) NOT NULL default '',\n  `OXTITLE_3` varchar(255) NOT NULL default '',\n  `OXPOS` int(11) NOT NULL default '0' COMMENT 'Sorting',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Creation time',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXSHOPID` (`OXSHOPID`)\n) ENGINE=InnoDB COMMENT 'Delivery (shipping) methods';\n\n#\n# Table structure for table `oxdiscount`\n#\n\nDROP TABLE IF EXISTS `oxdiscount`;\n\nCREATE TABLE `oxdiscount` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Discount id',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXACTIVE` tinyint(1) NOT NULL default '0' COMMENT 'Active',\n  `OXACTIVEFROM` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Active from specified date',\n  `OXACTIVETO` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Active to specified date',\n  `OXTITLE` varchar(128) NOT NULL default '' COMMENT 'Title (multilanguage)',\n  `OXTITLE_1` varchar( 128 ) NOT NULL,\n  `OXTITLE_2` varchar( 128 ) NOT NULL,\n  `OXTITLE_3` varchar( 128 ) NOT NULL,\n  `OXAMOUNT` double NOT NULL default '0' COMMENT 'Valid from specified amount of articles',\n  `OXAMOUNTTO` double NOT NULL default '999999' COMMENT 'Valid to specified amount of articles',\n  `OXPRICETO` double NOT NULL default '999999' COMMENT 'Valid to specified purchase price',\n  `OXPRICE` double NOT NULL default '0' COMMENT 'Valid from specified purchase price',\n  `OXADDSUMTYPE` enum('%','abs','itm') NOT NULL default '%' COMMENT 'Discount type (%,abs,itm)',\n  `OXADDSUM` double NOT NULL default '0' COMMENT 'Magnitude of the discount',\n  `OXITMARTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Free article id, that will be added as a discount',\n  `OXITMAMOUNT` double NOT NULL default '1' COMMENT 'The quantity of free article that will be added to basket with discounted article',\n  `OXITMMULTIPLE` int(1) NOT NULL default '0' COMMENT 'Should free article amount be multiplied by discounted item quantity in basket',\n  `OXSORT` int(5) NOT NULL default '0' COMMENT 'Defines the order discounts are applied to basket or product',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  UNIQUE INDEX `UNIQ_OXSORT` (`OXSHOPID`, `OXSORT`),\n  KEY `OXSHOPID` (`OXSHOPID`),\n  KEY `OXACTIVE` (`OXACTIVE`),\n  KEY `OXACTIVEFROM` (`OXACTIVEFROM`),\n  KEY `OXACTIVETO` (`OXACTIVETO`)\n) ENGINE=InnoDB COMMENT 'Article discounts';\n\n#\n# Table structure for table `oxfiles`\n#\n\nDROP TABLE IF EXISTS `oxfiles`;\n\nCREATE TABLE IF NOT EXISTS `oxfiles` (\n  `OXID` char(32) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL COMMENT 'File id',\n  `OXARTID` char(32) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL COMMENT 'Article id (oxarticles)',\n  `OXFILENAME` varchar(128) NOT NULL COMMENT 'Filename',\n  `OXSTOREHASH` char(32) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL COMMENT 'Hashed filename, used for file directory path creation',\n  `OXPURCHASEDONLY` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT 'Download is available only after purchase',\n  `OXMAXDOWNLOADS` int(11) NOT NULL default '-1' COMMENT 'Maximum count of downloads after order',\n  `OXMAXUNREGDOWNLOADS` int(11) NOT NULL default '-1' COMMENT 'Maximum count of downloads for not registered users after order',\n  `OXLINKEXPTIME` int(11) NOT NULL default '-1' COMMENT 'Expiration time of download link in hours',\n  `OXDOWNLOADEXPTIME` int(11) NOT NULL default '-1' COMMENT 'Expiration time of download link after the first download in hours',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Creation time',\n  PRIMARY KEY (`OXID`),\n  KEY `OXARTID` (`OXARTID`)\n) ENGINE=InnoDB COMMENT 'Files available for users to download';\n\n#\n# Table structure for table `oxgroups`\n#\n\nDROP TABLE IF EXISTS `oxgroups`;\n\nCREATE TABLE `oxgroups` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Group id',\n  `OXACTIVE` tinyint(1) NOT NULL default '1' COMMENT 'Active',\n  `OXTITLE` varchar(128) NOT NULL default '' COMMENT 'Title (multilanguage)',\n  `OXTITLE_1` varchar(128) NOT NULL default '',\n  `OXTITLE_2` varchar(128) NOT NULL default '',\n  `OXTITLE_3` varchar(128) NOT NULL default '',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXACTIVE` (`OXACTIVE`)\n) ENGINE=InnoDB COMMENT 'User groups';\n\n#\n# Table structure for table `oxinvitations`\n# for storing information about invited users\n# created 2010-01-06\n#\n\nDROP TABLE IF EXISTS `oxinvitations`;\n\nCREATE TABLE IF NOT EXISTS `oxinvitations` (\n   `OXUSERID` char(32) collate latin1_general_ci NOT NULL COMMENT 'User id (oxuser), who sent invitation',\n   `OXDATE` date NOT NULL COMMENT 'Creation time',\n   `OXEMAIL` varchar(255) collate latin1_general_ci NOT NULL COMMENT 'Recipient email',\n   `OXPENDING` mediumint(9) NOT NULL COMMENT 'Has recipient user registered',\n   `OXACCEPTED` mediumint(9) NOT NULL COMMENT 'Is recipient user accepted',\n   `OXTYPE` tinyint(4) NOT NULL default '1' COMMENT 'Invitation type',\n   `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n    KEY `OXUSERID` (`OXUSERID`),\n    KEY `OXDATE` (`OXDATE`)\n) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci COMMENT 'User sent invitations';\n\n#\n# Table structure for table `oxlinks`\n#\n\nDROP TABLE IF EXISTS `oxlinks`;\n\nCREATE TABLE `oxlinks` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Link id',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXACTIVE` tinyint(1) NOT NULL default '0' COMMENT 'Active',\n  `OXURL` varchar(255) NOT NULL default '' COMMENT 'Link url',\n  `OXURLDESC` text NOT NULL COMMENT 'Description (multilanguage)',\n  `OXURLDESC_1` text NOT NULL,\n  `OXURLDESC_2` text NOT NULL,\n  `OXURLDESC_3` text NOT NULL,\n  `OXINSERT` datetime default NULL COMMENT 'Creation time (set by user)',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXSHOPID` (`OXSHOPID`),\n  KEY `OXINSERT` (`OXINSERT`),\n  KEY `OXACTIVE` (`OXACTIVE`)\n) ENGINE=InnoDB COMMENT 'Links';\n\n#\n# Table structure for table `oxmanufacturers`\n#\n\nDROP TABLE IF EXISTS `oxmanufacturers`;\n\nCREATE TABLE `oxmanufacturers` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Manufacturer id',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXACTIVE` tinyint(1) NOT NULL default '1' COMMENT 'Is active',\n  `OXICON` varchar(128) NOT NULL default '' COMMENT 'Icon filename',\n  `OXTITLE` varchar(255) NOT NULL default '' COMMENT 'Title (multilanguage)',\n  `OXSHORTDESC` varchar(255) NOT NULL default '' COMMENT 'Short description (multilanguage)',\n  `OXTITLE_1` varchar(255) NOT NULL default '',\n  `OXSHORTDESC_1` varchar(255) NOT NULL default '',\n  `OXTITLE_2` varchar(255) NOT NULL default '',\n  `OXSHORTDESC_2` varchar(255) NOT NULL default '',\n  `OXTITLE_3` varchar(255) NOT NULL default '',\n  `OXSHORTDESC_3` varchar(255) NOT NULL default '',\n  `OXSHOWSUFFIX` tinyint(1) NOT NULL default '1' COMMENT 'Show SEO Suffix in Category',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`)\n) ENGINE=InnoDB COMMENT 'Shop manufacturers';\n\n#\n# Table structure for table `oxmediaurls`\n# For storing extended file urls\n# Created 2008-06-25\n#\n\nDROP TABLE IF EXISTS `oxmediaurls`;\n\nCREATE TABLE `oxmediaurls` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Media id',\n  `OXOBJECTID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Article id (oxarticles)',\n  `OXURL` varchar(255) NOT NULL COMMENT 'Media url or filename',\n  `OXDESC` varchar(255) NOT NULL COMMENT 'Description (multilanguage)',\n  `OXDESC_1` varchar(255) NOT NULL,\n  `OXDESC_2` varchar(255) NOT NULL,\n  `OXDESC_3` varchar(255) NOT NULL,\n  `OXISUPLOADED` int(1) NOT NULL default '0' COMMENT 'Is oxurl field used for filename or url',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n PRIMARY KEY ( `OXID` ) ,\n INDEX ( `OXOBJECTID` )\n) ENGINE = InnoDB COMMENT 'Stores objects media';\n\n#\n# Table structure for table `oxnewssubscribed`\n#\n\nDROP TABLE IF EXISTS `oxnewssubscribed`;\n\nCREATE TABLE `oxnewssubscribed` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Subscription id',\n  `OXUSERID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'User id (oxuser)',\n  `OXSAL` varchar(64) NOT NULL default '' COMMENT 'User title prefix (Mr/Mrs)',\n  `OXFNAME` char(128) NOT NULL default '' COMMENT 'First name',\n  `OXLNAME` char(128) NOT NULL default '' COMMENT 'Last name',\n  `OXEMAIL` char(128) NOT NULL default '' COMMENT 'Email',\n  `OXDBOPTIN` tinyint(1) NOT NULL default '0' COMMENT 'Subscription status: 0 - not subscribed, 1 - subscribed, 2 - not confirmed',\n  `OXEMAILFAILED` tinyint(1) NOT NULL default '0' COMMENT 'Subscription email sending status',\n  `OXSUBSCRIBED` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Subscription date',\n  `OXUNSUBSCRIBED` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Unsubscription date',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  PRIMARY KEY (`OXID`),\n  UNIQUE KEY `OXEMAIL` (`OXEMAIL`),\n  KEY `OXUSERID` (`OXUSERID`)\n) ENGINE=InnoDB COMMENT 'User subscriptions';\n\n#\n# Table structure for table `oxobject2action`\n#\n\nDROP TABLE IF EXISTS `oxobject2action`;\n\nCREATE TABLE IF NOT EXISTS `oxobject2action` (\n  `OXID` char(32) collate latin1_general_ci NOT NULL COMMENT 'Record id',\n  `OXACTIONID` char(32) collate latin1_general_ci NOT NULL default '' COMMENT 'Action id (oxactions)',\n  `OXOBJECTID` char(32) collate latin1_general_ci NOT NULL default '' COMMENT 'Object id (table set by oxclass)',\n  `OXCLASS` char(32) collate latin1_general_ci NOT NULL default '' COMMENT 'Object table name',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXOBJECTID` (`OXOBJECTID`),\n  KEY `OXACTIONID` (`OXACTIONID`,`OXCLASS`)\n) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci COMMENT 'Shows many-to-many relationship between actions (oxactions) and objects (table set by oxclass)';\n\n#\n# Table structure for table `oxobject2article`\n#\n\nDROP TABLE IF EXISTS `oxobject2article`;\n\nCREATE TABLE `oxobject2article` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Record id',\n  `OXOBJECTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Cross-selling Article id (oxarticles)',\n  `OXARTICLENID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Main Article id (oxarticles)',\n  `OXSORT` int(5) NOT NULL default '0' COMMENT 'Sorting',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXARTICLENID` (`OXARTICLENID`),\n  KEY `OXOBJECTID` (`OXOBJECTID`)\n) ENGINE=InnoDB COMMENT 'Shows many-to-many relationship between cross-selling articles';\n\n#\n# Table structure for table `oxobject2attribute`\n#\n\nDROP TABLE IF EXISTS `oxobject2attribute`;\n\nCREATE TABLE `oxobject2attribute` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Record id',\n  `OXOBJECTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Article id (oxarticles)',\n  `OXATTRID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Attribute id (oxattributes)',\n  `OXVALUE` varchar(255) NOT NULL default '' COMMENT 'Attribute value (multilanguage)',\n  `OXPOS` int(11) NOT NULL default '9999' COMMENT 'Sorting',\n  `OXVALUE_1` varchar(255) NOT NULL default '',\n  `OXVALUE_2` varchar(255) NOT NULL default '',\n  `OXVALUE_3` varchar(255) NOT NULL default '',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXOBJECTID` (`OXOBJECTID`),\n  KEY `OXATTRID` (`OXATTRID`)\n) ENGINE=InnoDB COMMENT 'Shows many-to-many relationship between articles and attributes';\n\n#\n# Table structure for table `oxobject2category`\n#\n\nDROP TABLE IF EXISTS `oxobject2category`;\n\nCREATE TABLE `oxobject2category` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Record id',\n  `OXOBJECTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Article id (oxarticles)',\n  `OXCATNID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Category id (oxcategory)',\n  `OXPOS` int(11) NOT NULL default '0' COMMENT 'Sorting',\n  `OXTIME` INT( 11 ) DEFAULT 0 NOT NULL COMMENT 'Creation time',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  UNIQUE KEY `OXMAINIDX` (`OXCATNID`,`OXOBJECTID`),\n  KEY ( `OXOBJECTID` ),\n  KEY (`OXPOS`),\n  KEY `OXTIME` (`OXTIME`)\n) ENGINE=InnoDB COMMENT 'Shows many-to-many relationship between articles and categories';\n\n#\n# Table structure for table `oxobject2delivery`\n#\n\nDROP TABLE IF EXISTS `oxobject2delivery`;\n\nCREATE TABLE `oxobject2delivery` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Record id',\n  `OXDELIVERYID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Delivery id (oxdelivery)',\n  `OXOBJECTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Object id (table determined by oxtype)',\n  `OXTYPE` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Record type',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXOBJECTID` (`OXOBJECTID`),\n  KEY `OXDELIVERYID` ( `OXDELIVERYID` , `OXTYPE` )\n) ENGINE=InnoDB COMMENT 'Shows many-to-many relationship between delivery cost rules and objects (table determined by oxtype)';\n\n#\n# Table structure for table `oxobject2discount`\n#\n\nDROP TABLE IF EXISTS `oxobject2discount`;\n\nCREATE TABLE `oxobject2discount` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Record id',\n  `OXDISCOUNTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Discount id (oxdiscount)',\n  `OXOBJECTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Object id (table determined by oxtype)',\n  `OXTYPE` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Record type',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `oxobjectid` (`OXOBJECTID`),\n  KEY `oxdiscidx` (`OXDISCOUNTID`,`OXTYPE`)\n) ENGINE=InnoDB COMMENT 'Shows many-to-many relationship between discounts and objects (table determined by oxtype)';\n\n#\n# Table structure for table `oxobject2group`\n#\n\nDROP TABLE IF EXISTS `oxobject2group`;\n\nCREATE TABLE `oxobject2group` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Record id',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXOBJECTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'User id',\n  `OXGROUPSID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Group id',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXOBJECTID` (`OXOBJECTID`),\n  UNIQUE INDEX `UNIQ_OBJECTGROUP` (`OXGROUPSID`, `OXOBJECTID`, `OXSHOPID`)\n) ENGINE=InnoDB;\n\n#\n# Table structure for table `oxobject2list`\n#\n\nDROP TABLE IF EXISTS `oxobject2list`;\n\nCREATE TABLE `oxobject2list` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Record id',\n  `OXOBJECTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Article id (oxarticles)',\n  `OXLISTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Listmania id (oxrecommlists)',\n  `OXDESC` text NOT NULL default '' COMMENT 'Description',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXOBJECTID` (`OXOBJECTID`),\n  KEY `OXLISTID` (`OXLISTID`)\n) ENGINE=InnoDB COMMENT 'Shows many-to-many relationship between articles and listmania lists';\n\n#\n# Table structure for table `oxobject2payment`\n#\n\nDROP TABLE IF EXISTS `oxobject2payment`;\n\nCREATE TABLE `oxobject2payment` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Record id',\n  `OXPAYMENTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Payment id (oxpayments)',\n  `OXOBJECTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Object id (table determined by oxtype)',\n  `OXTYPE` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Record type',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY ( `OXOBJECTID` ),\n  KEY ( `OXPAYMENTID` )\n) ENGINE=InnoDB COMMENT 'Shows many-to-many relationship between payments and objects (table determined by oxtype)';\n\n#\n# Table structure for table `oxobject2selectlist`\n#\n\nDROP TABLE IF EXISTS `oxobject2selectlist`;\n\nCREATE TABLE `oxobject2selectlist` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Record id',\n  `OXOBJECTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Article id (oxarticles)',\n  `OXSELNID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Selection list id (oxselectlist)',\n  `OXSORT` int(5) NOT NULL default '0' COMMENT 'Sorting',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXOBJECTID` (`OXOBJECTID`),\n  KEY `OXSELNID` (`OXSELNID`)\n) ENGINE=InnoDB COMMENT 'Shows many-to-many relationship between articles and selection lists';\n\n#\n# Table structure for table `oxobject2seodata`\n# For storing SEO meta data\n# Created 2010-05-11\n#\n\nDROP TABLE IF EXISTS `oxobject2seodata`;\n\nCREATE TABLE `oxobject2seodata` (\n  `OXOBJECTID` CHAR( 32 ) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Objects id',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXLANG` INT( 2 ) NOT NULL default '0' COMMENT 'Language id',\n  `OXKEYWORDS` TEXT NOT NULL COMMENT 'Keywords',\n  `OXDESCRIPTION` TEXT NOT NULL COMMENT 'Description',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY ( `OXOBJECTID` , `OXSHOPID` , `OXLANG` )\n) ENGINE = InnoDB  COMMENT 'Seo entries';\n\n#\n# Table structure for table `oxorder`\n#\n\nDROP TABLE IF EXISTS `oxorder`;\n\nCREATE TABLE `oxorder` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Order id',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXUSERID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'User id (oxuser)',\n  `OXORDERDATE` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Order date',\n  `OXORDERNR` int(11) UNSIGNED NOT NULL default '0' COMMENT 'Order number',\n  `OXBILLCOMPANY` varchar(255) NOT NULL default '' COMMENT 'Billing info: Company name',\n  `OXBILLEMAIL` varchar(255) NOT NULL default '' COMMENT 'Billing info: Email',\n  `OXBILLFNAME` varchar(255) NOT NULL default '' COMMENT 'Billing info: First name',\n  `OXBILLLNAME` varchar(255) NOT NULL default '' COMMENT 'Billing info: Last name',\n  `OXBILLSTREET` varchar(255) NOT NULL default '' COMMENT 'Billing info: Street name',\n  `OXBILLSTREETNR` varchar(16) NOT NULL default '' COMMENT 'Billing info: House number',\n  `OXBILLADDINFO` varchar(255) NOT NULL default '' COMMENT 'Billing info: Additional info',\n  `OXBILLUSTID` varchar(255) NOT NULL default '' COMMENT 'Billing info: VAT ID No.',\n  `OXBILLCITY` varchar(255) NOT NULL default '' COMMENT 'Billing info: City',\n  `OXBILLCOUNTRYID` varchar(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Billing info: Country id (oxcountry)',\n  `OXBILLSTATEID` varchar(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Billing info: US State id (oxstates)',\n  `OXBILLZIP` varchar(16) NOT NULL default '' COMMENT 'Billing info: Zip code',\n  `OXBILLFON` varchar(128) NOT NULL default '' COMMENT 'Billing info: Phone number',\n  `OXBILLFAX` varchar(128) NOT NULL default '' COMMENT 'Billing info: Fax number',\n  `OXBILLSAL` varchar(128) NOT NULL default '' COMMENT 'Billing info: User title prefix (Mr/Mrs)',\n  `OXDELCOMPANY` varchar(255) NOT NULL default '' COMMENT 'Shipping info: Company name',\n  `OXDELFNAME` varchar(255) NOT NULL default '' COMMENT 'Shipping info: First name',\n  `OXDELLNAME` varchar(255) NOT NULL default '' COMMENT 'Shipping info: Last name',\n  `OXDELSTREET` varchar(255) NOT NULL default '' COMMENT 'Shipping info: Street name',\n  `OXDELSTREETNR` varchar(16) NOT NULL default '' COMMENT 'Shipping info: House number',\n  `OXDELADDINFO` varchar(255) NOT NULL default '' COMMENT 'Shipping info: Additional info',\n  `OXDELCITY` varchar(255) NOT NULL default '' COMMENT 'Shipping info: City',\n  `OXDELCOUNTRYID` varchar(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Shipping info: Country id (oxcountry)',\n  `OXDELSTATEID` varchar(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Shipping info: US State id (oxstates)',\n  `OXDELZIP` varchar(16) NOT NULL default '' COMMENT 'Shipping info: Zip code',\n  `OXDELFON` varchar(128) NOT NULL default '' COMMENT 'Shipping info: Phone number',\n  `OXDELFAX` varchar(128) NOT NULL default '' COMMENT 'Shipping info: Fax number',\n  `OXDELSAL` varchar(128) NOT NULL default '' COMMENT 'Shipping info: User title prefix (Mr/Mrs)',\n  `OXPAYMENTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'User payment id (oxuserpayments)',\n  `OXPAYMENTTYPE` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Payment id (oxpayments)',\n  `OXTOTALNETSUM` double NOT NULL default '0' COMMENT 'Total net sum',\n  `OXTOTALBRUTSUM` double NOT NULL default '0' COMMENT 'Total brut sum',\n  `OXTOTALORDERSUM` double NOT NULL default '0' COMMENT 'Total order sum',\n  `OXARTVAT1` double NOT NULL default '0' COMMENT 'First VAT',\n  `OXARTVATPRICE1` double NOT NULL default '0' COMMENT 'First calculated VAT price',\n  `OXARTVAT2` double NOT NULL default '0' COMMENT 'Second VAT',\n  `OXARTVATPRICE2` double NOT NULL default '0' COMMENT 'Second calculated VAT price',\n  `OXDELCOST` double NOT NULL default '0' COMMENT 'Delivery price',\n  `OXDELVAT` double NOT NULL default '0' COMMENT 'Delivery VAT',\n  `OXPAYCOST` double NOT NULL default '0' COMMENT 'Payment cost',\n  `OXPAYVAT` double NOT NULL default '0' COMMENT 'Payment VAT',\n  `OXWRAPCOST` double NOT NULL default '0' COMMENT 'Wrapping cost',\n  `OXWRAPVAT` double NOT NULL default '0' COMMENT 'Wrapping VAT',\n  `OXGIFTCARDCOST` double NOT NULL default '0' COMMENT 'Giftcard cost',\n  `OXGIFTCARDVAT` double NOT NULL default '0' COMMENT 'Giftcard VAT',\n  `OXCARDID` varchar( 32 ) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Gift card id (oxwrapping)',\n  `OXCARDTEXT` text NOT NULL COMMENT 'Gift card text',\n  `OXDISCOUNT` double NOT NULL default '0' COMMENT 'Additional discount for order (abs)',\n  `OXEXPORT` tinyint(4) NOT NULL default '0' COMMENT 'Is exported',\n  `OXBILLNR` varchar(128) NOT NULL default '' COMMENT 'Invoice No.',\n  `OXBILLDATE` date NOT NULL default '0000-00-00' COMMENT 'Invoice sent date',\n  `OXTRACKCODE` varchar(128) NOT NULL default '' COMMENT 'Tracking code',\n  `OXSENDDATE` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Order shipping date',\n  `OXREMARK` text NOT NULL COMMENT 'User remarks',\n  `OXVOUCHERDISCOUNT` double NOT NULL default '0' COMMENT 'Coupon (voucher) discount price',\n  `OXCURRENCY` varchar(32) NOT NULL default '' COMMENT 'Currency',\n  `OXCURRATE` double NOT NULL default '0' COMMENT 'Currency rate',\n  `OXFOLDER` varchar(32) NOT NULL default '' COMMENT 'Folder: ORDERFOLDER_FINISHED, ORDERFOLDER_NEW, ORDERFOLDER_PROBLEMS',\n  `OXTRANSID` varchar(64) NOT NULL default '' COMMENT 'Paypal: Transaction id',\n  `OXPAYID` varchar(64) character set latin1 collate latin1_general_ci NOT NULL default '',\n  `OXXID` varchar(64) NOT NULL default '',\n  `OXPAID` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Time, when order was paid',\n  `OXSTORNO` tinyint(1) NOT NULL default '0' COMMENT 'Order cancelled',\n  `OXIP` varchar(39) NOT NULL default '' COMMENT 'User ip address',\n  `OXTRANSSTATUS` varchar(30) NOT NULL default '' COMMENT 'Order status: NOT_FINISHED, OK, ERROR',\n  `OXLANG` int(2) NOT NULL default '0' COMMENT 'Language id',\n  `OXINVOICENR` int(11) NOT NULL default '0' COMMENT 'Invoice number',\n  `OXDELTYPE` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Delivery id (oxdeliveryset)',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  `OXISNETTOMODE` tinyint(1) UNSIGNED NOT NULL DEFAULT  '0' COMMENT 'Order created in netto mode',\n  PRIMARY KEY  (`OXID`),\n  KEY `MAINIDX` (`OXSHOPID`,`OXSTORNO`,`OXORDERDATE`)\n) ENGINE=InnoDB COMMENT 'Shop orders information';\n\n#\n# Table structure for table `oxorderarticles`\n#\n\nDROP TABLE IF EXISTS `oxorderarticles`;\n\nCREATE TABLE `oxorderarticles` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Order article id',\n  `OXORDERID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Order id (oxorder)',\n  `OXAMOUNT` double NOT NULL default '0' COMMENT 'Amount',\n  `OXARTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Article id (oxarticles)',\n  `OXARTNUM` varchar(255) NOT NULL default '' COMMENT 'Article number',\n  `OXTITLE` varchar(255) NOT NULL default '' COMMENT 'Title',\n  `OXSHORTDESC` varchar(255) NOT NULL default '' COMMENT 'Short description',\n  `OXSELVARIANT` varchar(255) NOT NULL default '' COMMENT 'Selected variant',\n  `OXNETPRICE` double NOT NULL default '0' COMMENT 'Full netto price (oxnprice * oxamount)',\n  `OXBRUTPRICE` double NOT NULL default '0' COMMENT 'Full brutto price (oxbprice * oxamount)',\n  `OXVATPRICE` double NOT NULL default '0' COMMENT 'Calculated VAT price',\n  `OXVAT` double NOT NULL default '0' COMMENT 'VAT',\n  `OXPERSPARAM` text NOT NULL COMMENT 'Serialized persistent parameters',\n  `OXPRICE` double NOT NULL default '0' COMMENT 'Base price',\n  `OXBPRICE` double NOT NULL default '0' COMMENT 'Brutto price for one item',\n  `OXNPRICE` double NOT NULL default '0' COMMENT 'Netto price for one item',\n  `OXWRAPID` varchar( 32 ) NOT NULL default '' COMMENT 'Wrapping id (oxwrapping)',\n  `OXEXTURL` varchar(255) NOT NULL default '' COMMENT 'External URL to other information about the article',\n  `OXURLDESC` varchar(255) NOT NULL default '' COMMENT 'Text for external URL',\n  `OXURLIMG` varchar(128) NOT NULL default '' COMMENT 'External URL image',\n  `OXTHUMB` varchar(128) NOT NULL default '' COMMENT 'Thumbnail filename',\n  `OXPIC1` varchar(128) NOT NULL default '' COMMENT '1# Picture filename',\n  `OXPIC2` varchar(128) NOT NULL default '' COMMENT '2# Picture filename',\n  `OXPIC3` varchar(128) NOT NULL default '' COMMENT '3# Picture filename',\n  `OXPIC4` varchar(128) NOT NULL default '' COMMENT '4# Picture filename',\n  `OXPIC5` varchar(128) NOT NULL default '' COMMENT '5# Picture filename',\n  `OXWEIGHT` double NOT NULL default '0' COMMENT 'Weight (kg)',\n  `OXSTOCK` double NOT NULL default '-1' COMMENT 'Articles quantity in stock',\n  `OXDELIVERY` date NOT NULL default '0000-00-00' COMMENT 'Date, when the product will be available again if it is sold out',\n  `OXINSERT` date NOT NULL default '0000-00-00' COMMENT 'Creation time',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  `OXLENGTH` double NOT NULL default '0' COMMENT 'Article dimensions: Length',\n  `OXWIDTH` double NOT NULL default '0' COMMENT 'Article dimensions: Width',\n  `OXHEIGHT` double NOT NULL default '0' COMMENT 'Article dimensions: Height',\n  `OXFILE` varchar(128) NOT NULL default '' COMMENT 'File, shown in article media list',\n  `OXSEARCHKEYS` varchar(255) NOT NULL default '' COMMENT 'Search terms',\n  `OXTEMPLATE` varchar(128) NOT NULL default '' COMMENT 'Alternative template filename (use default, if empty)',\n  `OXQUESTIONEMAIL` varchar(255) NOT NULL default '' COMMENT 'E-mail for question',\n  `OXISSEARCH` tinyint(1) NOT NULL default '1' COMMENT 'Is article shown in search',\n  `OXFOLDER` varchar(32) NOT NULL default '' COMMENT 'Folder: ORDERFOLDER_FINISHED, ORDERFOLDER_NEW, ORDERFOLDER_PROBLEMS',\n  `OXSUBCLASS` varchar(32) NOT NULL default '' COMMENT 'Subclass',\n  `OXSTORNO` tinyint(1) NOT NULL default '0' COMMENT 'Order cancelled',\n  `OXORDERSHOPID` int(11) NOT NULL default 1 COMMENT 'Shop id (oxshops), in which order was done',\n  `OXISBUNDLE` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Bundled article',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXORDERID` (`OXORDERID`),\n  KEY `OXARTID` (`OXARTID`),\n  KEY `OXARTNUM` (`OXARTNUM`)\n) ENGINE=InnoDB COMMENT 'Ordered articles information';\n\n#\n# Table structure for table `oxorderfiles`\n#\n\nDROP TABLE IF EXISTS `oxorderfiles`;\n\nCREATE TABLE IF NOT EXISTS `oxorderfiles` (\n  `OXID` char(32) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL COMMENT 'Order file id',\n  `OXORDERID` char(32) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL COMMENT 'Order id (oxorder)',\n  `OXFILENAME` varchar(128) NOT NULL COMMENT 'Filename',\n  `OXFILEID` char(32) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL COMMENT 'File id (oxfiles)',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXORDERARTICLEID` char(32) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL COMMENT 'Ordered article id (oxorderarticles)',\n  `OXFIRSTDOWNLOAD` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT 'First time downloaded time',\n  `OXLASTDOWNLOAD` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT 'Last time downloaded time',\n  `OXDOWNLOADCOUNT` int(10) unsigned NOT NULL COMMENT 'Downloads count',\n  `OXMAXDOWNLOADCOUNT` int(10) unsigned NOT NULL COMMENT 'Maximum count of downloads',\n  `OXDOWNLOADEXPIRATIONTIME` int(10) unsigned NOT NULL COMMENT 'Download expiration time in hours',\n  `OXLINKEXPIRATIONTIME` int(10) unsigned NOT NULL COMMENT 'Link expiration time in hours',\n  `OXRESETCOUNT` int(10) unsigned NOT NULL COMMENT 'Count of resets',\n  `OXVALIDUNTIL` datetime NOT NULL COMMENT 'Download is valid until time specified',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY (`OXID`),\n  KEY `OXORDERID` (`OXORDERID`),\n  KEY `OXFILEID` (`OXFILEID`),\n  KEY `OXORDERARTICLEID` (`OXORDERARTICLEID`)\n) ENGINE=InnoDB COMMENT 'Files, given to users to download after order';\n\n#\n# Table structure for table `oxpayments`\n#\n\nDROP TABLE IF EXISTS `oxpayments`;\n\nCREATE TABLE `oxpayments` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Payment id',\n  `OXACTIVE` tinyint(1) NOT NULL default '1' COMMENT 'Active',\n  `OXDESC` varchar(128) NOT NULL default '' COMMENT 'Description (multilanguage)',\n  `OXADDSUM` double NOT NULL default '0' COMMENT 'Price Surcharge/Reduction amount',\n  `OXADDSUMTYPE` enum('abs','%') NOT NULL default 'abs' COMMENT 'Price Surcharge/Reduction type (abs|%)',\n  `OXADDSUMRULES` int(11) NOT NULL default '0' COMMENT 'Base of price surcharge/reduction: 1 - Value of all goods in cart, 2 - Discounts, 4 - Vouchers, 8 - Shipping costs, 16 - Gift Wrapping/Greeting Card',\n  `OXFROMBONI` int(11) NOT NULL default '0' COMMENT 'Minimal Credit Rating ',\n  `OXFROMAMOUNT` double NOT NULL default '0' COMMENT 'Purchase Price: From',\n  `OXTOAMOUNT` double NOT NULL default '0' COMMENT 'Purchase Price: To',\n  `OXVALDESC` text NOT NULL COMMENT 'Payment additional fields, separated by \"field1__@@field2\" (multilanguage)',\n  `OXCHECKED` tinyint(1) NOT NULL default '0' COMMENT 'Selected as the default method',\n  `OXDESC_1` varchar(128) NOT NULL default '',\n  `OXVALDESC_1` text NOT NULL,\n  `OXDESC_2` varchar(128) NOT NULL default '',\n  `OXVALDESC_2` text NOT NULL,\n  `OXDESC_3` varchar(128) NOT NULL default '',\n  `OXVALDESC_3` text NOT NULL,\n  `OXLONGDESC` text NOT NULL default '' COMMENT 'Long description (multilanguage)',\n  `OXLONGDESC_1` text NOT NULL default '',\n  `OXLONGDESC_2` text NOT NULL default '',\n  `OXLONGDESC_3` text NOT NULL default '',\n  `OXSORT` int(5) NOT NULL default 0 COMMENT 'Sorting',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXACTIVE` (`OXACTIVE`)\n) ENGINE=InnoDB COMMENT 'Payment methods';\n\n#\n# Table structure for table `oxprice2article`\n#\n\nDROP TABLE IF EXISTS `oxprice2article`;\n\nCREATE TABLE `oxprice2article` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Record id',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXARTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Article id (oxarticles)',\n  `OXADDABS` double NOT NULL default '0' COMMENT 'Price, that will be used for specified article if basket amount is between oxamount and oxamountto',\n  `OXADDPERC` double NOT NULL default '0' COMMENT 'Discount, that will be used for specified article if basket amount is between oxamount and oxamountto',\n  `OXAMOUNT` double NOT NULL default '0' COMMENT 'Quantity: From',\n  `OXAMOUNTTO` double NOT NULL default '0' COMMENT 'Quantity: To',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXSHOPID` (`OXSHOPID`),\n KEY `OXARTID` (`OXARTID`)\n) ENGINE=InnoDB COMMENT 'Article scale prices';\n\n#\n# Table structure for table `oxpricealarm`\n#\n\nDROP TABLE IF EXISTS `oxpricealarm`;\n\nCREATE TABLE `oxpricealarm` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Price alarm id',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXUSERID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'User id (oxuser)',\n  `OXEMAIL` varchar(128) NOT NULL default '' COMMENT 'Recipient email',\n  `OXARTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Article id (oxarticles)',\n  `OXPRICE` double NOT NULL default '0' COMMENT 'Expected (user) price, when notification email should be sent',\n  `OXCURRENCY` varchar(32) NOT NULL default '' COMMENT 'Currency',\n  `OXLANG` INT(2) NOT NULL default 0 COMMENT 'Language id',\n  `OXINSERT` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Creation time',\n  `OXSENDED` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Time, when notification was sent',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY (`OXID`)\n) ENGINE=InnoDB COMMENT 'Price fall alarm requests';\n\n#\n# Table structure for table `oxratings`\n#\n\nDROP TABLE IF EXISTS `oxratings`;\n\nCREATE TABLE `oxratings` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Rating id',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXUSERID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'User id (oxuser)',\n  `OXTYPE` enum('oxarticle','oxrecommlist') NOT NULL COMMENT 'Rating type (oxarticle, oxrecommlist)',\n  `OXOBJECTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Article or Listmania id (oxarticles or oxrecommlists)',\n  `OXRATING` int(1) NOT NULL default '0' COMMENT 'Rating',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `oxobjectsearch` (`OXTYPE`,`OXOBJECTID`)\n) ENGINE=InnoDB COMMENT 'Articles and Listmania ratings';\n#\n# Table structure for table `oxrecommlists`\n#\n\nDROP TABLE IF EXISTS `oxrecommlists`;\n\nCREATE TABLE `oxrecommlists` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Listmania id',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXUSERID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'User id (oxuser)',\n  `OXAUTHOR` varchar(255) NOT NULL default '' COMMENT 'Author first and last name',\n  `OXTITLE` varchar(255) NOT NULL default '' COMMENT 'Title',\n  `OXDESC` text NOT NULL COMMENT 'Description',\n  `OXRATINGCNT` int(11) NOT NULL default '0' COMMENT 'Rating votes count',\n  `OXRATING` double NOT NULL default '0' COMMENT 'Rating',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`)\n) ENGINE=InnoDB COMMENT 'Listmania';\n\n#\n# Table structure for table `oxremark`\n#\n\nDROP TABLE IF EXISTS `oxremark`;\n\nCREATE TABLE `oxremark` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Record id',\n  `OXPARENTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'User id (oxuser)',\n  `OXTYPE` enum('o','r','n','c') NOT NULL default 'r' COMMENT 'Record type: o - order, r - remark, n - newsletter, c - registration',\n  `OXHEADER` varchar(255) NOT NULL default '' COMMENT 'Header (default: Creation time)',\n  `OXTEXT` text NOT NULL COMMENT 'Remark text',\n  `OXCREATE` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Creation time',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXPARENTID` (`OXPARENTID`),\n  KEY `OXTYPE` (`OXTYPE`)\n) ENGINE=InnoDB COMMENT 'User History';\n\n#\n# Table structure for table `oxreviews`\n#\n\nDROP TABLE IF EXISTS `oxreviews`;\n\nCREATE TABLE `oxreviews` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Review id',\n  `OXACTIVE` tinyint(1) NOT NULL default '0' COMMENT 'Active',\n  `OXOBJECTID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Article or Listmania id (oxarticles or oxrecommlist)',\n  `OXTYPE` enum('oxarticle','oxrecommlist') NOT NULL COMMENT 'Review type (oxarticle, oxrecommlist)',\n  `OXTEXT` text NOT NULL COMMENT 'Review text',\n  `OXUSERID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'User id (oxuser)',\n  `OXCREATE` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Creation time',\n  `OXLANG` tinyint( 3 ) NOT NULL DEFAULT '0' COMMENT 'Language id',\n  `OXRATING` int(1) NOT NULL default '0' COMMENT 'Rating',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `oxobjectsearch` (`OXTYPE`,`OXOBJECTID`)\n) ENGINE=InnoDB COMMENT 'Articles and Listmania reviews';\n\n#\n# Table structure for table `oxselectlist`\n#\n\nDROP TABLE IF EXISTS `oxselectlist`;\n\nCREATE TABLE `oxselectlist` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Selection list id',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXTITLE` varchar(254) NOT NULL default '' COMMENT 'Title (multilanguage)',\n  `OXIDENT` varchar(255) NOT NULL default '' COMMENT 'Working Title',\n  `OXVALDESC` text NOT NULL COMMENT 'List fields, separated by \"[field_name]!P![price]__@@[field_name]__@@\" (multilanguage)',\n  `OXTITLE_1` varchar(255) NOT NULL default '',\n  `OXVALDESC_1` text NOT NULL,\n  `OXTITLE_2` varchar(255) NOT NULL default '',\n  `OXVALDESC_2` text NOT NULL,\n  `OXTITLE_3` varchar(255) NOT NULL default '',\n  `OXVALDESC_3` text NOT NULL,\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`)\n) ENGINE=InnoDB COMMENT 'Selection lists';\n\n#\n# Table structure for table `oxseo`\n# Created 2008.04.16\n#\n\nDROP TABLE IF EXISTS `oxseo`;\n\nCREATE TABLE `oxseo` (\n  `OXOBJECTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Object id',\n  `OXIDENT`    char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Hashed seo url (md5)',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXLANG`     int(2) NOT NULL default 0 COMMENT 'Language id',\n  `OXSTDURL`   varchar(2048) NOT NULL COMMENT 'Primary url, not seo encoded',\n  `OXSEOURL`   varchar(2048) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL COMMENT 'Old seo url',\n  `OXTYPE`     enum('static', 'oxarticle', 'oxcategory', 'oxvendor', 'oxcontent', 'dynamic', 'oxmanufacturer') NOT NULL COMMENT 'Record type',\n  `OXFIXED`    TINYINT(1) NOT NULL default 0 COMMENT 'Fixed',\n  `OXEXPIRED` tinyint(1) NOT NULL default '0' COMMENT 'Expired',\n  `OXPARAMS` char(32) NOT NULL default '' COMMENT 'Params',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n   PRIMARY KEY (`OXIDENT`, `OXSHOPID`, `OXLANG`),\n   UNIQUE KEY search (`OXTYPE`, `OXOBJECTID`, `OXSHOPID`, `OXLANG`,`OXPARAMS`),\n   KEY `OXOBJECTID` (`OXOBJECTID`,`OXSHOPID`, `OXLANG`),\n   KEY `SEARCHSTD` (OXSTDURL(100),`OXSHOPID`),\n   KEY `SEARCHSEO` (OXSEOURL(100))\n) ENGINE=InnoDB COMMENT 'Seo urls information';\n\n#\n# Table structure for table `oxseohistory`\n# For tracking old SEO urls\n# Created 2008-05-21\n#\n\nDROP TABLE IF EXISTS `oxseohistory`;\n\nCREATE TABLE `oxseohistory` (\n  `OXOBJECTID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Object id',\n  `OXIDENT` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Hashed url (md5)',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXLANG` int(2) NOT NULL default '0' COMMENT 'Language id',\n  `OXHITS` bigint(20) NOT NULL default '0' COMMENT 'Hits',\n  `OXINSERT` timestamp NULL default NULL COMMENT 'Creation time',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXIDENT`,`OXSHOPID`,`OXLANG`),\n  KEY `search` (`OXOBJECTID`,`OXSHOPID`,`OXLANG`)\n) ENGINE=InnoDB COMMENT 'Seo urls history. If url does not exists in oxseo, then checks here and redirects';\n\n#\n# Table structure for table `oxseologs`\n# For tracking untranslatable to SEO format non SEO urls\n# Created 2008-10-21\n#\n\nDROP TABLE IF EXISTS `oxseologs`;\n\nCREATE TABLE IF NOT EXISTS `oxseologs` (\n  `OXSTDURL` text NOT NULL COMMENT 'Primary url, not seo encoded',\n  `OXIDENT` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Hashed seo url',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXLANG` int(11) NOT NULL COMMENT 'Language id',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXIDENT`,`OXSHOPID`,`OXLANG`)\n) ENGINE=InnoDB COMMENT 'Seo logging. Logs bad requests';\n\n#\n# Table structure for table `oxshops`\n#\n\nDROP TABLE IF EXISTS `oxshops`;\n\nCREATE TABLE `oxshops` (\n  `OXID` int(11) NOT NULL default 1 COMMENT 'Shop id',\n  `OXACTIVE` tinyint(1) NOT NULL default '1' COMMENT 'Active',\n  `OXPRODUCTIVE` tinyint(1) NOT NULL default '0' COMMENT 'Productive Mode (if 0, debug info displayed)',\n  `OXDEFCURRENCY` varchar(32) NOT NULL default '' COMMENT 'Default currency',\n  `OXDEFLANGUAGE` int(11) NOT NULL default '0' COMMENT 'Default language id',\n  `OXNAME` varchar(255) NOT NULL default '' COMMENT 'Shop name',\n  `OXTITLEPREFIX` varchar(255) NOT NULL default '' COMMENT 'Seo title prefix (multilanguage)',\n  `OXTITLEPREFIX_1` varchar(255) NOT NULL default '',\n  `OXTITLEPREFIX_2` varchar(255) NOT NULL default '',\n  `OXTITLEPREFIX_3` varchar(255) NOT NULL default '',\n  `OXTITLESUFFIX` varchar(255) NOT NULL default '' COMMENT 'Seo title suffix (multilanguage)',\n  `OXTITLESUFFIX_1` varchar(255) NOT NULL default '',\n  `OXTITLESUFFIX_2` varchar(255) NOT NULL default '',\n  `OXTITLESUFFIX_3` varchar(255) NOT NULL default '',\n  `OXSTARTTITLE` varchar(255) NOT NULL default '' COMMENT 'Start page title (multilanguage)',\n  `OXSTARTTITLE_1` varchar(255) NOT NULL default '',\n  `OXSTARTTITLE_2` varchar(255) NOT NULL default '',\n  `OXSTARTTITLE_3` varchar(255) NOT NULL default '',\n  `OXINFOEMAIL` varchar(255) NOT NULL default '' COMMENT 'Informational email address',\n  `OXORDEREMAIL` varchar(255) NOT NULL default '' COMMENT 'Order email address',\n  `OXOWNEREMAIL` varchar(255) NOT NULL default '' COMMENT 'Owner email address',\n  `OXORDERSUBJECT` varchar(255) NOT NULL default '' COMMENT 'Order email subject (multilanguage)',\n  `OXREGISTERSUBJECT` varchar(255) NOT NULL default '' COMMENT 'Registration email subject (multilanguage)',\n  `OXFORGOTPWDSUBJECT` varchar(255) NOT NULL default '' COMMENT 'Forgot password email subject (multilanguage)',\n  `OXSENDEDNOWSUBJECT` varchar(255) NOT NULL default '' COMMENT 'Order sent email subject (multilanguage)',\n  `OXORDERSUBJECT_1` varchar(255) NOT NULL default '',\n  `OXREGISTERSUBJECT_1` varchar(255) NOT NULL default '',\n  `OXFORGOTPWDSUBJECT_1` varchar(255) NOT NULL default '',\n  `OXSENDEDNOWSUBJECT_1` varchar(255) NOT NULL default '',\n  `OXORDERSUBJECT_2` varchar(255) NOT NULL default '',\n  `OXREGISTERSUBJECT_2` varchar(255) NOT NULL default '',\n  `OXFORGOTPWDSUBJECT_2` varchar(255) NOT NULL default '',\n  `OXSENDEDNOWSUBJECT_2` varchar(255) NOT NULL default '',\n  `OXORDERSUBJECT_3` varchar(255) NOT NULL default '',\n  `OXREGISTERSUBJECT_3` varchar(255) NOT NULL default '',\n  `OXFORGOTPWDSUBJECT_3` varchar(255) NOT NULL default '',\n  `OXSENDEDNOWSUBJECT_3` varchar(255) NOT NULL default '',\n  `OXSMTP` varchar(255) NOT NULL default '' COMMENT 'SMTP server',\n  `OXSMTPUSER` varchar(128) NOT NULL default '' COMMENT 'SMTP user',\n  `OXSMTPPWD` varchar(128) NOT NULL default '' COMMENT 'SMTP password',\n  `OXCOMPANY` varchar(128) NOT NULL default '' COMMENT 'Your company',\n  `OXSTREET` varchar(255) NOT NULL default '' COMMENT 'Street',\n  `OXZIP` varchar(255) NOT NULL default '' COMMENT 'ZIP code',\n  `OXCITY` varchar(255) NOT NULL default '' COMMENT 'City',\n  `OXCOUNTRY` varchar(255) NOT NULL default '' COMMENT 'Country',\n  `OXBANKNAME` varchar(255) NOT NULL default '' COMMENT 'Bank name',\n  `OXBANKNUMBER` varchar(255) NOT NULL default '' COMMENT 'Account Number',\n  `OXBANKCODE` varchar(255) NOT NULL default '' COMMENT 'Routing Number',\n  `OXVATNUMBER` varchar(255) NOT NULL default '' COMMENT 'Sales Tax ID',\n  `OXTAXNUMBER` varchar(255) NOT NULL default '' COMMENT 'Tax ID',\n  `OXBICCODE` varchar(255) NOT NULL default '' COMMENT 'Bank BIC',\n  `OXIBANNUMBER` varchar(255) NOT NULL default '' COMMENT 'Bank IBAN',\n  `OXFNAME` varchar(255) NOT NULL default '' COMMENT 'First name',\n  `OXLNAME` varchar(255) NOT NULL default '' COMMENT 'Last name',\n  `OXTELEFON` varchar(255) NOT NULL default '' COMMENT 'Phone number',\n  `OXTELEFAX` varchar(255) NOT NULL default '' COMMENT 'Fax number',\n  `OXURL` varchar(255) NOT NULL default '' COMMENT 'Shop url',\n  `OXDEFCAT` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Default category id',\n  `OXHRBNR` varchar(64) NOT NULL default '' COMMENT 'CBR',\n  `OXCOURT` varchar(128) NOT NULL default '' COMMENT 'District Court',\n  `OXADBUTLERID` varchar(64) NOT NULL default '' COMMENT 'Adbutler code (belboon.de) - deprecated',\n  `OXAFFILINETID` varchar(64) NOT NULL default '' COMMENT 'Affilinet code (webmasterplan.com) - deprecated',\n  `OXSUPERCLICKSID` varchar(64) NOT NULL default '' COMMENT 'Superclix code (superclix.de) - deprecated',\n  `OXAFFILIWELTID` varchar(64) NOT NULL default '' COMMENT 'Affiliwelt code (affiliwelt.net) - deprecated',\n  `OXAFFILI24ID` varchar(64) NOT NULL default '' COMMENT 'Affili24 code (affili24.com) - deprecated',\n  `OXEDITION` CHAR( 2 ) NOT NULL COMMENT 'Shop Edition (CE,PE,EE (@deprecated since v6.0.0-RC.2 (2017-08-24))',\n  `OXVERSION` CHAR( 16 ) NOT NULL COMMENT 'Shop Version (@deprecated since v6.0.0-RC.2 (2017-08-22))',\n  `OXSEOACTIVE` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Seo active (multilanguage)',\n  `OXSEOACTIVE_1` tinyint(1) NOT NULL DEFAULT '1',\n  `OXSEOACTIVE_2` tinyint(1) NOT NULL DEFAULT '1',\n  `OXSEOACTIVE_3` tinyint(1) NOT NULL DEFAULT '1',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXACTIVE` (`OXACTIVE`)\n) ENGINE=InnoDB COMMENT 'Shop config';\n\n#\n# Table structure for table `oxstates`\n# for storing extended file urls\n# created 2010-01-06\n#\n\nDROP TABLE IF EXISTS `oxstates`;\n\nCREATE TABLE `oxstates` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'State id',\n  `OXCOUNTRYID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Country id (oxcountry)',\n  `OXTITLE` char(128) NOT NULL default '' COMMENT 'Title (multilanguage)',\n  `OXISOALPHA2` char(2) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'SEO short name',\n  `OXTITLE_1` char(128) NOT NULL default '',\n  `OXTITLE_2` char(128) NOT NULL default '',\n  `OXTITLE_3` char(128) NOT NULL default '',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`, `OXCOUNTRYID`),\n  KEY(`OXCOUNTRYID`)\n) ENGINE = InnoDB COMMENT 'US States list';\n\n#\n# Table structure for table `oxtplblocks`\n# for storing blocks for template parts override\n# created 2010-10-12\n#\n\nDROP TABLE IF EXISTS `oxtplblocks`;\n\nCREATE TABLE `oxtplblocks` (\n  `OXID`        char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Block id',\n  `OXACTIVE`    tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Is active',\n  `OXSHOPID`    int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXTHEME`     char(128) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Shop theme id',\n  `OXTEMPLATE`  char(255) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Template filename (with rel. path), where block is located',\n  `OXBLOCKNAME` char(128) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Block name',\n  `OXPOS`       int  NOT NULL COMMENT 'Sorting',\n  `OXFILE`      char(255) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Module template filename, where block replacement is located',\n  `OXMODULE`    char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Module, which uses this template',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY (`OXID`),\n  INDEX `search` (`OXACTIVE`, `OXTEMPLATE`, `OXPOS`),\n  INDEX `oxtheme` (`OXTHEME`)\n) ENGINE=InnoDB COMMENT 'Module template blocks';\n\n#\n# Table structure for table `oxuser`\n#\n\nDROP TABLE IF EXISTS `oxuser`;\n\nCREATE TABLE `oxuser` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'User id',\n  `OXACTIVE` tinyint(1) NOT NULL default '1' COMMENT 'Is active',\n  `OXRIGHTS` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'User rights: user, malladmin',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXUSERNAME` varchar(255) NOT NULL default '' COMMENT 'Username',\n  `OXPASSWORD` varchar(128) NOT NULL default '' COMMENT 'Hashed password',\n  `OXPASSSALT` char(128) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Password salt',\n  `OXCUSTNR` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Customer number',\n  `OXUSTID` varchar(255) NOT NULL default '' COMMENT 'VAT ID No.',\n  `OXCOMPANY` varchar(255) NOT NULL default '' COMMENT 'Company',\n  `OXFNAME` varchar(255) NOT NULL default '' COMMENT 'First name',\n  `OXLNAME` varchar(255) NOT NULL default '' COMMENT 'Last name',\n  `OXSTREET` varchar(255) NOT NULL default '' COMMENT 'Street',\n  `OXSTREETNR` varchar(16) NOT NULL default '' COMMENT 'House number',\n  `OXADDINFO` varchar(255) NOT NULL default '' COMMENT 'Additional info',\n  `OXCITY` varchar(255) NOT NULL default '' COMMENT 'City',\n  `OXCOUNTRYID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Country id (oxcountry)',\n  `OXSTATEID` varchar(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'State id (oxstates)',\n  `OXZIP` varchar(16) NOT NULL default '' COMMENT 'ZIP code',\n  `OXFON` varchar(128) NOT NULL default '' COMMENT 'Phone number',\n  `OXFAX` varchar(128) NOT NULL default '' COMMENT 'Fax number',\n  `OXSAL` varchar(128) NOT NULL default '' COMMENT 'User title (Mr/Mrs)',\n  `OXBONI` int(11) NOT NULL default '0' COMMENT 'Credit points',\n  `OXCREATE` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Creation time',\n  `OXREGISTER` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Registration time',\n  `OXPRIVFON` varchar(64) NOT NULL default '' COMMENT 'Personal phone number',\n  `OXMOBFON` varchar(64) NOT NULL default '' COMMENT 'Mobile phone number',\n  `OXBIRTHDATE` date NOT NULL default '0000-00-00' COMMENT 'Birthday date',\n  `OXURL` varchar(255) NOT NULL default '' COMMENT 'Url',\n  `OXUPDATEKEY` varchar( 32 ) NOT NULL default '' COMMENT 'Update key',\n  `OXUPDATEEXP` int(11) NOT NULL default '0' COMMENT 'Update key expiration time',\n  `OXPOINTS` double NOT NULL default '0' COMMENT 'User points (for registration, invitation, etc)',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  UNIQUE `OXUSERNAME` (`OXUSERNAME`, `OXSHOPID`),\n  KEY `OXPASSWORD` (`OXPASSWORD`),\n  KEY `OXCUSTNR` (`OXCUSTNR`),\n  KEY `OXACTIVE` (`OXACTIVE`),\n  KEY `OXLNAME` (`OXLNAME`),\n  KEY `OXUPDATEEXP` (`OXUPDATEEXP`)\n) ENGINE=InnoDB COMMENT 'Shop administrators and users';\n\n#\n# Table structure for table `oxuserbaskets`\n#\n\nDROP TABLE IF EXISTS `oxuserbaskets`;\n\nCREATE TABLE `oxuserbaskets` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Basket id',\n  `OXUSERID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'User id (oxuser)',\n  `OXTITLE` varchar(255) NOT NULL default '' COMMENT 'Basket title',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  `OXPUBLIC` tinyint(1) DEFAULT '0' NOT NULL COMMENT 'Is public',\n  `OXUPDATE` INT NOT NULL default 0 COMMENT 'Update timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXUPDATE` (`OXUPDATE`),\n  KEY `OXTITLE` (`OXTITLE`),\n  KEY `OXUSERID` (`OXUSERID`)\n) ENGINE=InnoDB COMMENT 'Active User baskets';\n\n#\n# Table structure for table `oxuserbasketitems`\n#\n\nDROP TABLE IF EXISTS `oxuserbasketitems`;\n\nCREATE TABLE `oxuserbasketitems` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Item id',\n  `OXBASKETID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Basket id (oxuserbaskets)',\n  `OXARTID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Article id (oxarticles)',\n  `OXAMOUNT` char(32) NOT NULL default '' COMMENT 'Amount',\n  `OXSELLIST` varchar(255) NOT NULL default '' COMMENT 'Selection list',\n  `OXPERSPARAM` text NOT NULL COMMENT 'Serialized persistent parameters',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXBASKETID` (`OXBASKETID`),\n  KEY `OXARTID` (`OXARTID`)\n) ENGINE=InnoDB COMMENT 'User basket items';\n\n#\n# Table structure for table `oxuserpayments`\n#\n\nDROP TABLE IF EXISTS `oxuserpayments`;\n\nCREATE TABLE `oxuserpayments` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Payment id',\n  `OXUSERID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'User id (oxusers)',\n  `OXPAYMENTSID` char(32) character set latin1 collate latin1_general_ci NOT NULL default '' COMMENT 'Payment id (oxpayments)',\n  `OXVALUE` text NOT NULL COMMENT 'DYN payment values array as string',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXUSERID` (`OXUSERID`)\n) ENGINE=InnoDB COMMENT 'User payments';\n\n#\n# Table structure for table `oxvendor`\n#\n\nDROP TABLE IF EXISTS `oxvendor`;\n\nCREATE TABLE `oxvendor` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Vendor id',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXACTIVE` tinyint(1) NOT NULL default '1' COMMENT 'Active',\n  `OXICON` varchar(128) NOT NULL default '' COMMENT 'Icon filename',\n  `OXTITLE` varchar(255) NOT NULL default '' COMMENT 'Title (multilanguage)',\n  `OXSHORTDESC` varchar(255) NOT NULL default '' COMMENT 'Short description (multilanguage)',\n  `OXTITLE_1` varchar(255) NOT NULL default '',\n  `OXSHORTDESC_1` varchar(255) NOT NULL default '',\n  `OXTITLE_2` varchar(255) NOT NULL default '',\n  `OXSHORTDESC_2` varchar(255) NOT NULL default '',\n  `OXTITLE_3` varchar(255) NOT NULL default '',\n  `OXSHORTDESC_3` varchar(255) NOT NULL default '',\n  `OXSHOWSUFFIX` tinyint(1) NOT NULL default '1' COMMENT 'Show SEO Suffix in Category',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  KEY `OXACTIVE` (`OXACTIVE`)\n) ENGINE=InnoDB COMMENT 'Distributors list';\n\n#\n# Table structure for table `oxvouchers`\n#\n\nDROP TABLE IF EXISTS `oxvouchers` ;\n\nCREATE  TABLE IF NOT EXISTS `oxvouchers` (\n  `OXDATEUSED` DATE NULL DEFAULT NULL COMMENT 'Date, when coupon was used (set on order complete)',\n  `OXORDERID` char(32) character set latin1 collate latin1_general_ci NOT NULL DEFAULT '' COMMENT 'Order id (oxorder)',\n  `OXUSERID` char(32) character set latin1 collate latin1_general_ci NOT NULL DEFAULT '' COMMENT 'User id (oxuser)',\n  `OXRESERVED` INT(11) NOT NULL DEFAULT 0 COMMENT 'Time, when coupon is added to basket',\n  `OXVOUCHERNR` varchar(255) NOT NULL DEFAULT '' COMMENT 'Coupon number',\n  `OXVOUCHERSERIEID` char(32) character set latin1 collate latin1_general_ci NOT NULL DEFAULT '' COMMENT 'Coupon Series id (oxvoucherseries)',\n  `OXDISCOUNT` FLOAT(9,2) NULL DEFAULT NULL COMMENT 'Discounted amount (if discount was used)',\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL DEFAULT '' COMMENT 'Coupon id',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  INDEX OXVOUCHERSERIEID (`OXVOUCHERSERIEID` ASC) ,\n  INDEX OXORDERID (`OXORDERID` ASC) ,\n  INDEX OXUSERID (`OXUSERID` ASC) ,\n  INDEX OXVOUCHERNR (`OXVOUCHERNR` ASC)\n) ENGINE = InnoDB COMMENT 'Generated coupons';\n\n#\n# Table structure for table `oxvoucherseries`\n#\n\nDROP TABLE IF EXISTS `oxvoucherseries` ;\n\nCREATE  TABLE IF NOT EXISTS `oxvoucherseries` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL DEFAULT '' COMMENT 'Series id',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXSERIENR` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Series name',\n  `OXSERIEDESCRIPTION` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Description',\n  `OXDISCOUNT` FLOAT(9,2) NOT NULL DEFAULT '0' COMMENT 'Discount amount',\n  `OXDISCOUNTTYPE` ENUM('percent','absolute') NOT NULL DEFAULT 'absolute' COMMENT 'Discount type (percent, absolute)',\n  `OXBEGINDATE` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT 'Valid from',\n  `OXENDDATE` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT 'Valid to',\n  `OXALLOWSAMESERIES` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Coupons of this series can be used with single order',\n  `OXALLOWOTHERSERIES` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Coupons of different series can be used with single order',\n  `OXALLOWUSEANOTHER` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Coupons of this series can be used in multiple orders',\n  `OXMINIMUMVALUE` FLOAT(9,2) NOT NULL DEFAULT '0.00' COMMENT 'Minimum Order Sum ',\n  `OXCALCULATEONCE` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Calculate only once (valid only for product or category vouchers)',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`),\n  INDEX OXSERIENR (`OXSERIENR` ASC) ,\n  INDEX OXSHOPID (`OXSHOPID` ASC)\n) ENGINE = InnoDB COMMENT 'Coupon series';\n\n#\n# Table structure for table `oxwrapping`\n#\n\nDROP TABLE IF EXISTS `oxwrapping`;\n\nCREATE TABLE `oxwrapping` (\n  `OXID` char(32) character set latin1 collate latin1_general_ci NOT NULL COMMENT 'Wrapping id',\n  `OXSHOPID` int(11) NOT NULL default '1' COMMENT 'Shop id (oxshops)',\n  `OXACTIVE` tinyint(1) NOT NULL default '1' COMMENT 'Active (multilanguage)',\n  `OXACTIVE_1` tinyint(1) NOT NULL default '1',\n  `OXACTIVE_2` tinyint(1) NOT NULL default '1',\n  `OXACTIVE_3` tinyint(1) NOT NULL default '1',\n  `OXTYPE` varchar(4) NOT NULL default 'WRAP' COMMENT 'Wrapping type: WRAP,CARD',\n  `OXNAME` varchar(128) NOT NULL default '' COMMENT 'Name (multilanguage)',\n  `OXNAME_1` varchar(128) NOT NULL default '',\n  `OXNAME_2` varchar(128) NOT NULL default '',\n  `OXNAME_3` varchar(128) NOT NULL default '',\n  `OXPIC` varchar(128) NOT NULL default '' COMMENT 'Image filename',\n  `OXPRICE` double NOT NULL default '0' COMMENT 'Price',\n  `OXTIMESTAMP` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Timestamp',\n  PRIMARY KEY  (`OXID`)\n) ENGINE=InnoDB COMMENT 'Wrappings';\n"
  },
  {
    "path": "source/Internal/Setup/Database/sql/initial_data.sql",
    "content": "SET @@session.sql_mode = '';\n\nINSERT INTO `oxactions` (`OXID`, `OXSHOPID`, `OXTYPE`, `OXTITLE`, `OXTITLE_1`, `OXTITLE_2`, `OXTITLE_3`, `OXLONGDESC`, `OXLONGDESC_1`, `OXLONGDESC_2`, `OXLONGDESC_3`, `OXACTIVE`, `OXACTIVEFROM`, `OXACTIVETO`, `OXPIC`, `OXPIC_1`, `OXPIC_2`, `OXPIC_3`, `OXLINK`, `OXLINK_1`, `OXLINK_2`, `OXLINK_3`, `OXSORT`) VALUES\n('oxbargain',    1, 0, 'Angebot der Woche', 'Week''s Special', '', '', '', '', '', '', 1, '0000-00-00 00:00:00', '0000-00-00 00:00:00', '', '', '', '', '', '', '', '', 0),\n('oxtop5',       1, 0, 'Topseller', 'Top seller', '', '', '', '', '', '', 1, '0000-00-00 00:00:00', '0000-00-00 00:00:00', '', '', '', '', '', '', '', '', 0),\n('oxnewest',     1, 0, 'Frisch eingetroffen', 'Just arrived', '', '', '', '', '', '', 1, '0000-00-00 00:00:00', '0000-00-00 00:00:00', '', '', '', '', '', '', '', '', 0);\n\nINSERT INTO `oxconfig` (`OXID`, `OXSHOPID`, `OXMODULE`, `OXVARNAME`, `OXVARTYPE`, `OXVARVALUE`) VALUES\n('0a5455450f97fdec9.37454802',\t1,\t'',\t'blAllowNegativeStock',\t'bool',\t''),\n('11296159b7641d31b93423972af6150a',\t1,\t'',\t'iTopNaviCatCount',\t'str',\t'4'),\n('15342e4cab0ee774acb390583838498a',\t1,\t'',\t'blShowBirthdayFields',\t'bool',\t'1'),\n('1545423fe8ce213a043534555223029a',\t1,\t'',\t'aNrofCatArticles',\t'arr',\t'a:4:{i:0;s:2:\\\"10\\\";i:1;s:2:\\\"20\\\";i:2;s:2:\\\"50\\\";i:3;s:3:\\\"100\\\";}'),\n('18a12329124850cd8f63cda6e8e7b4ea',\t1,\t'',\t'bl_showWishlist',\t'bool',\t'1'),\n('18a23429124850cd8f63cda6e8e7b4ea',\t1,\t'',\t'bl_showVouchers',\t'bool',\t'1'),\n('18a34529124850cd8f63cda6e8e7b4ea',\t1,\t'',\t'bl_showGiftWrapping',\t'bool',\t'1'),\n('18a9473894d473f6ed28f04e80d929fa',\t1,\t'',\t'bl_showCompareList',\t'bool',\t'1'),\n('18acb2f595da54b5f865e54aa5cdb96a',\t1,\t'',\t'bl_showListmania',\t'bool',\t'1'),\n('1eada690d18be312ef5e49b8451440e7',\t1,\t'',\t'blShowTSCODMessage',\t'bool',\t'1'),\n('1ec42a395d0595ee774109189884847a',\t1,\t'',\t'iNewBasketItemMessage',\t'select',\t'1'),\n('1ec42a395d0595ee774109189884879a',\t1,\t'',\t'sCatIconsize',\t'str',\t'168*100'),\n('1ec42a395d0595ee774109189884898a',\t1,\t'',\t'sDefaultListDisplayType',\t'select',\t'infogrid'),\n('1ec42a395d0595ee774109189884898x',\t1,\t'',\t'sCatPromotionsize',\t'str',\t'370*107'),\n('1ec42a395d0595ee774109189884899a',\t1,\t'',\t'aNrofCatArticlesInGrid',\t'arr',\t'a:4:{i:0;s:2:\\\"12\\\";i:1;s:2:\\\"16\\\";i:2;s:2:\\\"24\\\";i:3;s:2:\\\"32\\\";}'),\n('1ec42a395d0595ee774109189884899x',\t1,\t'',\t'blShowListDisplayType',\t'bool',\t'1'),\n('2a944b2cc31311e8957700163e4021bf',\t1,\t'',\t'includeProductReviewLinksInEmail',\t'bool',\t''),\n('2ca4277aa49a5bd27.44511187',\t1,\t'',\t'blStockOnDefaultMessage',\t'bool',\t'1'),\n('2ca4277aa49a634f8.76432326',\t1,\t'',\t'blStockOffDefaultMessage',\t'bool',\t'1'),\n('0282a93ba014458d7a9249e5aef1a8eb',\t1,\t'',\t'blStockLowDefaultMessage',\t'bool',\t'1'),\n('2e4452b5763e03c74.88240349',\t1,\t'',\t'blDisableDublArtOnCopy',\t'bool',\t'1'),\n('32ddeaf2694e06b47b6ff74eafc69b65',\t1,\t'',\t'sParcelService',\t'str',\t'http://www.dpd.de/cgi-bin/delistrack?typ=1&amp;lang=de&amp;pknr=##ID##'),\n('33341949f476b65e8.17282442',\t1,\t'',\t'iAttributesPercent',\t'str',\t'70'),\n('36d42513de8cab671.54909813',\t1,\t'',\t'bl_perfShowActionCatArticleCnt',\t'bool',\t'1'),\n('39893a0ef6a6e11645d4beee4fd0cd51',\t1,\t'',\t'aLanguageParams',\t'aarr',\t'a:2:{s:2:\"de\";a:3:{s:6:\"baseId\";i:0;s:6:\"active\";s:1:\"1\";s:4:\"sort\";s:1:\"1\";}s:2:\"en\";a:3:{s:6:\"baseId\";i:1;s:6:\"active\";s:1:\"1\";s:4:\"sort\";s:1:\"2\";}}'),\n('3c4f033dfb8fd4fe692715dda19ecd28',\t1,\t'',\t'aCurrencies',\t'arr',\t'a:4:{i:0;s:23:\\\"EUR@ 1.00@ ,@ .@ €@ 2\\\";i:1;s:24:\\\"GBP@ 0.8565@ .@  @ £@ 2\\\";i:2;s:25:\\\"CHF@ 1.4326@ ,@ .@ CHF@ 2\\\";i:3;s:23:\\\"USD@ 1.2994@ .@  @ $@ 2\\\";}'),\n('43040112c71dfb0f2.40367454',\t1,\t'',\t'sDefaultImageQuality',\t'str',\t'75'),\n('d592e41daa2d93b8244e6871282ab8c1', 1, '', 'blConvertImagesToWebP', 'bool', '0'),\n('44bcd90bd1d059.053753111',\t1,\t'',\t'sTagList',\t'str',\t'1153227019'),\n('4994145b9e8678993.26056670',\t1,\t'',\t'blShowSorting',\t'bool',\t'1'),\n('4994145b9e8736eb6.03785000',\t1,\t'',\t'iTop5Mode',\t'str',\t'1'),\n('4994145b9e87481c5.69580772',\t1,\t'',\t'aSortCols',\t'arr',\t'a:2:{i:0;s:7:\\\"oxtitle\\\";i:1;s:13:\\\"oxvarminprice\\\";}'),\n('5i1c49faf83b3fe3d6bdbfa301e2704d',\t1,\t'',\t'iLinkExpirationTime',\t'str',\t'168'),\n('5i1d215fe1d6f0e1061ba1134e0ee4f2',\t1,\t'',\t'iDownloadExpirationTime',\t'str',\t'24'),\n('603a1a28ff2a421b64c631ffaf97f324',\t1,\t'',\t'sGiCsvFieldEncloser',\t'str',\t'\\\"'),\n('62642dfaa1d87d064.50653921',\t1,\t'',\t'sDetailImageSize',\t'str',\t'250*200'),\n('62642dfaa1d88b1b2.94593071',\t1,\t'',\t'sZoomImageSize',\t'str',\t'450*450'),\n('6ec4235c2aa997942.70260123',\t1,\t'',\t'blWarnOnSameArtNums',\t'bool',\t'1'),\n('6ec4235c2aaa45d77.87437919',\t1,\t'',\t'sIconsize',\t'str',\t'56*42'),\n('6ec4235c2aaa8eec5.99966057',\t1,\t'',\t'sMidlleCustPrice',\t'str',\t'40'),\n('6ec4235c2aaa97585.69723730',\t1,\t'',\t'sLargeCustPrice',\t'str',\t'100'),\n('6ec4235c5182c3620.11050422',\t1,\t'',\t'iNrofNewcomerArticles',\t'str',\t'4'),\n('6f8453f77d174e0a0.31854175',\t1,\t'',\t'blOtherCountryOrder',\t'bool',\t'1'),\n('7044252b61dcb8ac9.31672388',\t1,\t'',\t'bl_perfLoadPriceForAddList',\t'bool',\t'1'),\n('77c425a29db68f0d9.00182375',\t1,\t'',\t'bl_perfLoadManufacturerTree',\t'bool',\t'1'),\n('79c3fbc9897c0d159.27469500',\t1,\t'',\t'blLoadVariants',\t'bool',\t'1'),\n('79e417a3916b910c8.31517473',\t1,\t'',\t'bl_perfLoadAktion',\t'bool',\t'1'),\n('79e417a4201010a12.85717286',\t1,\t'',\t'bl_perfLoadReviews',\t'bool',\t'1'),\n('79e417a420101f3e6.18536996',\t1,\t'',\t'bl_perfLoadCrossselling',\t'bool',\t'1'),\n('79e417a4201028c21.24163259',\t1,\t'',\t'bl_perfLoadAccessoires',\t'bool',\t'1'),\n('79e417a420103a598.95673089',\t1,\t'',\t'bl_perfLoadCustomerWhoBoughtThis',\t'bool',\t'1'),\n('79e417a4201044603.06076651',\t1,\t'',\t'bl_perfLoadSimilar',\t'bool',\t'1'),\n('79e417a420104dbd8.25267555',\t1,\t'',\t'bl_perfLoadSelectLists',\t'bool',\t'1'),\n('79e417a4201062a60.33852458',\t1,\t'',\t'bl_perfLoadDiscounts',\t'bool',\t'1'),\n('79e417a420106baa7.25594072',\t1,\t'',\t'bl_perfLoadDelivery',\t'bool',\t'1'),\n('79e417a420107ab46.59697382',\t1,\t'',\t'bl_perfLoadPrice',\t'bool',\t'1'),\n('79e417a442934fcb9.11733184',\t1,\t'',\t'bl_perfLoadCatTree',\t'bool',\t'1'),\n('79e417a45558d97f6.76133435',\t1,\t'',\t'bl_perfLoadCurrency',\t'bool',\t'1'),\n('79e417a45558e7851.36128674',\t1,\t'',\t'bl_perfLoadLanguages',\t'bool',\t'1'),\n('79e417a4eaad1a593.54850808',\t1,\t'',\t'blStoreIPs',\t'bool',\t''),\n('7a59f9000f39e5d9549a5d1e29c076a0',\t1,\t'',\t'blUseMultidimensionVariants',\t'bool',\t'1'),\n('7a59f9000f39e5d9549a5d1e29c076a2',\t1,\t'',\t'blOrderOptInEmail',\t'bool',\t'1'),\n('7e9426025ff199d75.57820200',\t1,\t'',\t'sStockWarningLimit',\t'str',\t'10'),\n('7fc4007ffb2639208.44268873',\t1,\t'',\t'sGZSLogFile',\t'str',\t''),\n('8563fba1965a11df3.12345678',\t1,\t'',\t'blWrappingVatOnTop',\t'bool',\t''),\n('8563fba1965a11df3.34244997',\t1,\t'',\t'blEnterNetPrice',\t'bool',\t''),\n('8563fba1965a1cc34.52696792',\t1,\t'',\t'blCalculateDelCostIfNotLoggedIn',\t'bool',\t''),\n('8563fba1965a1f266.82484369',\t1,\t'',\t'blAllowUnevenAmounts',\t'bool',\t''),\n('8563fba1965a219c9.51133344',\t1,\t'',\t'blUseStock',\t'bool',\t'1'),\n('8563fba1965a25500.87856483',\t1,\t'',\t'dDefaultVAT',\t'num',\t'19'),\n('8563fba1965a27185.06428911',\t1,\t'',\t'sDefaultLang',\t'str',\t'0'),\n('8563fba1965a2b330.65668120',\t1,\t'',\t'sMerchantID',\t'str',\t''),\n('8563fba1965a2d181.97927980',\t1,\t'',\t'sHost',\t'str',\t'https://txms.gzs.de:51384/'),\n('8563fba1965a2eee6.68137602',\t1,\t'',\t'sPaymentUser',\t'str',\t''),\n('8563fba1965a30cf7.41846088',\t1,\t'',\t'sPaymentPwd',\t'str',\t''),\n('8563fba1baec4d3b7.61553539',\t1,\t'',\t'iNrofSimilarArticles',\t'str',\t'5'),\n('8563fba1baec4f6d3.38812651',\t1,\t'',\t'iNrofCustomerWhoArticles',\t'str',\t'5'),\n('8563fba1baec515d0.57265727',\t1,\t'',\t'iNrofCrossellArticles',\t'str',\t'5'),\n('8563fba1baec55dc8.04115259',\t1,\t'',\t'iUseGDVersion',\t'str',\t'2'),\n('8563fba1baec57c19.08644217',\t1,\t'',\t'sThumbnailsize',\t'str',\t'100*100'),\n('8563fba1baec599d5.89404456',\t1,\t'',\t'sCatThumbnailsize',\t'str',\t'555*200'),\n('8563fba1baec5b7d3.75515041',\t1,\t'',\t'sCSVSign',\t'str',\t';'),\n('8563fba1baec5d615.45874801',\t1,\t'',\t'iExportNrofLines',\t'str',\t'250'),\n('8563fba1baec6eaf2.01241384',\t1,\t'',\t'iCntofMails',\t'str',\t'20'),\n('8563fba1baec73b00.28734905',\t1,\t'',\t'aOrderfolder',\t'aarr',\t'a:3:{s:15:\\\"ORDERFOLDER_NEW\\\";s:7:\\\"#0000FF\\\";s:20:\\\"ORDERFOLDER_FINISHED\\\";s:7:\\\"#0A9E18\\\";s:20:\\\"ORDERFOLDER_PROBLEMS\\\";s:7:\\\"#FF0000\\\";}'),\n('8563fba1c39367724.92308656',\t1,\t'',\t'blCheckTemplates',\t'bool',\t'1'),\n('8563fba1c39367724.92308656123111',\t1,\t'',\t'sDownloadsDir',\t'str',\t'out/downloads'),\n('8563fba1c393750a0.46170041',\t1,\t'',\t'sUtilModule',\t'str',\t''),\n('8563fba1c3937ee60.91079898',\t1,\t'',\t'iMallMode',\t'str',\t'1'),\n('8563fba1c39381962.39392958',\t1,\t'',\t'aCacheViews',\t'arr',\t'a:3:{i:0;s:5:\\\"start\\\";i:1;s:5:\\\"alist\\\";i:2;s:7:\\\"details\\\";}'),\n('8563fba1c39386cf4.18302736',\t1,\t'',\t'aSkipTags',\t'arr',\t'a:36:{i:0;s:3:\\\"der\\\";i:1;s:3:\\\"die\\\";i:2;s:3:\\\"das\\\";i:3;s:3:\\\"was\\\";i:4;s:3:\\\"wie\\\";i:5;s:3:\\\"wer\\\";i:6;s:2:\\\"in\\\";i:7;s:3:\\\"sie\\\";i:8;s:2:\\\"du\\\";i:9;s:3:\\\"aus\\\";i:10;s:3:\\\"von\\\";i:11;s:3:\\\"des\\\";i:12;s:3:\\\"hat\\\";i:13;s:5:\\\"einen\\\";i:14;s:4:\\\"eine\\\";i:15;s:3:\\\"ist\\\";i:16;s:5:\\\"einem\\\";i:17;s:4:\\\"dann\\\";i:18;s:5:\\\"haben\\\";i:19;s:6:\\\"dieser\\\";i:20;s:6:\\\"dieser\\\";i:21;s:3:\\\"dem\\\";i:22;s:4:\\\"sich\\\";i:23;s:2:\\\"er\\\";i:24;s:3:\\\"ich\\\";i:25;s:3:\\\"was\\\";i:26;s:4:\\\"fÜr\\\";i:27;s:3:\\\"und\\\";i:28;s:3:\\\"nur\\\";i:29;s:3:\\\"auf\\\";i:30;s:2:\\\"an\\\";i:31;s:4:\\\"this\\\";i:32;s:4:\\\"that\\\";i:33;s:2:\\\"if\\\";i:34;s:3:\\\"you\\\";i:35;s:3:\\\"the\\\";}'),\n('8563fba1c3938ebe7.95075058',\t1,\t'',\t'aLogSkipTags',\t'arr',\t'a:0:{}'),\n('89e42b02704ce5589.91950338',\t1,\t'',\t'iNewestArticlesMode',\t'str',\t'1'),\n('8b831f739c5d16cf4571b14a76006568',\t1,\t'',\t'aSEOReservedWords',\t'arr',\t'a:7:{i:0;s:5:\\\"admin\\\";i:1;s:4:\\\"core\\\";i:2;s:6:\\\"export\\\";i:3;s:7:\\\"modules\\\";i:4;s:3:\\\"out\\\";i:5;s:5:\\\"setup\\\";i:6;s:5:\\\"views\\\";}'),\n('9135a582a6971656110b9a98ca5be6d2',\t1,\t'',\t'blShippingCountryVat',\t'bool',\t''),\n('99065ff58e9d2c1b2e362e54c0bb54f3',\t1,\t'',\t'blNewArtByInsert',\t'bool',\t'1'),\n('9a8426df9d36443e7.48701626',\t1,\t'',\t'blSearchUseAND',\t'bool',\t''),\n('a104164f96fa51c41.58873414',\t1,\t'',\t'aSearchCols',\t'arr',\t'a:4:{i:0;s:7:\\\"oxtitle\\\";i:1;s:11:\\\"oxshortdesc\\\";i:2;s:12:\\\"oxsearchkeys\\\";i:3;s:8:\\\"oxartnum\\\";}'),\n('a1544b76735df7bd7.33980003',\t1,\t'',\t'blEnableIntangibleProdAgreement',\t'bool',\t'1'),\n('a7a425c02819f7253.64374401',\t1,\t'',\t'blAutoIcons',\t'bool',\t'1'),\n('a99427345bf85a602.27736147',\t1,\t'',\t'blDontShowEmptyCategories',\t'bool',\t''),\n('a99427345bf8fcff2.83464949',\t1,\t'',\t'bl_perfUseSelectlistPrice',\t'bool',\t''),\n('a99427345bf9a27a1.04791092',\t1,\t'',\t'bl_perfCalcVatOnlyForBasketOrder',\t'bool',\t''),\n('b0b4d221756b49c8d60a904c0b91b877',\t1,\t'',\t'blCheckSysReq',\t'bool',\t'1'),\n('b2b400dd011bf6273.08965005',\t1,\t'',\t'blVariantsSelection',\t'bool',\t''),\n('bd320d322fa2f638086787c512329eec',\t1,\t'',\t'dPointsForRegistration',\t'str',\t'10'),\n('bd3e73e699331eb92c557113bac02fc4',\t1,\t'',\t'dPointsForInvitation',\t'str',\t'10'),\n('bf041bd98dacd9021.61732877',\t1,\t'',\t'aInterfaceProfiles',\t'aarr',\t'a:4:{s:8:\\\"Standard\\\";s:2:\\\"10\\\";s:8:\\\"1024x768\\\";s:2:\\\"10\\\";s:9:\\\"1280x1024\\\";s:2:\\\"17\\\";s:9:\\\"1600x1200\\\";s:2:\\\"22\\\";}'),\n('c20424bf2f8e71271.42955545',\t1,\t'',\t'bl_perfLoadTreeForSearch',\t'bool',\t'1'),\n('ce143201f7e03e110.09792514',\t1,\t'',\t'aMustFillFields',\t'arr',\t'a:14:{i:0;s:15:\\\"oxuser__oxfname\\\";i:1;s:15:\\\"oxuser__oxlname\\\";i:2;s:16:\\\"oxuser__oxstreet\\\";i:3;s:18:\\\"oxuser__oxstreetnr\\\";i:4;s:13:\\\"oxuser__oxzip\\\";i:5;s:14:\\\"oxuser__oxcity\\\";i:6;s:19:\\\"oxuser__oxcountryid\\\";i:7;s:18:\\\"oxaddress__oxfname\\\";i:8;s:18:\\\"oxaddress__oxlname\\\";i:9;s:19:\\\"oxaddress__oxstreet\\\";i:10;s:21:\\\"oxaddress__oxstreetnr\\\";i:11;s:16:\\\"oxaddress__oxzip\\\";i:12;s:17:\\\"oxaddress__oxcity\\\";i:13;s:22:\\\"oxaddress__oxcountryid\\\";}'),\n('d144175015dcd2a39.15131643',\t1,\t'',\t'aHomeCountry',\t'arr',\t'a:1:{i:0;s:26:\\\"a7c40f631fc920687.20179984\\\";}'),\n('e1142ca231becd5c4.00590616',\t1,\t'',\t'blConfirmAGB',\t'bool',\t''),\n('e8e41bda6fa7631d8.13775806',\t1,\t'',\t'iSessionTimeout',\t'str',\t'60'),\n('fde4559837789b3c7.26965372',\t1,\t'',\t'aCMSfolder',\t'aarr',\t'a:4:{s:16:\\\"CMSFOLDER_EMAILS\\\";s:7:\\\"#706090\\\";s:18:\\\"CMSFOLDER_USERINFO\\\";s:7:\\\"#303030\\\";s:21:\\\"CMSFOLDER_PRODUCTINFO\\\";s:7:\\\"#303030\\\";s:14:\\\"CMSFOLDER_NONE\\\";s:7:\\\"#904040\\\";}'),\n('fecfcd8dbd01a491a94557448425acc8',\t1,\t'',\t'blShowTSInternationalFeesMessage',\t'bool',\t'1'),\n('l8g3e140a4bc7993d7d715df951dfe25',\t1,\t'',\t'iMaxDownloadsCountUnregistered',\t'str',\t'1'),\n('l8g957be9e7b13412960c7670f71ba31',\t1,\t'',\t'sAdditionalServVATCalcMethod',\t'str',\t'biggest_net'),\n('l8g957be9e7b13412960c7670f71ba3b',\t1,\t'',\t'iMaxDownloadsCount',\t'str',\t'0'),\n('mhjf24905a5b49c8d60aa31087b97971',\t1,\t'',\t'blEnableSeoCache',\t'bool',\t'1'),\n('mhjf24905a5b49c8d60aa31087b9797f',\t1,\t'',\t'blShowRememberMe',\t'bool',\t'1'),\n('mla50c74dd79703312ffb8cfd82c3741',\t1,\t'',\t'aLanguageURLs',\t'arr',\t'a:2:{i:0;s:0:\\\"\\\";i:1;N;}'),\n('mlabefd7ebdb5946e8f3f7e7a953b323',\t1,\t'',\t'aLanguageSSLURLs',\t'arr',\t'a:2:{i:0;s:0:\\\"\\\";i:1;N;}'),\n('mlae44cdad808d9b994c58540db39e7a',\t1,\t'',\t'aLanguages',\t'aarr',\t'a:2:{s:2:\\\"de\\\";s:7:\\\"Deutsch\\\";s:2:\\\"en\\\";s:7:\\\"English\\\";}'),\n('omc4555952125c3c2.98253113',\t1,\t'',\t'blDisableNavBars',\t'bool',\t'1'),\n('feb42aecdf7bacc7c76c9d36102da622',\t1,\t'',\t'blSkipDebitOldBankInfo',\t'bool',\t'1');\n\nINSERT INTO `oxcontents` (`OXID`, `OXLOADID`, `OXSHOPID`, `OXSNIPPET`, `OXTYPE`, `OXACTIVE`, `OXACTIVE_1`, `OXPOSITION`, `OXTITLE`, `OXCONTENT`, `OXTITLE_1`, `OXCONTENT_1`, `OXACTIVE_2`, `OXTITLE_2`, `OXCONTENT_2`, `OXACTIVE_3`, `OXTITLE_3`, `OXCONTENT_3`, `OXCATID`, `OXFOLDER`, `OXTERMVERSION`) VALUES\n('8709e45f31a86909e9f999222e80b1d0', 'oxstdfooter', 1, 1, 0, 1, 1, '', 'Standard Footer', '<div>OXID Online Shop - Alles rund um das Thema Wassersport, Sportbekleidung und Mode </div>', 'standard footer', '<div>OXID Online Shop - All about watersports, sportswear and fashion </div>', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', '', ''),\n('ad542e49bff479009.64538090', 'oxadminorderemail', 1, 1, 0, 1, 1, '', 'Ihre Bestellung Admin', 'Folgende Artikel wurden soeben unter {{  shop.oxshops__oxname.value  }} bestellt:<br>\\r\\n<br>', 'your order admin', 'The following products have been ordered in {{  shop.oxshops__oxname.value  }} right now:<br>\\r\\n<br>', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('c8d45408c4998f421.15746968', 'oxadminordernpemail', 1, 1, 0, 1, 1, '', 'Ihre Bestellung Admin (Fremdländer)', '<div>\\r\\n<p> <span style=\"color: #ff0000;\"><strong>Hinweis:</strong></span> Derzeit ist keine Liefermethode für dieses Land bekannt. Bitte Liefermöglichkeiten suchen und den Besteller unter Angabe der <strong>Lieferkosten</strong> informieren!\\r\\n&nbsp;</p> </div>\\r\\n<div>Folgende Artikel wurden soeben unter {{  shop.oxshops__oxname.value  }} bestellt:<br>\\r\\n<br>\\r\\n</div>', 'your order admin (other country)', '<p> <span style=\"color: #ff0000\"><strong>Information:</strong></span> Currently, there is no shipping method defined for this country. Please find a delivery option and inform the customer about the <strong>shipping costs</strong>.</p>\\r\\n<p>The following products have been ordered on {{  shop.oxshops__oxname.value  }}:<br />\\r\\n<br /></p>', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('c8d45408c718782f3.21298666', 'oxadminordernpplainemail', 1, 1, 0, 1, 1, '', 'Ihre Bestellung Admin (Fremdländer) Plain', 'Hinweis: Derzeit ist keine Liefermethode für dieses Land bekannt. Bitte Liefermöglichkeiten suchen und den Besteller informieren!\\r\\n\\r\\nFolgende Artikel wurden soeben unter {{  shop.oxshops__oxname.getRawValue()  }} bestellt:', 'your order admin plain (other country)', '<p>Information: Currently, there is no shipping method defined for this country. Please find a delivery option and inform the customer about the shipping costs.\\r\\n\\r\\nThe following products have been ordered on {{  shop.oxshops__oxname.getRawValue()  }}:</p>', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('ad542e49c19109ad6.04198712', 'oxadminorderplainemail', 1, 1, 0, 1, 1, '', 'Ihre Bestellung Admin Plain', '<p>Folgende Artikel wurden soeben unter {{  shop.oxshops__oxname.getRawValue()  }} bestellt:</p>', 'your order admin plain', 'The following products have been ordered in {{  shop.oxshops__oxname.getRawValue()  }} right now:', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('2eb4676806a3d2e87.06076523', 'oxagb', 1, 1, 0, 1, 1, '', 'AGB', '<div><strong>AGB</strong></div>\\r\\n<div><strong>&nbsp;</strong></div>\\r\\n<div>Fügen Sie hier Ihre allgemeinen Geschäftsbedingungen ein:</div>\\r\\n<div>&nbsp;</div>\\r\\n<div><span style=\"font-weight: bold\">Strukturvorschlag:</span><br>\\r\\n<br>\\r\\n<ol>\\r\\n<li>Geltungsbereich </li>\\r\\n<li>Vertragspartner </li>\\r\\n<li>Angebot und Vertragsschluss </li>\\r\\n<li>Widerrufsrecht, Widerrufsbelehrung, Widerrufsfolgen </li>\\r\\n<li>Preise und Versandkosten </li>\\r\\n<li>Lieferung </li>\\r\\n<li>Zahlung </li>\\r\\n<li>Eigentumsvorbehalt </li>\\r\\n<li>Gewährleistung </li>\\r\\n<li>Weitere Informationen</li></ol></div>', 'Terms and Conditions', 'Insert your terms and conditions here.', 1, '', '', 0, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_USERINFO', '1'),\n('c4241316c6e7b9503.93160420', 'oxbargain', 1, 1, 0, 1, 1, '', 'Angebot der Woche', '<table>{% for articlebargain_item in oView.getBargainArticleList() %} <tbody><tr><td>\\r\\n<div class=\"product_image_s_container\"><a href=\"{{ articlebargain_item.getLink() }}\"><img border=\"0\" alt=\"{{  articlebargain_item.oxarticles__oxtitle.value  }}{% if articlebargain_item.oxarticles__oxvarselect.value  %} {{  articlebargain_item.oxarticles__oxvarselect.value  }}{% endif %} {{ oxcmp_shop.oxshops__oxtitlesuffix.value }}\" src=\"{{  articlebargain_item.getDynImageDir() }}/{{ articlebargain_item.oxarticles__oxicon.value }}\"></a></div> </td><td class=\"boxrightproduct-td\"> <a href=\"{{ articlebargain_item.getLink() }}\" class=\"boxrightproduct-td\"><strong>{{  articlebargain_item.oxarticles__oxtitle.value|cat(\"\\r\\n\")|cat(articlebargain_item.oxarticles__oxvarselect.value)|striptags|smart_wordwrap(15, \"<br>\\r\\n\", 2, 1, \"...\")  }}</strong></a><br>\\r\\n {% if articlebargain_item.isBuyable()  %} <a href=\"{{ articlebargain_item.getToBasketLink() }}&amp;am=1\" class=\"details\" onclick=\"showBasketWnd();\" rel=\"nofollow\"><img border=\"0\" src=\"{{ oViewConf.getImageUrl(''arrow_details.gif'') }}\" alt=\"\"> Jetzt bestellen! </a> {% endif %} </td></tr>{% endfor %}\\r\\n</tbody></table>', 'Week''s Special', '<table>{% for articlebargain_item in oView.getBargainArticleList() %} <tbody><tr><td>\\r\\n<div class=\"product_image_s_container\"><a href=\"{{ articlebargain_item.getLink() }}\"><img border=\"0\" src=\"{{  articlebargain_item.getDynImageDir() }}/{{ articlebargain_item.oxarticles__oxicon.value }}\" alt=\"{{  articlebargain_item.oxarticles__oxtitle.value  }}{% if articlebargain_item.oxarticles__oxvarselect.value  %} {{  articlebargain_item.oxarticles__oxvarselect.value  }}{% endif %} {{ oxcmp_shop.oxshops__oxtitlesuffix.value }}\"></a></div> </td><td class=\"boxrightproduct-td\"> <a class=\"boxrightproduct-td\" href=\"{{ articlebargain_item.getLink() }}\"><strong>{{  articlebargain_item.oxarticles__oxtitle.value|cat(\"\\r\\n\")|cat(articlebargain_item.oxarticles__oxvarselect.value)|striptags|smart_wordwrap(15, \"<br>\\r\\n) \":2:1:\"...\"  }}</strong></a><br>\\r\\n {% if articlebargain_item.isBuyable() %} <a onclick=\"showBasketWnd();\" class=\"details\" href=\"{{ articlebargain_item.getToBasketLink() }}&amp;am=1\" rel=\"nofollow\"><img border=\"0\" alt=\"\" src=\"{{ oViewConf.getImageUrl(''arrow_details.gif'') }}\"> Order now! </a> {% endif %} </td></tr>{% endfor %} </tbody></table>', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('1544167b4666ccdc1.28484600', 'oxblocked', 1, 1, 0, 1, 1, '', 'Benutzer geblockt', '<div><span style=\"color: #ff0000;\"><strong>Der Zugang wurde Ihnen verweigert!</strong></span></div>\\r\\n<div>&nbsp;</div>\\r\\n<div>&nbsp;</div>', 'user blocked', '<div>\\r\\n   <span style=\"color: #ff0000;\"><strong>Permission denied!</strong></span>\\r\\n</div>', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_USERINFO', ''),\n('f41427a07519469f1.34718981', 'oxdeliveryinfo', 1, 1, 0, 1, 1, '', 'Zahlung und Lieferung', '<p>Fügen Sie hier Ihre Versandinformationen und -kosten ein.</p>', 'Shipping and Charges', '<p>Add your shipping information and costs here.</p>', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_USERINFO', ''),\n('42e4667ffcf844be0.22563656', 'oxemailfooter', 1, 1, 0, 1, 1, '', 'E-Mail Fußtext', '<p align=\"left\">--</p>\\r\\n<p>Bitte fügen Sie hier Ihre vollständige Anbieterkennzeichnung ein.</p>', 'E-mail footer', '<p align=\"left\">--</p>\\r\\n<p>Please insert your imprint here</p>', 1, '', '', 0, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('3194668fde854d711.73798992', 'oxemailfooterplain', 1, 1, 0, 1, 1, '', 'E-Mail Fußtext Plain', '-- Bitte fügen Sie hier Ihre vollständige Anbieterkennzeichnung ein.', 'E-mail footer plain', '-- Please insert your imprint here.', 1, '', '', 0, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('29142e76dd32dd477.41262508', 'oxforgotpwd', 1, 1, 0, 1, 1, '', 'Passwort vergessen', 'Sollten Sie innerhalb der nächsten Minuten KEINE E-Mail mit Ihren Zugangsdaten erhalten, so überprüfen Sie bitte: Haben Sie sich in unserem Shop bereits registriert? Wenn nicht, so tun Sie dies bitte einmalig im Rahmen des Bestellprozesses. Sie können dann selbst ein Passwort festlegen. Sobald Sie registriert sind, können Sie sich in Zukunft mit Ihrer E-Mail-Adresse und Ihrem Passwort einloggen.\\r\\n<ul>\\r\\n<li class=\"font11\">Wenn Sie sich sicher sind, dass Sie sich in unserem Shop bereits registriert haben, dann überprüfen Sie bitte, ob Sie sich bei der Eingabe Ihrer E-Mail-Adresse evtl. vertippt haben.</li></ul>\\r\\n<p>Sollten Sie trotz korrekter E-Mail-Adresse und bereits bestehender Registrierung weiterhin Probleme mit dem Login haben und auch keine \"Passwort vergessen\"-E-Mail erhalten, so wenden Sie sich bitte per E-Mail an: <a href=\"mailto:{{  oxcmp_shop.oxshops__oxinfoemail.value  }}?subject=Passwort\"><strong>{{  oxcmp_shop.oxshops__oxinfoemail.value  }}</strong></a></p>', 'Forgot password', '<p>If you don''t get an e-mail with your access data, please make sure that you have already registered with us. As soon as you are registered, you can login with your e-mail address and your password.</p>\\r\\n<ul>\\r\\n<li>\\r\\nIf you are sure you are already registered, please check the e-mail address you entered as user name.</li></ul>\\r\\n<p>\\r\\nIn case you still have problems logging in, please turn to us by e-mail: <a href=\"mailto:{{  oxcmp_shop.oxshops__oxinfoemail.value  }}?subject=Password\"><strong>{{  oxcmp_shop.oxshops__oxinfoemail.value  }}</strong></a></p>', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('2eb46767947d21851.22681675', 'oximpressum', 1, 1, 0, 1, 1, '', 'Impressum', '<p>Fügen Sie hier Ihre Anbieterkennzeichnung ein.</p>', 'About Us', '<p>Add provider identification here.</p>', 1, '', '', 0, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_USERINFO', ''),\n('ad542e49975709a72.52261121', 'oxnewsletteremail', 1, 1, 0, 1, 1, '', 'Newsletter eShop', 'Hallo, {{  user.oxuser__oxsal.value|translate_salutation  }} {{  user.oxuser__oxfname.value  }} {{  user.oxuser__oxlname.value  }},<br>\\r\\nvielen Dank für Ihre Anmeldung zu unserem Newsletter.<br>\\r\\n<br>\\r\\nUm den Newsletter freizuschalten klicken Sie bitte auf folgenden Link:<br>\\r\\n<br>\\r\\n<a href=\"{{ subscribeLink|raw }}\">{{ subscribeLink|raw }}</a><br>\\r\\n<br>\\r\\nIhr {{  shop.oxshops__oxname.value  }} Team<br>', 'newsletter confirmation', 'Hello, {{  user.oxuser__oxsal.value|translate_salutation  }} {{  user.oxuser__oxfname.value  }} {{  user.oxuser__oxlname.value  }},<br>\\r\\nthank you for your newsletter subscription.<br>\\r\\n<br>\\r\\nFor final registration, please click on this link:<br>\\r\\n<br>\\r\\n<a href=\"{{ subscribeLink|raw }}\">{{ subscribeLink|raw }}</a><br>\\r\\n<br>\\r\\nYour {{  shop.oxshops__oxname.value  }} Team<br>', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('ad542e4999ec01dd3.07214049', 'oxnewsletterplainemail', 1, 1, 0, 1, 1, '', 'Newsletter eShop Plain', '{{  shop.oxshops__oxname.getRawValue()  }} Newsletter Hallo, {{  user.oxuser__oxsal.value|translate_salutation  }} {{  user.oxuser__oxfname.getRawValue()  }} {{  user.oxuser__oxlname.getRawValue()  }}, vielen Dank für Ihre Anmeldung zu unserem Newsletter. Um den Newsletter freizuschalten klicken Sie bitte auf folgenden Link: {{ subscribeLink|raw }} Ihr {{  shop.oxshops__oxname.getRawValue()  }} Team', 'newsletter confirmation plain', '{{  shop.oxshops__oxname.getRawValue()  }} Newsletter \\r\\n\\r\\nHello, {{  user.oxuser__oxsal.value|translate_salutation  }} {{  user.oxuser__oxfname.getRawValue()  }} {{  user.oxuser__oxlname.getRawValue()  }}, \\r\\n\\r\\nthank you for your newsletter subscription. For final registration, please click on this link: \\r\\n{{ subscribeLink }} \\r\\n\\r\\nYour {{  shop.oxshops__oxname.getRawValue()  }} Team', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('f41427a10afab8641.52768563', 'oxnewstlerinfo', 1, 1, 0, 1, 1, '', 'Neuigkeiten bei uns', '<div>Mit dem {{  oxcmp_shop.oxshops__oxname.value  }}-Newsletter alle paar Wochen. <br>\\r\\nMit Tipps, Infos, Aktionen ... <br>\\r\\n<br>\\r\\nDas Abo kann jederzeit durch Austragen der E-Mail-Adresse beendet werden. <br>\\r\\nEine <span class=\"newsletter_title\">Weitergabe Ihrer Daten an Dritte lehnen wir ab</span>. <br>\\r\\n<br>\\r\\nSie bekommen zur Bestätigung nach dem Abonnement eine E-Mail - so stellen wir sicher, dass kein Unbefugter Sie in unseren Newsletter eintragen kann (sog. \"Double Opt-In\").<br>\\r\\n<br>\\r\\n</div>', 'newsletter info', '<p>Stay in touch with the periodic {{  oxcmp_shop.oxshops__oxname.value  }}-newsletter every couple of weeks. We gladly inform you about recent tips, promotions and new products.</p>\\r\\n<p>You can unsubscribe any time from the newsletter.</p>\\r\\n<p>We strictly refuse <span class=\"newsletter_title\">transferring your data to 3rd parties</span>.</p>\\r\\n<p>For subscription we use the so called &quot;double opt-in&quot; procedure to guarantee that no unauthorized person will register with your e-mail address.</p>', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_USERINFO', ''),\n('1074279e67a85f5b1.96907412', 'oxorderinfo', 1, 1, 0, 1, 1, '', 'Wie bestellen?', '<div>Beispieltext:</div>\\r\\n<div> </div>\\r\\n<div>{{  oxcmp_shop.oxshops__oxname.value  }}, Ihr Online-Shop für ... <br />\\r\\n<br />\\r\\nBei uns haben Sie die Wahl aus mehr als ... Artikeln von bester Qualität und namhaften Herstellern. Schauen Sie sich um, stöbern Sie in unseren Angeboten! <br />\\r\\n{{  oxcmp_shop.oxshops__oxname.value  }} steht Ihnen im Internet rund um die Uhr und 7 Tage die Woche offen.<br />\\r\\n<br />\\r\\nWenn Sie eine Bestellung aufgeben möchten, können Sie das:\\r\\n<ul>\\r\\n<li class=\"font11\">direkt im Internet über unseren Shop </li>\\r\\n<li class=\"font11\">per Fax unter {{  oxcmp_shop.oxshops__oxtelefax.value  }} </li>\\r\\n<li class=\"font11\">per Telefon unter {{  oxcmp_shop.oxshops__oxtelefon.value  }} </li>\\r\\n<li class=\"font11\">oder per E-Mail unter <a href=\"mailto:{{  oxcmp_shop.oxshops__oxowneremail.value  }}?subject=Bestellung\"><strong>{{  oxcmp_shop.oxshops__oxowneremail.value  }}</strong></a> </li></ul>Telefonisch sind wir für Sie <br />\\r\\nMontag bis Freitag von 10 bis 18 Uhr erreichbar. <br />\\r\\nWenn Sie auf der Suche nach einem Artikel sind, der zum Sortiment von {{  oxcmp_shop.oxshops__oxname.value  }} passen könnte, ihn aber nirgends finden, lassen Sie es uns wissen. Gern bemühen wir uns um eine Lösung für Sie. <br />\\r\\n<br />\\r\\nSchreiben Sie an <a href=\"mailto:{{  oxcmp_shop.oxshops__oxowneremail.value  }}?subject=Produktidee\"><strong>{{  oxcmp_shop.oxshops__oxowneremail.value  }}</strong></a>.</div>', 'How to order?', '<h1>Text Example</h1>\\r\\n<h2>{{  oxcmp_shop.oxshops__oxname.value  }}, your online store for ...</h2>\\r\\n<p>With us, you can choose from more than ... products of high quality and reputable manufacturers. Take a look around and browse through our offers!<br />\\r\\nOn the internet {{  oxcmp_shop.oxshops__oxname.value  }} is open 24/7.</p>\\r\\n<p>If you want to place an order you can purchase</p>\\r\\n<ul>\\r\\n<li class=\"font11\">via our online store</li>\\r\\n<li class=\"font11\">via fax {{  oxcmp_shop.oxshops__oxtelefax.value  }} </li>\\r\\n<li class=\"font11\">via telephone {{  oxcmp_shop.oxshops__oxtelefon.value  }} </li>\\r\\n<li class=\"font11\">or via e-mail <a href=\"mailto:{{  oxcmp_shop.oxshops__oxowneremail.value  }}?subject=Order\"><strong>{{  oxcmp_shop.oxshops__oxowneremail.value  }}</strong></a></li></ul>\\r\\n<p>By telephone, we are available<br />\\r\\nMonday to Friday 10 AM thru 6 PM. </p>\\r\\n<p>If you are looking for an item that did not match the range of {{  oxcmp_shop.oxshops__oxname.value  }}, let''s us know. We are happy to find a solution for you.</p>\\r\\n<p>Write to <a href=\"mailto:{{  oxcmp_shop.oxshops__oxowneremail.value  }}?subject=Product idea\"><strong>{{  oxcmp_shop.oxshops__oxowneremail.value  }}</strong></a></p>', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_USERINFO', ''),\n('67c5bcf75ee346bd9566bce6c8', 'oxcredits', 1, 0, 3, 0, 1, '', 'Credits', '', 'Credits', 'Please add your text here.', 1, '', '', 0, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_USERINFO', ''),\n('ad542e49d6de4a4f4.88594616', 'oxordersendemail', 1, 1, 0, 1, 1, '', 'Ihre Bestellung wurde versandt', 'Guten Tag, {{  order.oxorder__oxbillsal.value|translate_salutation  }} {{  order.oxorder__oxbillfname.value  }} {{  order.oxorder__oxbilllname.value  }},<br>\\r\\n<br>\\r\\nunser Vertriebszentrum hat soeben folgende Artikel versandt.<br>\\r\\n<br>', 'your order has been shipped', 'Hello {{  order.oxorder__oxbillsal.value|translate_salutation  }} {{  order.oxorder__oxbillfname.value  }} {{  order.oxorder__oxbilllname.value  }},<br />\\r\\n<br />\\r\\n<p>\\r\\nour distribution center just shipped this product:</p><br />', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('ad542e49d856b5b68.98220446', 'oxordersendplainemail', 1, 1, 0, 1, 1, '', 'Ihre Bestellung wurde versandt Plain', 'Guten Tag {{  order.oxorder__oxbillsal.value|translate_salutation  }} {{  order.oxorder__oxbillfname.getRawValue()  }} {{  order.oxorder__oxbilllname.getRawValue()  }},\\r\\n\\r\\nunser Vertriebszentrum hat soeben folgende Artikel versandt.', 'your order has been shipped plain', '<p>Hello {{  order.oxorder__oxbillsal.value|translate_salutation  }} {{  order.oxorder__oxbillfname.getRawValue()  }} {{  order.oxorder__oxbilllname.getRawValue()  }},\\r\\n\\r\\nour distribution center just shipped this product:</p>', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('ad542e49c585394e4.36951640', 'oxpricealarmemail', 1, 1, 0, 1, 1, '', 'Preisalarm', 'Preisalarm im {{  shop.oxshops__oxname.value  }}!<br>\\r\\n<br>\\r\\n{{  email  }} bietet für Artikel {{  product.oxarticles__oxtitle.value  }}, Artnum. {{  product.oxarticles__oxartnum.value  }}<br>\\r\\n<br>\\r\\nOriginalpreis: {{  product.getFPrice()  }} {{  currency.name }}<br>\\r\\nGEBOTEN: {{  bidprice  }} {{  currency.name }}<br>\\r\\n<br>\\r\\n<br>\\r\\nIhr Shop.<br>', 'price alert', 'Price alert at {{  shop.oxshops__oxname.value  }}!<br>\\r\\n<br>\\r\\n{{  email  }} bids for product {{  product.oxarticles__oxtitle.value  }}, product # {{  product.oxarticles__oxartnum.value  }}<br>\\r\\n<br>\\r\\nOriginal price: {{  currency.name }}{{  product.getFPrice()  }}<br>\\r\\nBid: {{  currency.name }}{{  bidprice  }}<br>\\r\\n<br>\\r\\n<br>\\r\\nYour store<br>', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('ad542e49c8ec04201.39247735', 'oxregisteremail', 1, 1, 0, 1, 1, '', 'Vielen Dank für Ihre Registrierung', 'Hallo, {{  user.oxuser__oxsal.value|translate_salutation  }} {{  user.oxuser__oxfname.value  }} {{  user.oxuser__oxlname.value  }}, vielen Dank für Ihre Registrierung bei {{  shop.oxshops__oxname.value  }}!<br>\\r\\n<br>\\r\\nSie können sich ab sofort auch mit Ihrer E-Mail-Adresse <strong>{{  user.oxuser__oxusername.value  }}</strong> einloggen.<br>\\r\\n<br>\\r\\nIhr {{  shop.oxshops__oxname.value  }} Team<br>', 'thanks for your registration', 'Hello, {{  user.oxuser__oxsal.value|translate_salutation  }} {{  user.oxuser__oxfname.value  }} {{  user.oxuser__oxlname.value  }}, <br />\\r\\n<br />\\r\\n<p>\\r\\nthank you for your registration at {{  shop.oxshops__oxname.value  }}!</p>\\r\\nFrom now on, you can log in with your email address <strong>{{  user.oxuser__oxusername.value  }}</strong>.<br />\\r\\n<br />\\r\\nYour {{  shop.oxshops__oxname.value  }} team<br />', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('ad542e49ca4750015.09588134', 'oxregisterplainemail', 1, 1, 0, 1, 1, '', 'Vielen Dank für Ihre Registrierung Plain', '{{  shop.oxshops__oxregistersubject.getRawValue()  }} Hallo, {{  user.oxuser__oxsal.value|translate_salutation  }} {{  user.oxuser__oxfname.getRawValue()  }} {{  user.oxuser__oxlname.getRawValue()  }}, vielen Dank für Ihre Registrierung bei {{  shop.oxshops__oxname.getRawValue()  }}! Sie können sich ab sofort auch mit Ihrer E-Mail-Adresse ({{  user.oxuser__oxusername.value  }}) einloggen. Ihr {{  shop.oxshops__oxname.getRawValue()  }} Team', 'thanks for your registration plain', 'Hello, {{  user.oxuser__oxsal.value|translate_salutation  }} {{  user.oxuser__oxfname.getRawValue()  }} {{  user.oxuser__oxlname.getRawValue()  }},\\r\\n\\r\\nthank you for your registration at {{  shop.oxshops__oxname.getRawValue()  }}!\\r\\nFrom now on, you can log in with your email address {{  user.oxuser__oxusername.value  }}.\\r\\n\\r\\nYour {{  shop.oxshops__oxname.getRawValue()  }} team', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('1ea45574543b21636.29288751', 'oxrightofwithdrawal', 1, 1, 0, 1, 1, '', 'Widerrufsrecht', '<div>Fügen Sie hier Ihre Widerrufsbelehrung ein.</div>', 'Right of Withdrawal', '<div>Insert here the Right of Withdrawal policy.</div>', 1, '', '', 0, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_USERINFO', ''),\n('f41427a099a603773.44301043', 'oxsecurityinfo', 1, 1, 0, 1, 1, '', 'Datenschutz', 'Fügen Sie hier Ihre Datenschutzbestimmungen ein.', 'Privacy Policy', 'Enter your privacy policy here.', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_USERINFO', ''),\n('ce79015b6f6f07612270975889', 'oxstartmetadescription', 1, 1, 0, 1, 1, '', 'META Description Startseite', 'Alles zum Thema Wassersport, Sportbekleidung und Mode. Umfangreiches Produktsortiment mit den neusten Trendprodukten. Blitzschneller Versand.<br />', 'META description start page', '<p>All about watersports, sportswear and fashion. Extensive product range including several trendy products. Fast shipping.</p>\\r\\n<p>&nbsp;</p>', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', '', ''),\n('ce77743c334edf92b0cab924a7', 'oxstartmetakeywords', 1, 1, 0, 1, 1, '', 'META Keywords Startseite', 'kite, kites, kiteboarding, kiteboards, wakeboarding, wakeboards, boards, strand, sommer, wassersport, mode, fashion, style, shirts, jeans, accessoires, angebote', 'META keywords start page', 'kite, kites, kiteboarding, kiteboards, wakeboarding, wakeboards, boards, beach, summer, watersports, funsports, fashion, style, shirts, jeans, accessories, special offers', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', '', ''),\n('ad542e49ae50c60f0.64307543', 'oxuserorderemail', 1, 1, 0, 1, 1, '', 'Ihre Bestellung', 'Vielen Dank für Ihre Bestellung!<br>\\r\\n<br>\\r\\nNachfolgend haben wir zur Kontrolle Ihre Bestellung noch einmal aufgelistet.<br>\\r\\nBei Fragen sind wir jederzeit für Sie da: Schreiben Sie einfach an {{  shop.oxshops__oxorderemail.value  }}!<br>\\r\\n<br>', 'your order', 'Thank you for your order!<br />\\r\\n<br />\\r\\nBelow, we have listed your order.<br />\\r\\nIf you have any questions, don''t hesitate to drop us an e-mail {{  shop.oxshops__oxorderemail.value  }}!<br />\\r\\n<br />', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('84a42e66105998a86.14045828', 'oxuserorderemailend', 1, 1, 0, 1, 1, '', 'Ihre Bestellung Abschluss', '<div align=\"left\">Fügen Sie hier Ihre Widerrufsbelehrung ein.</div>', 'your order terms', '<p>Right to Withdrawal can be inserted here.</p>', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('84a42e66123887821.29772527', 'oxuserorderemailendplain', 1, 1, 0, 1, 1, '', 'Ihre Bestellung Abschluss Plain', 'Fügen Sie hier Ihre Widerrufsbelehrung ein.', 'your order terms plain', '<p>Right to Withdrawal can be inserted here.</p>', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('c8d45408c08bbaf79.09887022', 'oxuserordernpemail', 1, 1, 0, 1, 1, '', 'Ihre Bestellung (Fremdländer)', '<div>Vielen Dank für Ihre Bestellung!</div>\\r\\n<p><strong><span style=\"color: #ff0000\">Hinweis:</span></strong> Derzeit ist uns keine Versandmethode für dieses Land bekannt. Wir werden versuchen, Versandmethoden zu finden und Sie über das Ergebnis unter Angabe der Versandkosten informieren. </p>Bei Fragen sind wir jederzeit für Sie da: Schreiben Sie einfach an {{  shop.oxshops__oxorderemail.value  }}! <br />\\r\\n<br />', 'your order (other country)', '<p>Thank you for your order!</p>\\r\\n<p><strong><span style=\"color: #ff0000\">Information:</span></strong> Currently, there is no shipping method defined for your country. We will find a method to deliver the goods you purchased and will inform you as soon as possible.</p>\\r\\n<p>If you have any requests, don''t hesitate to contact us! {{  shop.oxshops__oxorderemail.value  }}</p>', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('c8d45408c5c39ea22.75925645', 'oxuserordernpplainemail', 1, 1, 0, 1, 1, '', 'Ihre Bestellung (Fremdländer) Plain', 'Vielen Dank für Ihre Bestellung!\\r\\n\\r\\nHinweis: Derzeit ist uns keine Versandmethode für dieses Land bekannt. Wir werden versuchen, Versandmethoden zu finden und Sie über das Ergebnis unter Angabe der Versandkosten informieren.\\r\\n\\r\\nBei Fragen sind wir jederzeit für Sie da: Schreiben Sie einfach an {{  shop.oxshops__oxorderemail.value  }}!', 'your order plain (other country)', 'Thank you for your order!\\r\\nInformation: Currently, there is no shipping method defined for your country. We will find a method to deliver the goods you purchased and will inform you as soon as possible.\\r\\n\\r\\nIf you have any requests don''t hesitate to contact us! {{  shop.oxshops__oxorderemail.value  }}', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('ad542e49b08c65017.19848749', 'oxuserorderplainemail', 1, 1, 0, 1, 1, '', 'Ihre Bestellung Plain', 'Vielen Dank für Ihre Bestellung!\\r\\n\\r\\nNachfolgend haben wir zur Kontrolle Ihre Bestellung noch einmal aufgelistet.\\r\\nBei Fragen sind wir jederzeit für Sie da: Schreiben Sie einfach an {{  shop.oxshops__oxorderemail.value  }}!', 'your order plain', 'Thank you for your order!\\r\\n\\r\\nBelow we have listed your order.\\r\\nIf you have any questions, don''t hesitate to drop us an e-mail {{  shop.oxshops__oxorderemail.value  }}!', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('ad542e49541c1add', 'oxupdatepassinfoemail', 1, 1, 0, 1, 1, '', 'Ihr Passwort im eShop', 'Hallo {{  user.oxuser__oxsal.value|translate_salutation  }} {{  user.oxuser__oxfname.value  }} {{  user.oxuser__oxlname.value  }},\\r\\n<br /><br />\\r\\nöffnen Sie den folgenden Link, um ein neues Passwort für {{  shop.oxshops__oxname.value  }} einzurichten:\\r\\n<br /><br />\\r\\n<a href=\"{{  oViewConf.getBaseDir()  }}index.php?cl=forgotpwd&amp;uid={{  user.getUpdateId()  }}&amp;lang={{  oViewConf.getActLanguageId()  }}&amp;shp={{  shop.oxshops__oxid.value  }}\">{{  oViewConf.getBaseDir()  }}index.php?cl=forgotpwd&amp;uid={{  user.getUpdateId() }}&amp;lang={{  oViewConf.getActLanguageId()  }}&amp;shp={{  shop.oxshops__oxid.value  }}</a>\\r\\n<br /><br />\\r\\nDiesen Link können Sie innerhalb der nächsten {{  user.getUpdateLinkTerm() / 3600  }} Stunden aufrufen.\\r\\n<br /><br />\\r\\nIhr {{  shop.oxshops__oxname.value  }} Team\\r\\n<br />', 'password update info', 'Hello {{  user.oxuser__oxsal.value|translate_salutation  }} {{  user.oxuser__oxfname.value  }} {{  user.oxuser__oxlname.value  }},<br />\\r\\n<br />\\r\\nfollow this link to generate a new password for {{  shop.oxshops__oxname.value  }}:<br />\\r\\n<br /><a href=\"{{  oViewConf.getBaseDir()  }}index.php?cl=forgotpwd&amp;uid={{  user.getUpdateId()  }}&amp;lang={{  oViewConf.getActLanguageId()  }}&amp;shp={{  shop.oxshops__oxid.value  }}\">{{  oViewConf.getBaseDir()  }}index.php?cl=forgotpwd&amp;uid={{  user.getUpdateId() }}&amp;lang={{  oViewConf.getActLanguageId()  }}&amp;shp={{  shop.oxshops__oxid.value  }}</a><br />\\r\\n<br />\\r\\nYou can use this link within the next {{  user.getUpdateLinkTerm() / 3600  }} hours.<br />\\r\\n<br />\\r\\nYour {{  shop.oxshops__oxname.value  }} team<br />', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('ad542e495c392c6e', 'oxupdatepassinfoplainemail', 1, 1, 0, 1, 1, '', 'Ihr Passwort im eShop Plain', 'Hallo {{  user.oxuser__oxsal.value|translate_salutation  }} {{  user.oxuser__oxfname.getRawValue()  }} {{  user.oxuser__oxlname.getRawValue()  }},\\r\\n\\r\\nöffnen Sie den folgenden Link, um ein neues Passwort für {{  shop.oxshops__oxname.getRawValue()  }} einzurichten:\\r\\n\\r\\n{{  oViewConf.getBaseDir()  }}index.php?cl=forgotpwd&amp;uid={{  user.getUpdateId() }}&amp;lang={{  oViewConf.getActLanguageId()  }}&amp;shp={{  shop.oxshops__oxid.value  }}\\r\\n\\r\\nDiesen Link können Sie innerhalb der nächsten {{  user.getUpdateLinkTerm() / 3600  }} Stunden aufrufen.\\r\\n\\r\\nIhr {{  shop.oxshops__oxname.getRawValue()  }} Team', 'password update info plain', 'Hello {{  user.oxuser__oxsal.value|translate_salutation  }} {{  user.oxuser__oxfname.getRawValue()  }} {{  user.oxuser__oxlname.getRawValue()  }},\\r\\n\\r\\nfollow this link to generate a new password for {{  shop.oxshops__oxname.getRawValue()  }}:\\r\\n\\r\\n{{  oViewConf.getBaseDir()  }}index.php?cl=forgotpwd&amp;uid={{  user.getUpdateId() }}&amp;lang={{  oViewConf.getActLanguageId()  }}&amp;shp={{  shop.oxshops__oxid.value  }}\\r\\n\\r\\nYou can use this link within the next {{  user.getUpdateLinkTerm() / 3600  }} hours.\\r\\n\\r\\nYour {{  shop.oxshops__oxname.getRawValue()  }} team', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('460f3d25a752eeca8f8dbd66d04277c1', 'oxregisteraltemail', 1, 1, 0, 1, 1, '', 'Alternative E-Mail zur Registrierung HTML', 'Hallo {{  user.oxuser__oxsal.value|translate_salutation  }} {{  user.oxuser__oxfname.value  }} {{  user.oxuser__oxlname.value  }}, <br />\\r\\n<br />\\r\\n<p>\\r\\ndanke für die Registrierung im {{  shop.oxshops__oxname.value  }}!</p>\\r\\nVon jetzt an können Sie sich mit Ihrer E-Mail-Adresse <strong>{{  user.oxuser__oxusername.value  }}</strong>.<br />\\r\\n<br />\\r\\nFolgen Sie diesem Link, um die Registrierung zu bestätigen:<br />\\r\\n<br /><a href=\"{{  oViewConf.getBaseDir()  }}index.php?cl=register&fnc=confirmRegistration&uid={{  user.getUpdateId()  }}&amp;lang={{  oViewConf.getActLanguageId()  }}&shp={{  shop.oxshops__oxid.value  }}\">{{  oViewConf.getBaseDir()  }}index.php?cl=register&fnc=confirmRegistration&uid={{  user.getUpdateId() }}&amp;lang={{  oViewConf.getActLanguageId()  }}&shp={{  shop.oxshops__oxid.value  }}</a><br />\\r\\n<br />\\r\\nSie können diesen Link in den nächsten {{  user.getUpdateLinkTerm() / 3600  }} Stunden verwenden.<br />\\r\\n<br /><br />\\r\\nIhr Team vom {{  shop.oxshops__oxname.value  }}', 'Alternative Registration Email HTML', 'Hello, {{  user.oxuser__oxsal.value|translate_salutation  }} {{  user.oxuser__oxfname.value  }} {{  user.oxuser__oxlname.value  }}, <br />\\r\\n<br />\\r\\nthanks for your registration at {{  shop.oxshops__oxname.value  }}!<br />\\r\\nFrom now on, you can log in with your email address <strong>{{  user.oxuser__oxusername.value  }}</strong>.<br />\\r\\n<br />\\r\\nFollow this link to confirm your registration:<br />\\r\\n<br /><a href=\"{{  oViewConf.getBaseDir()  }}index.php?cl=register&fnc=confirmRegistration&uid={{  user.getUpdateId()  }}&amp;lang={{  oViewConf.getActLanguageId()  }}&shp={{  shop.oxshops__oxid.value  }}\">{{  oViewConf.getBaseDir()  }}index.php?cl=register&fnc=confirmRegistration&uid={{  user.getUpdateId() }}&amp;lang={{  oViewConf.getActLanguageId()  }}&shp={{  shop.oxshops__oxid.value  }}</a><br />\\r\\n<br />\\r\\nYou can use this link within the next {{  user.getUpdateLinkTerm() / 3600  }} hours.<br />\\r\\n<br />\\r\\n<br />\\r\\nYour {{  shop.oxshops__oxname.value  }} team', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('460273f2ae78b9c40c536a1c331317ee', 'oxregisterplainaltemail', 1, 1, 0, 1, 1, '', 'Alternative E-Mail zur Registrierung PLAIN', 'Hallo {{  user.oxuser__oxsal.value|translate_salutation  }} {{  user.oxuser__oxfname.getRawValue()  }} {{  user.oxuser__oxlname.getRawValue()  }},\\r\\n\\r\\ndanke für die Registrierung im {{  shop.oxshops__oxname.getRawValue()  }}!\\r\\nVon jetzt an können Sie sich mit Ihrer E-Mail-Adresse {{  user.oxuser__oxusername.value  }}.\\r\\n\\r\\nFolgen Sie diesem Link, um die Registrierung zu bestätigen:\\r\\n{{  oViewConf.getBaseDir()  }}index.php?cl=register&fnc=confirmRegistration&uid={{  user.getUpdateId()  }}&amp;lang={{  oViewConf.getActLanguageId()  }}&shp={{  shop.oxshops__oxid.value  }}\\r\\n\\r\\nSie können diesen Link in den nächsten {{  user.getUpdateLinkTerm() / 3600  }} Stunden verwenden.</p>\\r\\n\\r\\n\\r\\nIhr Team vom {{  shop.oxshops__oxname.getRawValue()  }}', 'Alternative Registration Email PLAIN', 'Hello, {{  user.oxuser__oxsal.value|translate_salutation  }} {{  user.oxuser__oxfname.getRawValue()  }} {{  user.oxuser__oxlname.getRawValue()  }},\\r\\n\\r\\nthanks for your registration at {{  shop.oxshops__oxname.getRawValue()  }}!\\r\\nFrom now on, you can log in with your email address {{  user.oxuser__oxusername.value  }}.\\r\\n\\r\\nFollow this link to confirm your registration:\\r\\n{{  oViewConf.getBaseDir()  }}index.php?cl=register&fnc=confirmRegistration&uid={{  user.getUpdateId()  }}&amp;lang={{  oViewConf.getActLanguageId()  }}&shp={{  shop.oxshops__oxid.value  }}\\r\\n\\r\\nYou can use this link within the next {{  user.getUpdateLinkTerm() / 3600  }} hours.<br />\\r\\n\\r\\n\\r\\nYour {{  shop.oxshops__oxname.getRawValue()  }} team', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_EMAILS', ''),\n('220404cee0caf470e227c1c9f1ec4ae2', 'oxrighttocancellegend', 1, 1, 0, 1, 1, '', 'AGB und Widerrufsrecht', '{% ifcontent ident \"oxagb\" set oCont %}\\r\\n    Ich habe die <a id=\"test_OrderOpenAGBBottom\" rel=\"nofollow\" href=\"{{  oCont.getLink()  }}\" onclick=\"window.open(''{{  oCont.getLink()|add_url_parameters(\"plain=1\") }}'', ''agb_popup'', ''resizable=yes,status=no,scrollbars=yes,menubar=no,width=620,height=400'');return false;\" class=\"fontunderline\">AGB</a> gelesen und erkläre mich mit ihnen einverstanden.&nbsp;\\r\\n{% endifcontent %}\\r\\n{% ifcontent ident \"oxrightofwithdrawal\" set oCont %}\\r\\n    Ich wurde über mein <a id=\"test_OrderOpenWithdrawalBottom\" rel=\"nofollow\" href=\"{{  oCont.getLink()  }}\" onclick=\"window.open(''{{  oCont.getLink()|add_url_parameters(\"plain=1\") }}'', ''rightofwithdrawal_popup'', ''resizable=yes,status=no,scrollbars=yes,menubar=no,width=620,height=400'');return false;\">{{  oCont.oxcontents__oxtitle.value  }}</a> informiert.\\r\\n{% endifcontent %}', 'Terms and Conditions and Right to Withdrawal', '{% ifcontent ident \"oxagb\" set oCont %} I agree to the <a id=\"test_OrderOpenAGBBottom\" rel=\"nofollow\" href=\"{{  oCont.getLink()  }}\" onclick=\"window.open(''{{  oCont.getLink()|add_url_parameters(\"plain=1\") }}'', ''agb_popup'', ''resizable=yes,status=no,scrollbars=yes,menubar=no,width=620,height=400'');return false;\" class=\"fontunderline\">Terms and Conditions</a>.&nbsp;\\r\\n{% endifcontent %}\\r\\n{% ifcontent ident \"oxrightofwithdrawal\" set oCont %}\\r\\n    I have been informed about my <a id=\"test_OrderOpenWithdrawalBottom\" rel=\"nofollow\" href=\"{{  oCont.getLink()  }}\" onclick=\"window.open(''{{  oCont.getLink()|add_url_parameters(\"plain=1\") }}'', ''rightofwithdrawal_popup'', ''resizable=yes,status=no,scrollbars=yes,menubar=no,width=620,height=400'');return false;\">{{  oCont.oxcontents__oxtitle.value  }}</a>.\\r\\n{% endifcontent %}', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_USERINFO', ''),\n('c4241316b2e5c1966.96997011', 'oxhelpalist', 1, 1, 0, 1, 1, '', 'Hilfe - Die Produktliste', '<p>Hier können zusätzliche Informationen, weiterführende Links, Bedienungshinweise etc. für die Hilfe-Funktion in den <em>Produktlisten</em> eingefügt werden. </p>', 'Help - Product List', '<p>Here, you can insert additional information, further links, user manual etc. for the &quot;Help&quot;-function on <em>product pages</em>.</p>', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_USERINFO', ''),\n('c4241316b2e5c1966.96997012', 'oxhelpdefault', 1, 1, 0, 1, 1, '', 'Hilfe - Main', '<p>Hier können zusätzliche Informationen, weiterführende Links, Bedienungshinweise etc. für die Hilfe-Funktion in der <em>Kategorieansicht</em> eingefügt werden. </p>', 'Help - Main', '<p>Here, you can insert additional information, further links, user manual etc. for the &quot;Help&quot;-function on <em>category pages</em>.</p>', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_USERINFO', ''),\n('c4241316b2e5c1966.96997013', 'oxhelpstart', 1, 1, 0, 1, 1, '', 'Hilfe - Die Startseite', '<p>Hier können zusätzliche Informationen, weiterführende Links, Bedienungshinweise etc. für die Hilfe-Funktion auf der <em>Startseite</em> eingefügt werden. </p>\\r\\n<p>&nbsp;</p>', 'Help - Start page', '<p>Here, you can insert additional information, further links, user manual etc. for the &quot;Help&quot;-function on the <em>start page</em>.</p><br />', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_USERINFO', ''),\n('220404cee0caf470e227c1c9f1ec4ae3', 'oxrighttocancellegend2', 1, 1, 0, 1, 1, '', 'AGB und Widerrufsrecht', '{% ifcontent ident \"oxagb\" set oCont %}\\r\\n    Es gelten unsere <a rel=\"nofollow\" href=\"{{  oCont.getLink()  }}\" onclick=\"window.open(''{{  oCont.getLink()|add_url_parameters(\"plain=1\") }}'', ''agb_popup'', ''resizable=yes,status=no,scrollbars=yes,menubar=no,width=620,height=400'');return false;\" class=\"fontunderline\">Allgemeinen Geschäftsbedingungen</a>.&nbsp;\\r\\n{% endifcontent %}\\r\\n{% ifcontent ident \"oxrightofwithdrawal\" set oCont %}\\r\\n    Hier finden Sie <a id=\"test_OrderOpenWithdrawalBottom\" rel=\"nofollow\" href=\"{{  oCont.getLink()  }}\" onclick=\"window.open(''{{  oCont.getLink()|add_url_parameters(\"plain=1\") }}'', ''rightofwithdrawal_popup'', ''resizable=yes,status=no,scrollbars=yes,menubar=no,width=620,height=400'');return false;\">Einzelheiten zum Widerrufsrecht</a>.\\r\\n{% endifcontent %}', 'Terms and Conditions and Right to Withdrawal', '{% ifcontent ident \"oxagb\" set oCont %} Our general <a rel=\"nofollow\" href=\"{{  oCont.getLink()  }}\" onclick=\"window.open(''{{  oCont.getLink()|add_url_parameters(\"plain=1\") }}'', ''agb_popup'', ''resizable=yes,status=no,scrollbars=yes,menubar=no,width=620,height=400'');return false;\" class=\"fontunderline\">terms and conditions</a> apply.&nbsp;\\r\\n{% endifcontent %}\\r\\n{% ifcontent ident \"oxrightofwithdrawal\" set oCont %}\\r\\n    Read details about  <a id=\"test_OrderOpenWithdrawalBottom\" rel=\"nofollow\" href=\"{{  oCont.getLink()  }}\" onclick=\"window.open(''{{  oCont.getLink()|add_url_parameters(\"plain=1\") }}'', ''rightofwithdrawal_popup'', ''resizable=yes,status=no,scrollbars=yes,menubar=no,width=620,height=400'');return false;\">right of withdrawal</a>.\\r\\n{% endifcontent %}', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_USERINFO', ''),\n('d74fdc1ed22a0d469bdcc5f003ca6575', 'oxregistrationdescription', 1, 1, 0, 1, 1, '', 'Registration Description', '<p>Mit einem persönlichen Kundenkonto haben Sie folgende Vorteile:<br />\\r\\n - Verwaltung der Lieferadressen<br />\\r\\n - Prüfung des Bestellstatus<br />\\r\\n - Bestellhistorie<br />\\r\\n - persönlicher Merkzettel<br />\\r\\n - persönliche Wunschliste<br />\\r\\n - Newsletter-Verwaltung<br />\\r\\n - Sonder- und Rabattaktionen</p>', 'Registration Description', '<p>A customer with an account has advantages like:<br />\\r\\n - Administration of shipping addresses<br />\\r\\n - Check order status<br />\\r\\n - Order History<br />\\r\\n - Personal Wish List<br />\\r\\n - Personal Gift Registry<br />\\r\\n - Newsletter subscription<br />\\r\\n - Special offers and discounts</p>', 0, '', '', 0, '', '', '30e44ab83fdee7564.23264141', '', ''),\n('d0f7ac8b29909908dc20d854224944fe', 'oxnopaymentmethod', 1, 1, 0, 1, 1, '', 'No payment method text', '<p>Derzeit ist keine Versandart für dieses Land\\r\\ndefiniert.</p>\\r\\n<p>Wir werden versuchen, Liefermöglichkeiten zu\\r\\nfinden und Sie über die Versandkosten informieren.</p>', 'No payment method text', '<p>Currently we have no shipping method set up\\r\\nfor this country.</p>\\r\\n<p>We are aiming to find a possible delivery\\r\\nmethod and we will inform you as soon as possible via e-mail about the result,\\r\\nincluding further information about delivery costs.</p>', 0, '', '', 0, '', '', '30e44ab83fdee7564.23264141', '', ''),\n('4a63033aa27409f15484340011e74e55', 'oxcookiesexplanation', 1, 1, 0, 1, 1, '', 'Cookies Explanation', 'Sie haben sich entschieden, keine Cookies von unserem Online-Shop zu akzeptieren. Die Cookies wurden gelöscht. Sie können in den Einstellungen Ihres Browsers die Verwendung von Cookies deaktivieren und den Online-Shop mit einigen funktionellen Einschränkungen nutzen. Sie können auch zurück zum Shop gehen, ohne die Einstellungen zu ändern, und den vollen Funktionsumfang des Online-Shops genießen.<br />\\r\\n<br />Informationen zu Cookies auf Wikipedia: <a href=\"http://de.wikipedia.org/wiki/HTTP-Cookie\"><strong>http://de.wikipedia.org/wiki/HTTP-Cookie</strong></a>', 'Cookies Explanation', 'You have decided to not accept cookies from our online shop. The cookies have been removed. You can deactivate the usage of cookies in the settings of your browser and visit the online shop with some functional limitations. You can also return to the shop without changing the browser settings and enjoy the full functionality.<br />\\r\\n<br />Information about cookies at Wikipedia: <a href=\"http://en.wikipedia.org/wiki/HTTP_cookie\"><strong>http://en.wikipedia.org/wiki/HTTP_cookie</strong></a>', 0, '', '', 0, '', '', '30e44ab83fdee7564.23264141', '', ''),\n('220404cee0caf470e227c1c9f1ec4aL3', 'oxdownloadableproductsagreement', 1, 1, 0, 1, 1, '', 'Für digitale Inhalte', 'Ja, ich möchte sofort Zugang zu dem digitalen Inhalt und weiß, dass mein Widerrufsrecht mit dem Zugang erlischt.', 'For the supply of digital content', 'I want immediate access to the digital content and I acknowledge that thereby I lose my right to cancel once the service has begun.', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_USERINFO', ''),\n('220404cee0caf470e227c1c9f1ec4aL4', 'oxserviceproductsagreement', 1, 1, 0, 1, 1, '', 'Für Dienstleistungen', 'Ja, bitte beginnen Sie sofort mit der Dienstleistung. Mein Widerrufsrecht erlischt mit vollständiger Ausführung.', 'For service contracts', 'I agree to the starting of the service and I acknowledge that I lose my right to cancel once the service has been fully performed.', 1, '', '', 1, '', '', '30e44ab83fdee7564.23264141', 'CMSFOLDER_USERINFO', '');\n\nINSERT INTO `oxcountry` (`OXID`, `OXACTIVE`, `OXTITLE`, `OXISOALPHA2`, `OXISOALPHA3`, `OXUNNUM3`, `OXVATINPREFIX`, `OXORDER`, `OXSHORTDESC`, `OXLONGDESC`, `OXTITLE_1`, `OXTITLE_2`, `OXTITLE_3`, `OXSHORTDESC_1`, `OXSHORTDESC_2`, `OXSHORTDESC_3`, `OXLONGDESC_1`, `OXLONGDESC_2`, `OXLONGDESC_3`, `OXVATSTATUS`) VALUES\n('2db455824e4a19cc7.14731328', 0, 'Anderes Land', '', '', '', '', 10000, '', 'Select this if you can not find your country.', 'Other country', '', '', '', '', '', 'Select this if you can not find your country.', '', '', 0),\n('a7c40f631fc920687.20179984', 1, 'Deutschland', 'DE', 'DEU', '276', 'DE', 9999, 'EU1', '', 'Germany', '', '', 'EU1', '', '', '', '', '', 1),\n('a7c40f6320aeb2ec2.72885259', 1, 'Österreich', 'AT', 'AUT', '40', 'AT', 9999, 'EU1', '', 'Austria', '', '', 'EU1', '', '', '', '', '', 1),\n('a7c40f6321c6f6109.43859248', 1, 'Schweiz', 'CH', 'CHE', '756', 'CH', 9999, 'EU1', '', 'Switzerland', '', '', 'EU1', '', '', '', '', '', 0),\n('a7c40f6322d842ae3.83331920', 0, 'Liechtenstein', 'LI', 'LIE', '438', 'LI', 9999, 'EU1', '', 'Liechtenstein', '', '', 'EU1', '', '', '', '', '', 0),\n('a7c40f6323c4bfb36.59919433', 0, 'Italien', 'IT', 'ITA', '380', 'IT', 9999, 'EU1', '', 'Italy', '', '', 'EU1', '', '', '', '', '', 1),\n('a7c40f63264309e05.58576680', 0, 'Luxemburg', 'LU', 'LUX', '442', 'LU', 9999, 'EU1', '', 'Luxembourg', '', '', 'EU1', '', '', '', '', '', 1),\n('a7c40f63272a57296.32117580', 0, 'Frankreich', 'FR', 'FRA', '250', 'FR', 9999, 'EU1', '', 'France', '', '', 'EU1', '', '', '', '', '', 1),\n('a7c40f632848c5217.53322339', 0, 'Schweden', 'SE', 'SWE', '752', 'SE', 9999, 'EU2', '', 'Sweden', '', '', 'EU2', '', '', '', '', '', 1),\n('a7c40f63293c19d65.37472814', 0, 'Finnland', 'FI', 'FIN', '246', 'FI', 9999, 'EU2', '', 'Finland', '', '', 'EU2', '', '', '', '', '', 1),\n('a7c40f632a0804ab5.18804076', 1, 'Vereinigtes Königreich', 'GB', 'GBR', '826', 'GB', 9999, 'EU2', '', 'United Kingdom', '', '', 'EU2', '', '', '', '', '', 1),\n('a7c40f632be4237c2.48517912', 0, 'Irland', 'IE', 'IRL', '372', 'IE', 9999, 'EU2', '', 'Ireland', '', '', 'EU2', '', '', '', '', '', 1),\n('a7c40f632cdd63c52.64272623', 0, 'Niederlande', 'NL', 'NLD', '528', 'NL', 9999, 'EU2', '', 'Netherlands', '', '', 'EU2', '', '', '', '', '', 1),\n('a7c40f632e04633c9.47194042', 0, 'Belgien', 'BE', 'BEL', '56', 'BE', 9999, 'Rest Europäische Union', '', 'Belgium', '', '', 'Rest of EU', '', '', '', '', '', 1),\n('a7c40f632f65bd8e2.84963272', 0, 'Portugal', 'PT', 'PRT', '620', 'PT', 9999, 'Rest Europäische Union', '', 'Portugal', '', '', 'Rest of EU', '', '', '', '', '', 1),\n('a7c40f633038cd578.22975442', 0, 'Spanien', 'ES', 'ESP', '724', 'ES', 9999, 'Rest Europäische Union', '', 'Spain', '', '', 'Rest of EU', '', '', '', '', '', 1),\n('a7c40f633114e8fc6.25257477', 0, 'Griechenland', 'GR', 'GRC', '300', 'EL', 9999, 'Rest Europäische Union', '', 'Greece', '', '', 'Rest of EU', '', '', '', '', '', 1),\n('8f241f11095306451.36998225', 0, 'Afghanistan', 'AF', 'AFG', '4', 'AF', 9999, 'Rest Welt', '', 'Afghanistan', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110953265a5.25286134', 0, 'Albanien', 'AL', 'ALB', '8', 'AL', 9999, 'Rest Europa', '', 'Albania', '', '', 'Rest Europe', '', '', '', '', '', 0),\n('8f241f1109533b943.50287900', 0, 'Algerien', 'DZ', 'DZA', '12', 'DZ', 9999, 'Rest Welt', '', 'Algeria', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109534f8c7.80349931', 0, 'Amerikanisch Samoa', 'AS', 'ASM', '16', 'AS', 9999, 'Rest Welt', '', 'American Samoa', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095363464.89657222', 0, 'Andorra', 'AD', 'AND', '20', 'AD', 9999, 'Europa', '', 'Andorra', '', '', 'Europe', '', '', '', '', '', 0),\n('8f241f11095377d33.28678901', 0, 'Angola', 'AO', 'AGO', '24', 'AO', 9999, 'Rest Welt', '', 'Angola', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095392e41.74397491', 0, 'Anguilla', 'AI', 'AIA', '660', 'AI', 9999, 'Rest Welt', '', 'Anguilla', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110953a8d10.29474848', 0, 'Antarktis', 'AQ', 'ATA', '10', 'AQ', 9999, 'Rest Welt', '', 'Antarctica', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110953be8f2.56248134', 0, 'Antigua und Barbuda', 'AG', 'ATG', '28', 'AG', 9999, 'Rest Welt', '', 'Antigua and Barbuda', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110953d2fb0.54260547', 0, 'Argentinien', 'AR', 'ARG', '32', 'AR', 9999, 'Rest Welt', '', 'Argentina', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110953e7993.88180360', 0, 'Armenien', 'AM', 'ARM', '51', 'AM', 9999, 'Rest Europa', '', 'Armenia', '', '', 'Rest Europe', '', '', '', '', '', 0),\n('8f241f110953facc6.31621036', 0, 'Aruba', 'AW', 'ABW', '533', 'AW', 9999, 'Rest Welt', '', 'Aruba', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095410f38.37165361', 0, 'Australien', 'AU', 'AUS', '36', 'AU', 9999, 'Rest Welt', '', 'Australia', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109543cf47.17877015', 0, 'Aserbaidschan', 'AZ', 'AZE', '31', 'AZ', 9999, 'Rest Welt', '', 'Azerbaijan', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095451379.72078871', 0, 'Bahamas', 'BS', 'BHS', '44', 'BS', 9999, 'Rest Welt', '', 'Bahamas', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110954662e3.27051654', 0, 'Bahrain', 'BH', 'BHR', '48', 'BH', 9999, 'Welt', '', 'Bahrain', '', '', 'World', '', '', '', '', '', 0),\n('8f241f1109547ae49.60154431', 0, 'Bangladesch', 'BD', 'BGD', '50', 'BD', 9999, 'Rest Welt', '', 'Bangladesh', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095497083.21181725', 0, 'Barbados', 'BB', 'BRB', '52', 'BB', 9999, 'Rest Welt', '', 'Barbados', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110954ac5b9.63105203', 0, 'Weißrussland', 'BY', 'BLR', '112', 'BY', 9999, 'Rest Europa', '', 'Belarus', '', '', 'Rest Europe', '', '', '', '', '', 0),\n('8f241f110954d3621.45362515', 0, 'Belize', 'BZ', 'BLZ', '84', 'BZ', 9999, 'Rest Welt', '', 'Belize', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110954ea065.41455848', 0, 'Benin', 'BJ', 'BEN', '204', 'BJ', 9999, 'Rest Welt', '', 'Benin', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110954fee13.50011948', 0, 'Bermuda', 'BM', 'BMU', '60', 'BM', 9999, 'Rest Welt', '', 'Bermuda', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095513ca0.75349731', 0, 'Bhutan', 'BT', 'BTN', '64', 'BT', 9999, 'Rest Welt', '', 'Bhutan', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109552aee2.91004965', 0, 'Bolivien', 'BO', 'BOL', '68', 'BO', 9999, 'Rest Welt', '', 'Bolivia', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109553f902.06960438', 0, 'Bosnien und Herzegowina', 'BA', 'BIH', '70', 'BA', 9999, 'Rest Europa', '', 'Bosnia and Herzegovina', '', '', 'Rest Europe', '', '', '', '', '', 0),\n('8f241f11095554834.54199483', 0, 'Botsuana', 'BW', 'BWA', '72', 'BW', 9999, 'Rest Welt', '', 'Botswana', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109556dd57.84292282', 0, 'Bouvetinsel', 'BV', 'BVT', '74', 'BV', 9999, 'Rest Welt', '', 'Bouvet Island', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095592407.89986143', 0, 'Brasilien', 'BR', 'BRA', '76', 'BR', 9999, 'Rest Welt', '', 'Brazil', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110955a7644.68859180', 0, 'Britisches Territorium im Indischen Ozean', 'IO', 'IOT', '86', 'IO', 9999, 'Rest Welt', '', 'British Indian Ocean Territory', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110955bde61.63256042', 0, 'Brunei Darussalam', 'BN', 'BRN', '96', 'BN', 9999, 'Rest Welt', '', 'Brunei Darussalam', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110955d3260.55487539', 0, 'Bulgarien', 'BG', 'BGR', '100', 'BG', 9999, 'Rest Europa', '', 'Bulgaria', '', '', 'Rest Europe', '', '', '', '', '', 1),\n('8f241f110955ea7c8.36762654', 0, 'Burkina Faso', 'BF', 'BFA', '854', 'BF', 9999, 'Rest Welt', '', 'Burkina Faso', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110956004d5.11534182', 0, 'Burundi', 'BI', 'BDI', '108', 'BI', 9999, 'Rest Welt', '', 'Burundi', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110956175f9.81682035', 0, 'Kambodscha', 'KH', 'KHM', '116', 'KH', 9999, 'Rest Welt', '', 'Cambodia', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095632828.20263574', 0, 'Kamerun', 'CM', 'CMR', '120', 'CM', 9999, 'Rest Welt', '', 'Cameroon', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095649d18.02676059', 0, 'Kanada', 'CA', 'CAN', '124', 'CA', 9999, 'Welt', '', 'Canada', '', '', 'World', '', '', '', '', '', 0),\n('8f241f1109565e671.48876354', 0, 'Kap Verde', 'CV', 'CPV', '132', 'CV', 9999, 'Rest Welt', '', 'Cape Verde', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095673248.50405852', 0, 'Kaimaninseln', 'KY', 'CYM', '136', 'KY', 9999, 'Rest Welt', '', 'Cayman Islands', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109568a509.03566030', 0, 'Zentralafrikanische Republik', 'CF', 'CAF', '140', 'CF', 9999, 'Rest Welt', '', 'Central African Republic', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109569d4c2.42800039', 0, 'Tschad', 'TD', 'TCD', '148', 'TD', 9999, 'Rest Welt', '', 'Chad', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110956b3ea7.11168270', 0, 'Chile', 'CL', 'CHL', '152', 'CL', 9999, 'Rest Welt', '', 'Chile', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110956c8860.37981845', 0, 'China', 'CN', 'CHN', '156', 'CN', 9999, 'Rest Welt', '', 'China', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110956df6b2.52283428', 0, 'Weihnachtsinsel', 'CX', 'CXR', '162', 'CX', 9999, 'Rest Welt', '', 'Christmas Island', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110956f54b4.26327849', 0, 'Kokosinseln (Keelinginseln)', 'CC', 'CCK', '166', 'CC', 9999, 'Rest Welt', '', 'Cocos (Keeling) Islands', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109570a1e3.69772638', 0, 'Kolumbien', 'CO', 'COL', '170', 'CO', 9999, 'Rest Welt', '', 'Colombia', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109571f018.46251535', 0, 'Komoren', 'KM', 'COM', '174', 'KM', 9999, 'Rest Welt', '', 'Comoros', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095732184.72771986', 0, 'Kongo', 'CG', 'COG', '178', 'CG', 9999, 'Rest Welt', '', 'Congo', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095746a92.94878441', 0, 'Cookinseln', 'CK', 'COK', '184', 'CK', 9999, 'Rest Welt', '', 'Cook Islands', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109575d708.20084150', 0, 'Costa Rica', 'CR', 'CRI', '188', 'CR', 9999, 'Rest Welt', '', 'Costa Rica', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095771f76.87904122', 0, 'Cote dŽIvoire', 'CI', 'CIV', '384', 'CI', 9999, 'Rest Welt', '', 'Cote d''Ivoire', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095789a04.65154246', 0, 'Kroatien', 'HR', 'HRV', '191', 'HR', 9999, 'Rest Europäische Union', '', 'Croatia', '', '', 'Rest of EU', '', '', '', '', '', 1),\n('8f241f1109579ef49.91803242', 0, 'Kuba', 'CU', 'CUB', '192', 'CU', 9999, 'Rest Welt', '', 'Cuba', '', '', 'World', '', '', '', '', '', 0),\n('8f241f110957b6896.52725150', 0, 'Zypern', 'CY', 'CYP', '196', 'CY', 9999, 'Rest Europa', '', 'Cyprus', '', '', 'Rest Europe', '', '', '', '', '', 1),\n('8f241f110957cb457.97820918', 0, 'Tschechische Republik', 'CZ', 'CZE', '203', 'CZ', 9999, 'Europa', '', 'Czech Republic', '', '', 'Europe', '', '', '', '', '', 1),\n('8f241f110957e6ef8.56458418', 0, 'Dänemark', 'DK', 'DNK', '208', 'DK', 9999, 'Europa', '', 'Denmark', '', '', 'Europe', '', '', '', '', '', 1),\n('8f241f110957fd356.02918645', 0, 'Dschibuti', 'DJ', 'DJI', '262', 'DJ', 9999, 'Rest Welt', '', 'Djibouti', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095811ea5.84717844', 0, 'Dominica', 'DM', 'DMA', '212', 'DM', 9999, 'Rest Welt', '', 'Dominica', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095825bf2.61063355', 0, 'Dominikanische Republik', 'DO', 'DOM', '214', 'DO', 9999, 'Rest Welt', '', 'Dominican Republic', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095839323.86755169', 0, 'Timor-Leste', 'TL', 'TLS', '626', 'TL', 9999, 'Rest Welt', '', 'Timor-Leste', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109584d512.06663789', 0, 'Ecuador', 'EC', 'ECU', '218', 'EC', 9999, 'Rest Welt', '', 'Ecuador', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095861fb7.55278256', 0, 'Ägypten', 'EG', 'EGY', '818', 'EG', 9999, 'Welt', '', 'Egypt', '', '', 'World', '', '', '', '', '', 0),\n('8f241f110958736a9.06061237', 0, 'El Salvador', 'SV', 'SLV', '222', 'SV', 9999, 'Rest Welt', '', 'El Salvador', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109588d077.74284490', 0, 'Äquatorialguinea', 'GQ', 'GNQ', '226', 'GQ', 9999, 'Rest Welt', '', 'Equatorial Guinea', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110958a2216.38324531', 0, 'Eritrea', 'ER', 'ERI', '232', 'ER', 9999, 'Rest Welt', '', 'Eritrea', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110958b69e4.93886171', 0, 'Estland', 'EE', 'EST', '233', 'EE', 9999, 'Rest Europäische Union', '', 'Estonia', '', '', 'Rest of EU', '', '', '', '', '', 1),\n('8f241f110958caf67.08982313', 0, 'Äthiopien', 'ET', 'ETH', '210', 'ET', 9999, 'Rest Welt', '', 'Ethiopia', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110958e2cc3.90770249', 0, 'Falklandinseln (Malwinen)', 'FK', 'FLK', '238', 'FK', 9999, 'Rest Welt', '', 'Falkland Islands (Malvinas)', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110958f7ba4.96908065', 0, 'Färöer', 'FO', 'FRO', '234', 'FO', 9999, 'Rest Welt', '', 'Faroe Islands', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109590d226.07938729', 0, 'Fidschi', 'FJ', 'FJI', '242', 'FJ', 9999, 'Rest Welt', '', 'Fiji', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109594fcb1.79441780', 0, 'Französisch Guiana', 'GF', 'GUF', '254', 'GF', 9999, 'Rest Welt', '', 'French Guiana', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110959636f5.71476354', 0, 'Französisch-Polynesien', 'PF', 'PYF', '258', 'PF', 9999, 'Rest Welt', '', 'French Polynesia', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110959784a3.34264829', 0, 'Französische Südgebiete', 'TF', 'ATF', '260', 'TF', 9999, 'Rest Welt', '', 'French Southern Territories', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095994cb6.59353392', 0, 'Gabun', 'GA', 'GAB', '266', 'GA', 9999, 'Rest Welt', '', 'Gabon', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110959ace77.17379319', 0, 'Gambia', 'GM', 'GMB', '270', 'GM', 9999, 'Rest Welt', '', 'Gambia', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110959c2341.01830199', 0, 'Georgien', 'GE', 'GEO', '268', 'GE', 9999, 'Rest Europa', '', 'Georgia', '', '', 'Rest Europe', '', '', '', '', '', 0),\n('8f241f110959e96b3.05752152', 0, 'Ghana', 'GH', 'GHA', '288', 'GH', 9999, 'Rest Welt', '', 'Ghana', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110959fdde0.68919405', 0, 'Gibraltar', 'GI', 'GIB', '292', 'GI', 9999, 'Rest Welt', '', 'Gibraltar', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095a29f47.04102343', 0, 'Grönland', 'GL', 'GRL', '304', 'GL', 9999, 'Europa', '', 'Greenland', '', '', 'Europe', '', '', '', '', '', 0),\n('8f241f11095a3f195.88886789', 0, 'Grenada', 'GD', 'GRD', '308', 'GD', 9999, 'Rest Welt', '', 'Grenada', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095a52578.45413493', 0, 'Guadeloupe', 'GP', 'GLP', '312', 'GP', 9999, 'Rest Welt', '', 'Guadeloupe', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095a717b3.68126681', 0, 'Guam', 'GU', 'GUM', '316', 'GU', 9999, 'Rest Welt', '', 'Guam', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095a870a5.42235635', 0, 'Guatemala', 'GT', 'GTM', '320', 'GT', 9999, 'Rest Welt', '', 'Guatemala', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095a9bf82.19989557', 0, 'Guinea', 'GN', 'GIN', '324', 'GN', 9999, 'Rest Welt', '', 'Guinea', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095ab2b56.83049280', 0, 'Guinea-Bissau', 'GW', 'GNB', '624', 'GW', 9999, 'Rest Welt', '', 'Guinea-Bissau', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095ac9d30.56640429', 0, 'Guyana', 'GY', 'GUY', '328', 'GY', 9999, 'Rest Welt', '', 'Guyana', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095aebb06.34405179', 0, 'Haiti', 'HT', 'HTI', '332', 'HT', 9999, 'Rest Welt', '', 'Haiti', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095aff2c3.98054755', 0, 'Heard Insel und McDonald Inseln', 'HM', 'HMD', '334', 'HM', 9999, 'Rest Welt', '', 'Heard Island And Mcdonald Islands', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095b13f57.56022305', 0, 'Honduras', 'HN', 'HND', '340', 'HN', 9999, 'Rest Welt', '', 'Honduras', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095b29021.49657118', 0, 'Hong Kong', 'HK', 'HKG', '344', 'HK', 9999, 'Rest Welt', '', 'Hong Kong', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095b3e016.98213173', 0, 'Ungarn', 'HU', 'HUN', '348', 'HU', 9999, 'Rest Europa', '', 'Hungary', '', '', 'Rest Europe', '', '', '', '', '', 1),\n('8f241f11095b55846.26192602', 0, 'Island', 'IS', 'ISL', '352', 'IS', 9999, 'Rest Europa', '', 'Iceland', '', '', 'Rest Europe', '', '', '', '', '', 0),\n('8f241f11095b6bb86.01364904', 0, 'Indien', 'IN', 'IND', '356', 'IN', 9999, 'Rest Welt', '', 'India', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095b80526.59927631', 0, 'Indonesien', 'ID', 'IDN', '360', 'ID', 9999, 'Rest Welt', '', 'Indonesia', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095b94476.05195832', 0, 'Iran', 'IR', 'IRN', '364', 'IR', 9999, 'Welt', '', 'Iran', '', '', 'World', '', '', '', '', '', 0),\n('8f241f11095bad5b2.42645724', 0, 'Irak', 'IQ', 'IRQ', '368', 'IQ', 9999, 'Welt', '', 'Iraq', '', '', 'World', '', '', '', '', '', 0),\n('8f241f11095bd65e1.59459683', 0, 'Israel', 'IL', 'ISR', '376', 'IL', 9999, 'Rest Europa', '', 'Israel', '', '', 'Rest Europe', '', '', '', '', '', 0),\n('8f241f11095bfe834.63390185', 0, 'Jamaika', 'JM', 'JAM', '388', 'JM', 9999, 'Rest Welt', '', 'Jamaica', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095c11d43.73419747', 0, 'Japan', 'JP', 'JPN', '392', 'JP', 9999, 'Rest Welt', '', 'Japan', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095c2b304.75906962', 0, 'Jordanien', 'JO', 'JOR', '400', 'JO', 9999, 'Rest Welt', '', 'Jordan', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095c3e2d1.36714463', 0, 'Kasachstan', 'KZ', 'KAZ', '398', 'KZ', 9999, 'Rest Europa', '', 'Kazakhstan', '', '', 'Rest Europe', '', '', '', '', '', 0),\n('8f241f11095c5b8e8.66333679', 0, 'Kenia', 'KE', 'KEN', '404', 'KE', 9999, 'Rest Welt', '', 'Kenya', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095c6e184.21450618', 0, 'Kiribati', 'KI', 'KIR', '296', 'KI', 9999, 'Rest Welt', '', 'Kiribati', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095c87284.37982544', 0, 'Nordkorea', 'KP', 'PRK', '408', 'KP', 9999, 'Rest Welt', '', 'North Korea', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095c9de64.01275726', 0, 'Südkorea', 'KR', 'KOR', '410', 'KR', 9999, 'Rest Welt', '', 'South Korea', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095cb1546.46652174', 0, 'Kuwait', 'KW', 'KWT', '414', 'KW', 9999, 'Welt', '', 'Kuwait', '', '', 'World', '', '', '', '', '', 0),\n('8f241f11095cc7ef5.28043767', 0, 'Kirgisistan', 'KG', 'KGZ', '417', 'KG', 9999, 'Rest Welt', '', 'Kyrgyzstan', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095cdccd5.96388808', 0, 'Laos', 'LA', 'LAO', '418', 'LA', 9999, 'Rest Welt', '', 'Laos', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095cf2ea6.73925511', 0, 'Lettland', 'LV', 'LVA', '428', 'LV', 9999, 'Rest Europäische Union', '', 'Latvia', '', '', 'Rest of EU', '', '', '', '', '', 1),\n('8f241f11095d07d87.58986129', 0, 'Libanon', 'LB', 'LBN', '422', 'LB', 9999, 'Welt', '', 'Lebanon', '', '', 'World', '', '', '', '', '', 0),\n('8f241f11095d1c9b2.21548132', 0, 'Lesotho', 'LS', 'LSO', '426', 'LS', 9999, 'Rest Welt', '', 'Lesotho', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095d2fd28.91858908', 0, 'Liberia', 'LR', 'LBR', '430', 'LR', 9999, 'Welt', '', 'Liberia', '', '', 'World', '', '', '', '', '', 0),\n('8f241f11095d46188.64679605', 0, 'Libyen', 'LY', 'LBY', '434', 'LY', 9999, 'Rest Welt', '', 'Libya', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095d6ffa8.86593236', 0, 'Litauen', 'LT', 'LTU', '440', 'LT', 9999, 'Rest Europäische Union', '', 'Lithuania', '', '', 'Rest of EU', '', '', '', '', '', 1),\n('8f241f11095d9c1b2.13577033', 0, 'Macao', 'MO', 'MAC', '446', 'MO', 9999, 'Rest Welt', '', 'Macao', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095db2291.58912887', 0, 'Mazedonien', 'MK', 'MKD', '807', 'MK', 9999, 'Rest Europa', '', 'Macedonia', '', '', 'Rest Europe', '', '', '', '', '', 0),\n('8f241f11095dccf17.06266806', 0, 'Madagaskar', 'MG', 'MDG', '450', 'MG', 9999, 'Rest Welt', '', 'Madagascar', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095de2119.60795833', 0, 'Malawi', 'MW', 'MWI', '454', 'MW', 9999, 'Rest Welt', '', 'Malawi', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095df78a8.44559506', 0, 'Malaysia', 'MY', 'MYS', '458', 'MY', 9999, 'Rest Welt', '', 'Malaysia', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095e0c6c9.43746477', 0, 'Malediven', 'MV', 'MDV', '462', 'MV', 9999, 'Rest Welt', '', 'Maldives', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095e24006.17141715', 0, 'Mali', 'ML', 'MLI', '466', 'ML', 9999, 'Rest Welt', '', 'Mali', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095e36eb3.69050509', 0, 'Malta', 'MT', 'MLT', '470', 'MT', 9999, 'Rest Welt', '', 'Malta', '', '', 'Rest World', '', '', '', '', '', 1),\n('8f241f11095e4e338.26817244', 0, 'Marshallinseln', 'MH', 'MHL', '584', 'MH', 9999, 'Rest Welt', '', 'Marshall Islands', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095e631e1.29476484', 0, 'Martinique', 'MQ', 'MTQ', '474', 'MQ', 9999, 'Rest Welt', '', 'Martinique', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095e7bff9.09518271', 0, 'Mauretanien', 'MR', 'MRT', '478', 'MR', 9999, 'Rest Welt', '', 'Mauritania', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095e90a81.01156393', 0, 'Mauritius', 'MU', 'MUS', '480', 'MU', 9999, 'Rest Welt', '', 'Mauritius', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095ea6249.81474246', 0, 'Mayotte', 'YT', 'MYT', '175', 'YT', 9999, 'Rest Welt', '', 'Mayotte', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095ebf3a6.86388577', 0, 'Mexiko', 'MX', 'MEX', '484', 'MX', 9999, 'Rest Welt', '', 'Mexico', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095ed4902.49276197', 0, 'Mikronesien, Föderierte Staaten von', 'FM', 'FSM', '583', 'FM', 9999, 'Rest Welt', '', 'Micronesia, Federated States Of', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095ee9923.85175653', 0, 'Moldawien', 'MD', 'MDA', '498', 'MD', 9999, 'Rest Europa', '', 'Moldova', '', '', 'Rest Europe', '', '', '', '', '', 0),\n('8f241f11095f00d65.30318330', 0, 'Monaco', 'MC', 'MCO', '492', 'MC', 9999, 'Europa', '', 'Monaco', '', '', 'Europe', '', '', '', '', '', 0),\n('8f241f11095f160c9.41059441', 0, 'Mongolei', 'MN', 'MNG', '496', 'MN', 9999, 'Rest Welt', '', 'Mongolia', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11095f314f5.05830324', 0, 'Montserrat', 'MS', 'MSR', '500', 'MS', 9999, 'Rest Welt', '', 'Montserrat', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096006828.49285591', 0, 'Marokko', 'MA', 'MAR', '504', 'MA', 9999, 'Welt', '', 'Morocco', '', '', 'World', '', '', '', '', '', 0),\n('8f241f1109601b419.55269691', 0, 'Mosambik', 'MZ', 'MOZ', '508', 'MZ', 9999, 'Rest Welt', '', 'Mozambique', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096030af5.65449043', 0, 'Myanmar', 'MM', 'MMR', '104', 'MM', 9999, 'Rest Welt', '', 'Myanmar', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096046575.31382060', 0, 'Namibia', 'NA', 'NAM', '516', 'NA', 9999, 'Rest Welt', '', 'Namibia', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109605b1f4.20574895', 0, 'Nauru', 'NR', 'NRU', '520', 'NR', 9999, 'Rest Welt', '', 'Nauru', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109607a9e7.03486450', 0, 'Nepal', 'NP', 'NPL', '524', 'NP', 9999, 'Rest Welt', '', 'Nepal', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110960aeb64.09757010', 0, 'Niederländische Antillen', 'AN', 'ANT', '530', 'AN', 9999, 'Rest Welt', '', 'Netherlands Antilles', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110960c3e97.21901471', 0, 'Neukaledonien', 'NC', 'NCL', '540', 'NC', 9999, 'Rest Welt', '', 'New Caledonia', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110960d8e58.96466103', 0, 'Neuseeland', 'NZ', 'NZL', '554', 'NZ', 9999, 'Rest Welt', '', 'New Zealand', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110960ec345.71805056', 0, 'Nicaragua', 'NI', 'NIC', '558', 'NI', 9999, 'Rest Welt', '', 'Nicaragua', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096101a79.70513227', 0, 'Niger', 'NE', 'NER', '562', 'NE', 9999, 'Rest Welt', '', 'Niger', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096116744.92008092', 0, 'Nigeria', 'NG', 'NGA', '566', 'NG', 9999, 'Rest Welt', '', 'Nigeria', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109612dc68.63806992', 0, 'Niue', 'NU', 'NIU', '570', 'NU', 9999, 'Rest Welt', '', 'Niue', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110961442c2.82573898', 0, 'Norfolkinsel', 'NF', 'NFK', '574', 'NF', 9999, 'Rest Welt', '', 'Norfolk Island', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109533b944.50289999', 0, 'Nordirland', 'XI', 'NIR', '826', 'XI', 9999, 'EU2', '', 'Northern Ireland', '', '', 'EU2', '', '', '', '', '', 1),\n('8f241f11096162678.71164081', 0, 'Nördliche Marianen', 'MP', 'MNP', '580', 'MP', 9999, 'Rest Welt', '', 'Northern Mariana Islands', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096176795.61257067', 0, 'Norwegen', 'NO', 'NOR', '578', 'NO', 9999, 'Rest Europa', '', 'Norway', '', '', 'Rest Europe', '', '', '', '', '', 0),\n('8f241f1109618d825.87661926', 0, 'Oman', 'OM', 'OMN', '512', 'OM', 9999, 'Rest Welt', '', 'Oman', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110961a2401.59039740', 0, 'Pakistan', 'PK', 'PAK', '586', 'PK', 9999, 'Rest Welt', '', 'Pakistan', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110961b7729.14290490', 0, 'Palau', 'PW', 'PLW', '585', 'PW', 9999, 'Rest Welt', '', 'Palau', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110961cc384.18166560', 0, 'Panama', 'PA', 'PAN', '591', 'PA', 9999, 'Rest Welt', '', 'Panama', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110961e3538.78435307', 0, 'Papua-Neuguinea', 'PG', 'PNG', '598', 'PG', 9999, 'Rest Welt', '', 'Papua New Guinea', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110961f9d61.52794273', 0, 'Paraguay', 'PY', 'PRY', '600', 'PY', 9999, 'Rest Welt', '', 'Paraguay', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109620b245.16261506', 0, 'Peru', 'PE', 'PER', '604', 'PE', 9999, 'Rest Welt', '', 'Peru', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109621faf8.40135556', 0, 'Philippinen', 'PH', 'PHL', '608', 'PH', 9999, 'Rest Welt', '', 'Philippines', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096234d62.44125992', 0, 'Pitcairn', 'PN', 'PCN', '612', 'PN', 9999, 'Rest Welt', '', 'Pitcairn', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109624d3f8.50953605', 0, 'Polen', 'PL', 'POL', '616', 'PL', 9999, 'Europa', '', 'Poland', '', '', 'Europe', '', '', '', '', '', 1),\n('8f241f11096279a22.50582479', 0, 'Puerto Rico', 'PR', 'PRI', '630', 'PR', 9999, 'Rest Welt', '', 'Puerto Rico', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109628f903.51478291', 0, 'Katar', 'QA', 'QAT', '634', 'QA', 9999, 'Rest Welt', '', 'Qatar', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110962a3ec5.65857240', 0, 'Réunion', 'RE', 'REU', '638', 'RE', 9999, 'Rest Welt', '', 'Réunion', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110962c3007.60363573', 0, 'Rumänien', 'RO', 'ROU', '642', 'RO', 9999, 'Rest Europa', '', 'Romania', '', '', 'Rest Europe', '', '', '', '', '', 1),\n('8f241f110962e40e6.75062153', 0, 'Russische Föderation', 'RU', 'RUS', '643', 'RU', 9999, 'Rest Europa', '', 'Russian Federation', '', '', 'Rest Europe', '', '', '', '', '', 0),\n('8f241f110962f8615.93666560', 0, 'Ruanda', 'RW', 'RWA', '646', 'RW', 9999, 'Rest Welt', '', 'Rwanda', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110963177a7.49289900', 0, 'St. Kitts und Nevis', 'KN', 'KNA', '659', 'KN', 9999, 'Rest Welt', '', 'Saint Kitts and Nevis', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109632fab4.68646740', 0, 'St. Lucia', 'LC', 'LCA', '662', 'LC', 9999, 'Rest Welt', '', 'Saint Lucia', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110963443c3.29598809', 0, 'St. Vincent und die Grenadinen', 'VC', 'VCT', '670', 'VC', 9999, 'Rest Welt', '', 'Saint Vincent and The Grenadines', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096359986.06476221', 0, 'Samoa', 'WS', 'WSM', '882', 'WS', 9999, 'Rest Welt', '', 'Samoa', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096375757.44126946', 0, 'San Marino', 'SM', 'SMR', '674', 'SM', 9999, 'Europa', '', 'San Marino', '', '', 'Europe', '', '', '', '', '', 0),\n('8f241f1109639b8c4.57484984', 0, 'Sao Tome und Principe', 'ST', 'STP', '678', 'ST', 9999, 'Rest Welt', '', 'Sao Tome and Principe', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110963b9b20.41500709', 0, 'Saudi-Arabien', 'SA', 'SAU', '682', 'SA', 9999, 'Welt', '', 'Saudi Arabia', '', '', 'World', '', '', '', '', '', 0),\n('8f241f110963d9962.36307144', 0, 'Senegal', 'SN', 'SEN', '686', 'SN', 9999, 'Rest Welt', '', 'Senegal', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110963f98d8.68428379', 0, 'Serbien', 'RS', 'SRB', '688', 'RS', 9999, 'Rest Europa', '', 'Serbia', '', '', 'Rest Europe', '', '', '', '', '', 0),\n('8f241f11096418496.77253079', 0, 'Seychellen', 'SC', 'SYC', '690', 'SC', 9999, 'Rest Welt', '', 'Seychelles', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096436968.69551351', 0, 'Sierra Leone', 'SL', 'SLE', '694', 'SL', 9999, 'Rest Welt', '', 'Sierra Leone', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096456a48.79608805', 0, 'Singapur', 'SG', 'SGP', '702', 'SG', 9999, 'Rest Welt', '', 'Singapore', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109647a265.29938154', 0, 'Slowakei', 'SK', 'SVK', '703', 'SK', 9999, 'Europa', '', 'Slovakia', '', '', 'Europe', '', '', '', '', '', 1),\n('8f241f11096497149.85116254', 0, 'Slowenien', 'SI', 'SVN', '705', 'SI', 9999, 'Rest Europa', '', 'Slovenia', '', '', 'Rest Europe', '', '', '', '', '', 1),\n('8f241f110964b7bf9.49501835', 0, 'Salomonen', 'SB', 'SLB', '90', 'SB', 9999, 'Rest Welt', '', 'Solomon Islands', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110964d5f29.11398308', 0, 'Somalia', 'SO', 'SOM', '706', 'SO', 9999, 'Rest Welt', '', 'Somalia', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110964f2623.74976876', 0, 'Südafrika', 'ZA', 'ZAF', '710', 'ZA', 9999, 'Rest Welt', '', 'South Africa', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096531330.03198083', 0, 'Sri Lanka', 'LK', 'LKA', '144', 'LK', 9999, 'Rest Welt', '', 'Sri Lanka', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109654dca4.99466434', 0, 'Saint Helena', 'SH', 'SHN', '654', 'SH', 9999, 'Rest Welt', '', 'Saint Helena', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109656cde9.10816078', 0, 'Saint Pierre und Miquelon', 'PM', 'SPM', '666', 'PM', 9999, 'Rest Welt', '', 'Saint Pierre and Miquelon', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109658cbe5.08293991', 0, 'Sudan', 'SD', 'SDN', '736', 'SD', 9999, 'Rest Welt', '', 'Sudan', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110965c7347.75108681', 0, 'Suriname', 'SR', 'SUR', '740', 'SR', 9999, 'Rest Welt', '', 'Suriname', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110965eb7b7.26149742', 0, 'Svalbard und Jan Mayen', 'SJ', 'SJM', '744', 'SJ', 9999, 'Rest Welt', '', 'Svalbard and Jan Mayen', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109660c113.62780718', 0, 'Swasiland', 'SZ', 'SWZ', '748', 'SZ', 9999, 'Rest Welt', '', 'Swaziland', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109666b7f3.81435898', 0, 'Syrien', 'SY', 'SYR', '760', 'SY', 9999, 'Rest Welt', '', 'Syria', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096687ec7.58824735', 0, 'Republik China (Taiwan)', 'TW', 'TWN', '158', 'TW', 9999, 'Rest Welt', '', 'Taiwan, Province of China', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110966a54d1.43798997', 0, 'Tadschikistan', 'TJ', 'TJK', '762', 'TJ', 9999, 'Rest Welt', '', 'Tajikistan', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110966c3a75.68297960', 0, 'Tansania', 'TZ', 'TZA', '834', 'TZ', 9999, 'Rest Welt', '', 'Tanzania', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096707e08.60512709', 0, 'Thailand', 'TH', 'THA', '764', 'TH', 9999, 'Rest Welt', '', 'Thailand', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110967241e1.34925220', 0, 'Togo', 'TG', 'TGO', '768', 'TG', 9999, 'Rest Welt', '', 'Togo', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096742565.72138875', 0, 'Tokelau', 'TK', 'TKL', '772', 'TK', 9999, 'Rest Welt', '', 'Tokelau', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096762b31.03069244', 0, 'Tonga', 'TO', 'TON', '776', 'TO', 9999, 'Rest Welt', '', 'Tonga', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109677ed23.84886671', 0, 'Trinidad und Tobago', 'TT', 'TTO', '780', 'TT', 9999, 'Rest Welt', '', 'Trinidad and Tobago', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109679d988.46004322', 0, 'Tunesien', 'TN', 'TUN', '788', 'TN', 9999, 'Welt', '', 'Tunisia', '', '', 'World', '', '', '', '', '', 0),\n('8f241f110967bba40.88233204', 0, 'Türkei', 'TR', 'TUR', '792', 'TR', 9999, 'Rest Europa', '', 'Turkey', '', '', 'Rest Europe', '', '', '', '', '', 0),\n('8f241f110967d8f65.52699796', 0, 'Turkmenistan', 'TM', 'TKM', '795', 'TM', 9999, 'Rest Welt', '', 'Turkmenistan', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110967f73f8.13141492', 0, 'Turks- und Caicosinseln', 'TC', 'TCA', '796', 'TC', 9999, 'Rest Welt', '', 'Turks and Caicos Islands', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109680ec30.97426963', 0, 'Tuvalu', 'TV', 'TUV', '798', 'TV', 9999, 'Rest Welt', '', 'Tuvalu', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096823019.47846368', 0, 'Uganda', 'UG', 'UGA', '800', 'UG', 9999, 'Rest Welt', '', 'Uganda', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110968391d2.37199812', 0, 'Ukraine', 'UA', 'UKR', '804', 'UA', 9999, 'Rest Europa', '', 'Ukraine', '', '', 'Rest Europe', '', '', '', '', '', 0),\n('8f241f1109684bf15.63071279', 0, 'Vereinigte Arabische Emirate', 'AE', 'ARE', '784', 'AE', 9999, 'Rest Welt', '', 'United Arab Emirates', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096877ac0.98748826', 1, 'Vereinigte Staaten von Amerika', 'US', 'USA', '840', 'US', 9999, 'Welt', '', 'United States', '', '', 'World', '', '', '', '', '', 0),\n('8f241f11096894977.41239553', 0, 'United States Minor Outlying Islands', 'UM', 'UMI', '581', 'UM', 9999, 'Rest Welt', '', 'United States Minor Outlying Islands', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110968a7cc9.56710143', 0, 'Uruguay', 'UY', 'URY', '858', 'UY', 9999, 'Rest Welt', '', 'Uruguay', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110968bec45.44161857', 0, 'Usbekistan', 'UZ', 'UZB', '860', 'UZ', 9999, 'Rest Welt', '', 'Uzbekistan', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110968d3f03.13630334', 0, 'Vanuatu', 'VU', 'VUT', '548', 'VU', 9999, 'Rest Welt', '', 'Vanuatu', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110968ebc30.63792746', 0, 'Heiliger Stuhl (Vatikanstadt)', 'VA', 'VAT', '336', 'VA', 9999, 'Europa', '', 'Holy See (Vatican City State)', '', '', 'Europe', '', '', '', '', '', 0),\n('8f241f11096902d92.14742486', 0, 'Venezuela', 'VE', 'VEN', '862', 'VE', 9999, 'Rest Welt', '', 'Venezuela', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096919d00.92534927', 0, 'Vietnam', 'VN', 'VNM', '704', 'VN', 9999, 'Rest Welt', '', 'Vietnam', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109692fc04.15216034', 0, 'Britische Jungferninseln', 'VG', 'VGB', '92', 'VG', 9999, 'Rest Welt', '', 'Virgin Islands, British', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096944468.61956573', 0, 'Amerikanische Jungferninseln', 'VI', 'VIR', '850', 'VI', 9999, 'Rest Welt', '', 'Virgin Islands, U.S.', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110969598c8.76966113', 0, 'Wallis und Futuna', 'WF', 'WLF', '876', 'WF', 9999, 'Rest Welt', '', 'Wallis and Futuna', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109696e4e9.33006292', 0, 'Westsahara', 'EH', 'ESH', '732', 'EH', 9999, 'Rest Welt', '', 'Western Sahara', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096982354.73448958', 0, 'Jemen', 'YE', 'YEM', '887', 'YE', 9999, 'Rest Welt', '', 'Yemen', '', '', 'Rest World', '', '', '', '', '', 0),\n('a7c40f632a0804ab5.18804099', 0, 'Åland Inseln', 'AX', 'ALA', '248', 'AX', 9999, 'Rest Welt', '', 'Åland Islands', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110969c34a2.42564730', 0, 'Sambia', 'ZM', 'ZMB', '894', 'ZM', 9999, 'Rest Welt', '', 'Zambia', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110969da699.04185888', 0, 'Simbabwe', 'ZW', 'ZWE', '716', 'ZW', 9999, 'Rest Welt', '', 'Zimbabwe', '', '', 'Rest World', '', '', '', '', '', 0),\n('56d308a822c18e106.3ba59048', 0, 'Montenegro', 'ME', 'MNE', '499', 'ME', 9999, 'Rest Europa', '', 'Montenegro', '', '', 'Rest Europe', '', '', '', '', '', 0),\n('8f241f1109575d708.20084199', 0, 'Kongo, Dem. Rep.', 'CD', 'COD', '180', 'CD', 9999, 'Rest Welt', '', 'Congo, The Democratic Republic Of The', '', '', 'Rest World', '', '', '', '', '', 0),\n('56d308a822c18e106.3ba59099', 0, 'Guernsey', 'GG', 'GGY', '831', 'GG', 9999, 'Rest Welt', '', 'Guernsey', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096982354.73448999', 0, 'Isle of Man', 'IM', 'IMN', '833', 'IM', 9999, 'Rest Welt', '', 'Isle Of Man', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f11096944468.61956599', 0, 'Jersey', 'JE', 'JEY', '832', 'JE', 9999, 'Rest Welt', '', 'Jersey', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110968ebc30.63792799', 0, 'Palästinische Gebiete', 'PS', 'PSE', '275', 'PS', 9999, 'Rest Welt', '', 'Palestinian Territory, Occupied', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f110968a7cc9.56710199', 0, 'Saint-Barthélemy', 'BL', 'BLM', '652', 'BL', 9999, 'Rest Welt', '', 'Saint Barthélemy', '', '', 'Rest World', '', '', '', '', '', 0),\n('a7c40f632f65bd8e2.84963299', 0, 'Saint-Martin', 'MF', 'MAF', '663', 'MF', 9999, 'Rest Welt', '', 'Saint Martin', '', '', 'Rest World', '', '', '', '', '', 0),\n('8f241f1109533b943.50287999', 0, 'Südgeorgien und die Südlichen Sandwichinseln', 'GS', 'SGS', '239', 'GS', 9999, 'Rest Welt', '', 'South Georgia and The South Sandwich Islands', '', '', 'Rest World', '', '', '', '', '', 0);\n\nINSERT INTO `oxdeliveryset` (`OXID`, `OXSHOPID`, `OXACTIVE`, `OXACTIVEFROM`, `OXACTIVETO`, `OXTITLE`, `OXTITLE_1`, `OXTITLE_2`, `OXTITLE_3`, `OXPOS`) VALUES\n('oxidstandard', 1, 1, '0000-00-00 00:00:00', '0000-00-00 00:00:00', 'Standard', 'Standard', '', '', 10);\n\nINSERT INTO `oxgroups` (`OXID`, `OXACTIVE`, `OXTITLE`, `OXTITLE_1`, `OXTITLE_2`, `OXTITLE_3`) VALUES\n('oxidblacklist', 1, 'Blacklist', 'Blacklist', '', ''),\n('oxidsmallcust', 1, 'Geringer Umsatz', 'Less Turnover', '', ''),\n('oxidmiddlecust', 1, 'Mittlerer Umsatz', 'Medium Turnover', '', ''),\n('oxidgoodcust', 1, 'Grosser Umsatz', 'Huge Turnover', '', ''),\n('oxidforeigncustomer', 1, 'Auslandskunde', 'Foreign Customer', '', ''),\n('oxidnewcustomer', 1, 'Inlandskunde', 'Domestic Customer', '', ''),\n('oxidpowershopper', 1, 'Powershopper', 'Powershopper', '', ''),\n('oxiddealer', 1, 'Händler', 'Retailer', '', ''),\n('oxidnewsletter', 1, 'Newsletter-Abonnenten', 'Newsletter Recipients', '', ''),\n('oxidadmin', 1, 'Shop-Admin', 'Store Administrator', '', ''),\n('oxidpriceb', 1, 'Preis B', 'Price B', '', ''),\n('oxidpricea', 1, 'Preis A', 'Price A', '', ''),\n('oxidpricec', 1, 'Preis C', 'Price C', '', ''),\n('oxidblocked', 1, 'BLOCKED', 'BLOCKED', '', ''),\n('oxidcustomer', 1, 'Kunde', 'Customer', '', ''),\n('oxidnotyetordered', 1, 'Noch nicht gekauft', 'Not Yet Purchased', '', '');\n\nINSERT INTO `oxpayments` (`OXID`, `OXACTIVE`, `OXDESC`, `OXADDSUM`, `OXADDSUMTYPE`, `OXADDSUMRULES`, `OXFROMBONI`, `OXFROMAMOUNT`, `OXTOAMOUNT`, `OXVALDESC`, `OXCHECKED`, `OXDESC_1`, `OXVALDESC_1`, `OXDESC_2`, `OXVALDESC_2`, `OXDESC_3`, `OXVALDESC_3`, `OXLONGDESC`, `OXLONGDESC_1`, `OXLONGDESC_2`, `OXLONGDESC_3`, `OXSORT`) VALUES\n('oxidcashondel', 1, 'Nachnahme', 7.5, 'abs', 0, 0, 0, 1000000, '', 1, 'COD (Cash on Delivery)', '', '', '', '', '', '', '', '', '', 600),\n('oxiddebitnote', 1, 'Bankeinzug/Lastschrift', 0, 'abs', 0, 0, 0, 1000000, 'lsbankname__@@lsblz__@@lsktonr__@@lsktoinhaber__@@', 0, 'Direct Debit', 'lsbankname__@@lsblz__@@lsktonr__@@lsktoinhaber__@@', '', '', '', '', 'Die Belastung Ihres Kontos erfolgt mit dem Versand der Ware.', 'Your bank account will be charged when the order is shipped.', '', '', 400),\n('oxidpayadvance', 1, 'Vorauskasse', 0, 'abs', 0, 0, 0, 1000000, '', 1, 'Cash in advance', '', '', '', '', '', '', '', '', '', 300),\n('oxidinvoice', 1, 'Rechnung', 0, 'abs', 0, 800, 0, 1000000, '', 0, 'Invoice', '', '', '', '', '', '', '', '', '', 200),\n('oxempty', 1, 'Empty', 0, 'abs', 0, 0, 0, 0, '', 0, 'Empty', '', '', '', '', '', 'for other countries', 'An example. Maybe for use with other countries', '', '', 100);\n\nINSERT INTO `oxseo` (`OXOBJECTID`, `OXIDENT`, `OXSHOPID`, `OXLANG`, `OXSTDURL`, `OXSEOURL`, `OXTYPE`, `OXFIXED`, `OXEXPIRED`, `OXPARAMS`) VALUES\n('c8edd7319fdc59a0f28db056f72b4d17', '023abc17c853f9bccc201c5afd549a92', 1, 1, 'index.php?cl=account_wishlist', 'en/my-gift-registry/', 'static', 0, 0, ''),\n('e5340b054530ea779fb1802e93c8183e', '02b4c1e4049b1baffba090c95a7edbf7', 1, 0, 'index.php?cl=invite', 'Laden-Sie-Ihre-Freunde/', 'static', 0, 0, ''),\n('f52b25bb4e58480a2df8c06b65b4901c', '063c82502d9dd528ce2cc40ceb76ade7', 1, 1, 'index.php?cl=compare', 'en/my-product-comparison/', 'static', 0, 0, ''),\n('67c5bcf75ee346bd9566bce6c8', '0d2f22b49c64eaa138d717ec752e3be3', 1, 0, 'index.php?cl=credits&amp;oxcid=67c5bcf75ee346bd9566bce6c8', 'Credits/', 'oxcontent', 1, 0, ''),\n('8854fb64f8934f8d399eac52cca4136f', '1368f5e45468ca1e1c7c84f174785c35', 1, 1, 'index.php?cl=account_noticelist', 'en/my-wish-list/', 'static', 0, 0, ''),\n('c8edd7319fdc59a0f28db056f72b4d17', '1f30ae9b1c78b7dc89d3e5fe9eb0de59', 1, 0, 'index.php?cl=account_wishlist', 'mein-wunschzettel/', 'static', 0, 0, ''),\n('a9d28c6f1eae708a031d930117fc3740', '3a41965fb36fb45df7f4f9a4377f6364', 1, 1, 'index.php?cl=newsletter', 'en/newsletter/', 'static', 0, 0, ''),\n('5abe5ca9e2a5166fb64f0eb76f45edac', '3bdd64bd8073d044d8fd60334ed9b725', 1, 1, 'index.php?cl=start', 'en/home/', 'static', 0, 0, ''),\n('9c08d8934d13b2af47383f5a24fedb5c', '4a70a4cd11d63fdce96fbe4ba8e714e6', 1, 1, 'index.php?cnid=oxmore&amp;cl=alist', 'en/more/', 'static', 0, 0, ''),\n('9c08d8934d13b2af47383f5a24fedb5c', '4d3d4d70b09b5d2bd992991361374c67', 1, 0, 'index.php?cnid=oxmore&amp;cl=alist', 'mehr/', 'static', 0, 0, ''),\n('21eddcf0e16b9fbdb044f4d9678b55c6', '510fef34e5d9b86f6a77af949d15950e', 1, 1, 'index.php?cl=account', 'en/my-account/', 'static', 0, 0, ''),\n('9d0f22a4f43ea825f9cd3ebf5a7bde23', '5668048844927ca2767140c219e04efc', 1, 1, 'index.php?cl=account_user', 'en/my-address/', 'static', 0, 0, ''),\n('3ebbaef4dd461b3c12020f8a076f0212', '5cc081514a72b0ce3d7eec4fb1e6f1dd', 1, 1, 'index.php?cl=forgotpwd', 'en/forgot-password/', 'static', 0, 0, ''),\n('c4b506be468a1f71f2eb6e7bbceb0c57', '5d752e9e8302eeb21df24d1aee573234', 1, 0, 'index.php?cl=wishlist', 'wunschzettel/', 'static', 0, 0, ''),\n('70cc13db1bda9cdb8403879a11032e5a', '5eb126725ba500e6bbf1aecaa876dc48', 1, 1, 'index.php?cl=review', 'en/product-review/', 'static', 0, 0, ''),\n('0b1c46ce2b6d34d1a25e1719809f9f8d', '6b30b01fe01b80894efc0f29610e5215', 1, 0, 'index.php?cl=account_password', 'mein-passwort/', 'static', 0, 0, ''),\n('0b1c46ce2b6d34d1a25e1719809f9f8d', '6c890ac1255a99f8d1eb46f1193843d6', 1, 1, 'index.php?cl=account_password', 'en/my-password/', 'static', 0, 0, ''),\n('e2af574a8e963c354d4555159e54a516', '7685924d3f3fb7e9bda421c57f665af4', 1, 1, 'index.php?cl=contact', 'en/contact/', 'static', 0, 0, ''),\n('21eddcf0e16b9fbdb044f4d9678b55c6', '89c5e6bf1b5441599c4815281debe8df', 1, 0, 'index.php?cl=account', 'mein-konto/', 'static', 0, 0, ''),\n('fc0914f85af554513b2bfc8f09368244', '8afe769d3de8b5db0a872b23f959dd36', 1, 1, 'index.php?cl=download', 'en/download/', 'static', 0, 0, ''),\n('67a55f21d85ee47a3431b8292758a7a1', '8e7ebaebb0a810576453082e1f8f2fa3', 1, 1, 'index.php?cl=account_recommlist', 'en/my-listmania-list/', 'static', 0, 0, ''),\n('f41fdc9d234527d959c9d4c412c8cca7', '9372b6be2d98021fb93302a6a34bfc8c', 1, 1, 'index.php?cl=recommlist', 'en/Recommendation-Lists/', 'static', 0, 0, ''),\n('7ec4cddc7e3fe3bcf0410569355ff84e', '9508bb0d70121d49e4d4554c5b1af81d', 1, 0, 'index.php?cl=links', 'links/', 'static', 0, 0, ''),\n('bb8c75b6c8a3932f504250014529b78b', '9ff5c21cbc84dbfe815013236e132baf', 1, 1, 'index.php?cl=account_order', 'en/order-history/', 'static', 0, 0, ''),\n('67c5bcf75ee346bd9566bce6c8',       'a150a7b6945213daa7f138e1a042cbb4', 1, 1, 'index.php?cl=credits&amp;oxcid=67c5bcf75ee346bd9566bce6c8', 'en/Credits/', 'oxcontent', 1, 0, ''),\n('2b3cb8ed3e86f31772f1ac6ac83315db', 'a1b330b85c6f51fd9b50b1eb19707d84', 1, 1, 'index.php?cl=register', 'en/open-account/', 'static', 0, 0, ''),\n('8854fb64f8934f8d399eac52cca4136f', 'a24b03f1a3f73c325a7647e6039e2359', 1, 0, 'index.php?cl=account_noticelist', 'mein-merkzettel/', 'static', 0, 0, ''),\n('f41fdc9d234527d959c9d4c412c8cca7', 'a4e5995182ade3cf039800c0ec2d512d', 1, 0, 'index.php?cl=recommlist', 'Empfehlungslisten/', 'static', 0, 0, ''),\n('e5340b054530ea779fb1802e93c8183e', 'a6b775aec57d06b46a958efbafdc7875', 1, 1, 'index.php?cl=invite', 'en/Invite-your-friends/', 'static', 0, 0, ''),\n('9d0f22a4f43ea825f9cd3ebf5a7bde23', 'a7d5ab5a4e87693998c5aec066dda6e6', 1, 0, 'index.php?cl=account_user', 'meine-adressen/', 'static', 0, 0, ''),\n('3ebbaef4dd461b3c12020f8a076f0212', 'a9afb500184c584fb5ab2ad9b4162e96', 1, 0, 'index.php?cl=forgotpwd', 'passwort-vergessen/', 'static', 0, 0, ''),\n('2b3cb8ed3e86f31772f1ac6ac83315db', 'acddcfef9c25bcae3b96eb00669541ff', 1, 0, 'index.php?cl=register', 'konto-eroeffnen/', 'static', 0, 0, ''),\n('c4b506be468a1f71f2eb6e7bbceb0c57', 'b93b703d313e89662773cffaab750f1d', 1, 1, 'index.php?cl=wishlist', 'en/gift-registry/', 'static', 0, 0, ''),\n('67a55f21d85ee47a3431b8292758a7a1', 'baa3b653a618696b06c70a6dda95ebde', 1, 0, 'index.php?cl=account_recommlist', 'meine-lieblingslisten/', 'static', 0, 0, ''),\n('fc0914f85af554513b2bfc8f09368244', 'c552cb8718cde5cb792e181f78f5fde1', 1, 0, 'index.php?cl=download', 'download/', 'static', 0, 0, ''),\n('882acc7454f973844d4917515181dcba', 'c74bbaf961498de897ba7eb98fea8274', 1, 1, 'index.php?cl=account_downloads', 'en/my-downloads/', 'static', 0, 0, ''),\n('70cc13db1bda9cdb8403879a11032e5a', 'cc28156a4f728c1b33e7e02fd846628e', 1, 0, 'index.php?cl=review', 'bewertungen/', 'static', 0, 0, ''),\n('5abe5ca9e2a5166fb64f0eb76f45edac', 'ccca27059506a916fb64d673a5dd676b', 1, 0, 'index.php?cl=start', 'startseite/', 'static', 0, 0, ''),\n('f52b25bb4e58480a2df8c06b65b4901c', 'e0dd29a75947539da6b1924d31c9849f', 1, 0, 'index.php?cl=compare', 'mein-produktvergleich/', 'static', 0, 0, ''),\n('a9d28c6f1eae708a031d930117fc3740', 'e604233c5a2804d21ec0252a445e93d3', 1, 0, 'index.php?cl=newsletter', 'newsletter/', 'static', 0, 0, ''),\n('8be5f76ba0ed85f4c2fd9e57cd137a05', 'e6331d115f5323610c639ef2f0369f8a', 1, 0, 'index.php?cl=basket', 'warenkorb/', 'static', 0, 0, ''),\n('bb8c75b6c8a3932f504250014529b78b', 'eb692d07ec8608d943db0a3bca29c4ce', 1, 0, 'index.php?cl=account_order', 'bestellhistorie/', 'static', 0, 0, ''),\n('8be5f76ba0ed85f4c2fd9e57cd137a05', 'ecaf0240f9f46bbee5ffc6dd73d0b7f0', 1, 1, 'index.php?cl=basket', 'en/cart/', 'static', 0, 0, ''),\n('882acc7454f973844d4917515181dcba', 'f1c7ccb53fc8d6f239b39d2fc2b76829', 1, 0, 'index.php?cl=account_downloads', 'de/my-downloads/', 'static', 0, 0, ''),\n('7ec4cddc7e3fe3bcf0410569355ff84e', 'f80ace8f87e1d7f446ab1fa58f275f4c', 1, 1, 'index.php?cl=links', 'en/links/', 'static', 0, 0, ''),\n('943173edecf6d6870a0f357b8ac84d32', 'f8335c84c1daa5b13657124f45c0e08b', 1, 0, 'index.php?cl=alist&amp;cnid=943173edecf6d6870a0f357b8ac84d32', 'Wakeboarding/', 'oxcategory', 0, 0, ''),\n('e2af574a8e963c354d4555159e54a516', 'f9d1a02ab749dc360c4e21f21de1beaf', 1, 0, 'index.php?cl=contact', 'kontakt/', 'static', 0, 0, ''),\n('0f4270b89fbef1481958381410a0dbca', 'fad614c0f4922228623ae0696b55481f', 1, 1, 'index.php?cl=alist&amp;cnid=0f4270b89fbef1481958381410a0dbca', 'en/Wakeboarding/Wakeboards/', 'oxcategory', 1, 0, ''),\n('d21804a99d13c9cc50c0c6206547922f', 'd50c753d406338d92f52fe55de1e98dd', 1, 1, 'index.php?cl=clearcookies', 'en/cookies/', 'static', 0, 0, ''),\n('d21804a99d13c9cc50c0c6206547922f', '295d6617dc22b6d8186667ecd6e3ae87', 1, 0, 'index.php?cl=clearcookies', 'cookies/', 'static', 0, 0, '');\n\nINSERT INTO `oxshops` (`OXID`, `OXACTIVE`, `OXPRODUCTIVE`, `OXDEFCURRENCY`, `OXDEFLANGUAGE`, `OXNAME`, `OXTITLEPREFIX`, `OXTITLEPREFIX_1`, `OXTITLEPREFIX_2`, `OXTITLEPREFIX_3`, `OXTITLESUFFIX`, `OXTITLESUFFIX_1`, `OXTITLESUFFIX_2`, `OXTITLESUFFIX_3`, `OXSTARTTITLE`, `OXSTARTTITLE_1`, `OXSTARTTITLE_2`, `OXSTARTTITLE_3`, `OXINFOEMAIL`, `OXORDEREMAIL`, `OXOWNEREMAIL`, `OXORDERSUBJECT`, `OXREGISTERSUBJECT`, `OXFORGOTPWDSUBJECT`, `OXSENDEDNOWSUBJECT`, `OXORDERSUBJECT_1`, `OXREGISTERSUBJECT_1`, `OXFORGOTPWDSUBJECT_1`, `OXSENDEDNOWSUBJECT_1`, `OXORDERSUBJECT_2`, `OXREGISTERSUBJECT_2`, `OXFORGOTPWDSUBJECT_2`, `OXSENDEDNOWSUBJECT_2`, `OXORDERSUBJECT_3`, `OXREGISTERSUBJECT_3`, `OXFORGOTPWDSUBJECT_3`, `OXSENDEDNOWSUBJECT_3`, `OXSMTP`, `OXSMTPUSER`, `OXSMTPPWD`, `OXCOMPANY`, `OXSTREET`, `OXZIP`, `OXCITY`, `OXCOUNTRY`, `OXBANKNAME`, `OXBANKNUMBER`, `OXBANKCODE`, `OXVATNUMBER`, `OXTAXNUMBER`, `OXBICCODE`, `OXIBANNUMBER`, `OXFNAME`, `OXLNAME`, `OXTELEFON`, `OXTELEFAX`, `OXURL`, `OXDEFCAT`, `OXHRBNR`, `OXCOURT`, `OXADBUTLERID`, `OXAFFILINETID`, `OXSUPERCLICKSID`, `OXAFFILIWELTID`, `OXAFFILI24ID`, `OXEDITION`, `OXVERSION`, `OXSEOACTIVE`, `OXSEOACTIVE_1`, `OXSEOACTIVE_2`, `OXSEOACTIVE_3`) VALUES\n(1, 1, 0, '', 0, 'OXID eShop', 'OXID eShop', 'OXID eShop', '', '', 'online kaufen', 'purchase online', '', '', 'Der Onlineshop', 'Online Shop', '', '', 'info@myoxideshop.com', 'reply@myoxideshop.com', 'order@myoxideshop.com', 'Ihre Bestellung bei OXID eSales', 'Vielen Dank für Ihre Registrierung im OXID eShop', 'Ihr Passwort im OXID eShop', 'Ihre OXID eSales Bestellung wurde versandt', 'Your order at OXID eShop', 'Thank you for your registration at OXID eShop', 'Your OXID eShop password', 'Your OXID eSales Order has been shipped', '', '', '', '', '', '', '', '', '', '', '', 'Your Company Name', '2425 Maple Street', '9041', 'Any City, CA', 'United States', 'Bank of America', '1234567890', '900 1234567', '', '', '', '', 'John', 'Doe', '217-8918712', '217-8918713', 'www.myoxideshop.com', '', '', '', '', '', '', '', '', 'CE', '6.0.0', 1, 1, 0, 0);\n\nINSERT INTO `oxstates` (`OXID`, `OXCOUNTRYID`, `OXTITLE`, `OXISOALPHA2`, `OXTITLE_1`, `OXTITLE_2`, `OXTITLE_3`) VALUES\n('AB', '8f241f11095649d18.02676059', 'Alberta', 'AB', 'Alberta', '', ''),\n('BC', '8f241f11095649d18.02676059', 'Britisch-Kolumbien', 'BC', 'British Columbia', '', ''),\n('MB', '8f241f11095649d18.02676059', 'Manitoba', 'MB', 'Manitoba', '', ''),\n('NB', '8f241f11095649d18.02676059', 'Neubraunschweig', 'NB', 'New Brunswick', '', ''),\n('NF', '8f241f11095649d18.02676059', 'Neufundland und Labrador', 'NF', 'Newfoundland and Labrador', '', ''),\n('NT', '8f241f11095649d18.02676059', 'Nordwest-Territorien', 'NT', 'Northwest Territories', '', ''),\n('NS', '8f241f11095649d18.02676059', 'Nova Scotia', 'NS', 'Nova Scotia', '', ''),\n('NU', '8f241f11095649d18.02676059', 'Nunavut', 'NU', 'Nunavut', '', ''),\n('ON', '8f241f11095649d18.02676059', 'Ontario', 'ON', 'Ontario', '', ''),\n('PE', '8f241f11095649d18.02676059', 'Prince Edward Island', 'PE', 'Prince Edward Island', '', ''),\n('QC', '8f241f11095649d18.02676059', 'Quebec', 'QC', 'Quebec', '', ''),\n('SK', '8f241f11095649d18.02676059', 'Saskatchewan', 'SK', 'Saskatchewan', '', ''),\n('YK', '8f241f11095649d18.02676059', 'Yukon', 'YK', 'Yukon', '', ''),\n('AL', '8f241f11096877ac0.98748826', 'Alabama', 'AL', 'Alabama', '', ''),\n('AK', '8f241f11096877ac0.98748826', 'Alaska', 'AK', 'Alaska', '', ''),\n('AS', '8f241f11096877ac0.98748826', 'Amerikanisch-Samoa', 'AS', 'American Samoa', '', ''),\n('AZ', '8f241f11096877ac0.98748826', 'Arizona', 'AZ', 'Arizona', '', ''),\n('AR', '8f241f11096877ac0.98748826', 'Arkansas', 'AR', 'Arkansas', '', ''),\n('CA', '8f241f11096877ac0.98748826', 'Kalifornien', 'CA', 'California', '', ''),\n('CO', '8f241f11096877ac0.98748826', 'Colorado', 'CO', 'Colorado', '', ''),\n('CT', '8f241f11096877ac0.98748826', 'Connecticut', 'CT', 'Connecticut', '', ''),\n('DE', '8f241f11096877ac0.98748826', 'Delaware', 'DE', 'Delaware', '', ''),\n('DC', '8f241f11096877ac0.98748826', 'District of Columbia', 'DC', 'District of Columbia', '', ''),\n('FM', '8f241f11096877ac0.98748826', 'Föderierten Staaten von Mikronesien', 'FM', 'Federated States of Micronesia', '', ''),\n('FL', '8f241f11096877ac0.98748826', 'Florida', 'FL', 'Florida', '', ''),\n('GA', '8f241f11096877ac0.98748826', 'Georgia', 'GA', 'Georgia', '', ''),\n('GU', '8f241f11096877ac0.98748826', 'Guam', 'GU', 'Guam', '', ''),\n('HI', '8f241f11096877ac0.98748826', 'Hawaii', 'HI', 'Hawaii', '', ''),\n('ID', '8f241f11096877ac0.98748826', 'Idaho', 'ID', 'Idaho', '', ''),\n('IL', '8f241f11096877ac0.98748826', 'Illinois', 'IL', 'Illinois', '', ''),\n('IN', '8f241f11096877ac0.98748826', 'Indiana', 'IN', 'Indiana', '', ''),\n('IA', '8f241f11096877ac0.98748826', 'Iowa', 'IA', 'Iowa', '', ''),\n('KS', '8f241f11096877ac0.98748826', 'Kansas', 'KS', 'Kansas', '', ''),\n('KY', '8f241f11096877ac0.98748826', 'Kentucky', 'KY', 'Kentucky', '', ''),\n('LA', '8f241f11096877ac0.98748826', 'Louisiana', 'LA', 'Louisiana', '', ''),\n('ME', '8f241f11096877ac0.98748826', 'Maine', 'ME', 'Maine', '', ''),\n('MH', '8f241f11096877ac0.98748826', 'Marshallinseln', 'MH', 'Marshall Islands', '', ''),\n('MD', '8f241f11096877ac0.98748826', 'Maryland', 'MD', 'Maryland', '', ''),\n('MA', '8f241f11096877ac0.98748826', 'Massachusetts', 'MA', 'Massachusetts', '', ''),\n('MI', '8f241f11096877ac0.98748826', 'Michigan', 'MI', 'Michigan', '', ''),\n('MN', '8f241f11096877ac0.98748826', 'Minnesota', 'MN', 'Minnesota', '', ''),\n('MS', '8f241f11096877ac0.98748826', 'Mississippi', 'MS', 'Mississippi', '', ''),\n('MO', '8f241f11096877ac0.98748826', 'Missouri', 'MO', 'Missouri', '', ''),\n('MT', '8f241f11096877ac0.98748826', 'Montana', 'MT', 'Montana', '', ''),\n('NE', '8f241f11096877ac0.98748826', 'Nebraska', 'NE', 'Nebraska', '', ''),\n('NV', '8f241f11096877ac0.98748826', 'Nevada', 'NV', 'Nevada', '', ''),\n('NH', '8f241f11096877ac0.98748826', 'New Hampshire', 'NH', 'New Hampshire', '', ''),\n('NJ', '8f241f11096877ac0.98748826', 'New Jersey', 'NJ', 'New Jersey', '', ''),\n('NM', '8f241f11096877ac0.98748826', 'Neumexiko', 'NM', 'New Mexico', '', ''),\n('NY', '8f241f11096877ac0.98748826', 'New York', 'NY', 'New York', '', ''),\n('NC', '8f241f11096877ac0.98748826', 'North Carolina', 'NC', 'North Carolina', '', ''),\n('ND', '8f241f11096877ac0.98748826', 'North Dakota', 'ND', 'North Dakota', '', ''),\n('MP', '8f241f11096877ac0.98748826', 'Nördlichen Marianen', 'MP', 'Northern Mariana Islands', '', ''),\n('OH', '8f241f11096877ac0.98748826', 'Ohio', 'OH', 'Ohio', '', ''),\n('OK', '8f241f11096877ac0.98748826', 'Oklahoma', 'OK', 'Oklahoma', '', ''),\n('OR', '8f241f11096877ac0.98748826', 'Oregon', 'OR', 'Oregon', '', ''),\n('PW', '8f241f11096877ac0.98748826', 'Palau', 'PW', 'Palau', '', ''),\n('PA', '8f241f11096877ac0.98748826', 'Pennsylvania', 'PA', 'Pennsylvania', '', ''),\n('PR', '8f241f11096877ac0.98748826', 'Puerto Rico', 'PR', 'Puerto Rico', '', ''),\n('RI', '8f241f11096877ac0.98748826', 'Rhode Island', 'RI', 'Rhode Island', '', ''),\n('SC', '8f241f11096877ac0.98748826', 'Südkarolina', 'SC', 'South Carolina', '', ''),\n('SD', '8f241f11096877ac0.98748826', 'Süddakota', 'SD', 'South Dakota', '', ''),\n('TN', '8f241f11096877ac0.98748826', 'Tennessee', 'TN', 'Tennessee', '', ''),\n('TX', '8f241f11096877ac0.98748826', 'Texas', 'TX', 'Texas', '', ''),\n('UT', '8f241f11096877ac0.98748826', 'Utah', 'UT', 'Utah', '', ''),\n('VT', '8f241f11096877ac0.98748826', 'Vermont', 'VT', 'Vermont', '', ''),\n('VI', '8f241f11096877ac0.98748826', 'Jungferninseln', 'VI', 'Virgin Islands', '', ''),\n('VA', '8f241f11096877ac0.98748826', 'Virginia', 'VA', 'Virginia', '', ''),\n('WA', '8f241f11096877ac0.98748826', 'Washington', 'WA', 'Washington', '', ''),\n('WV', '8f241f11096877ac0.98748826', 'West Virginia', 'WV', 'West Virginia', '', ''),\n('WI', '8f241f11096877ac0.98748826', 'Wisconsin', 'WI', 'Wisconsin', '', ''),\n('WY', '8f241f11096877ac0.98748826', 'Wyoming', 'WY', 'Wyoming', '', ''),\n('AA', '8f241f11096877ac0.98748826', 'Armed Forces Americas', 'AA', 'Armed Forces Americas', '', ''),\n('AE', '8f241f11096877ac0.98748826', 'Armed Forces', 'AE', 'Armed Forces', '', ''),\n('AP', '8f241f11096877ac0.98748826', 'Armed Forces Pacific', 'AP', 'Armed Forces Pacific', '', '');\n"
  },
  {
    "path": "source/Internal/Setup/Directory/DirectoryValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Directory;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse RecursiveDirectoryIterator;\nuse RecursiveIteratorIterator;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass DirectoryValidator implements DirectoryValidatorInterface\n{\n    private const DIRECTORIES_LIST = [\n        'out/pictures/promo/',\n        'out/pictures/master/',\n        'out/pictures/generated/',\n        'out/pictures/media/',\n        'out/media/',\n        'log/',\n        '../var/',\n    ];\n\n    public function __construct(\n        private readonly BasicContextInterface $basicContext\n    ) {\n    }\n\n    /**\n     * @throws NoPermissionDirectoryException\n     * @throws NonExistenceDirectoryException\n     */\n    public function validateDirectory(string $compileDirectory): void\n    {\n        $directories = $this->getDirectories($compileDirectory);\n\n        $this->checkDirectoriesExistent($directories);\n        $this->checkDirectoriesPermission($directories);\n    }\n\n    /**\n     * @throws NotAbsolutePathException\n     */\n    public function checkPathIsAbsolute(string $compileDirectory): void\n    {\n        if (!Path::isAbsolute($compileDirectory)) {\n            throw new NotAbsolutePathException(NotAbsolutePathException::NOT_ABSOLUTE_PATHS);\n        }\n    }\n\n    /**\n     * @throws NonExistenceDirectoryException\n     */\n    private function checkDirectoriesExistent(array $directories): void\n    {\n        foreach ($directories as $directory) {\n            if (!is_dir($directory)) {\n                throw new NonExistenceDirectoryException(\n                    NonExistenceDirectoryException::NON_EXISTENCE_DIRECTORY . ': ' . $directory\n                );\n            }\n        }\n    }\n\n    /**\n     * @throws NoPermissionDirectoryException\n     */\n    private function checkDirectoriesPermission(array $directories): void\n    {\n        $subDirectories = $this->getDirectoriesAndSubDirectories($directories);\n\n        foreach ($subDirectories as $subDirectory) {\n            if (!is_readable($subDirectory) || !is_writable($subDirectory)) {\n                throw new NoPermissionDirectoryException(\n                    NoPermissionDirectoryException::NO_PERMISSION_DIRECTORY . ': ' . $subDirectory\n                );\n            }\n        }\n    }\n\n    private function getDirectoriesAndSubDirectories(array $directories): array\n    {\n        foreach ($directories as $directory) {\n            $recursiveIterator = new RecursiveIteratorIterator(\n                new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS),\n                RecursiveIteratorIterator::SELF_FIRST,\n                RecursiveIteratorIterator::CATCH_GET_CHILD\n            );\n\n            foreach ($recursiveIterator as $path => $dir) {\n                if ($dir->isDir()) {\n                    $directories[] = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;\n                }\n            }\n        }\n\n        return $directories;\n    }\n\n    private function getDirectories(string $compileDirectory): array\n    {\n        $shopSourcePath = rtrim($this->basicContext->getSourcePath(), DIRECTORY_SEPARATOR) .  DIRECTORY_SEPARATOR;\n\n        $directories = array_map(\n            static function ($value) use ($shopSourcePath) {\n                return $shopSourcePath . $value;\n            },\n            self::DIRECTORIES_LIST\n        );\n\n        $directories[] = rtrim($compileDirectory, DIRECTORY_SEPARATOR) .  DIRECTORY_SEPARATOR;\n\n        return $directories;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Setup/Directory/DirectoryValidatorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Directory;\n\ninterface DirectoryValidatorInterface\n{\n    /**\n     * @throws NoPermissionDirectoryException\n     * @throws NonExistenceDirectoryException\n     */\n    public function validateDirectory(string $compileDirectory): void;\n\n    /**\n     * @throws NotAbsolutePathException\n     */\n    public function checkPathIsAbsolute(string $compileDirectory): void;\n}\n"
  },
  {
    "path": "source/Internal/Setup/Directory/NoPermissionDirectoryException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Directory;\n\nuse Exception;\n\nclass NoPermissionDirectoryException extends Exception\n{\n    public const NO_PERMISSION_DIRECTORY = 'Following folder has no writing and reading permission';\n}\n"
  },
  {
    "path": "source/Internal/Setup/Directory/NonExistenceDirectoryException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Directory;\n\nuse Exception;\n\nclass NonExistenceDirectoryException extends Exception\n{\n    public const NON_EXISTENCE_DIRECTORY = 'Following folder does not exist';\n}\n"
  },
  {
    "path": "source/Internal/Setup/Directory/NotAbsolutePathException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Directory;\n\nuse Exception;\n\nclass NotAbsolutePathException extends Exception\n{\n    public const NOT_ABSOLUTE_PATHS = 'shop-directory and compile-directory must be absolute paths';\n}\n"
  },
  {
    "path": "source/Internal/Setup/Directory/services.yaml",
    "content": "services:\n    _defaults:\n        autowire: true\n\n    OxidEsales\\EshopCommunity\\Internal\\Setup\\Directory\\DirectoryValidatorInterface:\n        class: OxidEsales\\EshopCommunity\\Internal\\Setup\\Directory\\DirectoryValidator\n"
  },
  {
    "path": "source/Internal/Setup/Htaccess/HtaccessAccessException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess;\n\nuse Exception;\n\nclass HtaccessAccessException extends Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Setup/Htaccess/HtaccessDao.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess;\n\nclass HtaccessDao implements HtaccessDaoInterface\n{\n    private const DIRECTIVE_REWRITE_BASE = 'RewriteBase';\n\n    private string $contents;\n\n    public function __construct(\n        private readonly string $filePath\n    ) {\n        $this->checkFileAccess();\n    }\n\n    public function setRewriteBase(string $rewriteBase): void\n    {\n        $this->readFile();\n        $this->updateDirective(self::DIRECTIVE_REWRITE_BASE, $rewriteBase);\n        $this->writeFile();\n    }\n\n    private function readFile(): void\n    {\n        $this->contents = file_get_contents($this->filePath);\n    }\n\n    private function updateDirective(string $key, string $value): void\n    {\n        $this->contents = preg_replace(\"/$key.*/\", \"$key $value\", $this->contents);\n    }\n\n    private function writeFile(): void\n    {\n        file_put_contents($this->filePath, $this->contents);\n    }\n\n    /** @throws HtaccessAccessException */\n    private function checkFileAccess(): void\n    {\n        clearstatcache();\n        if (!is_readable($this->filePath) || !is_writable($this->filePath)) {\n            throw new HtaccessAccessException(\"File not found or not accessible: `{$this->filePath}`\");\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Setup/Htaccess/HtaccessDaoFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\n\nclass HtaccessDaoFactory implements HtaccessDaoFactoryInterface\n{\n    private const FILENAME = '.htaccess';\n\n    public function __construct(private readonly BasicContextInterface $basicContext)\n    {\n    }\n\n    /**\n     * @throws HtaccessAccessException\n     */\n    public function createRootHtaccessDao(): HtaccessDaoInterface\n    {\n        return new HtaccessDao($this->getRootHtaccessPath());\n    }\n\n    /**\n     * @throws HtaccessAccessException\n     */\n    private function getRootHtaccessPath(): string\n    {\n        clearstatcache();\n        $path = realpath($this->basicContext->getSourcePath() . DIRECTORY_SEPARATOR . self::FILENAME);\n        if (!$path || !is_file($path)) {\n            throw new HtaccessAccessException(\n                sprintf('Root %s file not found or not accessible', self::FILENAME)\n            );\n        }\n\n        return $path;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Setup/Htaccess/HtaccessDaoFactoryInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess;\n\ninterface HtaccessDaoFactoryInterface\n{\n    public function createRootHtaccessDao(): HtaccessDaoInterface;\n}\n"
  },
  {
    "path": "source/Internal/Setup/Htaccess/HtaccessDaoInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess;\n\ninterface HtaccessDaoInterface\n{\n    public function setRewriteBase(string $rewriteBase): void;\n}\n"
  },
  {
    "path": "source/Internal/Setup/Htaccess/HtaccessUpdater.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Url\\UrlParserInterface;\n\nclass HtaccessUpdater implements HtaccessUpdaterInterface\n{\n    private const REWRITE_BASE_FOR_EMPTY_PATH = '/';\n\n    public function __construct(\n        private readonly HtaccessDaoFactoryInterface $htaccessDaoFactory,\n        private readonly UrlParserInterface $urlParser\n    ) {\n    }\n\n    /** @inheritDoc */\n    public function updateRewriteBaseDirective(ShopBaseUrl $shopBaseUrl): void\n    {\n        $this->htaccessDaoFactory->createRootHtaccessDao()->setRewriteBase(\n            $this->getRewriteBase($shopBaseUrl->getUrl())\n        );\n    }\n\n    private function getRewriteBase(string $url): string\n    {\n        return $this->urlParser->getPathWithoutTrailingSlash($url) ?: self::REWRITE_BASE_FOR_EMPTY_PATH;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Setup/Htaccess/HtaccessUpdaterInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess;\n\ninterface HtaccessUpdaterInterface\n{\n    /** @param string $url */\n    public function updateRewriteBaseDirective(ShopBaseUrl $shopBaseUrl): void;\n}\n"
  },
  {
    "path": "source/Internal/Setup/Htaccess/InvalidShopUrlException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess;\n\nuse Exception;\n\nclass InvalidShopUrlException extends Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Setup/Htaccess/ShopBaseUrl.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess;\n\nclass ShopBaseUrl\n{\n    public function __construct(\n        private readonly string $url,\n    ) {\n        $this->validateUrl();\n    }\n\n    public function getUrl(): string\n    {\n        return $this->url;\n    }\n\n    private function validateUrl(): void\n    {\n        if (!parse_url($this->url, PHP_URL_SCHEME) || !parse_url($this->url, PHP_URL_HOST)) {\n            throw new InvalidShopUrlException(\n                \"'$this->url' is not a valid URL!\"\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Setup/Htaccess/services.yaml",
    "content": "services:\n    _defaults:\n        autowire: true\n\n    OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\HtaccessDaoFactoryInterface:\n        class: OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\HtaccessDaoFactory\n\n    OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\HtaccessUpdaterInterface:\n        class: OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\HtaccessUpdater\n"
  },
  {
    "path": "source/Internal/Setup/Language/DefaultLanguage.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Language;\n\nuse function in_array;\n\nclass DefaultLanguage\n{\n    private string $language;\n\n    private array $availableLanguages = ['en', 'de'];\n\n    /**\n     * @throws IncorrectLanguageException\n     */\n    public function __construct(string $language)\n    {\n        if (in_array($language, $this->availableLanguages, true)) {\n            $this->language = $language;\n        } else {\n            throw new IncorrectLanguageException(\n                sprintf(\n                    'Invalid language argument: %s, available languages: %s',\n                    $language,\n                    implode(', ', $this->availableLanguages)\n                )\n            );\n        }\n    }\n\n    public function getCode(): string\n    {\n        return $this->language;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Setup/Language/IncorrectLanguageException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Language;\n\nuse Exception;\n\nclass IncorrectLanguageException extends Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Setup/Language/LanguageInstaller.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Language;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Dao\\ShopConfigurationSettingDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\DataObject\\ShopConfigurationSetting;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\DataObject\\ShopSettingType;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\n\nclass LanguageInstaller implements LanguageInstallerInterface\n{\n    public function __construct(\n        private ShopConfigurationSettingDaoInterface $shopConfigurationSettingDao,\n        private BasicContextInterface $context\n    ) {\n    }\n\n    public function install(DefaultLanguage $language): void\n    {\n        $this->setDefaultLanguage($language);\n        $this->updateActiveLanguage($language);\n    }\n\n    private function setDefaultLanguage(DefaultLanguage $language): void\n    {\n        $setting = new ShopConfigurationSetting();\n        $setting\n            ->setName('sDefaultLang')\n            ->setValue($language->getCode())\n            ->setType(ShopSettingType::STRING)\n            ->setShopId($this->context->getDefaultShopId());\n\n        $this->shopConfigurationSettingDao->save($setting);\n    }\n\n    private function updateActiveLanguage(DefaultLanguage $language): void\n    {\n        try {\n            $languagesSetting = $this->shopConfigurationSettingDao->get(\n                'aLanguageParams',\n                $this->context->getDefaultShopId()\n            );\n            $settingValue = $languagesSetting->getValue();\n\n            foreach ($settingValue as $languageCode => $settingLanguage) {\n                $settingValue[$languageCode]['active'] = $languageCode === $language->getCode() ? 1 : 0;\n            }\n\n            $languagesSetting->setValue($settingValue);\n            $this->shopConfigurationSettingDao->save($languagesSetting);\n        } catch (EntryDoesNotExistDaoException) {\n            // no setting, nothing to update\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Setup/Language/LanguageInstallerInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Language;\n\ninterface LanguageInstallerInterface\n{\n    public function install(DefaultLanguage $language): void;\n}\n"
  },
  {
    "path": "source/Internal/Setup/Language/services.yaml",
    "content": "services:\n    _defaults:\n        autowire: true\n\n    OxidEsales\\EshopCommunity\\Internal\\Setup\\Language\\LanguageInstallerInterface:\n        class: OxidEsales\\EshopCommunity\\Internal\\Setup\\Language\\LanguageInstaller\n"
  },
  {
    "path": "source/Internal/Setup/Parameters/SetupParameters.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Parameters;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\DataObject\\DatabaseConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\ShopBaseUrl;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Language\\DefaultLanguage;\n\nclass SetupParameters\n{\n    private string $cacheDir;\n    private DatabaseConfiguration $dbConfig;\n    private ShopBaseUrl $shopBaseUrl;\n    private DefaultLanguage $language;\n\n    public function getCacheDir(): string\n    {\n        return $this->cacheDir;\n    }\n\n    public function setCacheDir(string $cacheDir): void\n    {\n        $this->cacheDir = $cacheDir;\n    }\n\n    public function getDbConfig(): DatabaseConfiguration\n    {\n        return $this->dbConfig;\n    }\n\n    public function setDbConfig(DatabaseConfiguration $dbConfig): void\n    {\n        $this->dbConfig = $dbConfig;\n    }\n\n    public function getShopBaseUrl(): ShopBaseUrl\n    {\n        return $this->shopBaseUrl;\n    }\n\n    public function setShopBaseUrl(ShopBaseUrl $shopBaseUrl): void\n    {\n        $this->shopBaseUrl = $shopBaseUrl;\n    }\n\n    public function getLanguage(): DefaultLanguage\n    {\n        return $this->language;\n    }\n\n    public function setLanguage(DefaultLanguage $language): void\n    {\n        $this->language = $language;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Setup/Parameters/SetupParametersFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Parameters;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\DataObject\\DatabaseConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\ShopBaseUrl;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Language\\DefaultLanguage;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\n\nreadonly class SetupParametersFactory implements SetupParametersFactoryInterface\n{\n    public function __construct(\n        private BasicContextInterface $basicContext\n    ) {\n    }\n\n    public function create(DefaultLanguage $language): SetupParameters\n    {\n        $setupParameters = new SetupParameters();\n\n        $setupParameters->setCacheDir(\n            $this->basicContext->getCacheDirectory()\n        );\n        $setupParameters->setDbConfig(\n            new DatabaseConfiguration(\n                $this->basicContext->getDatabaseUrl()\n            )\n        );\n        $setupParameters->setShopBaseUrl(\n            new ShopBaseUrl(\n                $this->basicContext->getShopBaseUrl()\n            )\n        );\n        $setupParameters->setLanguage($language);\n\n        return $setupParameters;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Setup/Parameters/SetupParametersFactoryInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Parameters;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Language\\DefaultLanguage;\n\ninterface SetupParametersFactoryInterface\n{\n    public function create(DefaultLanguage $language): SetupParameters;\n}\n"
  },
  {
    "path": "source/Internal/Setup/Parameters/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Setup\\Parameters\\SetupParametersFactoryInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Setup\\Parameters\\SetupParametersFactory"
  },
  {
    "path": "source/Internal/Setup/ShopConfiguration/ShopConfigurationUpdater.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\ShopConfiguration;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Dao\\ShopConfigurationSettingDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\DataObject\\ShopConfigurationSetting;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\DataObject\\ShopSettingType;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\n\nclass ShopConfigurationUpdater implements ShopConfigurationUpdaterInterface\n{\n    public function __construct(\n        private readonly ShopConfigurationSettingDaoInterface $settingDao,\n        private readonly BasicContextInterface $basicContext,\n    ) {\n    }\n\n    public function saveShopSetupTime(): void\n    {\n        $setting = new ShopConfigurationSetting();\n        $setting\n            ->setName('sTagList')\n            ->setValue(time())\n            ->setType(ShopSettingType::STRING)\n            ->setShopId($this->basicContext->getDefaultShopId());\n\n        $this->settingDao->save($setting);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Setup/ShopConfiguration/ShopConfigurationUpdaterInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\ShopConfiguration;\n\ninterface ShopConfigurationUpdaterInterface\n{\n    public function saveShopSetupTime(): void;\n}\n"
  },
  {
    "path": "source/Internal/Setup/ShopConfiguration/services.yaml",
    "content": "services:\n    _defaults:\n        autowire: true\n\n    OxidEsales\\EshopCommunity\\Internal\\Setup\\ShopConfiguration\\ShopConfigurationUpdaterInterface:\n        class: OxidEsales\\EshopCommunity\\Internal\\Setup\\ShopConfiguration\\ShopConfigurationUpdater\n"
  },
  {
    "path": "source/Internal/Setup/ShopSetupCommand.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\ShopDbManagerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\HtaccessUpdaterInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Language\\DefaultLanguage;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Language\\LanguageInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Parameters\\SetupParametersFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\ShopConfiguration\\ShopConfigurationUpdaterInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Validator\\SetupInfrastructureValidatorInterface;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Input\\InputOption;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\nclass ShopSetupCommand extends Command\n{\n    private const DEFAULT_LANG = 'en';\n\n    public function __construct(\n        private readonly SetupParametersFactoryInterface $setupParametersFactory,\n        private readonly SetupInfrastructureValidatorInterface $setupInfrastructureValidator,\n        private readonly ShopDbManagerInterface $shopDbManager,\n        private readonly LanguageInstallerInterface $languageInstaller,\n        private readonly HtaccessUpdaterInterface $htaccessUpdateService,\n        private readonly ShopConfigurationUpdaterInterface $shopConfigurationUpdater\n    ) {\n        parent::__construct();\n    }\n\n    protected function configure(): void\n    {\n        $this->addOption(\n            name: 'language',\n            mode: InputOption::VALUE_OPTIONAL,\n            default: self::DEFAULT_LANG\n        );\n        $this->setDescription('Performs initial shop setup');\n    }\n\n    protected function execute(InputInterface $input, OutputInterface $output): int\n    {\n        $output->writeln('<info>Running pre-setup checks...</info>');\n        $setupParameters = $this->setupParametersFactory\n            ->create(new DefaultLanguage($input->getOption('language')));\n        $this->setupInfrastructureValidator\n            ->validate($setupParameters);\n\n        $output->writeln('<info>Updating htaccess file...</info>');\n        $this->htaccessUpdateService\n            ->updateRewriteBaseDirective(\n                $setupParameters->getShopBaseUrl()\n            );\n\n        $output->writeln('<info>Installing database...</info>');\n        $this->shopDbManager\n            ->create(\n                $setupParameters->getDbConfig()\n            );\n\n        $output->writeln('<info>Installing language...</info>');\n        $this->languageInstaller\n            ->install(\n                $setupParameters->getLanguage()\n            );\n\n        $this->shopConfigurationUpdater\n            ->saveShopSetupTime();\n\n        $output->writeln('<info>Setup has been finished.</info>');\n\n        return 0;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Setup/Validator/SetupInfrastructureValidator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\SetupDbConnectionValidatorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\SetupDbValidatorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Directory\\DirectoryValidatorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\HtaccessDaoFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Parameters\\SetupParameters;\n\nclass SetupInfrastructureValidator implements SetupInfrastructureValidatorInterface\n{\n    private SetupParameters $parameters;\n\n    public function __construct(\n        private readonly DirectoryValidatorInterface $directoriesValidator,\n        private readonly HtaccessDaoFactoryInterface $htaccessDaoFactory,\n        private readonly SetupDbConnectionValidatorInterface $setupDbConnectionValidator,\n        private readonly SetupDbValidatorInterface $setupDbValidator,\n    ) {\n    }\n\n    public function validate(SetupParameters $setupParameters): void\n    {\n        $this->parameters = $setupParameters;\n        $this->checkShopDirectoryPermissions();\n        $this->checkWebServerConfigFilePermissions();\n        $this->checkDatabaseServerConnectivity();\n        $this->checkDatabaseCanBeSetUp();\n    }\n\n    private function checkShopDirectoryPermissions(): void\n    {\n        $this->directoriesValidator\n            ->checkPathIsAbsolute(\n                $this->parameters->getCacheDir()\n            );\n        $this->directoriesValidator\n            ->validateDirectory(\n                $this->parameters->getCacheDir(),\n            );\n    }\n\n    private function checkWebServerConfigFilePermissions(): void\n    {\n        $this->htaccessDaoFactory->createRootHtaccessDao();\n    }\n\n    private function checkDatabaseServerConnectivity(): void\n    {\n        $this->setupDbConnectionValidator\n            ->validate(\n                $this->parameters->getDbConfig()\n            );\n    }\n\n    private function checkDatabaseCanBeSetUp(): void\n    {\n        $this->setupDbValidator\n            ->validate(\n                $this->parameters->getDbConfig()\n            );\n    }\n}\n"
  },
  {
    "path": "source/Internal/Setup/Validator/SetupInfrastructureValidatorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Setup\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Parameters\\SetupParameters;\n\ninterface SetupInfrastructureValidatorInterface\n{\n    public function validate(SetupParameters $setupParameters): void;\n}\n"
  },
  {
    "path": "source/Internal/Setup/Validator/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Setup\\Validator\\SetupInfrastructureValidatorInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Setup\\Validator\\SetupInfrastructureValidator"
  },
  {
    "path": "source/Internal/Setup/services.yaml",
    "content": "imports:\n  - { resource: Database/services.yaml }\n  - { resource: Directory/services.yaml }\n  - { resource: Htaccess/services.yaml }\n  - { resource: Language/services.yaml }\n  - { resource: Validator/services.yaml }\n  - { resource: Parameters/services.yaml }\n  - { resource: ShopConfiguration/services.yaml }\n\nservices:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Setup\\ShopSetupCommand:\n    class: OxidEsales\\EshopCommunity\\Internal\\Setup\\ShopSetupCommand\n    tags:\n      - { name: 'console.command', command: 'oe:setup:shop' }"
  },
  {
    "path": "source/Internal/Transition/Adapter/Email/EmailAdapterInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Email;\n\nuse OxidEsales\\EshopCommunity\\Core\\Email;\nuse Symfony\\Component\\Mime\\Email as SymfonyEmail;\n\ninterface EmailAdapterInterface\n{\n    public function convertToSymfonyEmail(Email $legacyEmail): SymfonyEmail;\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/Email/SymfonyMailerAdapter.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Email;\n\nuse OxidEsales\\EshopCommunity\\Core\\Email;\nuse PHPMailer\\PHPMailer\\PHPMailer;\nuse Symfony\\Component\\Mime\\Address;\nuse Symfony\\Component\\Mime\\Email as SymfonyEmail;\nuse Symfony\\Component\\Mime\\Part\\DataPart;\n\nclass SymfonyMailerAdapter implements EmailAdapterInterface\n{\n    public function convertToSymfonyEmail(Email $legacyEmail): SymfonyEmail\n    {\n        $email = new SymfonyEmail();\n        $cidMapping = $this->buildCidMapping($legacyEmail);\n\n        $this->addRecipients($email, $legacyEmail);\n        $this->addFrom($email, $legacyEmail);\n        $this->addReplyTo($email, $legacyEmail);\n        $this->addCc($email, $legacyEmail);\n        $this->addBcc($email, $legacyEmail);\n        $this->addSubject($email, $legacyEmail);\n        $this->addBody($email, $legacyEmail, $cidMapping);\n        $this->addAttachments($email, $legacyEmail, $cidMapping);\n        $this->addCustomHeaders($email, $legacyEmail);\n        $this->addPriority($email, $legacyEmail);\n        $this->addSender($email, $legacyEmail);\n\n        return $email;\n    }\n\n    private function buildCidMapping(Email $legacyEmail): array\n    {\n        $cidMapping = [];\n\n        foreach ($legacyEmail->getAttachments() as $attachment) {\n            $cid = $attachment[7] ?? null;\n            if (is_string($cid) && $cid !== '') {\n                $cidMapping[$cid] = str_contains($cid, '@') ? $cid : $cid . '@generated';\n            }\n        }\n\n        return $cidMapping;\n    }\n\n    private function addRecipients(SymfonyEmail $email, Email $legacyEmail): void\n    {\n        $recipients = $legacyEmail->getRecipient();\n        if (!is_array($recipients)) {\n            return;\n        }\n\n        foreach ($recipients as $recipient) {\n            $email->addTo(new Address($recipient[0], $recipient[1] ?? ''));\n        }\n    }\n\n    private function addFrom(SymfonyEmail $email, Email $legacyEmail): void\n    {\n        if (!$legacyEmail->getFrom()) {\n            return;\n        }\n\n        $email->from(new Address($legacyEmail->getFrom(), $legacyEmail->getFromName() ?? ''));\n    }\n\n    private function addReplyTo(SymfonyEmail $email, Email $legacyEmail): void\n    {\n        $replyTo = $legacyEmail->getReplyTo();\n        if (!is_array($replyTo)) {\n            return;\n        }\n\n        foreach ($replyTo as $entry) {\n            $email->addReplyTo(new Address($entry[0], $entry[1] ?? ''));\n        }\n    }\n\n    private function addCc(SymfonyEmail $email, Email $legacyEmail): void\n    {\n        foreach ($legacyEmail->getCc() as $entry) {\n            $email->addCc(new Address($entry[0], $entry[1] ?? ''));\n        }\n    }\n\n    private function addBcc(SymfonyEmail $email, Email $legacyEmail): void\n    {\n        foreach ($legacyEmail->getBcc() as $entry) {\n            $email->addBcc(new Address($entry[0], $entry[1] ?? ''));\n        }\n    }\n\n    private function addSubject(SymfonyEmail $email, Email $legacyEmail): void\n    {\n        $subject = $legacyEmail->getSubject();\n        if ($subject) {\n            $email->subject($subject);\n        }\n    }\n\n    private function addBody(SymfonyEmail $email, Email $legacyEmail, array $cidMapping): void\n    {\n        $isHtml = ($legacyEmail->ContentType ?? PHPMailer::CONTENT_TYPE_PLAINTEXT)\n            === PHPMailer::CONTENT_TYPE_TEXT_HTML;\n        $charset = $legacyEmail->getCharset() ?: PHPMailer::CHARSET_UTF8;\n        $body = $legacyEmail->getBody();\n\n        if ($body) {\n            if ($isHtml) {\n                $email->html($this->updateCidReferences($body, $cidMapping), $charset);\n            } else {\n                $email->text($body, $charset);\n            }\n        }\n\n        $altBody = $legacyEmail->getAltBody();\n        if ($isHtml && $altBody) {\n            $email->text($altBody, $charset);\n        }\n    }\n\n    private function updateCidReferences(string $body, array $cidMapping): string\n    {\n        foreach ($cidMapping as $originalCid => $normalizedCid) {\n            if ($originalCid !== $normalizedCid) {\n                $body = str_replace('cid:' . $originalCid, 'cid:' . $normalizedCid, $body);\n            }\n        }\n\n        return $body;\n    }\n\n    private function addAttachments(SymfonyEmail $email, Email $legacyEmail, array $cidMapping): void\n    {\n        foreach ($legacyEmail->getAttachments() as $attachment) {\n            $this->addAttachment(\n                $email,\n                $attachment[0],\n                $attachment[2] ?? '',\n                $attachment[4] ?? null,\n                $attachment[6] ?? 'attachment',\n                $this->resolveAttachmentCid($attachment[7] ?? null, $cidMapping),\n                (bool) ($attachment[5] ?? false)\n            );\n        }\n    }\n\n    private function resolveAttachmentCid(mixed $cid, array $cidMapping): ?string\n    {\n        if (!is_string($cid) || $cid === '') {\n            return null;\n        }\n\n        return $cidMapping[$cid] ?? $cid;\n    }\n\n    private function addAttachment(\n        SymfonyEmail $email,\n        string $content,\n        string $name,\n        ?string $mimeType,\n        string $disposition,\n        ?string $cid,\n        bool $isString\n    ): void {\n        if ($disposition === 'inline' && $cid !== null) {\n            $part = $isString\n                ? (new DataPart($content, $name, $mimeType))->asInline()\n                : DataPart::fromPath($content, $name, $mimeType)->asInline();\n            $part->setContentId($cid);\n            $email->addPart($part);\n            return;\n        }\n\n        if ($isString) {\n            $email->attach($content, $name, $mimeType);\n        } else {\n            $email->attachFromPath($content, $name, $mimeType);\n        }\n    }\n\n    private function addCustomHeaders(SymfonyEmail $email, Email $legacyEmail): void\n    {\n        foreach ($legacyEmail->getCustomHeaders() as $header) {\n            $email->getHeaders()->addTextHeader($header[0], $header[1]);\n        }\n    }\n\n    private function addPriority(SymfonyEmail $email, Email $legacyEmail): void\n    {\n        $priority = $legacyEmail->Priority ?? null;\n        if ($priority === null) {\n            return;\n        }\n\n        $email->priority(match ($priority) {\n            1 => SymfonyEmail::PRIORITY_HIGHEST,\n            2 => SymfonyEmail::PRIORITY_HIGH,\n            4 => SymfonyEmail::PRIORITY_LOW,\n            5 => SymfonyEmail::PRIORITY_LOWEST,\n            default => SymfonyEmail::PRIORITY_NORMAL,\n        });\n    }\n\n    private function addSender(SymfonyEmail $email, Email $legacyEmail): void\n    {\n        $sender = $legacyEmail->Sender ?? null;\n        if ($sender) {\n            $email->returnPath($sender);\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/Exception/TranslationNotFoundException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Exception;\n\n/**\n * Class TranslationNotFoundException\n *\n * @package OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\Exception\n */\nclass TranslationNotFoundException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/Session/SessionAdapter.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Session;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Session\\SessionInterface;\n\nclass SessionAdapter implements SessionInterface\n{\n    public function get(string $name, mixed $default = null): mixed\n    {\n        if (!$this->has($name)) {\n            return $default;\n        }\n\n        return Registry::getSession()->getVariable($name);\n    }\n\n    public function has(string $name): bool\n    {\n        return Registry::getSession()->hasVariable($name);\n    }\n\n    public function set(string $name, mixed $value): void\n    {\n        Registry::getSession()->setVariable($name, $value);\n    }\n\n    public function remove(string $name): mixed\n    {\n        if (!$this->has($name)) {\n            return null;\n        }\n\n        $value = $this->get($name);\n        Registry::getSession()->deleteVariable($name);\n\n        return $value;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/ShopAdapter.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter;\n\nuse OxidEsales\\Eshop\\Core\\NamespaceInformationProvider;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Routing\\ShopControllerMapProvider;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse OxidEsales\\Eshop\\Core\\Theme;\nuse OxidEsales\\Eshop\\Application\\Model\\Shop;\n\nclass ShopAdapter implements ShopAdapterInterface\n{\n    public function translateString($string): string\n    {\n        return Registry::getLang()->translateString($string);\n    }\n\n    /**\n     * @deprecated since v7.0.0 (2023-03-14).\n     * Please use OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Cache\\ModuleCacheInterface instead.\n     */\n    public function invalidateModuleCache(string $moduleId): void\n    {\n        /**\n         * @TODO we have to implement it in ModuleCacheServiceInterface or use ModuleCache::resetCache() method.\n         */\n\n        $this->invalidateModulesCache();\n    }\n\n    /**\n     * @deprecated since v7.0.0 (2023-03-14).\n     * Please use OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\ShopCacheCleanerInterface instead.\n     */\n    public function invalidateModulesCache(): void\n    {\n        $utils = Registry::getUtils();\n        $utils->resetLanguageCache();\n        $utils->resetMenuCache();\n        $utils->oxResetFileCache(true);\n\n        if (extension_loaded('apc') && ini_get('apc.enabled')) {\n            apc_clear_cache();\n        }\n    }\n\n    /**\n     * @deprecated use Id::generate() instead\n     */\n    public function generateUniqueId(): string\n    {\n        return Registry::getUtilsObject()->generateUId();\n    }\n\n    public function getShopControllerClassMap(): array\n    {\n        return oxNew(ShopControllerMapProvider::class)->getControllerMap();\n    }\n\n    public function isNamespace(string $namespace): bool\n    {\n        return NamespaceInformationProvider::isNamespacedClass($namespace);\n    }\n\n    public function isShopUnifiedNamespace(string $namespace): bool\n    {\n        return NamespaceInformationProvider::classBelongsToShopUnifiedNamespace($namespace);\n    }\n\n    public function isShopEditionNamespace(string $namespace): bool\n    {\n        return NamespaceInformationProvider::classBelongsToShopEditionNamespace($namespace);\n    }\n\n    public function validateShopId(int $shopId): bool\n    {\n        $shopModel = oxNew(Shop::class);\n        $shopModel->load($shopId);\n        return $shopModel->isLoaded();\n    }\n\n    /**\n     * Get active themes list.\n     * Examples:\n     *      if flow theme is active we will get ['flow']\n     *      if azure is extended by some other we will get ['azure', 'extending_theme']\n     *\n     * @return array\n     */\n    public function getActiveThemesList(): array\n    {\n        $config = Registry::getConfig();\n\n        $activeThemeList = [];\n        if (!$config->isAdmin()) {\n            $activeThemeList[] = $config->getConfigParam('sTheme');\n\n            if ($customThemeId = $config->getConfigParam('sCustomTheme')) {\n                $activeThemeList[] = $customThemeId;\n            }\n        }\n\n        return $activeThemeList;\n    }\n\n    public function getCustomTheme(): string\n    {\n        return (string)Registry::getConfig()->getConfigParam('sCustomTheme');\n    }\n\n    public function getActiveThemeId(): string\n    {\n        $customTheme = Registry::getConfig()->getConfigParam('sCustomTheme');\n        if ($customTheme) {\n            return $customTheme;\n        }\n\n        return (string)Registry::getConfig()->getConfigParam('sTheme');\n    }\n\n    public function themeExists(string $themeId): bool\n    {\n        return oxNew(Theme::class)->load($themeId);\n    }\n\n    public function activateTheme(string $themeId): void\n    {\n        $theme = oxNew(Theme::class);\n        $theme->load($themeId);\n        $theme->activate();\n    }\n\n    public function generateDatabaseViewName(string $tableName, int $languageId, int $shopId): string\n    {\n        return oxNew(TableViewNameGenerator::class)->getViewName($tableName, $languageId, $shopId);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/ShopAdapterInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter;\n\ninterface ShopAdapterInterface\n{\n    public function translateString($string): string;\n\n    public function invalidateModuleCache(string $moduleId);\n\n    public function invalidateModulesCache();\n\n    /**\n     * @deprecated use Id::generate() instead\n     */\n    public function generateUniqueId(): string;\n\n    public function getShopControllerClassMap(): array;\n\n    public function isNamespace(string $namespace): bool;\n\n    public function isShopUnifiedNamespace(string $namespace): bool;\n\n    public function isShopEditionNamespace(string $namespace): bool;\n\n    public function validateShopId(int $shopId): bool;\n\n    public function getActiveThemesList(): array;\n\n    public function getCustomTheme(): string;\n\n    public function getActiveThemeId(): string;\n\n    public function themeExists(string $themeId): bool;\n\n    public function activateTheme(string $themeId): void;\n\n    public function generateDatabaseViewName(string $tableName, int $languageId, int $shopId): string;\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/AbstractInsertNewBasketItemLogic.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nabstract class AbstractInsertNewBasketItemLogic\n{\n    /**\n     * @param array  $params\n     * @param object $templateEngine\n     *\n     * @return string\n     */\n    public function getNewBasketItemTemplate(array $params, $templateEngine): string\n    {\n        if (!$this->validateTemplateEngine($templateEngine)) {\n            throw new \\Exception('Please check if correct template engine is used.');\n        }\n        $renderedTemplate = '';\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        $types = ['0' => 'none', '1' => 'message', '2' => 'popup', '3' => 'basket'];\n        $newBasketItemMessage = $config->getConfigParam('iNewBasketItemMessage');\n\n        // If correct type of message is expected\n        if (\n            $newBasketItemMessage && $params['type']\n            && (!isset($types[$newBasketItemMessage])\n                || $params['type'] != $types[$newBasketItemMessage])\n        ) {\n            $correctMessageType = false;\n        } else {\n            $correctMessageType = true;\n        }\n\n        // name of template file where is stored message text\n        $templateName = $params['tpl'] ? $params['tpl'] : 'inc_newbasketitem.snippet.html.twig';\n\n        // always render for ajaxstyle popup\n        $render = isset($params['ajax']) && $params['ajax'] && ($newBasketItemMessage == 2);\n\n        // fetching article data\n        $newItem = \\OxidEsales\\Eshop\\Core\\Registry::getSession()->getVariable('_newitem');\n\n        if ($newItem && $correctMessageType) {\n            $this->loadArticleObject($newItem, $templateEngine);\n            $render = true;\n        }\n\n        // returning generated message content\n        if ($render && $correctMessageType) {\n            $renderedTemplate = $this->renderTemplate($templateName, $templateEngine);\n        }\n\n        return $renderedTemplate;\n    }\n\n    /**\n     * @param object $templateEngine\n     *\n     * @return mixed\n     */\n    abstract protected function validateTemplateEngine($templateEngine);\n\n    /**\n     * @param object $newItem\n     * @param object $templateEngine\n     *\n     * @return mixed\n     */\n    abstract protected function loadArticleObject($newItem, $templateEngine);\n\n    /**\n     * @param string $templateName\n     * @param object $templateEngine\n     *\n     * @return mixed\n     */\n    abstract protected function renderTemplate(string $templateName, $templateEngine);\n\n    public function getGlobals(): array\n    {\n        return [];\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/AddUrlParametersLogic.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse OxidEsales\\Eshop\\Core\\Str;\n\nclass AddUrlParametersLogic\n{\n    /**\n     * Add additional parameters to SEO url\n     *\n     * @param string $sUrl       Url\n     * @param string $sDynParams Dynamic URL parameters\n     *\n     * @return string\n     */\n    public function addUrlParameters(string $sUrl, string $sDynParams): string\n    {\n        // removing empty parameters\n        $sDynParams = $sDynParams ? Str::getStr()->preg_replace(['/^\\?/', '/^\\&(amp;)?$/'], '', $sDynParams) : false;\n        if ($sDynParams) {\n            $sUrl .= ((str_contains($sUrl, '?')) ? \"&amp;\" : \"?\") . $sDynParams;\n        }\n\n        return \\OxidEsales\\Eshop\\Core\\Registry::getUtilsUrl()->processSeoUrl($sUrl);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/ContentFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Content;\n\nclass ContentFactory\n{\n    /**\n     * @param string $key\n     * @param string $value\n     *\n     * @return null|Content\n     * @throws \\Exception\n     */\n    public function getContent(string $key, string $value): ?Content\n    {\n        $content = oxNew(Content::class);\n\n        if ($key == 'ident') {\n            $isLoaded = $content->loadbyIdent($value);\n        } elseif ($key == 'oxid') {\n            $isLoaded = $content->load($value);\n        } else {\n            throw new \\Exception(\"Cannot load content. Not provided neither ident nor oxid.\");\n        }\n\n        return $isLoaded ? $content : null;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/FileSizeLogic.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nclass FileSizeLogic\n{\n    /**\n     * @param int $size\n     *\n     * @return string\n     */\n    public function getFileSize($size): string\n    {\n        if ($size < 1024) {\n            return $size . \" B\";\n        }\n\n        $size = $size / 1024;\n\n        if ($size < 1024) {\n            return sprintf(\"%.1f KB\", $size);\n        }\n\n        $size = $size / 1024;\n\n        if ($size < 1024) {\n            return sprintf(\"%.1f MB\", $size);\n        }\n\n        $size = $size / 1024;\n\n        return sprintf(\"%.1f GB\", $size);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/FormatCurrencyLogic.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse stdClass;\n\nclass FormatCurrencyLogic\n{\n    /**\n     * @param string $sFormat\n     * @param string|int $sValue\n     *\n     * @return string\n     */\n    public function numberFormat($sFormat = \"EUR@ 1.00@ ,@ .@ EUR@ 2\", $sValue = 0)\n    {\n        // logic copied from \\OxidEsales\\Eshop\\Core\\Config::getCurrencyArray()\n        $sCur = explode(\"@\", $sFormat);\n        $oCur = new stdClass();\n        $oCur->id = 0;\n        $oCur->name = @trim($sCur[0]);\n        $oCur->rate = @trim($sCur[1]);\n        $oCur->dec = @trim($sCur[2]);\n        $oCur->thousand = @trim($sCur[3]);\n        $oCur->sign = @trim($sCur[4]);\n        $oCur->decimal = @trim($sCur[5]);\n\n        // change for US version\n        if (isset($sCur[6])) {\n            $oCur->side = @trim($sCur[6]);\n        }\n\n        return \\OxidEsales\\Eshop\\Core\\Registry::getLang()->formatCurrency($sValue, $oCur);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/FormatDateLogic.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nclass FormatDateLogic\n{\n    /**\n     * @param object|string $oConvObject\n     * @param string|null $sFieldType\n     * @param bool $blPassedValue\n     *\n     * @return string|null\n     */\n    public function formdate($oConvObject, ?string $sFieldType = null, bool $blPassedValue = false): ?string\n    {\n        // creating fake bject\n        if ($blPassedValue || is_string($oConvObject)) {\n            $sValue = $oConvObject;\n            $oConvObject = new \\OxidEsales\\Eshop\\Core\\Field();\n            $oConvObject->fldmax_length = \"0\";\n            $oConvObject->fldtype = $sFieldType;\n            $oConvObject->setValue($sValue);\n        }\n\n        $myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n        // if such format applies to this type of field - sets formatted value to passed object\n        if (!$myConfig->getConfigParam('blSkipFormatConversion')) {\n            if (($oConvObject && $oConvObject->fldtype == \"datetime\") || $sFieldType == \"datetime\") {\n                \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->convertDBDateTime($oConvObject);\n            } elseif (($oConvObject && $oConvObject->fldtype == \"timestamp\") || $sFieldType == \"timestamp\") {\n                \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->convertDBTimestamp($oConvObject);\n            } elseif (($oConvObject && $oConvObject->fldtype == \"date\") || $sFieldType == \"date\") {\n                \\OxidEsales\\Eshop\\Core\\Registry::getUtilsDate()->convertDBDate($oConvObject);\n            }\n        }\n\n        return $oConvObject->value ?? '';\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/FormatPriceLogic.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse OxidEsales\\Eshop\\Core\\Price;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\nclass FormatPriceLogic\n{\n    /**\n     * @param array $params\n     *\n     * @return string\n     */\n    public function formatPrice(array $params): string\n    {\n        $output = '';\n        $inputPrice = $params['price'];\n        if (!is_null($inputPrice)) {\n            $output = $this->calculatePrice($inputPrice, $params);\n        }\n\n        return $output;\n    }\n\n    /**\n     * @param mixed $inputPrice\n     * @param array $params\n     *\n     * @return string\n     */\n    private function calculatePrice($inputPrice, array $params): string\n    {\n        $config = Registry::getConfig();\n        $price = ($inputPrice instanceof Price) ? $inputPrice->getPrice() : (float) $inputPrice;\n        $currency = isset($params['currency']) ? (object) $params['currency'] : $config->getActShopCurrencyObject();\n        $output = '';\n\n        if (is_numeric($price)) {\n            $output = $this->getFormattedPrice($currency, $price);\n        }\n\n        return $output;\n    }\n\n    /**\n     * @param object $currency active currency object\n     * @param mixed  $price\n     *\n     * @return string\n     */\n    private function getFormattedPrice($currency, $price): string\n    {\n        $output = '';\n        $decimalSeparator = $currency->dec ?? ',';\n        $thousandsSeparator = $currency->thousand ?? '.';\n        $currencySymbol = $currency->sign ?? '';\n        $currencySymbolLocation = $currency->side ?? '';\n        $decimals = isset($currency->decimal) ? (int) $currency->decimal : 2;\n\n        if ((float) $price > 0 || $currencySymbol) {\n            $price = number_format($price, $decimals, $decimalSeparator, $thousandsSeparator);\n            $output = (isset($currencySymbolLocation) && $currencySymbolLocation == 'Front')\n                ? $currencySymbol . $price\n                : $price . ' ' . $currencySymbol;\n        }\n\n        return trim($output);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/FormatTimeLogic.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nclass FormatTimeLogic\n{\n    /**\n     * @param int $seconds\n     *\n     * @return string\n     */\n    public function getFormattedTime(int $seconds): string\n    {\n        $hours = floor($seconds / 3600);\n        $minutes = floor($seconds % 3600 / 60);\n        $seconds = $seconds % 60;\n\n        return sprintf(\"%02d:%02d:%02d\", $hours, $minutes, $seconds);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/IfContentLogic.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nclass IfContentLogic\n{\n    /**\n     * @param string|null $sIdent\n     * @param string|null $sOxid\n     *\n     * @return mixed\n     */\n    public function getContent(?string $sIdent = null, ?string $sOxid = null)\n    {\n        static $aContentCache = [];\n\n        if (\n            ($sIdent && isset($aContentCache[$sIdent])) ||\n            ($sOxid && isset($aContentCache[$sOxid]))\n        ) {\n            $oContent = $sOxid ? $aContentCache[$sOxid] : $aContentCache[$sIdent];\n        } else {\n            $oContent = oxNew(\"oxContent\");\n            $blLoaded = $sOxid ? $oContent->load($sOxid) : ($oContent->loadbyIdent($sIdent));\n            if ($blLoaded && $oContent->isActive()) {\n                $aContentCache[$oContent->getId()] = $aContentCache[$oContent->getLoadId()] = $oContent;\n            } else {\n                $oContent = false;\n                if ($sOxid) {\n                    $aContentCache[$sOxid] = $oContent;\n                } else {\n                    $aContentCache[$sIdent] = $oContent;\n                }\n            }\n        }\n\n        return $oContent;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/IncludeDynamicLogic.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nclass IncludeDynamicLogic\n{\n    /**\n     * @param array $parameters\n     *\n     * @return string\n     */\n    public function renderForCache(array $parameters): string\n    {\n        $content = \"<oxid_dynamic>\";\n\n        foreach ($parameters as $key => $value) {\n            $content .= \" $key='\" . base64_encode($value) . \"'\";\n        }\n\n        return $content . \"</oxid_dynamic>\";\n    }\n\n    /**\n     * @param array $parameters\n     *\n     * @return array\n     */\n    public function includeDynamicPrefix(array $parameters): array\n    {\n        $prefix = \"_\";\n        if (array_key_exists('type', $parameters)) {\n            $prefix .= $parameters['type'] . \"_\";\n        }\n        foreach ($parameters as $key => $value) {\n            unset($parameters[$key]);\n            if ($key != 'type' && $key != 'file') {\n                $parameters[$prefix . $key] = $value;\n            }\n        }\n\n        return $parameters;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/IncludeWidgetLogic.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse OxidEsales\\EshopCommunity\\Core\\WidgetControl;\n\nclass IncludeWidgetLogic\n{\n    /**\n     * @param array $params\n     *\n     * @return mixed\n     */\n    public function renderWidget(array $params)\n    {\n        $class = '';\n        if (isset($params['cl'])) {\n            $class = strtolower($params['cl']);\n            unset($params['cl']);\n        }\n\n        $parentViews = null;\n        if (!empty($params[\"_parent\"])) {\n            $parentViews = explode(\"|\", $params[\"_parent\"]);\n            unset($params[\"_parent\"]);\n        }\n\n        /**\n         * @var WidgetControl $widgetControl\n         */\n        $widgetControl = \\OxidEsales\\Eshop\\Core\\Registry::get(\\OxidEsales\\Eshop\\Core\\WidgetControl::class);\n\n        return $widgetControl->start($class, null, $params, $parentViews);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/InputHelpLogic.php",
    "content": "<?php\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse OxidEsales\\Eshop\\Core\\Exception\\StandardException;\n\nclass InputHelpLogic\n{\n    /**\n     * @param array $params\n     *\n     * @return null\n     */\n    public function getIdent($params)\n    {\n        return $params['ident'] ?? null;\n    }\n\n    /**\n     * @param array $params\n     *\n     * @return mixed\n     */\n    public function getTranslation($params)\n    {\n        $ident = $this->getIdent($params);\n        $lang = \\OxidEsales\\Eshop\\Core\\Registry::getLang();\n        $tplLanguage = $lang->getTplLanguage();\n        $config = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n        $isAdmin = $config->isAdmin();\n\n        return $lang->translateString($ident, $tplLanguage, $isAdmin);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/InsertNewBasketItemLogicTwig.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\ViewConfig;\nuse Twig\\Environment;\n\nclass InsertNewBasketItemLogicTwig extends AbstractInsertNewBasketItemLogic\n{\n    /**\n     * @param Environment $templateEngine\n     *\n     * @return bool\n     */\n    protected function validateTemplateEngine($templateEngine): bool\n    {\n        return $templateEngine instanceof Environment;\n    }\n\n    /**\n     * @param object      $newItem\n     * @param Environment $templateEngine\n     */\n    protected function loadArticleObject($newItem, $templateEngine)\n    {\n        // loading article object here because on some system passing article by session causes problems\n        $newItem->oArticle = oxNew(Article::class);\n        $newItem->oArticle->load($newItem->sId);\n\n        // passing variable to template with unique name\n        $templateEngine->addGlobal('_newitem', clone $newItem);\n\n        // deleting article object data\n        Registry::getSession()->deleteVariable('_newitem');\n    }\n\n    /**\n     * @param string      $templateName\n     * @param Environment $templateEngine\n     *\n     * @return string\n     */\n    protected function renderTemplate(string $templateName, $templateEngine): string\n    {\n        $template = $templateEngine->load($templateName);\n\n        return $template->render();\n    }\n\n    public function getGlobals(): array\n    {\n        return [\n            '_newitem' => null,\n            'oViewConf' => oxNew(ViewConfig::class),\n            'oxcmp_basket' => Registry::getSession()->getBasket(),\n        ];\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/ScriptLogic.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nclass ScriptLogic\n{\n    /**\n     * @param string $script\n     * @param bool   $isDynamic\n     */\n    public function add(string $script, bool $isDynamic = false): void\n    {\n        $register = oxNew(\\OxidEsales\\Eshop\\Core\\ViewHelper\\JavaScriptRegistrator::class);\n        $register->addSnippet($script, $isDynamic);\n    }\n\n    /**\n     * @param string $file\n     * @param int    $priority\n     * @param bool   $isDynamic\n     */\n    public function include(string $file, int $priority = 3, bool $isDynamic = false): void\n    {\n        $register = oxNew(\\OxidEsales\\Eshop\\Core\\ViewHelper\\JavaScriptRegistrator::class);\n        $register->addFile($file, $priority, $isDynamic);\n    }\n\n    /**\n     * @param string $widget\n     * @param bool   $forceRender\n     * @param bool   $isDynamic\n     *\n     * @return string\n     */\n    public function render(string $widget, bool $forceRender = false, bool $isDynamic = false): string\n    {\n        $renderer = oxNew(\\OxidEsales\\Eshop\\Core\\ViewHelper\\JavaScriptRenderer::class);\n\n        return $renderer->render($widget, $forceRender, $isDynamic);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/SeoUrlLogic.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\n\nclass SeoUrlLogic\n{\n    /**\n     * Output SEO style url\n     *\n     * @param array $params\n     *\n     * @return string\n     */\n    public function seoUrl(array $params): string\n    {\n        $sOxid = $params['oxid'] ?? null;\n        $sType = $params['type'] ?? null;\n        $sUrl = $sIdent = $params['ident'] ?? null;\n\n        // requesting specified object SEO url\n        if ($sType) {\n            $oObject = oxNew($sType);\n\n            // special case for content type object when ident is provided\n            if ($sType == 'oxcontent' && $sIdent && $oObject->loadByIdent($sIdent)) {\n                $sUrl = $oObject->getLink();\n            } elseif ($sOxid) {\n                // minimising aricle object loading\n                if (strtolower($sType) == \"oxarticle\") {\n                    $oObject->disablePriceLoad();\n                    $oObject->setNoVariantLoading(true);\n                }\n\n                if ($oObject->load($sOxid)) {\n                    $sUrl = $oObject->getLink();\n                }\n            }\n        } elseif ($sUrl && Registry::getUtils()->seoIsActive()) {\n            // if SEO is on ..\n            $sStaticUrl = Registry::getSeoEncoder()->getStaticUrl($sUrl);\n            if ($sStaticUrl) {\n                $sUrl = $sStaticUrl;\n            } else {\n                // in case language parameter is not added to url\n                $sUrl = Registry::getUtilsUrl()->processUrl($sUrl);\n            }\n        }\n\n        return $sUrl ?: '';\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/SmartWordwrapLogic.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nclass SmartWordwrapLogic\n{\n    /**\n     * @param string $string\n     * @param int    $length\n     * @param string $break\n     * @param int    $cutRows\n     * @param int    $tolerance\n     * @param string $etc\n     *\n     * @return string\n     */\n    public function wrapWords($string, $length, $break, $cutRows, $tolerance, $etc)\n    {\n        $wrapTag = \"<wrap>\";\n        $wrapChars = [\"-\"];\n        $afterWrapChars = [\"-\" . $wrapTag];\n\n\n        $string = trim($string);\n\n        if (strlen($string) <= $length) {\n            return $string;\n        }\n\n        // trying to wrap without cut\n        $str = wordwrap($string, $length, $wrapTag, false);\n        $arr = explode($wrapTag, $str);\n\n        $alt = [];\n\n        $ok = true;\n        foreach ($arr as $row) {\n            if (strlen($row) > ($length + $tolerance)) {\n                $tmpstr = str_replace($wrapChars, $afterWrapChars, $row);\n                $tmparr = explode($wrapTag, $tmpstr);\n\n                foreach ($tmparr as $altrow) {\n                    array_push($alt, $altrow);\n\n                    if (strlen($altrow) > ($length + $tolerance)) {\n                        $ok = false;\n                    }\n                }\n            } else {\n                array_push($alt, $row);\n            }\n        }\n\n        $arr = $alt;\n\n        if (!$ok) {\n            // trying to wrap with cut\n            $str = wordwrap($string, $length, $wrapTag, true);\n            $arr = explode($wrapTag, $str);\n        }\n\n        if ($cutRows && count($arr) > $cutRows) {\n            $arr = array_splice($arr, 0, $cutRows);\n\n            if (strlen($arr[$cutRows - 1] . $etc) > $length + $tolerance) {\n                $arr[$cutRows - 1] = substr($arr[$cutRows - 1], 0, $length - strlen($etc));\n            }\n\n            $arr[$cutRows - 1] = $arr[$cutRows - 1] . $etc;\n        }\n\n        return implode($break, $arr);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/StyleLogic.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nclass StyleLogic\n{\n    /**\n     * @param array $params\n     * @param bool  $isDynamic\n     *\n     * @return string\n     */\n    public function collectStyleSheets($params, $isDynamic)\n    {\n        $params = $this->fillDefaultParams($params);\n\n        return $this->getOutput($params, $isDynamic);\n    }\n\n    /**\n     * @param array $params\n     *\n     * @return array\n     */\n    private function fillDefaultParams($params)\n    {\n        $defaults = [\n            'widget'   => '',\n            'inWidget' => false,\n            'if'       => null,\n            'include'  => null,\n        ];\n\n        return array_merge($defaults, $params);\n    }\n\n    /**\n     * @param array $params\n     * @param bool  $isDynamic\n     *\n     * @return string\n     */\n    private function getOutput($params, $isDynamic)\n    {\n        $output = '';\n        $widget = $params['widget'];\n        $forceRender = $params['inWidget'];\n        if (!empty($params['include'])) {\n            $registrator = oxNew(\\OxidEsales\\Eshop\\Core\\ViewHelper\\StyleRegistrator::class);\n            $registrator->addFile($params['include'], $params['if'], $isDynamic);\n        } else {\n            $renderer = oxNew(\\OxidEsales\\Eshop\\Core\\ViewHelper\\StyleRenderer::class);\n            $output = $renderer->render($widget, $forceRender, $isDynamic);\n        }\n\n        return $output;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/TranslateFilterLogic.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Exception\\TranslationNotFoundException;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Translator\\TranslatorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\Eshop\\Core\\Exception\\StandardException;\n\nclass TranslateFilterLogic\n{\n    /**\n     * @var TranslatorInterface\n     */\n    private $translator;\n\n    /**\n     * @var ContextInterface\n     */\n    private $context;\n\n    /**\n     * TranslateFilterLogic constructor.\n     * @param ContextInterface $context\n     * @param TranslatorInterface           $translator\n     */\n    public function __construct(ContextInterface $context, TranslatorInterface $translator)\n    {\n        $this->context = $context;\n        $this->translator = $translator;\n    }\n\n    /**\n     * @param string $ident\n     * @param mixed  $args\n     *\n     * @return string\n     */\n    public function multiLang($ident, $args = []): string\n    {\n        $ident = isset($ident) ? $ident : 'IDENT MISSING';\n\n        $translation = $ident;\n        $translationFound = true;\n\n        try {\n            $translation = $this->translator->translate($ident);\n        } catch (TranslationNotFoundException $exception) {\n            $translationFound = false;\n        }\n\n        if ($translationFound) {\n            $translation = $this->assignArgumentsToTranslation($translation, $args);\n        } elseif (!$this->context->isShopInProductiveMode()) {\n            $translation = 'ERROR: Translation for ' . $ident . ' not found!';\n        }\n\n        return $translation;\n    }\n\n    /**\n     * @param string $translation\n     * @param mixed  $args\n     * @return string\n     */\n    private function assignArgumentsToTranslation(string $translation, $args): string\n    {\n        if ($args) {\n            if (is_array($args)) {\n                $translation = vsprintf($translation, $args);\n            } else {\n                $translation = sprintf($translation, $args);\n            }\n        }\n        return $translation;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/TranslateFunctionLogic.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Exception\\TranslationNotFoundException;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Translator\\TranslatorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\Eshop\\Core\\Exception\\StandardException;\n\nuse function is_array;\n\nclass TranslateFunctionLogic\n{\n    /**\n     * @var TranslatorInterface\n     */\n    private $translator;\n\n    /**\n     * @var ContextInterface\n     */\n    private $context;\n\n    /**\n     * TranslateFunctionLogic constructor.\n     * @param ContextInterface $context\n     * @param TranslatorInterface           $translator\n     */\n    public function __construct(ContextInterface $context, TranslatorInterface $translator)\n    {\n        $this->context = $context;\n        $this->translator = $translator;\n    }\n\n    /**\n     * @param array $params\n     *\n     * @return string\n     */\n    public function getTranslation(array $params): string\n    {\n        $ident = $params['ident'] ?? 'IDENT MISSING';\n        $suffix = $params['suffix'] ?? 'NO_SUFFIX';\n        $translation = $ident;\n        $suffixTranslation = $suffix;\n        $translationFound = true;\n\n        try {\n            $translation = $this->translator->translate($ident);\n            if ($this->isTranslatableSuffix($suffix)) {\n                $suffixTranslation = $this->translator->translate($suffix);\n            }\n        } catch (TranslationNotFoundException $exception) {\n            $translationFound = false;\n        }\n\n        if (!$translationFound && isset($params['alternative'])) {\n            $translation = $params['alternative'];\n            $translationFound = true;\n        }\n        if ($translationFound) {\n            $translation = $this->assignArgumentsToTranslation($translation, $params);\n            if ($this->isTranslatableSuffix($suffix)) {\n                $translation .= $suffixTranslation;\n            }\n        } elseif ($this->showError($params)) {\n            $translation = sprintf(\n                'ERROR: Translation for %s%s not found!',\n                $ident,\n                $this->isTranslatableSuffix($suffixTranslation) ? $suffixTranslation : ''\n            );\n        } else {\n            Registry::getLogger()->warning(\n                \"translation for $ident not found\"\n            );\n        }\n\n        return $translation;\n    }\n\n    private function isTranslatableSuffix(string $suffix): bool\n    {\n        return !empty($suffix) && $suffix !== 'NO_SUFFIX';\n    }\n\n    private function assignArgumentsToTranslation(string $translation, array $params): string\n    {\n        if (isset($params['args']) && $params['args'] !== false) {\n            $translation = is_array($params['args']) ?\n                vsprintf($translation, $params['args']) :\n                sprintf($translation, $params['args']);\n        }\n        return $translation;\n    }\n\n    /**\n     * @param array $params\n     * @return bool\n     */\n    private function showError(array $params): bool\n    {\n        $showError = isset($params['noerror']) ? !$params['noerror'] : true;\n        if (!$this->context->isAdmin() && $this->context->isShopInProductiveMode()) {\n            $showError = false;\n        }\n        return $showError;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/TranslateSalutationLogic.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Exception\\TranslationNotFoundException;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Translator\\TranslatorInterface;\nuse OxidEsales\\Eshop\\Core\\Exception\\StandardException;\n\nclass TranslateSalutationLogic\n{\n    /**\n     * @var TranslatorInterface\n     */\n    private $translator;\n\n    /**\n     * TranslateSalutationLogic constructor.\n     * @param TranslatorInterface           $translator\n     */\n    public function __construct(TranslatorInterface $translator)\n    {\n        $this->translator = $translator;\n    }\n\n    /**\n     * @param string $ident\n     *\n     * @return string\n     */\n    public function translateSalutation(string $ident = ''): string\n    {\n        $translation = $ident;\n        try {\n            $translation = $this->translator->translate($ident);\n        } catch (TranslationNotFoundException) {\n        }\n\n        return $translation;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/TruncateLogic.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse OxidEsales\\Eshop\\Core\\Str;\n\nclass TruncateLogic\n{\n    /**\n     * @param string|null $sString\n     * @param int $iLength\n     * @param string $sSufix\n     * @param bool $blBreakWords\n     * @param bool $middle\n     *\n     * @return string\n     */\n    public function truncate(\n        ?string $sString = null,\n        int $iLength = 80,\n        string $sSufix = '...',\n        bool $blBreakWords = false,\n        bool $middle = false\n    ): string {\n        if ($iLength == 0) {\n            return '';\n        } elseif ($iLength > 0 && Str::getStr()->strlen($sString) > $iLength) {\n            $iLength -= Str::getStr()->strlen($sSufix);\n\n            $sString = str_replace(['&#039;', '&quot;'], [\"'\", '\"'], $sString);\n\n            if (!$blBreakWords) {\n                $sString = Str::getStr()->preg_replace(\n                    '/\\s+?(\\S+)?$/',\n                    '',\n                    Str::getStr()->substr($sString, 0, $iLength + 1)\n                );\n            }\n\n            $sString = Str::getStr()->substr($sString, 0, $iLength) . $sSufix;\n\n            return str_replace([\"'\", '\"'], ['&#039;', '&quot;'], $sString);\n        }\n\n        return $sString ?: '';\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/WordwrapLogic.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse OxidEsales\\Eshop\\Core\\Str;\n\nclass WordwrapLogic\n{\n    /**\n     * @param string  $string\n     * @param integer $length\n     * @param string  $wrapper\n     * @param bool    $cut\n     *\n     * @return string\n     */\n    public function wordwrap(string $string, int $length = 80, string $wrapper = \"\\n\", bool $cut = false): string\n    {\n        return Str::getStr()->wordwrap($string, $length, $wrapper, $cut);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/TemplateLogic/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\ContentFactory:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\SeoUrlLogic:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\AddUrlParametersLogic:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\IncludeDynamicLogic:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\IfContentLogic:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\FormatPriceLogic:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\ScriptLogic:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\FormatDateLogic:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\TranslateFilterLogic:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\TranslateFunctionLogic:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\FormatTimeLogic:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\FileSizeLogic:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\StyleLogic:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\TruncateLogic:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\TranslateSalutationLogic:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\FormatCurrencyLogic:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\WordwrapLogic:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\IncludeWidgetLogic:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\SmartWordwrapLogic:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\InputHelpLogic:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\InsertNewBasketItemLogicTwig:\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/Translator/LegacyTemplateTranslator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Translator;\n\nuse OxidEsales\\Eshop\\Core\\Language;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Exception\\TranslationNotFoundException;\n\nclass LegacyTemplateTranslator implements TranslatorInterface\n{\n    public function __construct(private Language $language)\n    {\n    }\n\n    /**\n     * @param string $string\n     * @return string\n     * @throws TranslationNotFoundException\n     */\n    public function translate(string $string): string\n    {\n        $isAdmin = $this->language->isAdmin();\n        $tplLang = $this->language->getTplLanguage();\n        $translation = $this->language->translateString($string, $tplLang, $isAdmin);\n\n        if (!$this->language->isTranslated()) {\n            throw new TranslationNotFoundException();\n        }\n\n        return $translation;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/Translator/TranslatorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Translator;\n\ninterface TranslatorInterface\n{\n    /**\n     * @param string $string\n     *\n     * @return string\n     */\n    public function translate(string $string): string;\n}\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/Translator/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n    public: false\n\n  OxidEsales\\Eshop\\Core\\Language:\n    factory: ['OxidEsales\\Eshop\\Core\\Registry', 'getLang']\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Translator\\TranslatorInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Translator\\LegacyTemplateTranslator\n    arguments:\n      - '@OxidEsales\\Eshop\\Core\\Language'\n"
  },
  {
    "path": "source/Internal/Transition/Adapter/services.yaml",
    "content": "imports:\n  - { resource: TemplateLogic/services.yaml }\n  - { resource: Translator/services.yaml }\n\nservices:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\ShopAdapterInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\ShopAdapter\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Dao\\ShopConfigurationSettingDaoInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Dao\\ShopConfigurationSettingDao\n    public: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Config\\Dao\\ThemeSettingDaoInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Config\\Dao\\ThemeSettingDao\n\n  OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Utility\\ShopSettingEncoderInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Utility\\ShopSettingEncoder\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Email\\EmailAdapterInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Email\\SymfonyMailerAdapter\n    public: true\n"
  },
  {
    "path": "source/Internal/Transition/ShopEvents/AfterAdminAjaxRequestProcessedEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\nclass AfterAdminAjaxRequestProcessedEvent extends Event\n{\n}\n"
  },
  {
    "path": "source/Internal/Transition/ShopEvents/AfterModelDeleteEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\nclass AfterModelDeleteEvent extends Event\n{\n    use ModelChangeEventTrait;\n}\n"
  },
  {
    "path": "source/Internal/Transition/ShopEvents/AfterModelInsertEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\nclass AfterModelInsertEvent extends Event\n{\n    use ModelChangeEventTrait;\n}\n"
  },
  {
    "path": "source/Internal/Transition/ShopEvents/AfterModelUpdateEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\nclass AfterModelUpdateEvent extends Event\n{\n    use ModelChangeEventTrait;\n}\n"
  },
  {
    "path": "source/Internal/Transition/ShopEvents/AfterRequestProcessedEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\nclass AfterRequestProcessedEvent extends Event\n{\n}\n"
  },
  {
    "path": "source/Internal/Transition/ShopEvents/AllCookiesRemovedEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\nclass AllCookiesRemovedEvent extends Event\n{\n}\n"
  },
  {
    "path": "source/Internal/Transition/ShopEvents/ApplicationExitEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\nclass ApplicationExitEvent extends Event\n{\n}\n"
  },
  {
    "path": "source/Internal/Transition/ShopEvents/BasketChangedEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents;\n\nuse OxidEsales\\Eshop\\Application\\Component\\BasketComponent;\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\nclass BasketChangedEvent extends Event\n{\n    public function __construct(private BasketComponent $basketComponent)\n    {\n    }\n\n    /**\n     * Getter for basket component object.\n     *\n     * @return BasketComponent\n     */\n    public function getBasket(): BasketComponent\n    {\n        return $this->basketComponent;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/ShopEvents/BeforeHeadersSendEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents;\n\nuse OxidEsales\\Eshop\\Core\\ShopControl;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Controller\\ViewControllerInterface;\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\nclass BeforeHeadersSendEvent extends Event\n{\n    public function __construct(\n        private ShopControl $shopControl,\n        private ViewControllerInterface $controller\n    ) {\n    }\n\n    public function getShopControl(): ShopControl\n    {\n        return $this->shopControl;\n    }\n\n    public function getController(): ViewControllerInterface\n    {\n        return $this->controller;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/ShopEvents/BeforeModelDeleteEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\nclass BeforeModelDeleteEvent extends Event\n{\n    use ModelChangeEventTrait;\n}\n"
  },
  {
    "path": "source/Internal/Transition/ShopEvents/BeforeModelUpdateEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\n/**\n * @stable\n * @see OxidEsales/EshopCommunity/Internal/README.md\n */\nclass BeforeModelUpdateEvent extends Event\n{\n    use ModelChangeEventTrait;\n}\n"
  },
  {
    "path": "source/Internal/Transition/ShopEvents/BeforeSessionStartEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\nclass BeforeSessionStartEvent extends Event\n{\n}\n"
  },
  {
    "path": "source/Internal/Transition/ShopEvents/ModelChangeEventTrait.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents;\n\n/**\n * Model object\n *\n * @var \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n */\ntrait ModelChangeEventTrait\n{\n    private $model;\n\n    public function __construct(\\OxidEsales\\Eshop\\Core\\Model\\BaseModel $model)\n    {\n        $this->model = $model;\n    }\n\n    /**\n     * Getter for model class name.\n     *\n     * @return \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n     */\n    public function getModel(): \\OxidEsales\\Eshop\\Core\\Model\\BaseModel\n    {\n        return $this->model;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/ShopEvents/ViewRenderedEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\ShopEvents;\n\nuse OxidEsales\\Eshop\\Core\\ShopControl;\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\nclass ViewRenderedEvent extends Event\n{\n    public function __construct(private ShopControl $shopControl)\n    {\n    }\n\n    /**\n     * Getter for ShopControl object.\n     *\n     * @return ShopControl\n     */\n    public function getShopControl(): ShopControl\n    {\n        return $this->shopControl;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/ShopEvents/services.yaml",
    "content": "services:"
  },
  {
    "path": "source/Internal/Transition/Utility/BasicContext.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility;\n\nuse OxidEsales\\EshopCommunity\\Core\\Autoload\\BackwardsCompatibilityClassMapProvider;\nuse OxidEsales\\EshopCommunity\\Core\\ShopIdCalculator;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition\\Edition;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition\\EditionDirectoriesLocator;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition\\EditionPaths;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition\\EditionResolver;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\ProjectDirectoriesLocator;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\ProjectRootLocator;\nuse Symfony\\Component\\Filesystem\\Path;\nuse function sprintf;\n\nclass BasicContext implements BasicContextInterface\n{\n    private string $projectRoot;\n    private string $outPath;\n    private Edition $edition;\n\n    public function getContainerCacheFilePath(int $shopId): string\n    {\n        return Path::join(\n            $this->getCacheDirectory(),\n            'container',\n            sprintf('container_cache_shop_%d.%s.php', $shopId, getenv('OXID_ENV'))\n        );\n    }\n\n    public function getGeneratedServicesFilePath(): string\n    {\n        return Path::join($this->getShopRootPath(), 'var', 'generated', 'generated_services.yaml');\n    }\n\n    public function getActiveModuleServicesFilePath(int $shopId): string\n    {\n        return Path::join($this->getShopConfigurationDirectory($shopId), 'active_module_services.yaml');\n    }\n\n    public function getEditionSourcePath(Edition $edition): string\n    {\n        return (new EditionDirectoriesLocator())->getEditionSourcePath($edition);\n    }\n\n    public function getSourcePath(): string\n    {\n        return Path::join($this->getShopRootPath(), 'source');\n    }\n\n    public function getEdition(): Edition\n    {\n        if (!isset($this->edition)) {\n            $this->edition = (new EditionResolver())->getEdition();\n        }\n        return $this->edition;\n    }\n\n    public function getOutPath(): string\n    {\n        if (!isset($this->outPath)) {\n            $this->outPath = (new ProjectDirectoriesLocator())->getOutPath();\n        }\n        return $this->outPath;\n    }\n\n    public function getDefaultShopId(): int\n    {\n        return ShopIdCalculator::BASE_SHOP_ID;\n    }\n\n    public function getAllShopIds(): array\n    {\n        return [\n            $this->getDefaultShopId(),\n        ];\n    }\n\n    public function getBackwardsCompatibilityClassMap(): array\n    {\n        return (new BackwardsCompatibilityClassMapProvider())->getMap();\n    }\n\n    public function getProjectConfigurationDirectory(): string\n    {\n        return Path::join($this->getShopRootPath(), 'var', 'configuration');\n    }\n\n    public function getShopConfigurationDirectory(int $shopId): string\n    {\n        return Path::join(\n            $this->getProjectConfigurationDirectory(),\n            'shops',\n            (string)$shopId\n        );\n    }\n\n    public function getShopRootPath(): string\n    {\n        if (!isset($this->projectRoot)) {\n            $this->projectRoot = (new ProjectRootLocator())->getProjectRoot();\n        }\n        return $this->projectRoot;\n    }\n\n    public function getVendorPath(): string\n    {\n        return (new ProjectDirectoriesLocator())->getVendorPath();\n    }\n\n    public function getComposerVendorName(): string\n    {\n        return (EditionPaths::Community)->getVendorFolderName();\n    }\n\n    public function getConfigTableName(): string\n    {\n        return 'oxconfig';\n    }\n\n    public function getCacheDirectory(): string\n    {\n        return getenv('OXID_BUILD_DIRECTORY');\n    }\n\n    public function getModuleCacheDirectory(): string\n    {\n        return Path::join(\n            $this->getCacheDirectory(),\n            'modules'\n        );\n    }\n\n    public function getDatabaseUrl(): string\n    {\n        return getenv('OXID_DB_URL') ?: '';\n    }\n\n    public function getShopBaseUrl(): string\n    {\n        return getenv('OXID_SHOP_BASE_URL');\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Utility/BasicContextInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition\\Edition;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\DirectoryNotExistentException;\n\ninterface BasicContextInterface\n{\n    public function getActiveModuleServicesFilePath(int $shopId): string;\n\n    public function getAllShopIds(): array;\n\n    public function getBackwardsCompatibilityClassMap(): array;\n\n    public function getCacheDirectory(): string;\n\n    public function getComposerVendorName(): string;\n\n    public function getConfigTableName(): string;\n\n    public function getContainerCacheFilePath(int $shopId): string;\n\n    public function getDatabaseUrl(): string;\n\n    public function getDefaultShopId(): int;\n\n    public function getEdition(): Edition;\n\n    /**\n     * @throws DirectoryNotExistentException\n     */\n    public function getEditionSourcePath(Edition $edition): string;\n\n    public function getGeneratedServicesFilePath(): string;\n\n    public function getModuleCacheDirectory(): string;\n\n    public function getOutPath(): string;\n\n    public function getProjectConfigurationDirectory(): string;\n\n    public function getShopBaseUrl(): string;\n\n    public function getShopConfigurationDirectory(int $shopId): string;\n\n    public function getShopRootPath(): string;\n\n    public function getSourcePath(): string;\n\n    public function getVendorPath(): string;\n}\n"
  },
  {
    "path": "source/Internal/Transition/Utility/Context.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility;\n\nuse OxidEsales\\Eshop\\Core\\Config;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\Exception\\AdminUserNotFoundException;\nuse PDO;\nuse Psr\\Log\\LogLevel;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass Context extends BasicContext implements ContextInterface\n{\n    public function __construct(private readonly int $shopId)\n    {\n    }\n\n    public function getLogLevel(): string\n    {\n        $envValue = getenv('OXID_LOG_LEVEL');\n\n        return !empty($envValue) ? $envValue : LogLevel::ERROR;\n    }\n\n    public function getLogFilePath(): string\n    {\n        return Path::join($this->getSourcePath(), 'log', 'oxideshop.log');\n    }\n\n    public function getRequiredContactFormFields(): array\n    {\n        $contactFormRequiredFields = $this->getConfigParameter('contactFormRequiredFields');\n\n        return $contactFormRequiredFields ?? [];\n    }\n\n    public function getCurrentShopId(): int\n    {\n        return $this->shopId;\n    }\n\n    public function getAllShopIds(): array\n    {\n        $integerShopIds = [];\n\n        foreach (Registry::getConfig()->getShopIds() as $shopId) {\n            $integerShopIds[] = (int)$shopId;\n        }\n\n        return $integerShopIds;\n    }\n\n    public function isAdmin(): bool\n    {\n        return $this->isConfigLoaded() ? Registry::getConfig()->isAdmin() : isAdmin();\n    }\n\n    public function getAdminLogFilePath(): string\n    {\n        return Path::join($this->getSourcePath(), 'log', 'oxadmin.log');\n    }\n\n    /**\n     * We need to be careful when trying to fetch config parameters in this place as the\n     * shop might still be bootstrapping.\n     * The config must be already initialized before we can safely call Config::getConfigParam().\n     */\n    public function getSkipLogTags(): array\n    {\n        $skipLogTags = [];\n        if ($this->isConfigLoaded()) {\n            $skipLogTags = Registry::getConfig()->getConfigParam('aLogSkipTags');\n        }\n\n        return (array)$skipLogTags;\n    }\n\n    public function getAdminUserId(): string\n    {\n        $adminUserId = (string)Registry::getSession()->getVariable('auth');\n        if (empty($adminUserId)) {\n            throw new AdminUserNotFoundException();\n        }\n\n        return $adminUserId;\n    }\n\n    public function isShopInProductiveMode(): bool\n    {\n        return (bool)Registry::getConfig()->isProductiveMode();\n    }\n\n    public function isShopInDemoMode(): bool\n    {\n        return (bool)Registry::getConfig()->isDemoShop();\n    }\n\n    /**\n     * @return mixed\n     */\n    private function getConfigParameter($name, $default = null)\n    {\n        return Registry::getConfig()->getConfigParam($name, $default);\n    }\n\n    private function isConfigLoaded(): bool\n    {\n        return Registry::instanceExists(Config::class);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Transition/Utility/ContextInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\Exception\\AdminUserNotFoundException;\n\ninterface ContextInterface extends BasicContextInterface\n{\n    public function getAdminLogFilePath(): string;\n\n    /** @throws AdminUserNotFoundException */\n    public function getAdminUserId(): string;\n\n    public function getCurrentShopId(): int;\n\n    public function getLogFilePath(): string;\n\n    public function getLogLevel(): string;\n\n    public function getRequiredContactFormFields(): array;\n\n    public function getSkipLogTags(): array;\n\n    public function isAdmin(): bool;\n\n    public function isShopInDemoMode(): bool;\n\n    public function isShopInProductiveMode(): bool;\n}\n"
  },
  {
    "path": "source/Internal/Transition/Utility/Exception/AdminUserNotFoundException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\Exception;\n\n/**\n * Class AdminUserNotFoundException\n *\n * @package OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\Exception\n */\nclass AdminUserNotFoundException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Transition/Utility/bootstrap-services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContext\n    public: true\n"
  },
  {
    "path": "source/Internal/Transition/Utility/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\Context\n    public: true\n    arguments:\n      $shopId: '%oxid_esales.current_shop_id%'\n"
  },
  {
    "path": "source/Internal/Utility/Authentication/Exception/PasswordPolicyException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Utility\\Authentication\\Exception;\n\nclass PasswordPolicyException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Utility/Authentication/Policy/PasswordPolicy.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Utility\\Authentication\\Policy;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Authentication\\Exception\\PasswordPolicyException;\n\nclass PasswordPolicy implements PasswordPolicyInterface\n{\n    /**\n     * Enforces password policy\n     *\n     * @param string $password\n     *\n     * @throws PasswordPolicyException\n     */\n    public function enforcePasswordPolicy(string $password)\n    {\n        /**\n         * A password policy should at least ensure, that the same character encoding is used for hashing and\n         * verification. As there is no real way to ensure, that a byte stream is encoded in a certain character\n         * set, at least is should ensured that the password is valid UTF-8.\n         */\n        if (!$this->isValidUtf8($password)) {\n            throw new PasswordPolicyException('The password policy requires UTF-8 encoded strings');\n        }\n    }\n\n    /**\n     * @param string $password\n     *\n     * @return bool\n     */\n    private function isValidUtf8(string $password): bool\n    {\n        /**\n         * Use the PCRE_UTF8 pattern modifier to test, if the given string this is a valid UTF-8 string.\n         * See http://php.net/manual/de/reference.pcre.pattern.modifiers.php\n         * preg_match will return false on a invalid subject\n         * Not perfect, but good enough.\n         */\n        return false !== preg_match('//u', $password);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Utility/Authentication/Policy/PasswordPolicyInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Utility\\Authentication\\Policy;\n\ninterface PasswordPolicyInterface\n{\n    /**\n     * @param string $password\n     */\n    public function enforcePasswordPolicy(string $password);\n}\n"
  },
  {
    "path": "source/Internal/Utility/Email/EmailValidatorService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Utility\\Email;\n\n/**\n * Class EmailValidatorService\n * @package OxidEsales\\EshopCommunity\\Internal\\Utility\\Email\n */\nclass EmailValidatorService implements EmailValidatorServiceInterface\n{\n    /**\n     * @param mixed $email\n     *\n     * @return bool\n     */\n    public function isEmailValid($email): bool\n    {\n        return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;\n    }\n}\n"
  },
  {
    "path": "source/Internal/Utility/Email/EmailValidatorServiceBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Utility\\Email;\n\n/**\n * Class EmailValidatorServiceBridge\n * @package OxidEsales\\EshopCommunity\\Internal\\Utility\\Email\n */\nclass EmailValidatorServiceBridge implements EmailValidatorServiceBridgeInterface\n{\n    public function __construct(private EmailValidatorServiceInterface $emailValidatorService)\n    {\n    }\n\n    /**\n     * @param mixed $email\n     *\n     * @return bool\n     */\n    public function isEmailValid($email): bool\n    {\n        return $this->emailValidatorService->isEmailValid($email);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Utility/Email/EmailValidatorServiceBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Utility\\Email;\n\ninterface EmailValidatorServiceBridgeInterface extends EmailValidatorServiceInterface\n{\n}\n"
  },
  {
    "path": "source/Internal/Utility/Email/EmailValidatorServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Utility\\Email;\n\ninterface EmailValidatorServiceInterface\n{\n    /**\n     * @param mixed $email\n     *\n     * @return bool\n     */\n    public function isEmailValid($email): bool;\n}\n"
  },
  {
    "path": "source/Internal/Utility/Email/services.yaml",
    "content": "parameters:\n    oxid_esales.email.disable_order_emails: false\n\nservices:\n    _defaults:\n        autowire: true\n        public: false\n\n    OxidEsales\\EshopCommunity\\Internal\\Utility\\Email\\EmailValidatorServiceInterface:\n        class: OxidEsales\\EshopCommunity\\Internal\\Utility\\Email\\EmailValidatorService\n\n    OxidEsales\\EshopCommunity\\Internal\\Utility\\Email\\EmailValidatorServiceBridgeInterface:\n        class: OxidEsales\\EshopCommunity\\Internal\\Utility\\Email\\EmailValidatorServiceBridge\n        public: true\n"
  },
  {
    "path": "source/Internal/Utility/Hash/Exception/PasswordHashException.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Utility\\Hash\\Exception;\n\nclass PasswordHashException extends \\Exception\n{\n}\n"
  },
  {
    "path": "source/Internal/Utility/Hash/Service/Argon2IPasswordHashService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Utility\\Hash\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Hash\\Exception\\PasswordHashException;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Authentication\\Policy\\PasswordPolicyInterface;\n\n/**\n * Hashes with the ARGON2I algorithm\n */\nclass Argon2IPasswordHashService implements PasswordHashServiceInterface\n{\n    public function __construct(\n        private PasswordPolicyInterface $passwordPolicy,\n        private int $memoryCost,\n        private int $timeCost,\n        private int $threads\n    ) {\n    }\n\n    /**\n     * Creates a password hash\n     *\n     * @param string $password\n     *\n     * @throws PasswordHashException\n     *\n     * @return string\n     */\n    public function hash(string $password): string\n    {\n        $this->passwordPolicy->enforcePasswordPolicy($password);\n\n        $hash = password_hash(\n            $password,\n            PASSWORD_ARGON2I,\n            $this->getOptions()\n        );\n\n        if ($hash === false) {\n            throw new PasswordHashException(\n                'The password could not have been hashed.'\n            );\n        }\n\n        return $hash;\n    }\n\n    /**\n     * @param string $passwordHash\n     *\n     * @return bool\n     */\n    public function passwordNeedsRehash(string $passwordHash): bool\n    {\n        return password_needs_rehash($passwordHash, PASSWORD_ARGON2I, $this->getOptions());\n    }\n\n    /**\n     * @return array\n     */\n    private function getOptions(): array\n    {\n        return [\n            'memory_cost' => $this->memoryCost,\n            'time_cost' => $this->timeCost,\n            'threads' => $this->threads,\n        ];\n    }\n}\n"
  },
  {
    "path": "source/Internal/Utility/Hash/Service/BcryptPasswordHashService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Utility\\Hash\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Hash\\Exception\\PasswordHashException;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Authentication\\Policy\\PasswordPolicyInterface;\n\nclass BcryptPasswordHashService implements PasswordHashServiceInterface\n{\n    /**\n     * @var int $cost\n     * The value of the option cost has to be between 4 and 31.\n     */\n    private $cost;\n\n    /**\n     * @throws PasswordHashException\n     */\n    public function __construct(\n        private PasswordPolicyInterface $passwordPolicy,\n        int $cost\n    ) {\n        $this->validateCostOption($cost);\n        $this->cost = $cost;\n    }\n\n    /**\n     * Creates a password hash\n     *\n     * @param string $password\n     *\n     * @return string\n     * @throws PasswordHashException\n     */\n    public function hash(string $password): string\n    {\n        $this->passwordPolicy->enforcePasswordPolicy($password);\n\n        $hash = password_hash(\n            $password,\n            PASSWORD_BCRYPT,\n            $this->getOptions()\n        );\n\n        if ($hash === false) {\n            throw new PasswordHashException(\n                'The password could not have been hashed.'\n            );\n        }\n\n        return $hash;\n    }\n\n    /**\n     * @param string $passwordHash\n     *\n     * @return bool\n     */\n    public function passwordNeedsRehash(string $passwordHash): bool\n    {\n        return password_needs_rehash(\n            $passwordHash,\n            PASSWORD_BCRYPT,\n            $this->getOptions()\n        );\n    }\n\n    /**\n     * @return array\n     */\n    private function getOptions(): array\n    {\n        return ['cost' => $this->cost];\n    }\n\n\n    /**\n     * @param int $cost\n     *\n     * @throws PasswordHashException\n     */\n    private function validateCostOption(int $cost)\n    {\n        if ($cost < 4) {\n            throw new PasswordHashException('The cost option for bcrypt must not be smaller than 4.');\n        }\n        if ($cost > 31) {\n            throw new PasswordHashException('The cost option for bcrypt must not be bigger than 31.');\n        }\n    }\n}\n"
  },
  {
    "path": "source/Internal/Utility/Hash/Service/PasswordHashServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Utility\\Hash\\Service;\n\ninterface PasswordHashServiceInterface\n{\n    /**\n     * @param string $password\n     *\n     * @return string\n     */\n    public function hash(string $password): string;\n\n    /**\n     * @param string $passwordHash\n     *\n     * @return bool\n     */\n    public function passwordNeedsRehash(string $passwordHash): bool;\n}\n"
  },
  {
    "path": "source/Internal/Utility/Hash/services.yaml",
    "content": "parameters:\n  oxid_esales.utility.hash.service.password_hash.bcrypt.cost: 10\n  oxid_esales.utility.hash.service.password_hash.argon2.memory_cost: 1024\n  oxid_esales.utility.hash.service.password_hash.argon2.time_cost: 2\n  oxid_esales.utility.hash.service.password_hash.argon2.threads: 2\n\nservices:\n  _defaults:\n    autowire: true\n    public: false\n\n  OxidEsales\\EshopCommunity\\Internal\\Utility\\Hash\\Service\\PasswordHashServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Utility\\Hash\\Service\\BcryptPasswordHashService\n    arguments:\n      - '@OxidEsales\\EshopCommunity\\Internal\\Utility\\Authentication\\Policy\\PasswordPolicyInterface'\n      - '%oxid_esales.utility.hash.service.password_hash.bcrypt.cost%'"
  },
  {
    "path": "source/Internal/Utility/Header/Bridge/CsvHeaderGeneratorBridge.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Utility\\Header\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Header\\HeaderGeneratorInterface;\n\nclass CsvHeaderGeneratorBridge implements HeaderGeneratorBridgeInterface\n{\n    public function __construct(private HeaderGeneratorInterface $headerGenerator)\n    {\n    }\n\n    /**\n     * @param string $filename\n     */\n    public function generate(string $filename): void\n    {\n        $this->headerGenerator->generate($filename);\n    }\n}\n"
  },
  {
    "path": "source/Internal/Utility/Header/Bridge/HeaderGeneratorBridgeInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Utility\\Header\\Bridge;\n\ninterface HeaderGeneratorBridgeInterface\n{\n    /**\n     * @param string $filename\n     */\n    public function generate(string $filename): void;\n}\n"
  },
  {
    "path": "source/Internal/Utility/Header/CsvHeaderGenerator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Utility\\Header;\n\nclass CsvHeaderGenerator implements HeaderGeneratorInterface\n{\n    /**\n     * @param string $filename\n     */\n    public function generate(string $filename): void\n    {\n        header(\"Pragma: no-cache\");\n        header(\"Expires: 0\");\n        header('Content-Type: text/csv; charset=utf-8');\n        header(\"Content-Disposition: attachment;filename={$filename}\");\n    }\n}\n"
  },
  {
    "path": "source/Internal/Utility/Header/HeaderGeneratorInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Utility\\Header;\n\ninterface HeaderGeneratorInterface\n{\n    /**\n     * @param string $filename\n     */\n    public function generate(string $filename): void;\n}\n"
  },
  {
    "path": "source/Internal/Utility/Header/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n    public: false\n\n  OxidEsales\\EshopCommunity\\Internal\\Utility\\Header\\HeaderGeneratorInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Utility\\Header\\CsvHeaderGenerator\n\n  OxidEsales\\EshopCommunity\\Internal\\Utility\\Header\\Bridge\\HeaderGeneratorBridgeInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Utility\\Header\\Bridge\\CsvHeaderGeneratorBridge\n    public: true"
  },
  {
    "path": "source/Internal/Utility/Url/UrlParser.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Utility\\Url;\n\nclass UrlParser implements UrlParserInterface\n{\n    /** @inheritDoc */\n    public function getPathWithoutTrailingSlash(string $url): string\n    {\n        return $this->removeTrailingSlash(\n            $this->getPath($url)\n        );\n    }\n\n    /**\n     * @param string $url\n     * @return string\n     */\n    private function getPath(string $url): string\n    {\n        return (string)parse_url($url, PHP_URL_PATH);\n    }\n\n    /**\n     * @param string $path\n     * @return string\n     */\n    private function removeTrailingSlash(string $path): string\n    {\n        return rtrim($path, '/');\n    }\n}\n"
  },
  {
    "path": "source/Internal/Utility/Url/UrlParserInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Internal\\Utility\\Url;\n\ninterface UrlParserInterface\n{\n    /**\n     * @param string $url\n     * @return string\n     */\n    public function getPathWithoutTrailingSlash(string $url): string;\n}\n"
  },
  {
    "path": "source/Internal/Utility/Url/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n    public: false\n\n  OxidEsales\\EshopCommunity\\Internal\\Utility\\Url\\UrlParserInterface:\n    class: OxidEsales\\EshopCommunity\\Internal\\Utility\\Url\\UrlParser"
  },
  {
    "path": "source/Internal/Utility/services.yaml",
    "content": "imports:\n    - { resource: Hash/services.yaml }\n    - { resource: Email/services.yaml }\n    - { resource: Url/services.yaml }\n    - { resource: Header/services.yaml }\n"
  },
  {
    "path": "source/Internal/parameters.yaml",
    "content": "parameters:\n  oxid_esales.build_directory: '%env(OXID_BUILD_DIRECTORY)%'\n  oxid_esales.shop_url: '%env(OXID_SHOP_BASE_URL)%'\n  oxid_esales.log_level: '%env(OXID_LOG_LEVEL)%'\n  oxid_esales.debug_mode: '%env(bool:OXID_DEBUG_MODE)%'\n"
  },
  {
    "path": "source/Internal/services.yaml",
    "content": "imports:\n    - { resource: parameters.yaml }\n    - { resource: Container/services.yaml }\n    - { resource: Domain/services.yaml }\n    - { resource: Framework/services.yaml }\n    - { resource: Transition/Adapter/services.yaml }\n    - { resource: Transition/Utility/services.yaml }\n    - { resource: Setup/services.yaml }\n    - { resource: Utility/services.yaml }\n"
  },
  {
    "path": "source/admin/index.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nconst OX_IS_ADMIN = true;\ndefine('OX_ADMIN_DIR', basename(__DIR__));\n\nrequire_once __DIR__ . '/../index.php';\n"
  },
  {
    "path": "source/admin/oxajax.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nuse OxidEsales\\Eshop\\Core\\Exception\\FileException;\nuse OxidEsales\\Eshop\\Core\\Exception\\SystemComponentException;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\nif (!defined('OX_IS_ADMIN')) {\n    define('OX_IS_ADMIN', true);\n}\n\nif (!defined('OX_ADMIN_DIR')) {\n    define('OX_ADMIN_DIR', dirname(__FILE__));\n}\n\nrequire_once dirname(__FILE__) . \"/../bootstrap.php\";\n\n// processing ..\n$blAjaxCall = (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest');\nif ($blAjaxCall) {\n    $myConfig = Registry::getConfig();\n    $myConfig->init();\n\n    // Includes Utility module.\n    $sUtilModule = $myConfig->getConfigParam('sUtilModule');\n    if ($sUtilModule && file_exists(getShopBasePath() . \"modules/\" . $sUtilModule)) {\n        include_once getShopBasePath() . \"modules/\" . $sUtilModule;\n    }\n\n    $myConfig->setConfigParam('blAdmin', true);\n\n    // authorization\n    if (\n        !(\n        Registry::getSession()->checkSessionChallenge()\n        && count(Registry::getUtilsServer()->getOxCookie())\n        && Registry::getUtils()->checkAccessRights()\n        )\n    ) {\n        header(\"location:index.php\");\n        Registry::getUtils()->showMessageAndExit(\"\");\n    }\n\n    if ($sContainer = Registry::getRequest()->getRequestParameter('container')) {\n        $sContainer = strtolower(trim(basename($sContainer)));\n\n        try {\n            // Controller name for ajax class is automatically done from the request.\n            // Request comes from the same named class without _ajax.\n            $ajaxContainerClassName = $sContainer . '_ajax';\n            // Ensures that the right name is returned when a module introduce an ajax class.\n            $containerClass = Registry::getControllerClassNameResolver()->getClassNameById($ajaxContainerClassName);\n            $oAjaxComponent = oxNew($containerClass);\n        } catch (SystemComponentException $oCe) {\n            $oEx = new FileException();\n            $oEx->setMessage('EXCEPTION_FILENOTFOUND' . ' ' . $ajaxContainerClassName);\n            throw $oEx;\n        }\n\n        $oAjaxComponent->setName($sContainer);\n        $oAjaxComponent->processRequest(Registry::getRequest()->getRequestParameter('fnc'));\n    }\n\n    $myConfig->pageClose();\n}\n"
  },
  {
    "path": "source/api.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Api\\Api;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Api\\ExceptionHandler;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Env\\DotenvLoader;\nuse Symfony\\Component\\ErrorHandler\\Debug;\n\ndefine('INSTALLATION_ROOT_PATH', dirname(__DIR__));\ndefine('OX_BASE_PATH', INSTALLATION_ROOT_PATH . DIRECTORY_SEPARATOR . 'source' . DIRECTORY_SEPARATOR);\ndefine('VENDOR_PATH', INSTALLATION_ROOT_PATH . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR);\n\nrequire_once VENDOR_PATH . 'autoload.php';\n\nrequire_once INSTALLATION_ROOT_PATH . '/source/oxfunctions.php';\nrequire_once INSTALLATION_ROOT_PATH . '/source/overridablefunctions.php';\n\nnew DotenvLoader(INSTALLATION_ROOT_PATH)->loadEnvironmentVariables();\n\nset_exception_handler([new ExceptionHandler(), 'handle']);\n\nif (filter_var(getenv('OXID_DEBUG_MODE'), FILTER_VALIDATE_BOOLEAN)) {\n    Debug::enable();\n}\n\nnew Api()->run();\n"
  },
  {
    "path": "source/bin/.htaccess",
    "content": "# disabling file access\n<FilesMatch \".*\">\n    <IfModule mod_authz_core.c>\n        Require all denied\n    </IfModule>\n    <IfModule !mod_authz_core.c>\n        Order allow,deny\n        Deny from all\n    </IfModule>\n</FilesMatch>\n"
  },
  {
    "path": "source/bin/cron.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nrequire_once dirname(__FILE__) . \"/../bootstrap.php\";\n\n// initializes singleton config class\n$myConfig = \\OxidEsales\\Eshop\\Core\\Registry::getConfig();\n\n// executing maintenance tasks..\noxNew(\\OxidEsales\\Eshop\\Application\\Model\\Maintenance::class)->execute();\n\n// closing page, writing cache and so on..\n$myConfig->pageClose();\n"
  },
  {
    "path": "source/bootstrap.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\EshopCommunity\\Core\\Exception\\ExceptionHandler;\nuse OxidEsales\\EshopCommunity\\Core\\Autoload\\BackwardsCompatibilityAutoload;\nuse OxidEsales\\EshopCommunity\\Core\\Autoload\\ModuleAutoload;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Env\\DotenvLoader;\n\ndefine('INSTALLATION_ROOT_PATH', dirname(__DIR__));\nconst OX_BASE_PATH = INSTALLATION_ROOT_PATH . DIRECTORY_SEPARATOR . 'source' . DIRECTORY_SEPARATOR;\nconst VENDOR_PATH = INSTALLATION_ROOT_PATH . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR;\n\nrequire_once VENDOR_PATH . 'autoload.php';\n\n(new DotenvLoader(INSTALLATION_ROOT_PATH))->loadEnvironmentVariables();\n\nif (!function_exists('oxTriggerOfflinePageDisplay')) {\n    function oxTriggerOfflinePageDisplay(): void\n    {\n        if (strtolower(PHP_SAPI) !== 'cli') {\n            header('HTTP/1.1 500 Internal Server Error');\n            header('Connection: close');\n            $offlineFile = OX_BASE_PATH . 'offline.html';\n            if (is_readable($offlineFile)) {\n                echo file_get_contents($offlineFile);\n            }\n        }\n    }\n}\n\n/** For errors not caught by the application. */\nregister_shutdown_function(\n    static function () {\n        $lastError = error_get_last();\n        $fatalErrors = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR];\n        if ($lastError && in_array($lastError['type'], $fatalErrors, true)) {\n            file_put_contents(\n                OX_BASE_PATH . 'log' . DIRECTORY_SEPARATOR . 'oxideshop.log',\n                \"Application has shut down with an UNCAUGHT ERROR: '\" . print_r($lastError, true) . \"'\\n\",\n                FILE_APPEND\n            );\n\n            if (!filter_var(getenv('OXID_DEBUG_MODE'), FILTER_VALIDATE_BOOLEAN)) {\n                oxTriggerOfflinePageDisplay();\n            }\n            if ($lastError['type'] === E_ERROR) {\n                setcookie(name: 'sid', path: '/');\n                setcookie(name: 'admin_sid', path: '/');\n            }\n        }\n    }\n);\n\nspl_autoload_register([BackwardsCompatibilityAutoload::class, 'autoload']);\nspl_autoload_register([ModuleAutoload::class, 'autoload']);\n\n/** Set exception handler before including modules/functions.php, so it can be overwritten by shop operators. */\nset_exception_handler(\n    [\n        new ExceptionHandler(filter_var(getenv('OXID_DEBUG_MODE'), FILTER_VALIDATE_BOOLEAN)),\n        'handleUncaughtException'\n    ]\n);\n\nrequire_once OX_BASE_PATH . 'oxfunctions.php';\nif (is_readable(OX_BASE_PATH . 'modules/functions.php')) {\n    include OX_BASE_PATH . 'modules/functions.php';\n}\nrequire_once OX_BASE_PATH . 'overridablefunctions.php';\n\nini_set('session.name', 'sid');\nini_set('session.use_cookies', 0);\nini_set('session.use_trans_sid', 0);\nini_set('url_rewriter.tags', '');\n\ndate_default_timezone_set(getenv('OXID_DEFAULT_TIMEZONE') ?: 'Europe/Berlin');\n"
  },
  {
    "path": "source/export/.gitkeep",
    "content": ""
  },
  {
    "path": "source/getimg.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\n/**\n * In case you need to extend current generator class:\n *   - create some alternative file;\n *   - edit htaccess file and replace getimg.php with your custom handler;\n *   - add here function \"getGeneratorInstanceName()\" which returns name of your generator class;\n *   - implement class and required methods which extends \"oxdynimggenerator\" class\n *   e.g.:\n *\n *     file name \"testgenerator.php\"\n *\n *     function getGeneratorInstanceName()\n *     {\n *         return \"testImageGenerator\";\n *     }\n *     include_once \"oxdynimggenerator.php\";\n *     class testImageGenerator extends oxdynimggenerator.php {...}\n*/\n\n// including generator class\nrequire_once __DIR__ . \"/bootstrap.php\";\n\n// rendering requested image\nOxidEsales\\EshopCommunity\\Core\\DynamicImageGenerator::getInstance()->outputImage();\n"
  },
  {
    "path": "source/index.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nrequire_once __DIR__ . '/bootstrap.php';\n\nOxidEsales\\EshopCommunity\\Core\\Oxid::run();\n"
  },
  {
    "path": "source/log/.htaccess",
    "content": "# disabling log file access from outside\n<FilesMatch .*>\n    <IfModule mod_authz_core.c>\n        Require all denied\n    </IfModule>\n    <IfModule !mod_authz_core.c>\n        Order allow,deny\n        Deny from all\n    </IfModule>\n</FilesMatch>\n\nOptions -Indexes\n"
  },
  {
    "path": "source/migration/data/Version20170718124421.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Migrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations\\AbstractMigration;\n\n/**\n * Change oxtplblocks oxmodule field max length to 100\n * as it is in oxconfig and oxconfigdisplay tables\n */\nclass Version20170718124421 extends AbstractMigration\n{\n    /**\n     * @param Schema $schema\n     */\n    public function up(Schema $schema): void\n    {\n        $this->addSql(\"ALTER TABLE `oxtplblocks` \n          CHANGE `OXMODULE` `OXMODULE` varchar(100) \n          character set latin1 collate latin1_general_ci NOT NULL \n          COMMENT 'Module, which uses this template';\");\n    }\n\n    /**\n     * @param Schema $schema\n     */\n    public function down(Schema $schema): void\n    {\n    }\n\n    public function isTransactional(): bool\n    {\n        return false;\n    }\n}\n"
  },
  {
    "path": "source/migration/data/Version20171018144650.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Migrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations\\AbstractMigration;\n\n/**\n * Update from v6.0.0-rc.2 to v6.0.0-rc.3\n */\nclass Version20171018144650 extends AbstractMigration\n{\n    /**\n     * @param Schema $schema\n     */\n    public function up(Schema $schema): void\n    {\n        // All tables should have the same default character set and collation\n        $this->addSql(\"ALTER table `oxinvitations` COLLATE utf8_general_ci;\");\n\n        $this->addSql(\"ALTER table `oxobject2action` COLLATE utf8_general_ci;\");\n\n        // Convert the value from the configuration variable blLoadDynContents to blSendTechnicalInformationToOxid\n        $this->addSql(\"INSERT INTO `oxconfig` (`OXID`, `OXSHOPID`, `OXMODULE`, `OXVARNAME`, `OXVARTYPE`, `OXVARVALUE`)\n          SELECT\n            CONCAT(SUBSTRING(`OXID`,1, 16),SUBSTRING(REPLACE( UUID( ) ,  '-',  '' ), 17,32)) AS `OXID`,\n            `OXSHOPID`,\n            `OXMODULE`,\n            \\\"blSendTechnicalInformationToOxid\\\" AS `OXVARNAME`,\n            `OXVARTYPE`,\n            `OXVARVALUE`\n          FROM `oxconfig`\n          WHERE `OXVARNAME` = 'blLoadDynContents'\n          AND NOT EXISTS (\n            SELECT `OXVARNAME` FROM `oxconfig` WHERE `OXVARNAME` = 'blSendTechnicalInformationToOxid'\n          );\");\n    }\n\n    /**\n     * @param Schema $schema\n     */\n    public function down(Schema $schema): void\n    {\n    }\n\n    public function isTransactional(): bool\n    {\n        return false;\n    }\n}\n"
  },
  {
    "path": "source/migration/data/Version20180214152228.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Migrations;\n\nuse Doctrine\\Migrations\\AbstractMigration;\nuse Doctrine\\DBAL\\Schema\\Schema;\n\n/** Empty migration file to suppress warning */\nclass Version20180214152228 extends AbstractMigration\n{\n    public function up(Schema $schema): void\n    {\n    }\n\n    public function down(Schema $schema): void\n    {\n    }\n\n    public function isTransactional(): bool\n    {\n        return false;\n    }\n}\n"
  },
  {
    "path": "source/migration/data/Version20180228160418.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Migrations;\n\nuse Doctrine\\Migrations\\AbstractMigration;\nuse Doctrine\\DBAL\\Schema\\Schema;\n\n/**\n * change oxconfig::oxvarvalue type to mediumblob for large module lists\n */\nclass Version20180228160418 extends AbstractMigration\n{\n    /**\n     * @param Schema $schema\n     */\n    public function up(Schema $schema): void\n    {\n        $sql = \"ALTER TABLE `oxconfig` CHANGE COLUMN `OXVARVALUE` `OXVARVALUE` text NOT NULL COMMENT 'Variable value' AFTER `OXVARTYPE`;\";\n        $this->addSql($sql);\n    }\n\n    /**\n     * @param Schema $schema\n     */\n    public function down(Schema $schema): void\n    {\n    }\n\n    public function isTransactional(): bool\n    {\n        return false;\n    }\n}\n"
  },
  {
    "path": "source/migration/data/Version20180703135728.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Migrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations\\AbstractMigration;\n\n/**\n * Auto-generated Migration: Please modify to your needs!\n */\nclass Version20180703135728 extends AbstractMigration\n{\n    /**\n     * @param Schema $schema\n     */\n    public function up(Schema $schema): void\n    {\n        $varName = 'contactFormRequiredFields';\n        $varType = 'arr';\n        $rawValue = serialize(['email']);\n\n        $query = \"INSERT INTO `oxconfig` \n                  (\n                      `OXID`, \n                      `OXSHOPID`, \n                      `OXVARNAME`, \n                      `OXVARTYPE`, \n                      `OXVARVALUE`\n                  )\n                  SELECT  \n                      REPLACE(UUID( ) , '-', '' ), \n                      `OXID`,\n                      ?, \n                      ?, \n                      ?\n                  FROM `oxshops`                  \n                  WHERE NOT EXISTS (\n                      SELECT `OXVARNAME` \n                      FROM `oxconfig`\n                      WHERE `OXVARNAME` = ? \n                      AND `oxconfig`.OXSHOPID = `oxshops`.OXID \n                  )\";\n        $this->addSql(\n            $query,\n            [$varName, $varType, $rawValue, $varName]\n        );\n    }\n\n    /**\n     * @param Schema $schema\n     */\n    public function down(Schema $schema): void\n    {\n        // this down() migration is auto-generated, please modify it to your needs\n    }\n\n    public function isTransactional(): bool\n    {\n        return false;\n    }\n}\n"
  },
  {
    "path": "source/migration/data/Version20180928072235.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Migrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations\\AbstractMigration;\n\n/**\n * Auto-generated Migration: Please modify to your needs!\n */\nclass Version20180928072235 extends AbstractMigration\n{\n    /**\n     * @param Schema $schema\n     */\n    public function up(Schema $schema): void\n    {\n        $configSettingName = 'includeProductReviewLinksInEmail';\n        $configSettingType = 'bool';\n        $configSettingValue = '1';\n\n        $query = \"INSERT INTO `oxconfig` \n                  (\n                      `OXID`, \n                      `OXSHOPID`, \n                      `OXVARNAME`, \n                      `OXVARTYPE`, \n                      `OXVARVALUE`\n                  )\n                  SELECT  \n                      REPLACE(UUID() , '-', '' ), \n                      `OXID`,\n                      ?, \n                      ?, \n                      ?\n                  FROM `oxshops`                  \n                  WHERE NOT EXISTS (\n                      SELECT `OXVARNAME` \n                      FROM `oxconfig`\n                      WHERE `OXVARNAME` = ? \n                      AND `oxconfig`.OXSHOPID = `oxshops`.OXID \n                  )\";\n\n        $this->addSql(\n            $query,\n            [\n                $configSettingName,\n                $configSettingType,\n                $configSettingValue,\n                $configSettingName,\n            ]\n        );\n    }\n\n    /**\n     * @param Schema $schema\n     */\n    public function down(Schema $schema): void\n    {\n        // this down() migration is auto-generated, please modify it to your needs\n    }\n}\n"
  },
  {
    "path": "source/migration/data/Version20191007144155.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Migrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations\\AbstractMigration;\n\n/**\n * Auto-generated Migration: Please modify to your needs!\n */\nfinal class Version20191007144155 extends AbstractMigration\n{\n    public function up(Schema $schema): void\n    {\n        $query = \"ALTER TABLE oxseo \n            DROP INDEX `OXOBJECTID`, \n            ADD INDEX `OXOBJECTID` (`OXOBJECTID`,`OXSHOPID`,`OXLANG`)\";\n\n        $this->addSql($query);\n    }\n\n    public function down(Schema $schema): void\n    {\n        // this down() migration is auto-generated, please modify it to your needs\n    }\n}\n"
  },
  {
    "path": "source/migration/data/Version20201029110624.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Migrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations\\AbstractMigration;\n\nfinal class Version20201029110624 extends AbstractMigration\n{\n    public function up(Schema $schema): void\n    {\n        $this->addSql('CREATE INDEX OXRIGHTS ON oxuser (oxrights)');\n    }\n\n    public function down(Schema $schema): void\n    {\n        $this->addSql('DROP INDEX OXRIGHTS ON oxuser');\n    }\n\n    public function isTransactional(): bool\n    {\n        return false;\n    }\n}\n"
  },
  {
    "path": "source/migration/data/Version20201103010101.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Migrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations\\AbstractMigration;\n\nfinal class Version20201103010101 extends AbstractMigration\n{\n    public function up(Schema $schema): void\n    {\n        $this->addSql(\n            'ALTER TABLE `oxdeliveryset` ADD COLUMN `OXTRACKINGURL` VARCHAR(255) NOT NULL'\n        );\n    }\n\n    public function down(Schema $schema): void\n    {\n    }\n}\n"
  },
  {
    "path": "source/migration/data/Version20201203101929.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Migrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations\\AbstractMigration;\n\n/**\n * change oxuserbasket::oxpublic default value to 0\n */\nfinal class Version20201203101929 extends AbstractMigration\n{\n    public function up(Schema $schema): void\n    {\n        $query = \"ALTER TABLE `oxuserbaskets` \n                ALTER `OXPUBLIC` SET DEFAULT '0';\";\n\n        $this->addSql($query);\n    }\n\n    public function down(Schema $schema): void\n    {\n        // this down() migration is auto-generated, please modify it to your needs\n    }\n}\n"
  },
  {
    "path": "source/migration/data/Version20211117193324.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Migrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations\\AbstractMigration;\n\nfinal class Version20211117193324 extends AbstractMigration\n{\n    public function getDescription() : string\n    {\n        return 'Update oxcontents text fields sizes';\n    }\n\n    public function up(Schema $schema) : void\n    {\n        $this->addSql(\"ALTER TABLE `oxcontents` MODIFY column OXCONTENT MEDIUMTEXT NOT NULL COMMENT 'Content (multilanguage)'\");\n        $this->addSql(\"ALTER TABLE `oxcontents` MODIFY column OXCONTENT_1 MEDIUMTEXT NOT NULL\");\n        $this->addSql(\"ALTER TABLE `oxcontents` MODIFY column OXCONTENT_2 MEDIUMTEXT NOT NULL\");\n        $this->addSql(\"ALTER TABLE `oxcontents` MODIFY column OXCONTENT_3 MEDIUMTEXT NOT NULL\");\n    }\n\n    public function down(Schema $schema) : void\n    {\n        // this down() migration is auto-generated, please modify it to your needs\n    }\n}"
  },
  {
    "path": "source/migration/data/Version20230109135625.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Migrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations\\AbstractMigration;\n\nfinal class Version20230109135625 extends AbstractMigration\n{\n    public function up(Schema $schema): void\n    {\n        $this->addSql('ALTER TABLE `oxmanufacturers` ADD column `OXSORT` INT NOT NULL DEFAULT 0 AFTER `OXSHOWSUFFIX`');\n        $this->addSql('CREATE INDEX OXSORT ON `oxmanufacturers` (OXSORT)');\n    }\n\n    public function down(Schema $schema): void\n    {\n    }\n}\n"
  },
  {
    "path": "source/migration/data/Version20230301123522.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Migrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations\\AbstractMigration;\n\nfinal class Version20230301123522 extends AbstractMigration\n{\n    public function up(Schema $schema): void\n    {\n        $this->addSql('ALTER TABLE `oxmanufacturers` ADD column `OXICON_ALT` VARCHAR(128) NOT NULL default \"\" COMMENT \"Alternative Icon filename\" AFTER `OXICON`');\n        $this->addSql('ALTER TABLE `oxmanufacturers` ADD column `OXPICTURE` VARCHAR(128) NOT NULL default \"\" COMMENT \"Picture filename\" AFTER `OXICON_ALT`');\n        $this->addSql('ALTER TABLE `oxmanufacturers` ADD column `OXTHUMBNAIL` VARCHAR(128) NOT NULL default \"\" COMMENT \"Picture thumbnail filename\" AFTER `OXPICTURE`');\n        $this->addSql('ALTER TABLE `oxmanufacturers` ADD column `OXPROMOTION_ICON` VARCHAR(128) NOT NULL default \"\" COMMENT \"Icon for promotion filename\" AFTER `OXTHUMBNAIL`');\n    }\n\n    public function down(Schema $schema): void\n    {\n    }\n}\n"
  },
  {
    "path": "source/migration/data/Version20231128113123.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Migrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations\\AbstractMigration;\n\nfinal class Version20231128113123 extends AbstractMigration\n{\n    public function getDescription(): string\n    {\n        return 'Add Low Stock Message';\n    }\n\n    public function up(Schema $schema): void\n    {\n        $this->addSql(\n            'ALTER TABLE `oxarticles` ADD column `OXLOWSTOCKTEXT` TEXT NOT NULL default \"\" COMMENT '\n                . '\"Message, which is shown if the article is in low stock (multilanguage)\"'\n        );\n        $this->addSql(\n            'ALTER TABLE `oxarticles` ADD column `OXLOWSTOCKTEXT_1` TEXT NOT NULL default \"\"'\n        );\n        $this->addSql(\n            'ALTER TABLE `oxarticles` ADD column `OXLOWSTOCKTEXT_2` TEXT NOT NULL default \"\"'\n        );\n        $this->addSql(\n            'ALTER TABLE `oxarticles` ADD column `OXLOWSTOCKTEXT_3` TEXT NOT NULL default \"\"'\n        );\n        $this->addSql(\n            'ALTER TABLE `oxarticles` ADD column `OXLOWSTOCKACTIVE` TINYINT(1)'\n        );\n    }\n\n    public function down(Schema $schema): void\n    {\n    }\n}\n"
  },
  {
    "path": "source/migration/data/Version20250320120000.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Migrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations\\AbstractMigration;\n\nfinal class Version20250320120000 extends AbstractMigration\n{\n    public function getDescription(): string\n    {\n        return 'Add Media Tables with lowercase columns';\n    }\n\n    public function up(Schema $schema): void\n    {\n        $this->addSql(\n            'CREATE TABLE `oxmedia` (\n                `id` char(32) NOT NULL,\n                `path` varchar(255) NOT NULL,\n                `type` varchar(32) NOT NULL,\n                `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n                `updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n                PRIMARY KEY (`id`)\n            )'\n        );\n\n        $this->addSql(\n            'CREATE TABLE `oxproduct_media` (\n                `id` char(32) NOT NULL,\n                `product_id` char(32) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL,\n                `media_id` char(32) NOT NULL,\n                `position` int(11) NOT NULL DEFAULT 0,\n                `active` tinyint(1) NOT NULL DEFAULT 1,\n                `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n                `updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n                PRIMARY KEY (`id`),\n                INDEX `product_id` (`product_id`),\n                INDEX `media_id` (`media_id`)\n            )'\n        );\n\n        $this->addSql(\n            'CREATE TABLE `oxproduct_media_roles` (\n                `product_media_id` char(32) NOT NULL,\n                `role` varchar(32) NOT NULL,\n                `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n                PRIMARY KEY (`product_media_id`, `role`)\n                )'\n        );\n    }\n\n    public function down(Schema $schema): void\n    {\n    }\n}\n"
  },
  {
    "path": "source/migration/data/Version20250911140000.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Migrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations\\AbstractMigration;\n\nfinal class Version20250911140000 extends AbstractMigration\n{\n    public function getDescription(): string\n    {\n        return 'Convert aDetailImageSizes to sDetailImageSize using oxpic1 value';\n    }\n\n    public function up(Schema $schema): void\n    {\n        $rows = $this->connection->fetchAllAssociative(\n            \"SELECT OXSHOPID, OXVARVALUE \n             FROM oxconfig \n             WHERE OXVARNAME = 'aDetailImageSizes' AND OXVARTYPE = 'aarr'\"\n        );\n\n        foreach ($rows as $row) {\n            $data = unserialize($row['OXVARVALUE']);\n\n            if (is_array($data) && isset($data['oxpic1'])) {\n                $query = \"INSERT INTO `oxconfig` \n                          (\n                              `OXID`, \n                              `OXSHOPID`, \n                              `OXVARNAME`, \n                              `OXVARTYPE`, \n                              `OXVARVALUE`\n                          )\n                          SELECT  \n                              REPLACE(UUID(), '-', ''), \n                              ?,\n                              'sDetailImageSize', \n                              'str', \n                              ?\n                          WHERE NOT EXISTS (\n                              SELECT `OXVARNAME` \n                              FROM `oxconfig`\n                              WHERE `OXVARNAME` = 'sDetailImageSize' \n                              AND `OXSHOPID` = ?\n                          )\";\n                $this->addSql($query, [$row['OXSHOPID'], $data['oxpic1'], $row['OXSHOPID']]);\n            }\n        }\n\n        $this->addSql(\"DELETE FROM `oxconfig` WHERE `OXVARNAME` = 'aDetailImageSizes' AND `OXVARTYPE` = 'aarr'\");\n    }\n\n    public function down(Schema $schema): void\n    {\n    }\n}\n"
  },
  {
    "path": "source/migration/data/Version20251009121500.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Migrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations\\AbstractMigration;\n\nfinal class Version20251009121500 extends AbstractMigration\n{\n    public function up(Schema $schema): void\n    {\n        $this->addSql(\"DELETE FROM `oxconfig` WHERE `OXVARNAME` = 'blSendTechnicalInformationToOxid'\");\n    }\n\n    public function down(Schema $schema): void\n    {\n    }\n}\n"
  },
  {
    "path": "source/migration/migrations.yml",
    "content": "table_storage:\n  table_name: oxmigrations_ce\nmigrations_paths:\n  'OxidEsales\\EshopCommunity\\Migrations': data\n"
  },
  {
    "path": "source/migration/project_data/.gitkeep",
    "content": ""
  },
  {
    "path": "source/migration/project_migrations.yml",
    "content": "table_storage:\n  table_name: oxmigrations_project\nmigrations_paths:\n  'OxidEsales\\EshopCommunity\\MigrationsProject': project_data\n"
  },
  {
    "path": "source/modules/functions.php.dist",
    "content": "<?php\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\n/**\n * Add custom functions here.\n *\n * Shop operators could overwrite the standard exception handler here to use different logging mechanism for example.\n */\n"
  },
  {
    "path": "source/offline.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <meta name=\"robots\" content=\"noindex, nofollow\">\n    <meta http-equiv=\"refresh\" content=\"10; URL=/index.php\">\n    <title>Maintenance mode / Wartungsarbeiten</title>\n    <style>\n        body {\n            font-family: \"Source Sans Pro\", \"DIN Next LT Pro\", Helvetica, Arial, sans-serif;\n            line-height: 1.8;\n            color: #333;\n        }\n        a, a:link, a:active, a:visited, a:hover {\n            color: #666;\n            text-decoration: underline;\n        }\n        .page {\n            text-align: center;\n            margin-top: 100px;\n        }\n        .logo {\n            max-width: 300px;\n            margin-bottom: 50px;\n        }\n        .text {\n            font-size: 20px;\n        }\n    </style>\n</head>\n<body>\n<div class=\"page\">\n    <img class=\"logo\" src=\"out/apex/img/logo.svg\" alt=\"\">\n    <br>\n        <span class=\"text\">\n            Maintenance mode, please try again later.<br>\n            <a href=\"/index.php\">Click here to reload shop.</a><br><br>\n            <span lang=\"de\">\n                Wartungsarbeiten, bitte versuchen Sie es später noch einmal.<br>\n                <a href=\"/index.php\">Klicken Sie hier, um den Shop erneut zu laden.</a>\n            </span>\n        </span>\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "source/out/.gitignore",
    "content": "/*\n!/.gitignore\n\n!/downloads\n/downloads/*\n!/downloads/.htaccess\n!/downloads/README.txt\n\n!/downloads/uploads\n/downloads/uploads/*\n!/downloads/uploads/.gitkeep\n\n!/media\n/media/*\n!/media/.gitkeep\n\n!/pictures\n/pictures/*\n\n!/pictures/generated\n/pictures/generated/*\n!/pictures/generated/.gitkeep\n\n!/pictures/master\n/pictures/master/*\n!/pictures/master/nopic.jpg\n!/pictures/master/nopic.webp\n\n!/pictures/media\n/pictures/media/*\n!/pictures/media/.gitkeep\n!/pictures/media/nopic.jpg\n!/pictures/media/nopic.webp\n\n!/pictures/promo\n/pictures/promo/*\n!/pictures/promo/.gitkeep\n\n!/pictures/vendor\n/pictures/vendor/*\n\n!/pictures/vendor/icon\n/pictures/vendor/icon/*\n!/pictures/vendor/icon/.gitkeep\n"
  },
  {
    "path": "source/out/downloads/.htaccess",
    "content": "<IfModule mod_authz_core.c>\n    Require all denied\n</IfModule>\n<IfModule !mod_authz_core.c>\n    Order allow,deny\n    Deny from all\n</IfModule>\n"
  },
  {
    "path": "source/out/downloads/README.txt",
    "content": "In this directory we store downloadable files. \nFiles uploaded over admin interface are renamed to hash and stored in separate dirs. Manually (ftp) uploaded files goes to \"uploads\" directory.\n\nAlways make sure that this directory is protected from direct access over web."
  },
  {
    "path": "source/out/downloads/uploads/.gitkeep",
    "content": ""
  },
  {
    "path": "source/out/media/.gitkeep",
    "content": ""
  },
  {
    "path": "source/out/pictures/generated/.gitkeep",
    "content": "\n"
  },
  {
    "path": "source/out/pictures/media/.gitkeep",
    "content": ""
  },
  {
    "path": "source/out/pictures/promo/.gitkeep",
    "content": ""
  },
  {
    "path": "source/out/pictures/vendor/icon/.gitkeep",
    "content": ""
  },
  {
    "path": "source/overridablefunctions.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\ContainerFactory;\nuse Psr\\Log\\LoggerInterface;\n\nif (!function_exists('getShopBasePath')) {\n    /**\n     * Returns framework base path.\n     *\n     * @return string\n     */\n    function getShopBasePath()\n    {\n        return OX_BASE_PATH;\n    }\n}\n\nif (!function_exists('error_404_handler')) {\n    /**\n     * error_404_handler handler for 404 (page not found) error\n     *\n     * @param string $sUrl url wich was given, can be not specified in some cases\n     */\n    function error_404_handler($sUrl = '')\n    {\n        Registry::getUtils()->handlePageNotFoundError($sUrl);\n    }\n}\n\nif (!function_exists('isSearchEngineUrl')) {\n\n    /**\n     * Returns search engine url status\n     *\n     * @return bool\n     */\n    function isSearchEngineUrl()\n    {\n        return false;\n    }\n}\n\nif (!function_exists('startProfile')) {\n    /**\n     * Start profiling\n     *\n     * @param string $sProfileName name of profile\n     */\n    function startProfile($sProfileName)\n    {\n        global $aStartTimes;\n        global $executionCounts;\n        if (!isset($executionCounts[$sProfileName])) {\n            $executionCounts[$sProfileName] = 0;\n        }\n        if (!isset($aStartTimes[$sProfileName])) {\n            $aStartTimes[$sProfileName] = 0;\n        }\n        $executionCounts[$sProfileName]++;\n        $aStartTimes[$sProfileName] = microtime(true);\n    }\n}\n\nif (!function_exists('stopProfile')) {\n    /**\n     * Stop profiling\n     *\n     * @param string $sProfileName name of profile\n     */\n    function stopProfile($sProfileName)\n    {\n        global $aProfileTimes;\n        global $aStartTimes;\n        if (!isset($aProfileTimes[$sProfileName])) {\n            $aProfileTimes[$sProfileName] = 0;\n        }\n        $aProfileTimes[$sProfileName] += microtime(true) - $aStartTimes[$sProfileName];\n    }\n}\n\nif (!function_exists('getLangTableIdx')) {\n\n    /**\n     * Returns language table index\n     *\n     * @param int $iLangId language id\n     *\n     * @return string\n     */\n    function getLangTableIdx($iLangId)\n    {\n        $iLangPerTable = Registry::getConfig()->getConfigParam(\"iLangPerTable\");\n        //#0002718 min language count per table 2\n        $iLangPerTable = ($iLangPerTable > 1) ? $iLangPerTable : 8;\n\n        return (int) ($iLangId / $iLangPerTable);\n    }\n}\n\nif (!function_exists('getLangTableName')) {\n\n    /**\n     * Returns language table name\n     *\n     * @param string $sTable  table name\n     * @param int    $iLangId language id\n     *\n     * @return string\n     */\n    function getLangTableName($sTable, $iLangId)\n    {\n        $iTableIdx = getLangTableIdx($iLangId);\n        if ($iTableIdx && in_array($sTable, Registry::getLang()->getMultiLangTables(), true)) {\n            $sLangTableSuffix = Registry::getConfig()->getConfigParam('sLangTableSuffix');\n            $sLangTableSuffix = $sLangTableSuffix ?: '_set';\n\n            $sTable .= $sLangTableSuffix . $iTableIdx;\n        }\n\n        return $sTable;\n    }\n}\n\nif (!function_exists('getLogger')) {\n    /**\n     * Returns the Logger\n     *\n     * @return LoggerInterface\n     */\n    function getLogger()\n    {\n        return ContainerFactory::getInstance()->getContainer()->get(LoggerInterface::class);\n    }\n}\n"
  },
  {
    "path": "source/oxfunctions.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\Eshop\\Core\\UtilsObject;\n\nfunction isAdmin(): bool\n{\n    return defined('OX_IS_ADMIN') && OX_IS_ADMIN;\n}\n\n/**\n * Creates and returns new object. If creation is not available, dies and outputs\n * error message.\n *\n * @template T\n * @param class-string<T> $className\n * param mixed  ...$args   constructor arguments\n *\n * @return T\n */\nfunction oxNew(string $className, ...$args)\n{\n    startProfile('oxNew');\n    $object = call_user_func_array([UtilsObject::getInstance(), 'oxNew'], array_merge([$className], $args));\n    stopProfile('oxNew');\n\n    return $object;\n}\n"
  },
  {
    "path": "source/oxseo.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\n// mod_rewrite check\nif (isset($_REQUEST['mod_rewrite_module_is'])) {\n    $sMode = $_REQUEST['mod_rewrite_module_is'];\n    if ($sMode == 'on') {\n        die(\"mod_rewrite_on\");\n    } else {\n        die(\"mod_rewrite_off\");\n    }\n}\n\n/**\n * Detects serchengine URLs\n *\n * @return bool true\n */\nfunction isSearchEngineUrl()\n{\n    return true;\n}\n\n// executing regular routines ...\nrequire 'index.php';\n"
  },
  {
    "path": "source/robots.txt",
    "content": "User-agent: *\nDisallow: /admin/\nDisallow: /Core/\nDisallow: /views/\nDisallow: /log/\n#\nDisallow: /newsletter/\nDisallow: /en/newsletter/\nDisallow: /index.php?cl=newsletter\n#\nDisallow: /AGB/\nDisallow: /en/Terms-and-Conditions/\n#\nDisallow: /warenkorb/\nDisallow: /en/cart/\nDisallow: /index.php?cl=basket\n#\nDisallow: /mein-konto/\nDisallow: /en/my-account/\nDisallow: /index.php?cl=account\n#\nDisallow: /mein-merkzettel/\nDisallow: /en/my-wishlist/\nDisallow: /index.php?cl=account_noticelist\n#\nDisallow: /mein-wunschzettel/\nDisallow: /en/my-gift-registry/\nDisallow: /index.php?cl=account_wishlist\n#\nDisallow: /konto-eroeffnen/\nDisallow: /en/open-account/\nDisallow: /index.php?cl=register\n#\nDisallow: /passwort-vergessen/\nDisallow: /en/forgot-password/\nDisallow: /index.php?cl=forgotpwd\n#\nDisallow: /index.php?cl=moredetails\n#\nDisallow: /index.php?cl=review\n#\nDisallow: /index.php?cl=search\n#\nDisallow: /EXCEPTION_LOG.txt\n#\n# wildcards at the end, because of some crawlers see it as errors\nDisallow: /*?cl=newsletter\nDisallow: /*&cl=newsletter\n#\nDisallow: /*?cl=basket\nDisallow: /*&cl=basket\n#\nDisallow: /*?cl=account\nDisallow: /*&cl=account\n#\nDisallow: /*?cl=account_noticelist\nDisallow: /*&cl=account_noticelist\n#\nDisallow: /*?cl=account_wishlist\nDisallow: /*&cl=account_wishlist\n#\nDisallow: /*?cl=register\nDisallow: /*&cl=register\n#\nDisallow: /*?cl=forgotpwd\nDisallow: /*&cl=forgotpwd\n#\nDisallow: /*?cl=moredetails\nDisallow: /*&cl=moredetails\n#\nDisallow: /*?cl=review\nDisallow: /*&cl=review\n#\nDisallow: /*?cl=search\nDisallow: /*&cl=search\n#\nDisallow: /*&fnc=tobasket\nDisallow: /*&fnc=tocomparelist\nDisallow: /*&addcompare=\n#\nDisallow: /*/sid/\nDisallow: /*?sid=\nDisallow: /*&sid=\n#\nDisallow: /*?cur=\nDisallow: /*&cur\n"
  },
  {
    "path": "source/widget.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nrequire_once dirname(__FILE__) . \"/bootstrap.php\";\n\n//Starts the shop\nOxidEsales\\EshopCommunity\\Core\\Oxid::runWidget();\n"
  },
  {
    "path": "tests/CachingTrait.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests;\n\nuse OxidEsales\\Eshop\\Core\\ConfigFile;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContext;\nuse ReflectionClass;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse RecursiveIteratorIterator;\nuse RecursiveDirectoryIterator;\n\n/**\n * @deprecated trait will be removed, use in-built PHPUnit test processes isolation instead\n */\ntrait CachingTrait\n{\n    /**\n     * The registry is complicated, because it needs the ConfigFile\n     * set. So just cleaning the $instances cache does not work.\n     */\n    public function resetRegistry(): void\n    {\n        $configFile = Registry::get(ConfigFile::class);\n        $this->removeClassCache(Registry::class, 'instances', []);\n        Registry::set(ConfigFile::class, $configFile);\n    }\n\n    public function cleanupCaching(): void\n    {\n        $this->cleanUpCompilationDirectory();\n        $this->resetRegistry();\n        $this->removeClassCaches();\n    }\n\n    private function cleanUpCompilationDirectory(): void\n    {\n        $basicContext = new BasicContext();\n        $this->cleanUpDirectory($basicContext->getCacheDirectory());\n    }\n\n    private function removeClassCaches(): void\n    {\n        $this->removeClassCache(DatabaseProvider::class, 'db', null);\n    }\n\n    private function removeClassCache(string $class, string $property, $default): void\n    {\n        $reflectionClass = new ReflectionClass($class);\n        $reflectionProperty = $reflectionClass->getProperty($property);\n        $reflectionProperty->setValue($reflectionProperty, $default);\n    }\n\n    private function cleanUpDirectory($directory): void\n    {\n        $fileSystem = new Filesystem();\n        if ($fileSystem->exists($directory)) {\n            $recursiveIterator = new RecursiveIteratorIterator(\n                new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS),\n                RecursiveIteratorIterator::LEAVES_ONLY,\n                RecursiveIteratorIterator::CATCH_GET_CHILD\n            );\n\n            $fileSystem->remove($recursiveIterator);\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/.gitignore",
    "content": "_output/*\n!_output/.gitkeep\nSupport/_generated/*\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/ActiveCategoryAtStartCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse Codeception\\Util\\Locator;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin')]\nfinal class ActiveCategoryAtStartCest\n{\n    public function setActiveCategoryAtStart(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Activate and deactivate category at start');\n\n        $I->clearShopCache();\n        $adminPanel = $I->loginAdmin();\n        $coreSettings = $adminPanel->openCoreSettings();\n        $settingsTab = $coreSettings->openSettingsTab();\n\n        $settingsTab =  $settingsTab->openShopFrontendDropdown();\n\n        $I->seeElement(\"//input[@value='---']\");\n        $categoryPopup = $settingsTab->openStartCategoryPopup();\n\n        $category = 'Test category 1 [DE] šÄßüл';\n        $categoryPopup = $categoryPopup->selectCategory($category);\n        $categoryPopup = $categoryPopup->unsetCategory();\n        $categoryPopup->selectCategory($category);\n\n        $I->closeTab();\n        $I->switchToPreviousTab();\n\n        $I->clearShopCache();\n        $adminPanel = $I->loginAdmin();\n\n        $coreSettings = $adminPanel->openCoreSettings();\n        $settingsTab = $coreSettings->openSettingsTab();\n\n        $settingsTab->openShopFrontendDropdown();\n        $I->seeElement(Locator::find('input', ['value' => $category]));\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/AdminCreateCategoryCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin')]\nfinal class AdminCreateCategoryCest\n{\n    private string $categoryName = 'test category';\n\n    public function createCategory(AcceptanceTester $I): void\n    {\n        $I->wantToTest('create a category with image');\n\n        $adminPanel = $I->loginAdmin();\n        $categoriesPage = $adminPanel->openCategories();\n        $mainCategoryPage = $categoriesPage->switchLanguage('Deutsch');\n        $mainCategoryPage->create($this->categoryName);\n        $mainCategoryPage->uploadThumbnail('some_icon.png');\n\n        $I->seeInDatabase('oxcategories', ['oxtitle' => $this->categoryName]);\n    }\n\n    public function createCategoryWrongImageExtension(AcceptanceTester $I): void\n    {\n        $I->wantToTest('create a category with wrong image extension');\n\n        $adminPanel = $I->loginAdmin();\n        $categoriesPage = $adminPanel->openCategories();\n        $mainCategoryPage = $categoriesPage->switchLanguage('Deutsch');\n        $mainCategoryPage->create($this->categoryName);\n        $mainCategoryPage->uploadThumbnail('product_image.php');\n\n        $I->seeText(Translator::translate('ERROR_MESSAGE_WRONG_IMAGE_FILE_TYPE'));\n    }\n\n    public function createCategoryWrongImageType(AcceptanceTester $I): void\n    {\n        $I->wantToTest('create a category with wrong image type');\n\n        $adminPanel = $I->loginAdmin();\n        $categoriesPage = $adminPanel->openCategories();\n        $mainCategoryPage = $categoriesPage->switchLanguage('Deutsch');\n        $mainCategoryPage->create($this->categoryName);\n        $mainCategoryPage->uploadThumbnail('fake_image.png');\n\n        $I->seeText(Translator::translate('ERROR_MESSAGE_WRONG_IMAGE_FILE_TYPE'));\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/AssignProductsToCategoryCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin')]\nfinal class AssignProductsToCategoryCest\n{\n    public function testAssignProductsToCategory(AcceptanceTester $I): void\n    {\n        $I->wantToTest('assigning products to a category');\n\n        $I->amGoingTo('open category products assignment popup');\n        $adminPanel = $I->loginAdmin();\n        $categoriesPage = $adminPanel->openCategories();\n        $mainCategoryPage = $categoriesPage->selectProductCategory('Test category 1 [DE] šÄßüл');\n        $assignProductsPopup = $mainCategoryPage->openAssignProductsPopup();\n\n        $I->expect('products to be initially unassigned');\n        $assignProductsPopup\n            ->seeProductInUnassignedList('1000')\n            ->seeProductInUnassignedList('10014');\n\n        $I->amGoingTo('assign all products to category');\n        $assignProductsPopup->assignAllProducts();\n\n        $I->expect('all products to appear in assigned list');\n        $assignProductsPopup\n            ->seeProductInAssignedList('1000')\n            ->seeProductInAssignedList('10014');\n\n        $I->amGoingTo('unassign all products from category');\n        $assignProductsPopup->unassignAllProducts();\n\n        $I->expect('all products to be back in unassigned list');\n        $assignProductsPopup\n            ->seeProductInUnassignedList('1000')\n            ->seeProductInUnassignedList('10014');\n\n        $I->amGoingTo('assign single product to category');\n        $assignProductsPopup->assignProductByArtNr('1000');\n\n        $I->expect('only selected product to be assigned');\n        $assignProductsPopup\n            ->seeProductInAssignedList('1000')\n            ->seeProductInUnassignedList('10014');\n    }\n\n    public function testAssignProductsWithVariantsToCategory(AcceptanceTester $I): void\n    {\n        $I->wantToTest('variant products visibility in category assignment');\n\n        $I->amGoingTo('enable variants display in assignment lists');\n        $adminPanel = $I->loginAdmin();\n        $coreSettings = $adminPanel->openCoreSettings();\n        $systemTab = $coreSettings->openSystemTab();\n        $variantsTab = $systemTab->openVariants();\n        $variantsTab->enableVariantsInAssignmentLists();\n\n        $I->amGoingTo('check product variants visibility');\n        $categoriesPage = $adminPanel->openCategories();\n        $mainCategoryPage = $categoriesPage->selectProductCategory('Test category 1 [DE] šÄßüл');\n        $assignProductsPopup = $mainCategoryPage->openAssignProductsPopup();\n\n        $I->expect('to see parent product and all variants');\n        $assignProductsPopup\n            ->seeProductInUnassignedList('1002')\n            ->seeProductInUnassignedList('1002-1')\n            ->seeProductInUnassignedList('1002-2');\n\n        $I->amGoingTo('assign all products with variants');\n        $assignProductsPopup->assignAllProducts();\n\n        $I->expect('parent and variants to be in assigned list');\n        $assignProductsPopup\n            ->seeProductInAssignedList('1002')\n            ->seeProductInAssignedList('1002-1')\n            ->seeProductInAssignedList('1002-2');\n\n        $I->amGoingTo('disable variants display');\n        $I->closeTab();\n        $coreSettings = $adminPanel->openCoreSettings();\n        $systemTab = $coreSettings->openSystemTab();\n        $variantsTab = $systemTab->openVariants();\n        $variantsTab->disableVariantsInAssignmentLists();\n\n        $categoriesPage = $adminPanel->openCategories();\n        $mainCategoryPage = $categoriesPage->selectProductCategory('Test category 1 [DE] šÄßüл');\n        $assignProductsPopup = $mainCategoryPage->openAssignProductsPopup();\n\n        $I->expect('to see only parent product without variants');\n        $assignProductsPopup\n            ->seeProductInAssignedList('1002')\n            ->dontSeeProductInUnassignedList('1002-1')\n            ->dontSeeProductInAssignedList('1002-2');\n    }\n\n    public function testSortCategoryProducts(AcceptanceTester $I): void\n    {\n        $I->wantToTest('category products sorting');\n\n        $I->amGoingTo('open products sorting popup');\n        $sortProductsPopup = $I\n            ->loginAdmin()\n            ->openCategories()\n            ->selectProductCategory('Test category 0 [DE] šÄßüл')\n            ->openSortingTab()\n            ->openSortingProductsPopup();\n\n        $I->expect('products to be in default order');\n        $sortProductsPopup\n            ->seeProductInPosition('1000', 1)\n            ->seeProductInPosition('1001', 2);\n\n        $I->amGoingTo('create new product sorting');\n        $sortProductsPopup\n            ->assignProductByArtNr('1001')\n            ->assignProductByArtNr('1000')\n            ->saveSorting();\n\n        $I->amGoingTo('apply column sorting');\n        $sortProductsPopup->sortByColumn(3);\n\n        $I->expect('products to be in new order');\n        $sortProductsPopup\n            ->seeProductInPosition('1001', 1)\n            ->seeProductInPosition('1000', 2);\n\n        $I->amGoingTo('reset product sorting');\n        $sortProductsPopup->deleteSorting();\n\n        $I->expect('products to return to default order');\n        $sortProductsPopup\n            ->seeProductInPosition('1000', 1)\n            ->seeProductInPosition('1001', 2);\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/DownloadableProductCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse Codeception\\Util\\Fixtures;\nuse DateTime;\nuse OxidEsales\\Codeception\\Admin\\AdminPanel;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin')]\nfinal class DownloadableProductCest\n{\n    private string $orderNo;\n    private string $productId;\n    private string $productTitle;\n\n    public function _before(AcceptanceTester $I): void\n    {\n        $order = Fixtures::get('testorder');\n        $orderId = $order['OXID'];\n        $this->orderNo = $order['OXORDERNR'];\n        $orderProductData = $order['PRODUCTS'][0];\n        $this->productId = $orderProductData['OXARTID'];\n        $this->productTitle = $orderProductData['OXTITLE'];\n\n        $I->updateInDatabase('oxarticles', ['oxisdownloadable' => 1], ['oxartnum' => $this->productId]);\n\n        $I->haveInDatabase(\n            'oxorderfiles',\n            [\n                'OXID' => 'testdownloadProductCest',\n                'OXORDERID' => $orderId,\n                'OXFILENAME' => 'testFile3',\n                'OXFILEID' => '1000l',\n                'OXSHOPID' => 1,\n                'OXORDERARTICLEID' => $orderProductData['OXID'],\n                'OXDOWNLOADCOUNT' => '0',\n                'OXMAXDOWNLOADCOUNT' => 2,\n                'OXDOWNLOADEXPIRATIONTIME' => 24,\n                'OXLINKEXPIRATIONTIME' => 240,\n                'OXRESETCOUNT' => 0,\n                'OXVALIDUNTIL' => (new DateTime())->modify('+1 week')->format('Y-m-d H:i:s'),\n                'OXTIMESTAMP' => (new DateTime())->format('Y-m-d H:i:s')\n            ]\n        );\n\n        $I->haveInDatabase(\n            'oxfiles',\n            [\n                'OXID' => '1000l',\n                'OXARTID' => $this->productId,\n                'OXFILENAME' => 'testFile3',\n                'OXPURCHASEDONLY' => 1,\n                'OXSTOREHASH' => 'e48a1b571bd2d2e60fb2d9b1b76b35d5',\n            ]\n        );\n    }\n\n    public function _after(AcceptanceTester $I): void\n    {\n        $I->updateConfigInDatabase('blEnableDownloads', \"false\", 'bool');\n        $I->updateConfigInDatabase('iMaxDownloadsCount', \"0\", 'str');\n        $I->updateConfigInDatabase('iLinkExpirationTime', \"168\", 'str');\n        $I->updateConfigInDatabase('iMaxDownloadsCountUnregistered', \"1\", 'str');\n    }\n\n    public function downloadableFiles(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Product downloadable files');\n\n        $adminPanel = $I->loginAdmin();\n\n        $this->enableDownloadableFiles($I, $adminPanel);\n        $this->setDownloadableFileForAProduct($I, $adminPanel);\n        $this->makeOrderComplete($I, $adminPanel);\n    }\n\n    private function enableDownloadableFiles(AcceptanceTester $I, AdminPanel $adminPanel): void\n    {\n        $coreSettings = $adminPanel->openCoreSettings();\n        $settingsTab = $coreSettings->openSettingsTab();\n        $settingsTab->openDownloadableProducts();\n        $I->checkOption('confbools[blEnableDownloads]');\n        $I->fillField(\"confstrs[iMaxDownloadsCount]\", \"2\");\n        $I->fillField(\"confstrs[iLinkExpirationTime]\", \"240\");\n        $I->fillField(\"confstrs[iDownloadExpirationTime]\", \"24\");\n        $I->fillField(\"confstrs[iMaxDownloadsCountUnregistered]\", \"1\");\n        $I->clickAndWait(['name' => 'save']);\n    }\n\n    private function setDownloadableFileForAProduct(AcceptanceTester $I, AdminPanel $adminPanel): void\n    {\n        $products = $adminPanel->openProducts();\n        $products->findByProductNumber($this->productId);\n        $products->openDownloadsTab();\n        $I->checkOption('editval[oxarticles__oxisdownloadable]');\n        $I->clickAndWait(['name' => 'save']);\n    }\n\n    private function makeOrderComplete(AcceptanceTester $I, AdminPanel $adminPanel): void\n    {\n        $orders = $adminPanel->openOrders();\n        $orders->findByOrderNumber($this->orderNo);\n        $orders->openDownloadsTab();\n        $firstDownloadableProductLocator = \"//tr[@id='file.1']\";\n\n        $I->assertEquals($this->productId, $I->grabTextFrom(\"{$firstDownloadableProductLocator}/td[1]\"));\n        $I->assertEquals($this->productTitle, $I->grabTextFrom(\"$firstDownloadableProductLocator/td[2]\"));\n        $I->assertEquals(\"testFile3\", $I->grabTextFrom(\"$firstDownloadableProductLocator/td[3]\"));\n        $I->assertEquals(\"0000-00-00 00:00:00\", $I->grabTextFrom(\"$firstDownloadableProductLocator/td[4]\"));\n        $I->assertEquals(\"0000-00-00 00:00:00\", $I->grabTextFrom(\"$firstDownloadableProductLocator/td[5]\"));\n        $I->assertEquals(\"0\", $I->grabTextFrom(\"$firstDownloadableProductLocator/td[6]\"));\n        $I->assertEquals(\"2\", $I->grabTextFrom(\"$firstDownloadableProductLocator/td[7]\"));\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/ExportNewsletterRecipientsCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin')]\nfinal class ExportNewsletterRecipientsCest\n{\n    public function checkExportRecipients(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Check Export Newsletter Recipients');\n\n        $adminPanel = $I->loginAdmin();\n        $newsletter = $adminPanel->openNewsletter();\n        $newsletter->exportReciepents();\n        $I->waitForPageLoad();\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/GenericExportCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin')]\nfinal class GenericExportCest\n{\n    #[Group('genericExport', 'exclude_from_compilation')]\n    public function testGenericExport(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Generic Export outputs product with rendered long description');\n        $adminPanel = $I->loginAdmin();\n\n        $I->amGoingTo('add some content with template tags to the long description');\n        $contents = uniqid('some-long-description-', true);\n        $descriptionWithTags = \"$contents{{ 'now' | date('Y') }}\";\n        $renderedDescription = $contents . date('Y');\n        $products = $adminPanel->openProducts();\n        $products\n            ->findByProductNumber('1000')\n            ->setLongDescription($descriptionWithTags)\n            ->save();\n\n        $I->amGoingTo('export category of the modified product and see that long description is rendered');\n        $adminPanel\n            ->openGenericExport()\n            ->selectExportCategory('Test category 0 [DE] šÄßüл')\n            ->doExport()\n            ->seeInExportResultsFile($renderedDescription);\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/GenericImportCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin', 'genericImport')]\nfinal class GenericImportCest\n{\n    public function testGenericImportWithHeaders(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Generic Import for file with CSV header');\n        $tableName = 'oxartextends';\n        $adminPanel = $I->loginAdmin();\n        $genericImport = $adminPanel->openGenericImport();\n\n        $I->amGoingTo('attach and set parameters for CSV file on Step 1');\n        $genericImport->setCsvSourceFile(\"genericImport/{$tableName}_with_header.csv\")\n            ->setTargetTable($tableName)\n            ->setFirstCsvRowContainsHeaders()\n            ->proceedToFieldMapping($tableName);\n\n        $I->amGoingTo('check field mapping on Step 2');\n        $genericImport->seeCsvColumnToFieldMapping(1, 'OXID')\n            ->seeCsvColumnToFieldMapping(2, 'OXLONGDESC')\n            ->seeCsvColumnToFieldMapping(3, 'OXLONGDESC_1')\n            ->doImport();\n\n        $I->amGoingTo('check if product detail page contains updated data');\n        $products = $adminPanel->openProducts();\n        $mainProductPage = $products->findByProductNumber('1001');\n        $I->seeInField($mainProductPage->longDescriptionInput, 'long desc DE with header');\n        $mainProductPage->switchLanguage('English');\n        $I->seeInField($mainProductPage->longDescriptionInput, 'long desc EN with header');\n    }\n\n    public function testGenericImportNoHeaders(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Generic Import for file without CSV header');\n        $tableName = 'oxartextends';\n        $adminPanel = $I->loginAdmin();\n        $genericImport = $adminPanel->openGenericImport();\n\n        $I->amGoingTo('attach and set parameters for CSV file on Step 1');\n        $genericImport->setCsvSourceFile(\"genericImport/{$tableName}_without_header.csv\")\n            ->setTargetTable($tableName);\n\n        $I->expect('that I can use non-default field enclosure and terminator for CSV file');\n        $genericImport->setCsvFieldEnclosure(\"'\")\n            ->setCsvFieldTerminator(',')\n            ->proceedToFieldMapping($tableName);\n\n        $I->amGoingTo('define field mapping on Step 2');\n        $genericImport->setCsvColumnToFieldMapping(1, 'OXID')\n            ->setCsvColumnToFieldMapping(2, 'OXLONGDESC')\n            ->setCsvColumnToFieldMapping(3, 'OXLONGDESC_1')\n            ->doImport();\n\n        $I->amGoingTo('check if product detail page contains updated data');\n        $products = $adminPanel->openProducts();\n        $mainProductPage = $products->findByProductNumber('1001');\n        $I->seeInField($mainProductPage->longDescriptionInput, 'long desc DE no header');\n        $mainProductPage->switchLanguage('English');\n        $I->seeInField($mainProductPage->longDescriptionInput, 'long desc EN no header');\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/LoginCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse Codeception\\Util\\Fixtures;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin')]\nfinal class LoginCest\n{\n    public function setSessionCookie(AcceptanceTester $I): void\n    {\n        $I->wantToTest('correct session ID name is set in cookies');\n\n        $userData = $this->getAdminUserData();\n\n        $loginPage = $I->openAdmin();\n        $loginPage->login($userData['userLoginName'], $userData['userPassword']);\n\n        $I->seeCookie('admin_sid');\n        $I->dontSeeCookie('sid');\n    }\n\n    private function getAdminUserData(): array\n    {\n        return Fixtures::get('adminUser');\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/ManufacturerCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\After;\nuse Codeception\\Attribute\\Before;\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\Codeception\\Admin\\DataObject\\Manufacturer;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\nuse function codecept_data_dir;\n\n#[Group('admin', 'manufacturer')]\nfinal class ManufacturerCest\n{\n    private string $existingDataFile = 'some_icon.png';\n    private string $uniqueDataFile;\n\n    #[Before('createUniqueFixtureFile')]\n    #[After('removeUniqueFixtureFile')]\n    public function createManufacturer(AcceptanceTester $I): void\n    {\n        $I->wantToTest('create and read for manufacturer form');\n\n        $manufacturer = $this->getManufacturer();\n        $manufacturersPage = $I\n            ->loginAdmin()\n            ->openManufacturers();\n        $manufacturersPage\n            ->createManufacturer($manufacturer)\n            ->findByManufacturerTitle($manufacturer->getTitle())\n            ->openPicturesTab()\n            ->uploadIcon($manufacturer->getIcon());\n        $manufacturersPage\n            ->findByManufacturerTitle($manufacturer->getTitle())\n            ->openMainTab()\n            ->seeManufacturer($manufacturer)\n            ->openPicturesTab()\n            ->seeIcon($manufacturer->getIcon());\n    }\n\n    private function createUniqueFixtureFile(): void\n    {\n        $this->uniqueDataFile = uniqid('some-icon-', true) . '.png';\n        copy(\n            codecept_data_dir($this->existingDataFile),\n            codecept_data_dir($this->uniqueDataFile)\n        );\n    }\n\n    private function getManufacturer(): Manufacturer\n    {\n        $manufacturer = new Manufacturer();\n        $manufacturer->setActive(true);\n        $manufacturer->setTitle(uniqid('Title -', true));\n        $manufacturer->setShortDescription(uniqid('Short description - ', true));\n        $manufacturer->setIcon($this->uniqueDataFile);\n        $manufacturer->setSortValue(5);\n\n        return $manufacturer;\n    }\n\n    private function removeUniqueFixtureFile(): void\n    {\n        unlink(codecept_data_dir($this->uniqueDataFile));\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/MasterCoreStockSettingsCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin', 'core', 'stock')]\nfinal class MasterCoreStockSettingsCest\n{\n    public function testStockDefaultMessageSettings(AcceptanceTester $I): void\n    {\n        $I->wantToTest('stock default message settings management');\n\n        $adminPanel = $I->loginAdmin();\n        $coreSettings = $adminPanel->openCoreSettings();\n        $settingsTab = $coreSettings->openSettingsTab();\n\n        $I->amGoingTo('check that all stock default message options are activated');\n        $stockSettings = $settingsTab->openStockSettings();\n        $stockSettings->seeInStockMessageSelected();\n        $stockSettings->seeLowStockMessageSelected();\n        $stockSettings->seeOutOfStockMessageSelected();\n\n        $I->amGoingTo('deactivate all stock default message settings and verify the changes');\n        $stockSettings->uncheckInStockMessageOption();\n        $stockSettings->uncheckLowStockMessageOption();\n        $stockSettings->uncheckOutOfStockMessageOption();\n        $settingsTab->save();\n\n        $stockSettings = $settingsTab->openStockSettings();\n        $stockSettings->dontSeeLowStockMessageSelected();\n        $stockSettings->dontSeeInStockMessageSelected();\n        $stockSettings->dontSeeOutOfStockMessageSelected();\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/ModuleActivationCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin')]\nfinal class ModuleActivationCest\n{\n    private string $testModule1Id = 'codeception_testModule';\n    private string $testModule1Path = 'modules/testModule';\n\n    public function _before(AcceptanceTester $I): void\n    {\n        $I->installModule(\n            codecept_data_dir($this->testModule1Path)\n        );\n    }\n\n    public function _after(AcceptanceTester $I): void\n    {\n        $I->deactivateModule($this->testModule1Id);\n        $I->uninstallModule($this->testModule1Id);\n    }\n\n    public function moduleActivation(AcceptanceTester $I): void\n    {\n        $I->wantToTest('module activation in normal mode');\n\n        $this->openModuleOverview($I);\n\n        $I->seeElement('#module_activate');\n        $I->dontSeeElement('#module_deactivate');\n\n        $I->clickAndWait('#module_activate');\n\n        $I->waitForElementVisible('#module_deactivate');\n        $I->dontSeeElement('#module_activate');\n    }\n\n    public function moduleActivationInDemoMode(AcceptanceTester $I): void\n    {\n        $I->wantToTest('module activation disabled in demo mode');\n        $I->updateProjectConfigurations(['oxid_esales.demo_shop_mode' => true], []);\n\n        $this->openModuleOverview($I);\n\n        $I->dontSeeElement('#module_activate');\n        $I->dontSeeElement('#module_deactivate');\n        $I->seeText(Translator::translate('MODULE_ACTIVATION_NOT_POSSIBLE_IN_DEMOMODE'));\n\n        $I->activateModule($this->testModule1Id);\n\n        $I->dontSeeElement('#module_deactivate');\n        $I->dontSeeElement('#module_activate');\n        $I->seeText(Translator::translate('MODULE_ACTIVATION_NOT_POSSIBLE_IN_DEMOMODE'));\n\n        $I->restoreProjectConfigurations();\n    }\n\n    private function openModuleOverview(AcceptanceTester $I): void\n    {\n        $loginPage = $I->loginAdmin();\n        $moduleList = $loginPage->openModules();\n        $module = $moduleList->selectModule('Codeception test module #1');\n        $module->openModuleTab('Overview');\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/ModuleSettingsCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse Facebook\\WebDriver\\WebDriverElement;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\nuse function codecept_data_dir;\n\n#[Group('admin', 'moduleInstall')]\nfinal class ModuleSettingsCest\n{\n    private string $testModule1Id = 'codeception_testModule';\n    private string $testModule1Path = 'modules/testModule';\n\n    public function _before(AcceptanceTester $I): void\n    {\n        $I->installModule(\n            codecept_data_dir($this->testModule1Path)\n        );\n        $I->activateModule($this->testModule1Id);\n    }\n\n    public function _after(AcceptanceTester $I): void\n    {\n        $I->deactivateModule($this->testModule1Id);\n        $I->uninstallModule($this->testModule1Id);\n    }\n\n    public function moduleEmptySettingsForm(AcceptanceTester $I): void\n    {\n        $I->wantToTest('module empty settings are loaded from metadata and form save works');\n        $adminPanel = $I->loginAdmin();\n        $moduleList = $adminPanel->openModules();\n        $module =  $moduleList->selectModule('Codeception test module #1');\n        $module->openModuleTab('Settings');\n\n        $I->clickAndWait(Translator::translate('Empty Settings Group'));\n        $this->checkEmptyInitialSettingsLoaded($I);\n\n        $this->modifyEmptyInitialSettings($I);\n        $I->clickAndWait('save');\n        $I->selectEditFrame();\n        $I->waitForDocumentReadyState();\n\n        $I->amGoingTo('make sure that the form was reloaded and group contents are now collapsed');\n        $I->clickAndWait('Empty Settings Group');\n        $this->checkModifiedSettingsNotEmpty($I);\n    }\n\n    public function moduleSettingsForm(AcceptanceTester $I): void\n    {\n        $I->wantToTest('module settings are loaded from metadata');\n        $adminPanel = $I->loginAdmin();\n        $moduleList = $adminPanel->openModules();\n        $module =  $moduleList->selectModule('Codeception test module #1');\n        $module->openModuleTab('Settings');\n\n        $I->clickAndWait(Translator::translate('Filled Settings Group'));\n        $this->checkFilledInitialSettingsLoaded($I);\n    }\n\n    private function checkEmptyInitialSettingsLoaded(AcceptanceTester $I): void\n    {\n        $I->dontSeeCheckboxIsChecked('confbools[testEmptyBoolConfig]');\n        $I->seeInField('confstrs[testEmptyStrConfig]', '');\n        $I->seeInField('confarrs[testEmptyArrConfig]', '');\n        $I->seeInField('confaarrs[testEmptyAArrConfig]', '');\n        $I->seeOptionIsSelected('confselects[testEmptySelectConfig]', 'Option 0');\n        $I->seeInField('confpassword[testEmptyPasswordConfig]', '');\n    }\n\n    private function checkFilledInitialSettingsLoaded(AcceptanceTester $I): void\n    {\n        $I->seeCheckboxIsChecked('confbools[testFilledBoolConfig]');\n        $I->seeInField('confstrs[testFilledStrConfig]', 'testStr');\n        $I->seeInField('confarrs[testFilledArrConfig]', \"option1\\noption2\");\n        $I->seeInField('confaarrs[testFilledAArrConfig]', \"key1 => option1\\nkey2 => option2\");\n        $I->seeInField('confselects[testFilledSelectConfig]', 'Option 2');\n        $I->dontSee('confpassword[testFilledPasswordConfig]');\n        $I->seeInField('confpassword[testFilledPasswordConfig]', '');\n    }\n\n    private function modifyEmptyInitialSettings(AcceptanceTester $I): void\n    {\n        $I->checkOption('confbools[testEmptyBoolConfig]');\n        $I->fillField('confstrs[testEmptyStrConfig]', 'new-string');\n        $I->fillField('confarrs[testEmptyArrConfig]', \"new-option-1\\nnew-option-2\");\n        $I->fillField('confaarrs[testEmptyAArrConfig]', \"key1 => new-option-1\\nkey2 => new-option-2\");\n        $I->selectOption('confselects[testEmptySelectConfig]', 2);\n        $I->fillField('.password_input', 'test-password');\n        $I->fillField('confpassword[testEmptyPasswordConfig]', 'test-password');\n    }\n\n    private function checkModifiedSettingsNotEmpty(AcceptanceTester $I): void\n    {\n        $I->seeCheckboxIsChecked('confbools[testEmptyBoolConfig]');\n        $I->seeInField('confstrs[testEmptyStrConfig]', 'new-string');\n        $I->seeInField('confarrs[testEmptyArrConfig]', \"new-option-1\\nnew-option-2\");\n        $I->seeInField('confaarrs[testEmptyAArrConfig]', \"key1 => new-option-1\\nkey2 => new-option-2\");\n        $I->seeOptionIsSelected('confselects[testEmptySelectConfig]', 'Option 2');\n        $I->dontSee('confpassword[testEmptyPasswordConfig]');\n        $I->seeInField('confpassword[testEmptyPasswordConfig]', '');\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/ModuleSortListCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin')]\nfinal class ModuleSortListCest\n{\n    private string $testModuleId = 'codeception_testModule';\n    private string $testModulePath = 'modules/testModule';\n    private string $testModuleWithProblemsId = 'codeception_test-module-problems';\n    private string $testModuleWithProblemsPath = 'modules/test-module-problems';\n    private string $fixtureParentController = 'OxidEsales---Eshop---Application---Controller---ContentController';\n    private string $fixtureModuleController =\n        'OxidEsales\\\\\\EshopCommunity\\\\\\Tests\\\\\\Codeception\\\\\\Support\\\\\\Data\\\\\\modules\\\\\\testModule\\\\\\Controller\\\\\\ContentController';\n\n    public function moduleClassExtensionsArePresentOnInstalledModulePage(AcceptanceTester $I): void\n    {\n        $I->wantToTest('module class extensions are present on installed modules page for active and inactive module');\n        $I->installModule(\n            codecept_data_dir($this->testModulePath)\n        );\n        $I->deactivateModule($this->testModuleId);\n\n        $moduleList = $I\n            ->loginAdmin()\n            ->openModules()\n            ->selectModule('Codeception test module #1');\n        $moduleList->openModuleTab('Installed Shop Modules');\n\n        $I->seeElement(\"li#$this->fixtureParentController\");\n        $I->seeElement(\"li#$this->fixtureModuleController .disabled\");\n\n        $moduleList->openModuleTab('Overview');\n        $moduleList->activateModule();\n\n        $moduleList->openModuleTab('Installed Shop Modules');\n        $I->seeElement(\"li#$this->fixtureParentController\");\n        $I->seeElement(\"li#$this->fixtureModuleController\");\n        $I->dontSeeElement(\"li#$this->fixtureModuleController .disabled\");\n\n        $I->uninstallModule($this->testModuleId);\n    }\n\n    public function moduleWithProblemsSortList(AcceptanceTester $I): void\n    {\n        $I->wantToTest('module sort list functionality with problematic module');\n        $I->installModule(\n            codecept_data_dir($this->testModuleWithProblemsPath)\n        );\n        $moduleList = $I\n            ->loginAdmin()\n            ->openModules()\n            ->selectModule('Module with problems (Namespaced)');\n\n        $moduleList->openModuleTab('Overview');\n        $moduleList->activateModule();\n\n        $I->expect('to see info about module problems');\n        $moduleList->openModuleTab('Installed Shop Modules');\n        $I->seeText(Translator::translate('MODULE_EXTENSIONISDELETED'));\n        $I->see(Translator::translate('MODULE_PROBLEMATIC_FILES'));\n        $I->see(Article::class);\n        $I->see('NonExistentFile');\n\n        $I->amGoingTo('remove problematic configs');\n        $I->clickAndWait(['name' => 'yesButton']);\n        $I->waitForElementNotVisible(['name' => 'yesButton']);\n        $I->dontSee(Translator::translate('MODULE_EXTENSIONISDELETED'));\n\n        $I->expect('to see that the module is not active');\n        $moduleList->openModuleTab('Overview');\n        $I->seeElement('#module_activate');\n        $I->dontSeeElement('#module_deactivate');\n\n        $I->uninstallModule($this->testModuleWithProblemsId);\n    }\n\n    public function _failed(AcceptanceTester $I): void\n    {\n        try {\n            $I->uninstallModule($this->testModuleId);\n        } catch (\\Throwable) {\n        }\n        try {\n            $I->uninstallModule($this->testModuleWithProblemsId);\n        } catch (\\Throwable) {\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/NavigationCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin')]\nfinal class NavigationCest\n{\n    public function shopsStartPageButton(AcceptanceTester $I): void\n    {\n        $I->wantToTest('\"Shop\\'s start page\" button');\n        $adminPanel = $I->loginAdmin();\n        $I->seeInCurrentUrl('admin');\n        $adminPanel->openShopsStartPage();\n        $I->dontSeeInCurrentUrl('admin');\n    }\n\n    public function systemInfo(AcceptanceTester $I): void\n    {\n        $I->wantToTest('System info page is accessible');\n\n        $adminPanel = $I->loginAdmin();\n        $adminPanel->openSystemInfo();\n\n        $I->seeText('PHP Version');\n        $I->seeText('Configuration');\n    }\n\n    public function systemRequirements(AcceptanceTester $I): void\n    {\n        $I->wantToTest('System requirements page is accessible and translated');\n        $untranslatedKeyPrefix = 'SYSREQ_';\n\n        $adminPanel = $I->loginAdmin();\n        $adminPanel->openSystemHealth();\n\n        $I->seeText(Translator::translate('State of system health'));\n        $I->dontSee($untranslatedKeyPrefix);\n    }\n\n    public function tools(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Tools page is accessible and translated');\n        $untranslatedKeyPrefix = 'TOOLS_';\n\n        $adminPanel = $I->loginAdmin();\n        $tools = $adminPanel->openTools();\n\n        $I->waitForText(Translator::translate('Update SQL'));\n        $I->seeElement($tools->sqlTextInput);\n        $I->seeElement($tools->uploadSqlFileInput);\n        $I->seeElement($tools->runUpdateSqlButton);\n        $I->seeElement($tools->updateDbViewsButton);\n\n        $I->dontSee($untranslatedKeyPrefix);\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/NewCMSCreationCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin', 'exclude_from_compilation')]\nfinal class NewCMSCreationCest\n{\n    public function newCMSCreation(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Create a new CMS and check if it is saved in database');\n\n        $title = 'New CMS Content';\n        $content = 'This is a new CMS content';\n        $ident = 'newcmscontent';\n\n        $adminPanel = $I->loginAdmin();\n        $languages = $adminPanel->openCMSPages();\n        $languages->createNewCMS($title, $ident, $content);\n        $languages->find('where[oxcontents][oxtitle]', $title);\n\n        $I->assertEquals($title, $I->grabFromDatabase('oxcontents', 'oxtitle', ['oxloadid' => $ident]));\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/NewLanguageCreationCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\Codeception\\Admin\\Languages;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin')]\nfinal class NewLanguageCreationCest\n{\n    public function newLanguagesCreation(AcceptanceTester $I): void\n    {\n        $I->wantToTest('if we can create four new languages successfully.');\n\n        $adminPanel = $I->loginAdmin();\n        $languages = $adminPanel->openLanguages();\n\n        $I->amGoingTo('create the first language.');\n        $this->createNewLanguage($languages, $I, 'lt', 'Lietuviu');\n\n        $I->amGoingTo('create the second language.');\n        $this->createNewLanguage($languages, $I, 'hu', 'Hungarian');\n\n        $I->amGoingTo('create the third language.');\n        $this->createNewLanguage($languages, $I, 'mt', 'Maltese');\n\n        $I->amGoingTo('create the fourth language.');\n        $this->createNewLanguage($languages, $I, 'es', 'Spanish');\n\n        $I->amGoingTo('generate DB views.');\n        $tools = $adminPanel->openTools();\n        $tools->updateDbViews();\n\n        $I->amGoingTo('check the new languages fields.');\n        $this->checkLanguageFields($I);\n    }\n\n    private function createNewLanguage(Languages $languages, AcceptanceTester $I, string $code, string $name): void\n    {\n        $languages->createNewLanguage($code, $name);\n        $I->amGoingTo('check extra messages.');\n        $this->checkMessages($I);\n    }\n\n    private function checkMessages(AcceptanceTester $I): void\n    {\n        $I->selectEditFrame();\n        $I->expect('not to see the multilingual fields error message. Four language fields are predefined,\n        so on the addition of a fifth language, new fields should be added as well.');\n        $I->dontSee(Translator::translate('LANGUAGE_ERROR_ADDING_MULTILANG_FIELDS'));\n    }\n\n    private function checkLanguageFields(AcceptanceTester $I): void\n    {\n        $I->retryGrabFromDatabase('oxv_oxarticles_lt', 'oxid', ['oxartnum' => '3503']);\n        $I->retryGrabFromDatabase('oxv_oxarticles_hu', 'oxid', ['oxartnum' => '3503']);\n        $I->retryGrabFromDatabase('oxv_oxarticles_mt', 'oxid', ['oxartnum' => '3503']);\n        $I->retryGrabFromDatabase('oxv_oxarticles_es', 'oxid', ['oxartnum' => '3503']);\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/NotRegisteredUserOrderCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse DateTime;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin')]\nfinal class NotRegisteredUserOrderCest\n{\n    public function _before(AcceptanceTester $I): void\n    {\n        $this->insertAnOrderInDatabase($I);\n    }\n\n    public function checkEditingNotRegisteredUserOrder(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Check editing not registered user order');\n\n        $orderOverview = $I\n            ->loginAdmin()\n            ->openOrders()\n            ->findByOrderNumber('2');\n\n        $addressesTab = $orderOverview->openAddressesTab();\n        $I->seeInField($addressesTab->firstNameInAddressesTab, 'name');\n        $I->seeInField($addressesTab->lastNameInAddressesTab, 'surname');\n        $I->seeInField($addressesTab->loginNameInAddressesTab, 'example01@oxid-esales.dev');\n        $I->seeInField($addressesTab->zipCodeInAddressesTab, '3000');\n        $I->seeInField($addressesTab->cityInAddressesTab, 'city');\n\n        $productsTab = $orderOverview\n            ->openProductsTab()\n            ->addANewProductToTheOrder('1002-1');\n\n        $I->waitForElement($productsTab->secondProductInProductTab);\n        $I->seeText('1002-1', $productsTab->secondProductInProductTab);\n    }\n\n    private function insertAnOrderInDatabase(AcceptanceTester $I): void\n    {\n        $I->haveInDatabase(\n            'oxorder',\n            [\n                'OXID' => 'NotRegisteredOrderId',\n                'OXSHOPID' => 1,\n                'OXUSERID' => 'NotRegisteredUserId',\n                'OXORDERDATE' => (new DateTime())->format('Y-m-d H:i:s'),\n                'OXORDERNR' => 2,\n                'OXBILLEMAIL' => 'example01@oxid-esales.dev',\n                'OXBILLFNAME' => 'name',\n                'OXBILLLNAME' => 'surname',\n                'OXBILLSTREET' => 'street',\n                'OXBILLSTREETNR' => '1',\n                'OXBILLCITY' => 'city',\n                'OXBILLCOUNTRYID' => 'testcountry_be',\n                'OXBILLSTATEID' => 'BB',\n                'OXBILLZIP' => '3000',\n                'OXPAYMENTID' => 'NotRegisteredPaymentId',\n                'OXPAYMENTTYPE' => 'oxidcashondel',\n                'OXREMARK' => 'remark text',\n                'OXTRANSSTATUS' => 'OK',\n                'OXFOLDER' => 'ORDERFOLDER_NEW',\n                'OXDELTYPE' => 'oxidstandard',\n                'OXTIMESTAMP' => (new DateTime())->format('Y-m-d H:i:s')\n            ]\n        );\n\n        $I->haveInDatabase(\n            'oxorderarticles',\n            [\n                'OXID' => 'NotRegisteredOrderArticlesId',\n                'OXORDERID' => 'NotRegisteredOrderId',\n                'OXAMOUNT' => 1,\n                'OXARTID' => '1002-1',\n                'OXARTNUM' => '1002-1',\n                'OXTITLE' => 'Test product 2 [EN] šÄßüл',\n                'OXSHORTDESC' => 'Test product 2 short desc [EN] šÄßüл',\n                'OXSELVARIANT' => 'var1 [EN] šÄßüл',\n                'OXNETPRICE' => 46.22,\n                'OXBRUTPRICE' => 55,\n                'OXVATPRICE' => 8.78,\n                'OXVAT' => 19,\n                'OXSTOCK' => 5,\n                'OXINSERT' => '2008-02-04',\n                'OXTIMESTAMP' => (new DateTime())->format('Y-m-d H:i:s'),\n                'OXSEARCHKEYS' => 'šÄßüл1002',\n                'OXISSEARCH' => 1,\n                'OXORDERSHOPID' => 1\n            ]\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/OrderEmailCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse Codeception\\Util\\Fixtures;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin', 'order', 'email')]\nfinal class OrderEmailCest\n{\n    public function testSendOrderWithAddress(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Sending email on order shipment');\n\n        $shop = Fixtures::get('shop-1');\n        $order = Fixtures::get('testorder');\n\n        $I->loginAdmin()\n            ->openOrders()\n            ->findByOrderNumber($order['OXORDERNR'])\n            ->shipOrderWithEmail();\n\n        $I->openRecentEmail();\n\n        $I->amGoingTo('Check Email subject');\n\n        $I->seeInEmailSubject($shop['OXSENDEDNOWSUBJECT']);\n\n        $I->amGoingTo('Check Email Sender Receiver');\n\n        $I->seeInEmailTo($order['OXBILLEMAIL']);\n        $I->seeInEmailTo($order['OXBILLFNAME'] . ' ' . $order['OXBILLLNAME']);\n        $I->seeInEmailFrom($shop['OXOWNEREMAIL']);\n        $I->seeInEmailFrom($shop['OXNAME']);\n\n        $I->amGoingTo('Check Email html contents');\n\n        $this->seeAddressElementsInHtmlContent($I, $order);\n        $this->seeProductsInHtmlContent($I, $order['PRODUCTS']);\n\n        $I->amGoingTo('Check Email plain contents');\n\n        $this->seeAddressElementsInPlainContent($I, $order);\n        $this->seeProductsInPlainContent($I, $order['PRODUCTS']);\n    }\n\n    private function seeAddressElementsInHtmlContent(AcceptanceTester $I, array $order): void\n    {\n        $I->seeInEmailHtmlBody($order['OXBILLADDINFO']);\n        $I->seeInEmailHtmlBody($order['OXBILLZIP']);\n        $I->seeInEmailHtmlBody($order['OXBILLSTREET']);\n        $I->seeInEmailHtmlBody($order['OXBILLSTREETNR']);\n        $I->seeInEmailHtmlBody($order['OXBILLCITY']);\n        $I->seeInEmailHtmlBody($order['OXREMARK']);\n    }\n\n    private function seeProductsInHtmlContent(AcceptanceTester $I, array $products): void\n    {\n        foreach ($products as $product) {\n            $I->seeInEmailHtmlBody($product['OXTITLE']);\n        }\n    }\n\n    private function seeAddressElementsInPlainContent(AcceptanceTester $I, array $order): void\n    {\n        $I->seeInEmailPlainBody($order['OXBILLADDINFO']);\n        $I->seeInEmailPlainBody($order['OXBILLZIP']);\n        $I->seeInEmailPlainBody($order['OXBILLSTREET']);\n        $I->seeInEmailPlainBody($order['OXBILLSTREETNR']);\n        $I->seeInEmailPlainBody($order['OXBILLCITY']);\n        $I->seeInEmailPlainBody($order['OXREMARK']);\n    }\n\n    private function seeProductsInPlainContent(AcceptanceTester $I, array $products): void\n    {\n        foreach ($products as $product) {\n            $I->seeInEmailPlainBody($product['OXAMOUNT']);\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/OrderTotalsAndCurrencyDisplayCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse DateTime;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin', 'order')]\nfinal class OrderTotalsAndCurrencyDisplayCest\n{\n    public function testOrderTotalsAndCurrencyPosition(AcceptanceTester $I): void\n    {\n        $I->wantTo('verify order totals and currency display in admin panel');\n\n        $this->prepareTestOrder($I);\n\n        $I->amGoingTo('check order totals with default currency (EUR)');\n        $adminPanel = $I->loginAdmin();\n        $ordersPage = $adminPanel->openOrders()->findByOrderNumber('123');\n\n        $I->expect('to see correct order counts and sums in EUR');\n        $ordersPage->seeOrdersTodayCount('1');\n        $ordersPage->seeOrdersTodaySum('20.000,00 EUR');\n        $ordersPage->seeTotalOrdersCount('2');\n        $ordersPage->seeTotalOrdersSum('20.000,00 EUR');\n\n        $I->amGoingTo('change the currency settings to GBP as default');\n        $coreSettings = $adminPanel->openCoreSettings();\n        $settingsTab = $coreSettings->openSettingsTab();\n        $settingsTab->openAdditionalSettings();\n\n        $I->fillField(\n            'confarrs[aCurrencies]',\n            \"GBP@ 0.8565@ .@  @ £@ 2\\nCHF@ 1.4326@ ,@ .@ CHF@ 2\\nUSD@ 1.2994@ .@  @ $@ 2\\nEUR@ 1.00@ ,@ .@ €@ 2\"\n        );\n\n        $settingsTab->save();\n\n        $I->amGoingTo('check order totals after currency change');\n        $ordersPage = $adminPanel->openOrders()->findByOrderNumber('123');\n\n        $I->expect('to see correct order counts and sums in GBP');\n        $ordersPage->seeOrdersTodayCount('1');\n        $ordersPage->seeOrdersTodaySum('17130.00 GBP');\n        $ordersPage->seeTotalOrdersCount('2');\n        $ordersPage->seeTotalOrdersSum('17130.00 GBP');\n    }\n\n    private function prepareTestOrder(AcceptanceTester $I): void\n    {\n        $I->haveInDatabase('oxorder', [\n            'OXID' => 'testorder2',\n            'OXSHOPID' => 1,\n            'OXUSERID' => 'testuser',\n            'OXORDERDATE' => (new DateTime())->format('Y-m-d H:i:s'),\n            'OXORDERNR' => 123,\n            'OXBILLEMAIL' => 'test@billemail.com',\n            'OXBILLFNAME' => 'test bill fname',\n            'OXBILLLNAME' => 'test bill lname',\n            'OXBILLSTREET' => 'test address street',\n            'OXBILLSTREETNR' => '123',\n            'OXBILLCITY' => 'test address city',\n            'OXBILLCOUNTRYID' => 'testcountry_de',\n            'OXBILLZIP' => '55555',\n            'OXFOLDER' => 'ORDERFOLDER_NEW',\n            'OXTOTALNETSUM' => 18398.26,\n            'OXTOTALBRUTSUM' => 20000.00,\n            'OXTOTALORDERSUM' => 20000.00,\n            'OXCURRENCY' => 'EUR',\n            'OXCURRATE' => 1.00\n        ]);\n\n        $I->haveInDatabase('oxorderarticles', [\n            'OXID' => 'testorderarticle1',\n            'OXORDERID' => 'testorder2',\n            'OXAMOUNT' => 1,\n            'OXARTID' => '1000',\n            'OXARTNUM' => '1000',\n            'OXTITLE' => 'Test product 1',\n            'OXBRUTPRICE' => 5000.00,\n            'OXORDERSHOPID' => 1\n        ]);\n\n        $I->haveInDatabase('oxorderarticles', [\n            'OXID' => 'testorderarticle2',\n            'OXORDERID' => 'testorder2',\n            'OXAMOUNT' => 1,\n            'OXARTID' => '1001',\n            'OXARTNUM' => '1001',\n            'OXTITLE' => 'Test product 2',\n            'OXBRUTPRICE' => 15000.00,\n            'OXORDERSHOPID' => 1\n        ]);\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/ProductListStatusTestCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse DateTime;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin', 'product')]\nfinal class ProductListStatusTestCest\n{\n    private string $productID = '1000';\n\n    public function _before(AcceptanceTester $I): void\n    {\n        $I->updateConfigInDatabase('blUseTimeCheck', true, 'bool');\n    }\n\n    public function checkProductsStatuses(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Product statuses by time range');\n\n        $admin = $I->loginAdmin();\n        $productList = $admin->openProducts();\n\n        $I->expect('the given product is active in the list');\n\n        $I->updateInDatabase(\n            'oxarticles',\n            [\n                'OXACTIVE' => false,\n                'OXACTIVEFROM' => (new DateTime())->modify('-1 day')->format('Y-m-d H:i:s'),\n                'OXACTIVETO' => (new DateTime())->modify('+1 day')->format('Y-m-d H:i:s')\n            ],\n            [\n                'OXID' => $this->productID\n            ]\n        );\n\n        $productList->filterByProductNumber($this->productID);\n        $I->selectListFrame();\n        $I->waitForElement($productList->productStatusClass);\n\n        $I->assertStringContainsString(\n            'temp-active',\n            $I->grabAttributeFrom($productList->productStatusClass, 'class')\n        );\n\n        $I->expect('the given product is not active in the list');\n\n        $I->updateInDatabase(\n            'oxarticles',\n            [\n                'OXACTIVE' => false,\n                'OXACTIVEFROM' => (new DateTime())->modify('+1 day')->format('Y-m-d H:i:s'),\n                'OXACTIVETO' => (new DateTime())->modify('+2 day')->format('Y-m-d H:i:s')\n            ],\n            [\n                'OXID' => $this->productID\n            ]\n        );\n\n        $productList->filterByProductNumber($this->productID);\n        $I->selectListFrame();\n        $I->waitForElement($productList->productStatusClass);\n\n        $I->assertStringContainsString(\n            'temp-inactive',\n            $I->grabAttributeFrom($productList->productStatusClass, 'class')\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/ProductPicturesCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\nuse Symfony\\Component\\Filesystem\\Path;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\FileSizeLogic;\n\nuse function sprintf;\n\n#[Group('admin', 'product', 'pictures')]\nfinal class ProductPicturesCest\n{\n    private string $productNumber = '1000';\n    private string $image1 = 'media/product/image_1.webp';\n    private string $image2 = 'media/product/image_2.webp';\n    private string $image3 = 'media/product/image_3.webp';\n\n    public function _after(AcceptanceTester $I): void\n    {\n        $I->restoreProjectConfigurations();\n    }\n\n    public function uploadSingleImage(AcceptanceTester $I): void\n    {\n        $I->wantToTest('uploading a single valid image to a product');\n        $I->amGoingTo('set the minimum image size to 1 KB to allow uploading small images');\n        $I->updateProjectConfigurations(['oxid_esales.product.media.file.min_size_kb' => 1], []);\n        $I\n            ->loginAdmin()\n            ->openProducts()\n            ->findByProductNumber($this->productNumber)\n            ->openPicturesTab()\n            ->uploadFile($this->image1)\n            ->seeUploadedImage(1)\n            ->seeUploadedImageIsActive(1)\n            ->seeEmptyThumbnailPlaceholder()\n            ->seeEmptyIconPlaceholder()\n            ->canSeeUploadedImageInLightbox(1)\n            ->uploadThumbnail($this->image1)\n            ->seeThumbnailEndsWith(basename($this->image1))\n            ->uploadIcon($this->image1)\n            ->seeIconEndsWith(basename($this->image1))\n            ->deactivateUploadedImage(1)\n            ->seeUploadedImageIsInactive(1)\n            ->activateUploadedImage(1)\n            ->seeUploadedImageIsActive(1)\n            ->deleteUploadedImage(1)\n            ->dontSeeUploadedImage(1);\n    }\n\n    public function uploadMultipleImages(AcceptanceTester $I): void\n    {\n        $I->wantToTest('uploading multiple valid images to a product');\n        $I->amGoingTo('set the minimum image size to 1 KB to allow uploading small images');\n        $I->updateProjectConfigurations(['oxid_esales.product.media.file.min_size_kb' => 1], []);\n        $I\n            ->loginAdmin()\n            ->openProducts()\n            ->findByProductNumber($this->productNumber)\n            ->openPicturesTab()\n            ->uploadFile($this->image1)\n            ->uploadFile($this->image2)\n            ->uploadFile($this->image3)\n            ->uploadThumbnail($this->image1)\n            ->uploadIcon($this->image3)\n            ->seeThumbnail()\n            ->seeIcon()\n            ->seeThumbnailEndsWith(basename($this->image1))\n            ->seeIconEndsWith(basename($this->image3))\n            ->seeUploadedImageAtPosition(basename($this->image1), 1)\n            ->seeUploadedImageAtPosition(basename($this->image2), 2)\n            ->seeUploadedImageAtPosition(basename($this->image3), 3);\n        $I->amGoingTo(\n            'skip sorting images by drag-n-drop, as the current Selenium driver does not implement it correctly'\n        );\n    }\n\n    public function uploadInvalidImage(AcceptanceTester $I): void\n    {\n        $I->wantToTest('uploading invalid image will display an error message with filename');\n        $minSize = '1024';\n        $I->amGoingTo(\"set the minimum image size to make previously valid image fixtures invalid\");\n        $I->updateProjectConfigurations(['oxid_esales.product.media.file.min_size_kb' => $minSize], []);\n        $picturesPage = $I\n            ->loginAdmin()\n            ->openProducts()\n            ->findByProductNumber($this->productNumber)\n            ->openPicturesTab();\n\n        $picturesPage\n            ->uploadFile($this->image1)\n            ->seeImageUploadError(basename($this->image1))\n            ->seeImageUploadError(\n                sprintf(\n                    Translator::translate('ERR_MEDIA_SIZE_TOO_SMALL'),\n                    (new FileSizeLogic())->getFileSize(\n                        filesize(\n                            Path::join(\n                                codecept_data_dir(),\n                                $this->image1\n                            )\n                        )\n                    ),\n                    (new FileSizeLogic())->getFileSize(((int) $minSize) * 1024)\n                )\n            );\n\n        $picturesPage\n            ->uploadFile($this->image2)\n            ->seeImageUploadError(basename($this->image2))\n            ->seeImageUploadError(\n                sprintf(\n                    Translator::translate('ERR_MEDIA_SIZE_TOO_SMALL'),\n                    (new FileSizeLogic())->getFileSize(\n                        filesize(\n                            Path::join(\n                                codecept_data_dir(),\n                                $this->image2\n                            )\n                        )\n                    ),\n                    (new FileSizeLogic())->getFileSize(((int) $minSize) * 1024)\n                )\n            );\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/ProductStockTestCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin', 'product', 'stock')]\nfinal class ProductStockTestCest\n{\n    private string $productID = '1000';\n\n    public function setLowStockMessage(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Set low stock message for product');\n\n        $productsMainPage = $I->loginAdmin()->openProducts();\n        $productMainTab = $productsMainPage->findByProductNumber($this->productID);\n        $stockTab = $productMainTab->openStockTab();\n        $lowStockMessage = 'This product is in low stock' . $this->productID;\n        $remindAmount = 20.5;\n\n        $I->amGoingTo('Set and activate low stock message');\n\n        $stockTab->checkLowStockMessageOption()\n            ->setRemindAmountValue($remindAmount)\n            ->setLowStockMessageValue($lowStockMessage)\n            ->save();\n\n        $stockTab->seeRemindAmountValue($remindAmount);\n        $stockTab->seeLowStockMessageSelected();\n        $stockTab->seeLowStockMessageValue($lowStockMessage);\n\n        $I->amGoingTo('Disable low stock message');\n\n        $stockTab->uncheckLowStockMessageOption()\n            ->save();\n\n        $stockTab->dontSeeLowStockMessageSelected();\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/ProductVariantWithSelectionsCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin')]\nfinal class ProductVariantWithSelectionsCest\n{\n    public function selectionInheritanceByProductVariant(AcceptanceTester $I): void\n    {\n        $I->wantToTest('product variant inherits selections from its parent');\n\n        $admin = $I->loginAdmin();\n        $products = $admin->openProducts();\n        $productsMainPage = $products->switchLanguage('Deutsch');\n\n        $parentMainPage = $productsMainPage->findByProductNumber('1002');\n        $parentSelectionPage = $parentMainPage->openSelectionTab();\n        $parentSelectionPage\n            ->openAssignSelectionListPopup()\n            ->assignSelectionByTitle('test selection list [DE] šÄßüл');\n        $I->closeTab();\n\n        $parentVariantPage = $parentSelectionPage->openVariantsTab();\n        $variantMainPage = $parentVariantPage->openEditProductVariant(1);\n        $I->seeInField($variantMainPage->numberInput, '1002-1');\n\n        $variantMainPage\n            ->openSelectionTab()\n            ->openAssignSelectionListPopup()\n            ->seeProductAssigned('test selection list [DE] šÄßüл');\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/RemoveUserOrderCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse DateTime;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin')]\nfinal class RemoveUserOrderCest\n{\n    private string $orderId = 'justSomeOxorderId';\n    private string $orderArticleId = 'justSomeOxorderArticleID';\n\n    public function _before(AcceptanceTester $I): void\n    {\n        $this->insertAnOrderInDatabase($I);\n    }\n\n    public function adminDeleteUserOrder(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Admin is able to delete a user order');\n\n        $adminPanel = $I->loginAdmin();\n\n        $I->seeInDatabase(\n            'oxarticles',\n            [\n                'OXARTNUM' => '1002-1',\n                'OXSTOCK'  => 5\n            ]\n        );\n        $orders = $adminPanel->openOrders();\n        $orders = $orders->findByOrderNumber('2');\n        $orders->deleteOrder();\n\n        $I->waitForPageLoad();\n\n        $I->seeInDatabase(\n            'oxarticles',\n            [\n                'OXARTNUM' => '1002-1',\n                'OXSTOCK' => 6\n            ]\n        );\n    }\n\n    public function adminCancelUserOrder(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Admin is able to cancel a user order');\n\n        $adminPanel = $I->loginAdmin();\n\n        $I->seeInDatabase(\n            'oxarticles',\n            [\n                'OXARTNUM' => '1002-1',\n                'OXSTOCK'  => 5\n            ]\n        );\n        $orders = $adminPanel->openOrders();\n        $orders = $orders->findByOrderNumber('2');\n        $orders->cancelOrder();\n\n        $I->waitForPageLoad();\n\n        $I->seeInDatabase(\n            'oxarticles',\n            [\n                'OXARTNUM' => '1002-1',\n                'OXSTOCK'  => 6\n            ]\n        );\n    }\n\n\n    private function insertAnOrderInDatabase(AcceptanceTester $I): void\n    {\n        $I->haveInDatabase(\n            'oxorder',\n            [\n                'OXID' => $this->orderId,\n                'OXSHOPID' => 1,\n                'OXUSERID' => 'someUserID',\n                'OXORDERDATE' => (new DateTime())->format('Y-m-d H:i:s'),\n                'OXORDERNR' => 2,\n                'OXBILLEMAIL' => 'example01@oxid-esales.dev',\n                'OXBILLFNAME' => 'name',\n                'OXBILLLNAME' => 'surname',\n                'OXBILLSTREET' => 'street',\n                'OXBILLSTREETNR' => '1',\n                'OXBILLCITY' => 'city',\n                'OXBILLCOUNTRYID' => 'a7c40f631fc920687.20179984',\n                'OXBILLSTATEID' => 'BB',\n                'OXBILLZIP' => '3000',\n                'OXPAYMENTID' => 'NotRegisteredPaymentId',\n                'OXPAYMENTTYPE' => 'oxidcashondel',\n                'OXREMARK' => 'remark text',\n                'OXTRANSSTATUS' => 'OK',\n                'OXFOLDER' => 'ORDERFOLDER_NEW',\n                'OXDELTYPE' => 'oxidstandard',\n                'OXTIMESTAMP' => (new DateTime())->format('Y-m-d H:i:s')\n            ]\n        );\n\n        $I->haveInDatabase(\n            'oxorderarticles',\n            [\n                'OXID' => $this->orderArticleId,\n                'OXORDERID' => $this->orderId,\n                'OXAMOUNT' => 1,\n                'OXARTID' => '1002-1',\n                'OXARTNUM' => '1002-1',\n                'OXTITLE' => 'Test product 2 [EN] šÄßüл',\n                'OXSHORTDESC' => 'Test product 2 short desc [EN] šÄßüл',\n                'OXSELVARIANT' => 'var1 [EN] šÄßüл',\n                'OXNETPRICE' => 46.22,\n                'OXBRUTPRICE' => 55,\n                'OXVATPRICE' => 8.78,\n                'OXVAT' => 19,\n                'OXSTOCK' => 5,\n                'OXINSERT' => '2008-02-04',\n                'OXTIMESTAMP' => (new DateTime())->format('Y-m-d H:i:s'),\n                'OXSEARCHKEYS' => 'šÄßüл1002',\n                'OXISSEARCH' => 1,\n                'OXORDERSHOPID' => 1\n            ]\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/SeoCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin')]\nfinal class SeoCest\n{\n    public function updateStaticUrl(AcceptanceTester $I): void\n    {\n        $I->wantToTest('static SEO URL form');\n        $I\n            ->loginAdmin()\n            ->openCoreSettings()\n            ->openSEOTab()\n            ->selectStaticSeoUrl('index.php?cl=account')\n            ->seeInStaticSeoUrlFields(\n                'index.php?cl=account',\n                'mein-konto/',\n                'en/my-account/'\n            )\n            ->fillStaticSeoUrlFields(\n                'some-new-german-url/',\n                'some-new-english-url/'\n            )\n            ->save()\n            ->selectStaticSeoUrl('index.php?cl=account')\n            ->seeInStaticSeoUrlFields(\n                'index.php?cl=account',\n                'some-new-german-url/',\n                'en/some-new-english-url/'\n            );\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/ServiceDiagnosticToolCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin', 'diagnostic_tool')]\nfinal class ServiceDiagnosticToolCest\n{\n    public function functionalityDiagnosticTools(AcceptanceTester $I): void\n    {\n        $I->wantToTest('functionality of diagnostic tools');\n\n        $adminPanel = $I->loginAdmin();\n\n        $diagToolPanel = $adminPanel->openDiagnosticsTool();\n        $diagToolPanel->startDiagnostics();\n        $diagToolPanel->seeDiagnosticResults();\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/SessionHandlingCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse Codeception\\Util\\Fixtures;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\nfinal class SessionHandlingCest\n{\n    #[Group('session')]\n    public function adminSessionAfterPasswordChange(AcceptanceTester $I): void\n    {\n        $I->wantToTest('that admin will be logged out if someone changes his password from another active session');\n        $userData = Fixtures::get('adminUser');\n        $adminLoginPage = $I->openAdmin();\n        $I->amGoingTo('log the existing admin in');\n        $adminLoginPage->login($userData['userLoginName'], $userData['userPassword']);\n\n        $I->amGoingTo('mock password change for this admin from another browser session');\n        $I->updateInDatabase(\n            'oxuser',\n            ['OXPASSWORD' => 'some-new-password-hash'],\n            ['OXUSERNAME' => $userData['userLoginName']]\n        );\n\n        $I->amGoingTo('send any page request after password change');\n        $I->reloadPage();\n\n        $I->expect('that admin is logged out');\n        $adminLoginPage->seeLoginForm();\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/ShopLicenseCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin')]\nfinal class ShopLicenseCest\n{\n    public function testVersionCheckerPage(AcceptanceTester $I): void\n    {\n        $I->wantToTest('admin can access readable info about shop version update status');\n\n        $I\n            ->loginAdmin()\n            ->openCoreSettings()\n            ->openLicenseTab()\n            ->seeShopVersionInfo();\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/SystemInfoCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin', 'system-info')]\nfinal class SystemInfoCest\n{\n    public function defaultTimezone(AcceptanceTester $I): void\n    {\n        $I->wantToTest('the default timezone is set on application start.');\n\n        $I->loginAdmin()\n            ->openSystemInfo()\n            ->seeRowInDateTable(\n                'Default timezone',\n                getenv('OXID_DEFAULT_TIMEZONE')\n            );\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/UpdateSQLToolCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('admin', 'update_sql_tool')]\nfinal class UpdateSQLToolCest\n{\n    public function updateSQLTool(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Update SQL Tool page is functioning');\n\n        $adminPanel = $I->loginAdmin();\n        $toolsPanel = $adminPanel->openTools();\n\n        $sqlCommand = 'update oxpayments set oxactive=0';\n        $toolsPanel->runSqlUpdate($sqlCommand);\n        $toolsPanel->seeInSqlOutput($sqlCommand);\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/UserCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\Codeception\\Admin\\DataObject\\AdminUser;\nuse OxidEsales\\Codeception\\Admin\\DataObject\\AdminUserAddress;\nuse OxidEsales\\Codeception\\Admin\\DataObject\\AdminUserExtendedInfo;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('user')]\nfinal class UserCest\n{\n    private const BELGIUM = 'Belgium';\n    private const GERMANY = 'Germany';\n\n    public function mainTab(AcceptanceTester $I): void\n    {\n        $I->wantToTest('functionality on the Main tab');\n\n        $I->amGoingTo('prepare test data');\n        $I->comment('this user data has no user-rights specified');\n        $user1 = $this->getAdminUser1();\n        $I->comment('this user data contains invalid birth-month value');\n        $user2 = $this->getAdminUser2();\n        $address1 = $this->getAdminUserAddress1();\n        $address2 = $this->getAdminUserAddress2();\n        $mainTab = $I\n            ->loginAdmin()\n            ->openUsers()\n            ->createNewUser(\n                $user1,\n                $address1\n            );\n\n        $I->expect('to see user has \"Customer\", the default user-rights value, assigned');\n        $user1->setUserRights('Customer');\n        $mainTab->seeUserInformation(\n            $user1,\n            $address1\n        );\n\n        $I->amGoingTo('modify user info with the new data');\n        $mainTab->editUser(\n            $user2,\n            $address2\n        );\n        $I->expect('to see user has \"01\", the default birth-month value, assigned');\n        $user2->setBirthMonth('01');\n        $I->expect('user can be found by the new username and has the new info saved');\n        $mainTab\n            ->findByUserName(\n                $user2->getUsername()\n            )\n            ->seeUserInformation(\n                $user2,\n                $address2\n            )\n            ->openExtendedTab()\n            ->seeUserAddress($address2);\n    }\n\n    public function historyTab(AcceptanceTester $I): void\n    {\n        $I->wantToTest('functionality on the History tab');\n\n        $I->amGoingTo('prepare test data');\n        $user = $this->getAdminUser1();\n        $addressEmpty = new AdminUserAddress();\n        $remarkText = 'new note_šÄßüл';\n        $this->createAdminTestUser(\n            $I,\n            $user,\n            $addressEmpty\n        );\n\n        $I->comment('the first remark \"usrRegistered\" is added after editing user');\n        $I->amGoingTo('create/delete another remark');\n        $I\n            ->loginAdmin()\n            ->openUsers()\n            ->findByUserName(\n                $user->getUsername()\n            )\n            ->editUserInformation(\n                $user,\n                $addressEmpty\n            )\n            ->openHistoryTab()\n            ->createNewRemark($remarkText)\n            ->openHistoryTab()\n            ->selectUserRemark('0')\n            ->seeRemarkText($remarkText)\n            ->deleteRemark()\n            ->selectUserRemark('0')\n            ->dontSeeRemarkText($remarkText);\n\n    }\n\n    public function addressesTab(AcceptanceTester $I): void\n    {\n        $I->wantToTest('functionality on the Addresses tab');\n\n        $I->amGoingTo('prepare test data');\n        $user = $this->getAdminUser1();\n        $address1 = $this->getAdminUserAddress1();\n        $address2 = $this->getAdminUserAddress2();\n        $addressEmpty = new AdminUserAddress();\n        $this->createAdminTestUser(\n            $I,\n            $user,\n            $addressEmpty\n        );\n\n        $I\n            ->loginAdmin()\n            ->openUsers()\n            ->findByUserName(\n                $user->getUsername()\n            )\n            ->openAddressesTab()\n            ->createNewAddress($address1)\n            ->createNewAddress($address2)\n            ->selectAddress($address1)\n            ->seeAddressInformation($address1)\n            ->selectAddress($address2)\n            ->seeAddressInformation($address2)\n            ->deleteSelectedAddress()\n            ->selectAddress($address1)\n            ->deleteSelectedAddress()\n            ->seeAddressInformation($addressEmpty);\n    }\n\n    public function extendedInfoTab(AcceptanceTester $I): void\n    {\n        $I->wantToTest('functionality on the Extended Info tab');\n\n        $I->amGoingTo('prepare test data');\n        $user = $this->getAdminUser1();\n        $address = $this->getAdminUserAddress1();\n        $extendedInfo1 = $this->getAdminUserExtendedInfo1();\n        $extendedInfo2 = $this->getAdminUserExtendedInfo2();\n        $this->createAdminTestUser(\n            $I,\n            $user,\n            $address\n        );\n\n        $I->loginAdmin()\n            ->openUsers()\n            ->findByUserName(\n                $user->getUsername()\n            )\n            ->openExtendedTab()\n            ->seeUserAddress($address)\n            ->editExtendedInfo($extendedInfo1)\n            ->seeUserExtendedInformation($extendedInfo1)\n            ->editExtendedInfo($extendedInfo2)\n            ->seeUserExtendedInformation($extendedInfo2);\n    }\n\n    private function createAdminTestUser(\n        AcceptanceTester $I,\n        AdminUser $user,\n        AdminUserAddress $userAddress\n    ): void {\n        $I->haveInDatabase(\n            'oxuser',\n            [\n                'OXID'        => 'kdiruuc',\n                'OXACTIVE'    => $user->getActive(),\n                'OXRIGHTS'    => 'malladmin',\n                'OXSHOPID'    => 1,\n                'OXUSERNAME'  => $user->getUsername(),\n                'OXPASSWORD'  => '1397d0b4392f452a5bd058891c9b255e',\n                'OXPASSSALT'  => '3032396331663033316535343361356231363666653666316533376235353830',\n                'OXCUSTNR'    => $user->getCustomerNumber(),\n                'OXUSTID'     => $user->getUstid(),\n                'OXCOMPANY'   => $userAddress->getCompany(),\n                'OXFNAME'     => $userAddress->getFirstName(),\n                'OXLNAME'     => $userAddress->getLastName(),\n                'OXSTREET'    => $userAddress->getStreet(),\n                'OXSTREETNR'  => $userAddress->getStreetNumber(),\n                'OXADDINFO'   => $userAddress->getAdditionalInfo(),\n                'OXCITY'      => $userAddress->getCity(),\n                'OXCOUNTRYID' => $this->mapAddressToDatabaseCountryId(\n                    $userAddress->getCountryId()\n                ),\n                'OXSTATEID'   => $userAddress->getStateId(),\n                'OXZIP'       => $userAddress->getZip(),\n                'OXFON'       => $userAddress->getPhone(),\n                'OXFAX'       => $userAddress->getFax(),\n                'OXSAL'       => $userAddress->getTitle(),\n                'OXBIRTHDATE' => \"{$user->getBirthYear()}-{$user->getBirthMonth()}-{$user->getBirthday()}\",\n            ]\n        );\n    }\n\n    private function getAdminUser1(): AdminUser\n    {\n        $adminUser = new AdminUser();\n        $adminUser->setActive(true);\n        $adminUser->setUsername('example01@oxid-esales.dev');\n        $adminUser->setCustomerNumber('20');\n        $adminUser->setBirthday('01');\n        $adminUser->setBirthMonth('12');\n        $adminUser->setBirthYear('1980');\n        $adminUser->setUstid('111222');\n\n        return $adminUser;\n    }\n\n    private function getAdminUser2(): AdminUser\n    {\n        $adminUser = new AdminUser();\n        $adminUser->setActive(false);\n        $adminUser->setUserRights('Admin');\n        $adminUser->setPassword('adminpass');\n        $adminUser->setUsername('example00@oxid-esales.dev');\n        $adminUser->setCustomerNumber('121');\n        $adminUser->setBirthday('01');\n        $adminUser->setBirthYear('1980');\n        $adminUser->setUstid('111222');\n        $adminUser->setBirthday('03');\n        $adminUser->setBirthMonth('13');\n        $adminUser->setBirthYear('1979');\n        return $adminUser;\n    }\n\n    private function getAdminUserAddress1(): AdminUserAddress\n    {\n        $adminUserAddress = new AdminUserAddress();\n        $adminUserAddress->setTitle('Mrs');\n        $adminUserAddress->setFirstName('Name_šÄßüл');\n        $adminUserAddress->setLastName('Surname_šÄßüл');\n        $adminUserAddress->setCompany('company_šÄßüл');\n        $adminUserAddress->setStreet('street_šÄßüл');\n        $adminUserAddress->setStreetNumber('1');\n        $adminUserAddress->setZip('3000');\n        $adminUserAddress->setCity('City_šÄßüл');\n        $adminUserAddress->setAdditionalInfo('additional info_šÄßüл');\n        $adminUserAddress->setCountryId(self::GERMANY);\n        $adminUserAddress->setStateId('BW');\n        $adminUserAddress->setPhone('111222333');\n        $adminUserAddress->setFax('222333444');\n\n        return $adminUserAddress;\n    }\n\n    private function getAdminUserAddress2(): AdminUserAddress\n    {\n        $adminUserAddress = new AdminUserAddress();\n        $adminUserAddress->setTitle('Mr');\n        $adminUserAddress->setFirstName('Name1');\n        $adminUserAddress->setLastName('Surname1');\n        $adminUserAddress->setCompany('company1');\n        $adminUserAddress->setStreet('street1');\n        $adminUserAddress->setStreetNumber('11');\n        $adminUserAddress->setZip('30001');\n        $adminUserAddress->setCity('City11');\n        $adminUserAddress->setAdditionalInfo('additional info1');\n        $adminUserAddress->setCountryId(self::BELGIUM);\n        $adminUserAddress->setStateId('BE');\n        $adminUserAddress->setPhone('1112223331');\n        $adminUserAddress->setFax('2223334441');\n\n        return $adminUserAddress;\n    }\n\n    private function getAdminUserExtendedInfo1(): AdminUserExtendedInfo\n    {\n        $adminUserExtendedInfo = new AdminUserExtendedInfo();\n        $adminUserExtendedInfo->setEveningPhone('5554445551');\n        $adminUserExtendedInfo->setCellularPhone('6665556661');\n        $adminUserExtendedInfo->setReceivesNewsletter(false);\n        $adminUserExtendedInfo->setEmailInvalid(false);\n        $adminUserExtendedInfo->setCreditRating('1000');\n        $adminUserExtendedInfo->setUrl('https://www.example1.com');\n        return $adminUserExtendedInfo;\n    }\n\n    private function getAdminUserExtendedInfo2(): AdminUserExtendedInfo\n    {\n        $adminUserChangedExtendedInfo = new AdminUserExtendedInfo();\n        $adminUserChangedExtendedInfo->setEveningPhone('555444555');\n        $adminUserChangedExtendedInfo->setCellularPhone('666555666');\n        $adminUserChangedExtendedInfo->setReceivesNewsletter(true);\n        $adminUserChangedExtendedInfo->setEmailInvalid(true);\n        $adminUserChangedExtendedInfo->setCreditRating('1500');\n        $adminUserChangedExtendedInfo->setUrl('https://www.example.com');\n\n        return $adminUserChangedExtendedInfo;\n    }\n\n    private function mapAddressToDatabaseCountryId(string $countryName): string\n    {\n        return match ($countryName) {\n            self::GERMANY => 'testcountry_de',\n            self::BELGIUM => 'testcountry_be',\n            '' => '',\n        };\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/Admin/UserCredentialsCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance\\Admin;\n\nuse Codeception\\Attribute\\Group;\nuse Codeception\\Util\\Fixtures;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('user')]\nfinal class UserCredentialsCest\n{\n    #[Group('session')]\n    public function updatePassword(AcceptanceTester $I): void\n    {\n        $I->wantToTest('that admin can update his own password');\n        $newPass = uniqid('new-pass-', true);\n        $userData = Fixtures::get('adminUser');\n        $I->amGoingTo('log the existing admin in, find him in the list and update his password');\n        $I->expect('that admin will be logged-out after password change, but can log-in with the new one');\n        $I\n            ->openAdmin()\n            ->login($userData['userLoginName'], $userData['userPassword'])\n            ->openUsers()\n            ->findByUserName($userData['userId'])\n            ->updatePassword($newPass)\n            ->login($userData['userLoginName'], $newPass);\n    }\n\n    public function testChangeUserEmail(AcceptanceTester $I): void\n    {\n        $I->wantToTest('changing user email addresses with validation in admin');\n\n        $userData = Fixtures::get('existingUser');\n        $guestUserData = Fixtures::get('existingGuestUser');\n        $adminUserData = Fixtures::get('adminUser');\n        $newEmail = 'example02@oxid-esales.dev';\n\n        $I->amGoingTo('login as admin and find the test user');\n        $adminUsersPage = $I->loginAdmin()\n            ->openUsers()\n            ->findByUserName($userData['userLoginName']);\n\n        $I->amGoingTo('try to change email to an existing guest user email');\n        $adminUsersPage->updateUsername($guestUserData['userLoginName']);\n        $I->expect('to see an error message about existing user');\n        $I->seeText(Translator::translate('EXCEPTION_USER_USEREXISTS'));\n\n        $I->amGoingTo('try to change email to an existing admin email');\n        $adminUsersPage->updateUsername($adminUserData['userLoginName']);\n        $I->expect('to see an error message about existing user');\n        $I->seeText(Translator::translate('EXCEPTION_USER_USEREXISTS'));\n\n        $I->amGoingTo('change user email to a new valid email address');\n        $adminUsersPage->updateUsername($newEmail);\n        $I->expect('not to see any error message');\n        $I->dontSee(Translator::translate('EXCEPTION_USER_USEREXISTS'));\n\n        $I->amGoingTo('change the email back to the original one');\n        $adminUsersPage->updateUsername($userData['userLoginName']);\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/BasketRefreshWithDeletedProductCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse Codeception\\Util\\Fixtures;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\Codeception\\Step\\Basket;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\nuse function sprintf;\n\n#[Group('basket')]\nfinal class BasketRefreshWithDeletedProductCest\n{\n    public function testBasketRefreshWithDeletedProduct(AcceptanceTester $I): void\n    {\n        $I->wantToTest('basket is correctly refreshed when a product is deleted by admin');\n\n        $basket = new Basket($I);\n        $I->openShop();\n\n        $productData = Fixtures::get('product-' . '1000');\n        $basketPage = $basket->addProductToBasketAndOpenBasket($productData['OXID'], 1);\n\n        $I->deleteFromDatabase('oxarticles', ['OXID' => $productData['OXID']]);\n\n        $basketPage->updateProductAmount(2);\n\n        $I->seeText(sprintf(\n            Translator::translate('ERROR_MESSAGE_ARTICLE_ARTICLE_DOES_NOT_EXIST'),\n            $productData['OXTITLE_1']\n        ));\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/CMSPageChangeIdentCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\nfinal class CMSPageChangeIdentCest\n{\n    private string $testCmsPageIdent = '_test_oxstdfooter';\n    private string $cmsPageDemoIdent = 'oxstdfooter';\n\n    #[Group('todo_add_clean_cache_after_database_update')]\n    public function CMSPageChangeIdent(AcceptanceTester $I): void\n    {\n        $I->clearShopCache();\n        $I->openShop();\n\n        $cmsPageContent = $I->grabFromDatabase(\n            'oxcontents',\n            'OXCONTENT_1',\n            ['OXLOADID' => $this->cmsPageDemoIdent]\n        );\n\n        $I->see(strip_tags($cmsPageContent));\n\n        $I->updateInDatabase(\n            'oxcontents',\n            ['OXLOADID' => $this->testCmsPageIdent],\n            ['OXLOADID' => $this->cmsPageDemoIdent]\n        );\n\n        $I->clearShopCache();\n        $I->openShop();\n\n        $I->dontSee(strip_tags($cmsPageContent));\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/CategoryDetailCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Util\\Fixtures;\nuse OxidEsales\\Codeception\\Step\\CategoryNavigation;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\nuse PHPUnit\\Framework\\Attributes\\Group;\n\n\n#[Group('category_description')]\nfinal class CategoryDetailCest\n{\n    public function categoryDetailPageInformation(AcceptanceTester $I): void\n    {\n        $I->wantToTest('category information in detail page');\n\n        $categoryData = Fixtures::get('testCategory0');\n\n        $categoryNavigation = new CategoryNavigation($I);\n        $categoryDetailPage = $categoryNavigation->openCategoryDetailsPage($categoryData['id']);\n        $categoryDetailPage->seeCategoryData($categoryData);\n    }\n\n    public function subcategoryDetailPageInformation(AcceptanceTester $I): void\n    {\n        $I->wantToTest('subcategory information in detail page');\n\n        $categoryData = Fixtures::get('testCategory0');\n        $subCategoryData = Fixtures::get('testCategory1');\n\n        $categoryNavigation = new CategoryNavigation($I);\n        $categoryDetailPage = $categoryNavigation->openCategoryDetailsPage($categoryData['id']);\n        $categoryDetailPage->seeSubCategoryData($subCategoryData);\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/CategoryProductListCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('category_product_list')]\nfinal class CategoryProductListCest\n{\n    public function filterAndNavigateThroughCategoryList(AcceptanceTester $I): void\n    {\n        $I->wantToTest('category product list filter functionality');\n        $this->setNumberOfProductsInCategoryList($I);\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 €'\n        ];\n\n        $productData2 = [\n            'id' => '1001',\n            'title' => 'Test product 1 [EN] šÄßüл',\n            'description' => 'Test product 1 short desc [EN] šÄßüл',\n            'price' => '100,00 €'\n        ];\n\n        $homePage = $I->openShop();\n        $productList = $homePage->openCategoryPage('Test category 0 [EN] šÄßüл');\n        $productList->selectFilter('Test attribute 1 [EN] šÄßüл', 'attr value 1 [EN] šÄßüл')\n            ->dontSeeProductData($productData2)\n            ->dontSeeSelectedFilter('Test attribute 2 [EN] šÄßüл', 'attr value 12 [EN] šÄßüл');\n        $productList = $productList\n            ->resetFilter()\n            ->selectFilter('Test attribute 2 [EN] šÄßüл', 'attr value 12 [EN] šÄßüл')\n            ->dontSeeProductData($productData)\n            ->resetFilter()\n            ->selectProductsPerPage('1')\n            ->selectFilter('Test attribute 3 [EN] šÄßüл', 'attr value 3 [EN] šÄßüл')\n            ->seeProductData($productData)\n            ->openNextListPage()\n            ->seeProductData($productData2)\n            ->seeSelectedFilter('Test attribute 3 [EN] šÄßüл', 'attr value 3 [EN] šÄßüл')\n            ->openPreviousListPage()\n            ->seeSelectedFilter('Test attribute 3 [EN] šÄßüл', 'attr value 3 [EN] šÄßüл')\n            ->resetFilter();\n\n        $I->dontSeeElement($productList->resetListFilter);\n    }\n\n    public function sortAndNavigateThroughCategoryList(AcceptanceTester $I): void\n    {\n        $I->wantToTest('category product list sorting');\n        $this->setNumberOfProductsInCategoryList($I);\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 €'\n        ];\n\n        $productData2 = [\n            'id' => '1001',\n            'title' => 'Test product 1 [EN] šÄßüл',\n            'description' => 'Test product 1 short desc [EN] šÄßüл',\n            'price' => '100,00 €'\n        ];\n\n        $homePage = $I->openShop();\n        $productList = $homePage->openCategoryPage('Test category 0 [EN] šÄßüл');\n\n        $productList->selectSorting('oxtitle', 'asc')\n            ->seeProductData($productData)\n            ->seeProductData($productData2, 2)\n            ->selectSorting('oxprice', 'desc')\n            ->selectProductsPerPage('1')\n            ->seeProductData($productData2)\n            ->openNextListPage()\n            ->seeProductData($productData);\n\n        $I->amGoingTo('disable sorting at all');\n        $I->updateConfigInDatabase('blShowSorting', false);\n        $I->openShop()->openCategoryPage('Test category 0 [EN] šÄßüл');\n        $I->dontSee(Translator::translate('SORT_BY'));\n    }\n\n    public function navigateThroughPriceCategoryList(AcceptanceTester $I): void\n    {\n        $I->wantToTest('price category functionality');\n\n        $I->updateInDatabase('oxcategories', ['OXACTIVE' => 1, 'OXACTIVE_1' => 1], ['OXID' => 'testpricecat']);\n        $this->setNumberOfProductsInCategoryList($I);\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 €'\n        ];\n\n        $productData2 = [\n            'id' => '1002',\n            'title' => 'Test product 2 [EN] šÄßüл',\n            'description' => 'Test product 2 short desc [EN] šÄßüл',\n            'price' => 'from 55,00 €'\n        ];\n\n        $homePage = $I->openShop();\n        $productList = $homePage->openCategoryPage('price [EN] šÄßüл');\n\n        $productList->selectSorting('oxtitle')\n            ->seeProductData($productData)\n            ->seeProductData($productData2, 2)\n            ->selectSorting('oxprice', 'desc')\n            ->selectProductsPerPage('1')\n            ->seeProductData($productData2)\n            ->openNextListPage()\n            ->seeProductData($productData);\n    }\n\n    private function setNumberOfProductsInCategoryList(AcceptanceTester $I): void\n    {\n        $I->updateConfigInDatabase('aNrofCatArticles', serialize([20, 1, 2, 10, 100]), 'arr');\n        $I->updateConfigInDatabase('aNrofCatArticlesInGrid', serialize([20, 1, 2, 10, 100]), \"arr\");\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/CheckoutProcessCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse Codeception\\Util\\Fixtures;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\Codeception\\Page\\Account\\UserAccount;\nuse OxidEsales\\Codeception\\Page\\Checkout\\OrderCheckout;\nuse OxidEsales\\Codeception\\Page\\Checkout\\UserCheckout;\nuse OxidEsales\\Codeception\\Step\\Basket;\nuse OxidEsales\\Codeception\\Step\\UserRegistrationInCheckout;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\nuse function sprintf;\n\n#[Group('basketfrontend')]\nfinal class CheckoutProcessCest\n{\n    public function _before(AcceptanceTester $I): void\n    {\n        $I->updateConfigInDatabase('blShowVATForDelivery', false, 'bool');\n        $I->updateConfigInDatabase('blShowVATForPayCharge', false, 'bool');\n        $I->updateConfigInDatabase('blShowVATForWrapping', false, 'bool');\n    }\n\n    public function checkBasketFlyout(AcceptanceTester $I): void\n    {\n        $basket = new Basket($I);\n        $I->wantToTest('basket flyout');\n\n        $homePage = $I->openShop();\n\n        //add Product to basket\n        $basketItem1 = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'amount' => 1,\n            'price' => '50,00 €'\n        ];\n\n        $basket->addProductToBasket($basketItem1['id'], 1);\n        $homePage = $homePage->seeMiniBasketContains([$basketItem1], '50,00 €', '1');\n\n        $basketItem1 = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'amount' => 2,\n            'price' => '100,00 €'\n        ];\n\n        $basketItem2 = [\n            'id' => '1001',\n            'title' => 'Test product 1 [EN] šÄßüл',\n            'amount' => 1,\n            'price' => '100,00 €'\n        ];\n        $basket->addProductToBasket($basketItem1['id'], 1);\n        $basket->addProductToBasket($basketItem2['id'], 1);\n        $userCheckoutPage = $homePage->seeMiniBasketContains([$basketItem1, $basketItem2], '200,00 €', '3')\n            ->openCheckout();\n\n        $breadCrumbName = Translator::translate('ADDRESS');\n        $userCheckoutPage->seeOnBreadCrumb($breadCrumbName);\n\n        $userData = Fixtures::get('existingUser');\n        $homePage = $userCheckoutPage->openHomePage()\n            ->loginUser($userData['userLoginName'], $userData['userPassword']);\n\n        $paymentCheckoutPage = $homePage->openMiniBasket()->openCheckout();\n\n        $breadCrumbName = Translator::translate('PAY');\n        $paymentCheckoutPage->seeOnBreadCrumb($breadCrumbName);\n    }\n\n    public function createOrder(AcceptanceTester $I): void\n    {\n        $I->wantToTest('simple order steps (without any special cases)');\n\n        $basket = new Basket($I);\n\n        $userData = Fixtures::get('existingUser');\n\n        $basketItem1 = [\n            'id' => '1001',\n            'title' => 'Test product 1 [EN] šÄßüл',\n            'amount' => 1,\n            'totalPrice' => '100,00 €'\n        ];\n\n        $basketItem2 = [\n            'id' => '1002-2',\n            'title' => 'Test product 2 [EN] šÄßüл',\n            'amount' => 1,\n            'totalPrice' => '67,00 €'\n        ];\n        $homePage = $I->openShop();\n\n        //add Product to basket\n        $basket->addProductToBasket($basketItem1['id'], 1);\n        $basket->addProductToBasket($basketItem2['id'], 1);\n\n        $homePage = $homePage->loginUser($userData['userLoginName'], $userData['userPassword']);\n        $basketPage = $homePage->openMiniBasket()->openBasketDisplay();\n        $basketPage = $basketPage->addCouponToBasket('123123');\n        $basketPage = $basketPage->openGiftSelection(1)\n            ->selectWrapping(1, 'testwrapping')\n            ->selectCard('testcard')\n            ->addGreetingMessage('Greeting card text')\n            ->submitChanges();\n        $I->seeText('Greeting card text');\n        $userCheckoutPage = $basketPage->goToNextStep();\n        $paymentPage = $userCheckoutPage->enterOrderRemark('remark text')->goToNextStep();\n\n        $I->seeText(Translator::translate('PAYMENT_METHOD'));\n\n        $orderPage = $paymentPage->selectPayment('oxidcashondel')\n            ->goToNextStep()\n            ->validateRemarkText('remark text');\n\n        $I->seeText('Test wrapping [EN] šÄßüл');\n        $I->seeText('Greeting card text');\n        $I->dontSee(Translator::translate('HERE_YOU_CAN_ENETER_MESSAGE'));\n        $userCheckoutPage = $orderPage->editUserAddress();\n        $orderPage = $userCheckoutPage->enterOrderRemark('my message')->goToNextStep()->goToNextStep();\n        $orderPage->validateRemarkText('my message');\n\n        $paymentPage = $orderPage->editPaymentMethod();\n        $orderPage = $paymentPage->selectPayment('oxidpayadvance')->goToNextStep();\n\n        $orderPage->validateShippingMethod('Standard');\n        $orderPage->validatePaymentMethod('Cash in advance');\n        $paymentPage = $orderPage->editShippingMethod();\n        $orderPage = $paymentPage->selectPayment('oxidcashondel')->goToNextStep();\n\n        $orderPage->validateShippingMethod('Standard');\n        $orderPage->validatePaymentMethod('COD (Cash on Delivery)');\n        $orderPage->validateOrderItems([$basketItem1, $basketItem2]);\n        $orderPage->validateCoupon('123123', '-83,50 €');\n        $orderPage->seeSummaryVat('10', '4,55 €');\n        $orderPage->seeSummaryVat('19', '5,35 €');\n\n        $priceInformation = [\n            'net' => '73,60 €',\n            'gross' => '167,00 €',\n            'shipping' => '0,00 €',\n            'payment' => '7,50 €',\n            'total' => '92,10 €',\n        ];\n        $orderPage->seeSummaryNet($priceInformation['net']);\n        $orderPage->seeSummaryGross($priceInformation['gross']);\n        $orderPage->seeSummaryShippingGross($priceInformation['shipping']);\n        $orderPage->seeSummaryPaymentGross($priceInformation['payment']);\n        $orderPage->seeSummaryGrandTotal($priceInformation['total']);\n        $orderPage->seeSummaryWrappingGross('0,90 €');\n        $orderPage->seeSummaryGiftCardGross('0,20 €');\n    }\n\n    #[Group('todo_add_clean_cache_after_database_update')]\n    public function buyOutOfStockNotBuyableProductDuringOrder(AcceptanceTester $I): void\n    {\n        $basket = new Basket($I);\n        $I->wantToTest('if no fatal errors or exceptions are thrown, but an error message is shown, if the same\n        product was sold out by other user during the checkout');\n\n        $userData = Fixtures::get('existingUser');\n\n        $homePage = $I\n            ->openShop()\n            ->loginUser($userData['userLoginName'], $userData['userPassword']);\n\n        $basketItem1 = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'amount' => 5,\n            'totalPrice' => '250,00 €'\n        ];\n\n        $basketItem2 = [\n            'id' => '1001',\n            'title' => 'Test product 1 [EN] šÄßüл',\n            'amount' => 1,\n            'totalPrice' => '100,00 €'\n        ];\n\n        //add Product to basket\n        $basket->addProductToBasket($basketItem1['id'], 5);\n        $basket->addProductToBasket($basketItem2['id'], 1);\n        $basketPage = $homePage\n            ->openMiniBasket()\n            ->openBasketDisplay()\n            ->seeBasketContains([$basketItem1, $basketItem2], '350,00 €');\n\n        // making product out of stock now\n        $I->updateInDatabase('oxarticles', ['oxstock' => '3', 'oxstockflag' => '3'], ['oxid' => '1000']);\n        $I->seeInDatabase('oxarticles', [\n            'oxid' => '1000',\n            'oxstock' => '3',\n            'oxstockflag' => '3',\n            ]);\n\n        $basketPage->updateProductAmount(7);\n\n        $I->seeText(Translator::translate('ERROR_MESSAGE_OUTOFSTOCK_OUTOFSTOCK'));\n\n        $basketItem1 = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'amount' => 3,\n            'totalPrice' => '150,00 €'\n        ];\n        $paymentPage = $basketPage->seeBasketContains([$basketItem1, $basketItem2], '250,00 €')\n            ->goToNextStep()\n            ->goToNextStep();\n\n        //in second step, product availability is not checked.\n        $I->seeText(Translator::translate('PAYMENT_METHOD'));\n\n        $orderPage = $paymentPage->selectPayment('oxidcashondel')\n            ->goToNextStep();\n\n        // someone bought some more items while client filled steps\n        $I->updateInDatabase('oxarticles', ['oxstock' => '1', 'oxstockflag' => '3'], ['oxid' => '1000']);\n        $I->seeInDatabase('oxarticles', [\n            'oxid' => '1000',\n            'oxstock' => '1',\n            'oxstockflag' => '3',\n            ]);\n\n        $orderPage->submitOrder();\n\n        //in second step, product availability is not checked.\n        $I->seeText(Translator::translate('ERROR_MESSAGE_OUTOFSTOCK_OUTOFSTOCK'));\n\n        // someone bought all items while client filled steps\n        $I->updateInDatabase('oxarticles', ['oxstock' => '0', 'oxstockflag' => '3'], ['oxid' => '1000']);\n        $I->seeInDatabase('oxarticles', [\n            'oxid' => '1000',\n            'oxstock' => '0',\n            'oxstockflag' => '3',\n        ]);\n\n        $orderPage->submitOrder();\n\n        //in second step, product availability is not checked.\n        $I->seeText(Translator::translate('ERROR_MESSAGE_ARTICLE_ARTICLE_NOT_BUYABLE'));\n\n        $orderPage->submitOrderSuccessfully();\n    }\n\n    public function checkMinimalOrderPrice(AcceptanceTester $I): void\n    {\n        $I->wantToTest('minimal order price in checkout process (min order sum is 49 €)');\n\n        // prepare data for test\n        $I->updateInDatabase('oxdelivery', ['OXTITLE_1' => 'OXTITLE'], ['OXTITLE_1' => '']);\n        $I->updateInDatabase('oxdiscount', ['OXACTIVE' => 1], ['OXID' => 'testcatdiscount']);\n\n        $I->updateConfigInDatabase('iMinOrderPrice', '49', 'str');\n        $I->updateConfigInDatabase('basketLowOrderDisplayOrderButton', 'hide', 'select');\n        $I->updateConfigInDatabase('miniBasketLowOrderDisplayOrderButton', 'hide', 'select');\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'amount' => 1,\n            'totalPrice' => '50,00 €'\n        ];\n\n        $userData = Fixtures::get('existingUser');\n\n        $homePage = $I->openShop();\n\n        //add Product to basket\n        $basket = new Basket($I);\n        $basket->addProductToBasket($productData['id'], 1);\n        $miniBasket = $homePage->openMiniBasket();\n\n        $basketPage = $miniBasket->openBasketDisplay()\n            ->seeBasketContains([$productData], '50,00 €');\n        $I->dontSee(Translator::translate('MIN_ORDER_PRICE') . ' 49,00 €');\n        $basketPage->seeNextStep();\n\n        $basketPage = $basketPage->loginUser($userData['userLoginName'], $userData['userPassword']);\n        $I->seeText(Translator::translate('MIN_ORDER_PRICE') . ' 49,00 €');\n        $basketPage->dontSeeNextStep();\n\n        //Minibasket message and button\n        $miniBasket->openMiniBasket();\n        $I->see(Translator::translate('MIN_ORDER_PRICE') . ' 49,00 €');\n        $I->dontSeeElement(sprintf('//a[contains(text(),\"%s\")]', Translator::translate('CHECKOUT'))); //Minibasket checkout button\n\n        //Update to display buttons\n        $I->updateConfigInDatabase('basketLowOrderDisplayOrderButton', 'show', 'select');\n        $I->updateConfigInDatabase('miniBasketLowOrderDisplayOrderButton', 'show', 'select');\n\n        $I->openShop()->openBasket();\n        $I->see(Translator::translate('MIN_ORDER_PRICE') . ' 49,00 €');\n        $I->see(Translator::translate('CHECKOUT'));\n\n        $basket->openMiniBasket();\n        $I->see(Translator::translate('MIN_ORDER_PRICE') . ' 49,00 €');\n        $I->seeElement(sprintf('//a[contains(text(),\"%s\")]', Translator::translate('CHECKOUT'))); //Minibasket checkout button\n        $basket->closeMiniBasket();\n\n        //Reset to default settings\n        $I->updateConfigInDatabase('basketLowOrderDisplayOrderButton', 'hide', 'select');\n\n        $productData['amount'] = 2;\n        $productData['totalPrice'] = '90,00 €';\n        $basketPage = $basketPage->updateProductAmount(2)\n            ->seeBasketContains([$productData], '90,00 €');\n        $I->dontSee(Translator::translate('MIN_ORDER_PRICE') . ' 49,00 €');\n        $basketPage->seeNextStep();\n\n        $basketPage = $basketPage->addCouponToBasket('123123');\n        $I->seeText(Translator::translate('MIN_ORDER_PRICE') . ' 49,00 €');\n        $basketPage->dontSeeNextStep();\n\n        $basketPage = $basketPage->removeCouponFromBasket();\n        $I->dontSee(Translator::translate('MIN_ORDER_PRICE') . ' 49,00 €');\n        $userCheckoutPage = $basketPage->goToNextStep();\n        $breadCrumbName = Translator::translate('ADDRESS');\n        $userCheckoutPage->seeOnBreadCrumb($breadCrumbName);\n    }\n\n    public function buyProductWithBundledItem(AcceptanceTester $I): void\n    {\n        $basket = new Basket($I);\n        $I->wantToTest('bundled product');\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'amount' => 1,\n            'totalPrice' => '50,00 €'\n        ];\n\n        $bundledProductData = [\n            'id' => '1001',\n            'title' => 'Test product 1 [EN] šÄßüл',\n            'amount' => '+1'\n        ];\n\n        $this->prepareTestDataForBundledProduct($I, $productData['id'], $bundledProductData['id']);\n\n        $homePage = $I->openShop();\n\n        //add Product to basket\n        $basket->addProductToBasket($productData['id'], 1);\n        $homePage->openMiniBasket()\n            ->openBasketDisplay()\n            ->seeBasketContains([$productData], '50,00 €')\n            ->seeBasketContainsBundledProduct($bundledProductData, 2);\n    }\n\n    public function checkGuestUserNameSwitching(AcceptanceTester $I): void\n    {\n        $I->wantToTest('guest checkout with username switching');\n        $I->updateConfigInDatabase('blShowBirthdayFields', true, 'bool');\n\n        $basket = new Basket($I);\n        $userRegistration = new UserRegistrationInCheckout($I);\n        $email1 = 'abc@def.gh';\n        $email2 = 'xyz@def.gh';\n\n        /** Start guest1 checkout with email1, then logout */\n        $basket->addProductToBasketAndOpenUserCheckout('1000', 10);\n        $userRegistration->createNotRegisteredUserInCheckout(\n            $email1,\n            $this->getUserFormData(),\n            $this->getUserAddressFormData()\n        );\n        $I->clearShopCache();\n\n        /** Start guest2 checkout with email2 */\n        $basket->addProductToBasketAndOpenUserCheckout('1000', 100);\n        $paymentPage = $userRegistration->createNotRegisteredUserInCheckout(\n            $email2,\n            $this->getUserFormData(),\n            $this->getUserAddressFormData()\n        );\n\n        /** Check both accounts are present in DB */\n        $I->seeInDatabase('oxuser', ['oxusername' => $email1]);\n        $I->seeInDatabase('oxuser', ['oxusername' => $email2]);\n\n        /** Check guest2 can use email1 (@see #0006965) */\n        $paymentPage->goToPreviousStep()\n            ->openUserBillingAddressForm()\n            ->modifyUserName($email1)\n            ->goToNextStep();\n\n        /** Check user2 is removed from DB */\n        $I->seeInDatabase('oxuser', ['oxusername' => $email1]);\n        $I->dontSeeInDatabase('oxuser', ['oxusername' => $email2]);\n\n        /** Re-open and re-submit user form without changes, check payment methods are available (@see #0007109) */\n        $paymentPage->goToPreviousStep()\n            ->goToNextStep()\n            ->selectPayment('oxidcashondel');\n    }\n\n    #[Group('checkout_process_address', 'exclude_from_compilation')]\n    public function checkCreateShippingAddress(AcceptanceTester $I): void\n    {\n        $I->wantToTest('creating shipping address during authenticated user`s checkout');\n        $basket = new Basket($I);\n        $userData = Fixtures::get('existingUser');\n        $existingProductId = '1001';\n        $userShippingAddress = [\n            'userSalutation' => 'Mrs',\n            'userFirstName' => 'Some first name',\n            'userLastName' => 'Some last name',\n            'companyName' => 'Some company',\n            'street' => 'Some street',\n            'streetNr' => '1-1',\n            'ZIP' => '1234',\n            'city' => 'Some city',\n            'fonNr' => '111-111-1',\n            'faxNr' => '111-111-111-1',\n            'countryId' => 'Germany',\n            'stateId' => 'Berlin',\n        ];\n\n        $homePage = $I->openShop();\n        $homePage->loginUser($userData['userLoginName'], $userData['userPassword']);\n        $basket->addProductToBasket($existingProductId, 1);\n        /** @var UserCheckout $userCheckoutPage */\n        $userCheckoutPage = $homePage->openMiniBasket()\n            ->openBasketDisplay()\n            ->goToNextStep();\n\n        $userCheckoutPage->openShippingAddressForm();\n        $userCheckoutPage->enterShippingAddressData($userShippingAddress);\n\n        $userCheckoutPage->goToNextStep()\n            ->goToNextStep()\n            ->validateUserDeliveryAddress($userShippingAddress);\n    }\n\n    public function checkForceIdDisabledDuringCheckout(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Change session during payment');\n        $I->updateConfigInDatabase('blShowBirthdayFields', true, 'bool');\n\n        $basket = new Basket($I);\n        $userRegistration = new UserRegistrationInCheckout($I);\n        $email1 = 'abc@def.gh';\n\n        $basket->addProductToBasketAndOpenUserCheckout('1000', 10);\n        $userRegistration->createNotRegisteredUserInCheckout(\n            $email1,\n            $this->getUserFormData(),\n            $this->getUserAddressFormData()\n        );\n\n        $userSid = $I->grabCookie('sid');\n        $I->amOnPage('/index.php?cl=payment&new_user=1&success=1&force_sid=pdgfk373csd38v3uhm02mo4qeu');\n\n        $I->assertEquals($userSid, $I->grabCookie('sid'));\n    }\n\n    public function checkNoSessionCookiesCheckout(AcceptanceTester $I): void\n    {\n        $I->wantToTest('checkout process can be completed with cookies disabled');\n\n        $I->amGoingTo('disable storing session in cookies via configuration');\n        $I->updateProjectConfigurations(['oxid_esales.cookies_session' => false], []);\n\n        $I->amGoingTo('go through checkout steps');\n        (new Basket($I))->addProductToBasketAndOpenUserCheckout('1000', 10);\n        $orderSubmitPage = (new UserRegistrationInCheckout($I))\n            ->createNotRegisteredUserInCheckout(\n                'abc@def.gh',\n                $this->getUserFormData(),\n                $this->getUserAddressFormData()\n            )\n            ->selectPayment('oxidcashondel')\n            ->goToNextStep();\n\n        $I->expect('that cookies were disabled all the way through checkout');\n        $I->dontSeeCookie('sid');\n\n        $I->expect('to submit order without errors');\n        $orderSubmitPage->submitOrderSuccessfully();\n\n        $I->amGoingTo('do cleanup');\n        $I->restoreProjectConfigurations();\n    }\n\n    public function checkAttributesInBasket(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Check if attributes are visible in basket');\n\n        $I->updateInDatabase('oxattribute', ['OXDISPLAYINBASKET' => 1], ['OXID' => 'testattribute1']);\n        $I->haveInDatabase(\n            'oxobject2attribute',\n            [\n                'OXID' => '1001attribute',\n                'OXOBJECTID' => '1001',\n                'OXATTRID' => '9438ac75bac3e344628b14bf7ed82c15',\n                'OXVALUE' => 'Schwarz',\n                'OXVALUE_1' => 'Black',\n            ]\n        );\n\n        (new Basket($I))->addProductToBasket('1001', 1);\n\n        $I\n            ->openShop()\n            ->openMiniBasket()\n            ->openBasketDisplay()\n            ->seeBasketContainsAttribute('attr value 11 [EN] šÄßüл', 1);\n    }\n\n    public function vatsInCheckoutSummary(AcceptanceTester $I): void\n    {\n        $I->wantToTest('enabling configuration for displaying VAT values works on checkout summary');\n\n        $I->amGoingTo('enable displaying the VATs via config');\n        $I->updateConfigInDatabase('blShowVATForDelivery', 'true', 'bool');\n        $I->updateConfigInDatabase('blShowVATForPayCharge', 'true', 'bool');\n        $I->updateConfigInDatabase('blShowVATForWrapping', 'true', 'bool');\n        $I->amGoingTo('add some product to the basket to prepare for the checkout');\n        (new Basket($I))->addProductToBasket('1000', 3);\n\n        $I->amGoingTo('select additional product wrapping and click-through to the summary');\n        $loginData = Fixtures::get('existingUser');\n\n        /** @var OrderCheckout $orderCheckout */\n        $orderCheckout = $I\n            ->openShop()\n            ->loginUser(\n                $loginData['userLoginName'],\n                $loginData['userPassword']\n            )\n            ->openMiniBasket()\n            ->openBasketDisplay()\n            ->openGiftSelection(1)\n            ->selectWrapping(1, 'testwrapping')\n            ->submitChanges()\n            ->goToNextStep()\n            ->goToNextStep()\n            ->goToNextStep();\n\n        $I->amGoingTo('validate checkout summary values');\n        $orderCheckout->seeSummaryVat('5');\n        $orderCheckout->seeSummaryNet('142,86 €');\n        $orderCheckout->seeSummaryGross('150,00 €');\n        $orderCheckout->seeSummaryShippingNet('0,00 €');\n        $orderCheckout->seeSummaryPaymentNet('7,14 €');\n        $orderCheckout->seeSummaryPaymentVat('0,36 €', '5');\n        $orderCheckout->seeSummaryWrappingNet('2,57 €');\n        $orderCheckout->seeSummaryWrappingVat('0,13 €');\n        $orderCheckout->seeSummaryGrandTotal('160,20 €');\n    }\n\n    public function modifyShippingAndPaymentMethods(AcceptanceTester $I): void\n    {\n        $I->wantToTest('switching between payment and shipping methods');\n\n        $I->amGoingTo('make non-zero delivery cost to see it at checkout');\n        $I->updateInDatabase(\n            'oxdelivery',\n            ['OXADDSUM' => 123],\n            ['OXID' => 'testdelivery']\n        );\n\n        $I->amGoingTo('add some product to the basket to prepare for the checkout');\n        $basket = new Basket($I);\n        $basket->addProductToBasket('1001', 1);\n        $basket->addProductToBasket('1002-2', 1);\n\n        $I->amGoingTo('click-through the checkout to the summary');\n        $userData = Fixtures::get('existingUser');\n        /** @var OrderCheckout $orderCheckoutPage */\n        $orderCheckoutPage = $I\n            ->openShop()\n            ->loginUser(\n                $userData['userLoginName'],\n                $userData['userPassword']\n            )\n            ->openMiniBasket()\n            ->openBasketDisplay()\n            ->goToNextStep()\n            ->goToNextStep()\n            ->goToNextStep();\n\n        $I->amGoingTo('go back to modify the default shipping method and see the change at checkout');\n        $paymentPage = $orderCheckoutPage->editShippingMethod();\n        $paymentPage->selectShipping('Alternative');\n        $orderCheckoutPage = $paymentPage->goToNextStep();\n        $orderCheckoutPage->validateShippingMethod('Alternative');\n        $orderCheckoutPage->seeSummaryShippingGross('123,00 €');\n        $orderCheckoutPage->seeSummarySurchargePaymentMethod('7,50 €');\n\n        $I->amGoingTo('go back to modify the default payment method and see the change at checkout');\n        $paymentPage = $orderCheckoutPage->editPaymentMethod();\n        $paymentPage->selectPayment('oxidpayadvance');\n        $paymentPage->goToNextStep();\n        $orderCheckoutPage->validatePaymentMethod('Cash in advance');\n        $orderCheckoutPage->dontSeeSummarySurchargePaymentMethod();\n\n        $I->amGoingTo('check if ordering works');\n        $orderCheckoutPage->submitOrderSuccessfully();\n    }\n\n    public function checkPaymentStep(AcceptanceTester $I): void\n    {\n        $basket = new Basket($I);\n        $I->wantToTest('the payment step');\n\n        $homePage = $I->openShop();\n\n        $basket->addProductToBasket(\"1001\", 1);\n        $basket->addProductToBasket(\"1002-2\", 1);\n\n        $userData = Fixtures::get('existingUser');\n        $homePage->loginUser($userData['userLoginName'], $userData['userPassword']);\n        $basketPage = $homePage->openMiniBasket()->openBasketDisplay();\n\n        $basketPage->addCouponToBasket(\"123123\");\n        $paymentMethodPage = $basketPage->goToNextStep()->goToNextStep();\n        $I->seeText(Translator::translate('PAYMENT_METHOD'));\n\n        /** @var OrderCheckout $orderCheckoutPage */\n        $orderCheckoutPage = $paymentMethodPage->goToNextStep();\n\n        $orderCheckoutPage->validatePaymentMethod('COD (Cash on delivery)');\n\n        $basketItem1 = [\n            'id' => '1001',\n            'title' => 'Test product 1 [EN] šÄßüл',\n            'amount' => 1,\n            'totalPrice' => '100,00 €'\n        ];\n\n        $basketItem2 = [\n            'id' => '1002-2',\n            'title' => 'Test product 2 [EN] šÄßüл',\n            'amount' => 1,\n            'totalPrice' => '67,00 €'\n        ];\n\n        $orderCheckoutPage->validateOrderItems([$basketItem1, $basketItem2]);\n\n        $orderCheckoutPage->seeSummaryShippingGross('0,00 €');\n        $newShippingMethod = 'Alternative';\n        $paymentPage = $orderCheckoutPage->editShippingMethod();\n        $paymentPage->selectShipping($newShippingMethod);\n        $orderCheckoutPage = $paymentPage->goToNextStep();\n        $orderCheckoutPage->validateShippingMethod($newShippingMethod);\n        $orderCheckoutPage->seeSummaryShippingGross('0,00 €');\n\n        $I->waitForElement('//div[contains(text(),\"Surcharge Payment method\")]/span');\n        $orderCheckoutPage->seeSummarySurchargePaymentMethod('7,50 €');\n        $paymentPage = $orderCheckoutPage->editPaymentMethod();\n        $paymentPage->selectPayment('oxidpayadvance');\n        $paymentPage->goToNextStep();\n        $orderCheckoutPage->validatePaymentMethod('Cash in advance');\n        $orderCheckoutPage->dontSeeSummarySurchargePaymentMethod();\n\n        $orderCheckoutPage->submitOrderSuccessfully();\n    }\n\n    public function checkOrderToOtherCountries(AcceptanceTester $I): void\n    {\n        $I->wantToTest('ordering to another country');\n\n        $homePage = $I->openShop();\n        $userData = Fixtures::get('existingUser');\n        $homePage->loginUser($userData['userLoginName'], $userData['userPassword']);\n\n        $basket = new Basket($I);\n        $basket->addProductToBasket(\"1000\", 3);\n\n        $paymentCheckout = $homePage->openMiniBasket()->openCheckout();\n        $paymentCheckout->selectShippingIsAvailable();\n\n        $userShippingAddress = [\n            'userSalutation' => 'Mrs',\n            'userFirstName' => 'Some first name',\n            'userLastName' => 'Some last name',\n            'companyName' => 'Some company',\n            'street' => 'Some street',\n            'streetNr' => '1-1',\n            'ZIP' => '1234',\n            'city' => 'Some city',\n            'fonNr' => '111-111-1',\n            'faxNr' => '111-111-111-1',\n            'countryId' => 'United States',\n            'stateId' => 'CO',\n        ];\n\n        $userCheckout = $paymentCheckout->goToPreviousStep();\n        $userCheckout->openUserBillingAddressForm();\n        $userCheckout->enterAddressData($userShippingAddress);\n\n        $paymentCheckout = $userCheckout->goToNextStep();\n        $paymentCheckout->selectShippingIsNotAvailable();\n\n        $checkoutPage = $paymentCheckout->goToNextStep();\n\n        $checkoutPage->validateShippingMethod('');\n        $checkoutPage->validatePaymentMethod('Empty');\n        $checkoutPage->submitOrderSuccessfully();\n    }\n\n    public function checkFrontendPerfOptionsAlsoBought(AcceptanceTester $I): void\n    {\n        $basket = new Basket($I);\n        $I->wantToTest('the frontend performance option enabling/disabling \"also bought\"');\n\n        $homePage = $I->openShop();\n\n        $basket->addProductToBasket(\"1000\", 1);\n        $basket->addProductToBasket(\"1001\", 1);\n\n        $userData = Fixtures::get('existingUser');\n        $homePage->loginUser($userData['userLoginName'], $userData['userPassword']);\n\n        $basket->openMiniBasket();\n        $thankYouPage = $basket->openCheckout()->goToNextStep()->submitOrderSuccessfully();\n        $thankYouPage->backToShop();\n\n        $searchList = $homePage->searchFor('1000');\n        $productPage = $searchList->openProduct();\n        $productPage->openAlsoBoughtProduct();\n        $productPage->seeProductTitle('Test product 1 [EN]');\n\n        $basket->addProductToBasket(\"1000\", 1);\n        $basket->openMiniBasket();\n        $thankYouPage = $basket->openCheckout()->goToNextStep()->submitOrderSuccessfully();\n        $thankYouPage->openAlsoBoughtProduct();\n        $productPage->seeProductTitle('Test product 1 [EN]');\n\n        // Performance options turn \"who bought also bought\" off\n        $I->updateConfigInDatabase('bl_perfLoadCustomerWhoBoughtThis', false);\n\n        $searchList = $homePage->searchFor('1000');\n        $productPage = $searchList->openProduct();\n        $productPage->dontSeeAlsoBought();\n\n        $basket->addProductToBasket(\"1000\", 1);\n        $basket->openMiniBasket();\n        $thankYouPage = $basket->openCheckout()->goToNextStep()->submitOrderSuccessfully();\n        $thankYouPage->dontSeeAlsoBought();\n    }\n\n    public function checkMyAccountOrderHistory(AcceptanceTester $I): void\n    {\n        $I->wantToTest('my accounts order history');\n\n        $homePage = $I->openShop();\n        $userData = Fixtures::get('existingUser');\n        $homePage->loginUser($userData['userLoginName'], $userData['userPassword']);\n\n        $basket = new Basket($I);\n        $basket->addProductToBasket(\"1001\", 2);\n        $basket->openMiniBasket();\n        $thankYouPage = $basket->openCheckout()->goToNextStep()->submitOrderSuccessfully();\n        $orderHistory = $thankYouPage->goToOrderHistory();\n        $orderHistory->seePageHeader();\n\n        // check a different way to get to the orderHistory\n        $user_account = new UserAccount($I);\n        $orderHistory = $user_account->openAccountPage()->openOrderHistory();\n\n        $orderHistory->seePageHeader();\n\n        $orderInformation = [\n            'orderNumber' => '1',\n            'status' => Translator::translate('SHIPPED'),\n            'name' => 'UserNamešÄßüл UserSurnamešÄßüл',\n            'itemNumber' => '1',\n            'amount' => '1',\n            'product' => 'Test product 1 [EN] šÄßüл'\n        ];\n\n        $orderHistory->seeOrder($orderInformation);\n        $productDetailsPage = $orderHistory->openProduct($orderInformation);\n        $productDetailsPage->seeProductTitle('Test product 1 [EN] šÄßüл');\n    }\n\n    #[Group('checkout_process_address', 'exclude_from_compilation')]\n    public function checkOrderStepChangedAddress(AcceptanceTester $I): void\n    {\n        $basket = new Basket($I);\n        $I->wantToTest('whether changing the shipping/billing address in the payment/shipping step ' .\n            'works and is displayed on the final checkout page');\n\n        $homePage = $I->openShop();\n\n        $basket->addProductToBasket(\"1001\", 1);\n\n        $userData = Fixtures::get('existingUser');\n        $homePage->loginUser($userData['userLoginName'], $userData['userPassword']);\n\n        /** @var OrderCheckout $orderCheckout */\n        $orderCheckout = $basket->openMiniBasket()->openCheckout()->goToNextStep();\n\n        $userData = [\n            'shippingFirstName' => 'UserNamešÄßüл',\n            'shippingLastName' => 'UserSurnamešÄßüл',\n            'shippingCompany' => 'UserCompany šÄßüл',\n            'shippingAdditionalInfo' => 'User additional info šÄßüл',\n            'shippingStreet' => 'Musterstr.šÄßüл',\n            'shippingStreetNr' => '1',\n            'shippingZip' => '79098',\n            'shippingCity' => 'Musterstadt šÄßüл',\n            'shippingPhone' => '0800 111111',\n            'shippingFax' => '0800 111112',\n            'shippingCellPhone' => '0800 111114',\n            'shippingPersonalPhone' => '0800 11111',\n            'shippingCountry' => 'Germany',\n            'shippingEmail' => 'example_test@oxid-esales.dev',\n            'shippingTitle' => 'Mr'\n        ];\n\n        foreach ($userData as $addressPart) {\n            $orderCheckout->seeUserDeliveryAddressPart($addressPart);\n        }\n\n        $userCheckout = $orderCheckout->editUserAddress();\n        $userCheckout->openShippingAddressForm();\n\n        $addressData = [\n            'userSalutation' => 'Mrs',\n            'userFirstName' => 'John',\n            'userLastName' => 'Doe',\n            'companyName' => 'XYZ Corp.',\n            'street' => 'Main St',\n            'streetNr' => '10',\n            'ZIP' => '90210',\n            'city' => 'Los Angeles',\n            'additionalInfo' => 'Floor 5, Office 2',\n            'fonNr' => '123-456-7890',\n            'faxNr' => '098-765-4321',\n            'countryId' => 'Austria',\n        ];\n        $userCheckout->enterShippingAddressData($addressData);\n\n        $paymentCheckout = $userCheckout->goToNextStep();\n        $paymentCheckout->selectShippingIsNotAvailable();\n\n        $orderCheckout = $paymentCheckout->goToNextStep();\n        $userCheckout = $orderCheckout->editUserAddress();\n\n        $country = 'Germany';\n        $userCheckout->selectCountry($country);\n\n        $paymentCheckout = $userCheckout->goToNextStep();\n        $paymentCheckout->selectShippingIsAvailable();\n        $orderCheckout = $paymentCheckout->goToNextStep();\n        $orderCheckout->submitOrderSuccessfully();\n    }\n\n    public function testWarningAboutBasketChanges(AcceptanceTester $I): void\n    {\n        $I->wantToTest('user sees warning before submitting an order if his cart was modified from another session');\n        $user = Fixtures::get('existingUser');\n        $product1 = Fixtures::get('product-1000');\n        $product2 = Fixtures::get('product-1001');\n\n        $I->amGoingTo('add a product and go through till the last order step');\n        $I->openShop()\n            ->loginUser(\n                $user['userLoginName'],\n                $user['userPassword']\n            );\n        $basket = new Basket($I);\n        $basket->addProductToBasket($product1['OXID'], 1);\n        $orderCheckout = $basket->openMiniBasket()->openCheckout()->goToNextStep();\n\n        $I->amGoingTo('add one more product to existing basket in another session (browser tab)');\n        $I->openNewTab();\n        $I->openShop();\n        (new Basket($I))->addProductToBasket($product2['OXID'], 1);\n\n        $I->amGoingTo('go back to the initial session (tab)');\n        $I->closeTab();\n\n        $I->amGoingTo('try to submit an order and make sure I see the warning');\n        $orderCheckout->submitOrder();\n        $I->seeText(Translator::translate('BASKET_ITEMS_CHANGED_ERROR'));\n        $I->see($product1['OXTITLE_1']);\n        $I->see($product2['OXTITLE_1']);\n\n        $I->amGoingTo('confirm that order can be submitted after the warning was shown');\n        $orderCheckout->submitOrderSuccessfully();\n    }\n\n    public function testWarningAboutBasketChangesWithEmptyBasket(AcceptanceTester $I): void\n    {\n        $I->wantToTest('card-modified-warning-message behaviour when cart was emptied from another session');\n        $user = Fixtures::get('existingUser');\n        $product = Fixtures::get('product-1000');\n\n        $I->amGoingTo('add a product and go through till the last order step');\n        $I->openShop()\n            ->loginUser(\n                $user['userLoginName'],\n                $user['userPassword']\n            );\n        $basket = new Basket($I);\n        $basket->addProductToBasket($product['OXID'], 1);\n        $orderCheckout = $basket->openMiniBasket()->openCheckout()->goToNextStep();\n\n        $I->amGoingTo('remove that product from basket in another session (browser tab)');\n        $I->openNewTab();\n        $I->openShop();\n        (new Basket($I))->openMiniBasket()\n            ->openBasketDisplay()\n            ->updateProductAmount(0);\n\n        $I->amGoingTo('go back to the initial session (tab)');\n        $I->closeTab();\n\n        $I->amGoingTo('try to submit an order and make sure I see the warning');\n        $orderCheckout->submitOrder();\n        $I->seeText(Translator::translate('BASKET_ITEMS_CHANGED_ERROR'));\n        $I->seeText(Translator::translate('BASKET_EMPTY'));\n\n        $I->amGoingTo('confirm that warning is gone after page reload');\n        $I->reloadPage();\n        $I->dontSee(Translator::translate('BASKET_ITEMS_CHANGED_ERROR'));\n    }\n\n    public function testCheckoutWithDeletedProduct(AcceptanceTester $I): void\n    {\n        $I->wantToTest('checkout will refresh basket items when a product is deleted by admin');\n        $user = Fixtures::get('existingUser');\n        $product = Fixtures::get('product-1000');\n\n        $I->amGoingTo('add a product and go through till the last order step');\n        $I->openShop()\n            ->loginUser(\n                $user['userLoginName'],\n                $user['userPassword']\n            );\n        $basket = new Basket($I);\n        $basket->addProductToBasket($product['OXID'], 1);\n        $orderCheckout = $basket->openMiniBasket()->openCheckout()->goToNextStep();\n\n        $I->deleteFromDatabase('oxarticles', ['OXID' => $product['OXID']]);\n\n        $I->amGoingTo('try to submit an order and make sure I see the warning');\n        $orderCheckout->submitOrder();\n        $I->seeText(sprintf(\n            Translator::translate('ERROR_MESSAGE_ARTICLE_ARTICLE_DOES_NOT_EXIST'),\n            $product['OXTITLE_1']\n        ));\n    }\n\n    private function prepareTestDataForBundledProduct(AcceptanceTester $I, string $productId, string $bundleId): void\n    {\n        $I->updateInDatabase('oxarticles', ['OXBUNDLEID' => $bundleId], ['OXID' => $productId]);\n    }\n\n    private function getUserFormData(): array\n    {\n        return [\n            'userBirthDateDayField' => '31',\n            'userBirthDateMonthField' => '12',\n            'userBirthDateYearField' => '2000',\n            'userUstIDField' => '',\n            'userMobFonField' => '',\n            'userPrivateFonField' => '',\n        ];\n    }\n\n    private function getUserAddressFormData(): array\n    {\n        return [\n            'userSalutation' => 'Mrs',\n            'userFirstName' => 'some-name',\n            'userLastName' => 'some-last-name',\n            'street' => 'some-street',\n            'streetNr' => '1',\n            'ZIP' => 'zip-1234',\n            'city' => 'some-city',\n            'countryId' => 'Germany',\n            'companyName' => '',\n            'additionalInfo' => '',\n            'fonNr' => '',\n            'faxNr' => '',\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/ContactFormCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\Codeception\\Page\\DataObject\\ContactData;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('ContactForm')]\nfinal class ContactFormCest\n{\n    public function contactForm(AcceptanceTester $I): void\n    {\n        $I->wantToTest('contact form with default required fields');\n        $contactPage = $I\n            ->openShop()\n            ->openContactPage();\n        $I->seeText(Translator::translate(\"COMPLETE_MARKED_FIELDS\"));\n\n        $I->amGoingTo('provide invalid form data and submit');\n        $contactData = $this->getContactData();\n        $contactData->setEmail('');\n        $contactPage->fillInContactData($contactData);\n        $contactPage->sendContactData();\n\n        $I->expect('validation fails with empty default required field');\n        $I->seeText(Translator::translate('DD_FORM_VALIDATION_REQUIRED'));\n        $I->dontSee(Translator::translate(\"THANK_YOU\"));\n\n        $I->amGoingTo('provide valid form data and submit');\n        $contactData = $this->getContactData();\n        $contactPage->fillInContactData($contactData);\n        $contactPage->sendContactData();\n\n        $I->expect('form works with valid data');\n        $I->seeText(Translator::translate('THANK_YOU'));\n        $I->dontSee(Translator::translate('DD_FORM_VALIDATION_REQUIRED'));\n    }\n\n    public function contactFormConfigured(AcceptanceTester $I): void\n    {\n        $I->wantToTest('contact form with custom required fields');\n\n        $I->amGoingTo('configure custom fields as required');\n        $I->updateConfigInDatabase(\n            'contactFormRequiredFields',\n            serialize(['email', 'firstName']),\n            'arr'\n        );\n\n        $contactPage = $I\n            ->openShop()\n            ->openContactPage();\n        $I->amGoingTo('provide invalid form data and submit');\n        $contactData = $this->getContactData();\n        $contactData->setFirstName('');\n        $contactPage->fillInContactData($contactData);\n        $contactPage->sendContactData();\n        $I->expect('form submit doesn\\'t work without first name');\n        $I->seeText(Translator::translate('DD_FORM_VALIDATION_REQUIRED'));\n        $I->dontSee(Translator::translate(\"THANK_YOU\"));\n    }\n\n    private function getContactData(): ContactData\n    {\n        $contactData = new ContactData();\n        $contactData->setSalutation(Translator::translate('MR'));\n        $contactData->setFirstName('first name');\n        $contactData->setLastName('Last name');\n        $contactData->setEmail('example_test@oxid-esales.dev');\n        $contactData->setSubject('subject');\n        $contactData->setMessage('message text');\n\n        return $contactData;\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/CookiesNoticeCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('cookieNotice')]\nfinal class CookiesNoticeCest\n{\n    public function testCookieNoticeAccept(AcceptanceTester $I): void\n    {\n        $I->wantToTest('accept cookie functionality');\n\n        $this->setCookieNoticeInactive($I);\n\n        $I->openShop()\n            ->dontSeeCookieNotice();\n\n        $this->setCookieNoticeActive($I);\n        $I->openShop()\n            ->seeCookieNotice()\n            ->closeCookieNotice();\n\n        $I->openShop()\n            ->dontSeeCookieNotice();\n    }\n\n    public function testCookieNoticeReject(AcceptanceTester $I): void\n    {\n        $I->wantToTest('reject cookie functionality');\n\n        $this->setCookieNoticeActive($I);\n\n        $I->openShop()\n            ->seeCookieNotice()\n            ->rejectCookies()\n            ->seeRejectInfo();\n    }\n\n    private function setCookieNoticeActive(AcceptanceTester $I): void\n    {\n        $I->updateConfigInDatabase('blShowCookiesNotification', true);\n        $I->clearShopCache();\n    }\n\n    private function setCookieNoticeInactive(AcceptanceTester $I): void\n    {\n        $I->updateConfigInDatabase('blShowCookiesNotification', false);\n        $I->clearShopCache();\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/CurrencyCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('currency_test')]\nfinal class CurrencyCest\n{\n    public function testCurrencySwitch(AcceptanceTester $I): void\n    {\n        $I->wantToTest('currency switching');\n\n        $homePage = $I->openShop()->openHomePage();\n        $homePage->switchCurrency('EUR');\n        $productList = $homePage->openCategoryPage('Test category 0 [EN] šÄßüл');\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 €'\n        ];\n\n        $productList->seeProductData($productData);\n\n        $productList->switchCurrency('GBP');\n        $productData['price'] = '42.83 £';\n        $productList->seeProductData($productData);\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/DistributorCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\Codeception\\Page\\Lists\\DistributorList;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('manufacturer')]\nfinal class DistributorCest\n{\n    public function checkDistributorList(AcceptanceTester $I): void\n    {\n        $I->wantToTest('distributor functionality and product list navigation');\n        $I->updateConfigInDatabase('aNrofCatArticles', serialize([10, 50, 100, 2, 1]), 'arr');\n        $I->updateConfigInDatabase('aNrofCatArticlesInGrid', serialize([10, 50, 100, 2, 1]), 'arr');\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 €'\n        ];\n\n        $productData2 = [\n            'id' => '1001',\n            'title' => 'Test product 1 [EN] šÄßüл',\n            'description' => 'Test product 1 short desc [EN] šÄßüл',\n            'price' => '100,00 €'\n        ];\n\n        $distributorListPage = new DistributorList($I);\n        $I->amOnPage($distributorListPage->route([]));\n        $distributorListPage->seeDistributorData(\n            [\n                'title' => 'Distributor [EN] šÄßüл',\n                'count' => '3'\n            ],\n            1\n        )\n            ->openDistributorPage(1)\n            ->seePageInformation([\n                'title' => 'Distributor [EN] šÄßüл',\n                'description' => 'Distributor description [EN] šÄßüл'\n            ])->selectSorting('oxtitle', 'asc')\n            ->seeProductData($productData, 1)\n            ->seeProductData($productData2, 2)\n            ->selectSorting('oxprice', 'desc')\n            ->selectProductsPerPage('2')\n            ->seeProductData($productData2, 1)\n            ->openNextListPage()\n            ->seeProductData($productData, 1)\n            ->openPreviousListPage()\n            ->seeProductData($productData2, 1);\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/DownloadableProductCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse DateTime;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\Codeception\\Page\\Home;\nuse OxidEsales\\Codeception\\Step\\Basket;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\nfinal class DownloadableProductCest\n{\n    private string $orderId;\n\n    public function _before(AcceptanceTester $I): void\n    {\n        $I->updateConfigInDatabase('blEnableDownloads', true, 'bool');\n        $I->updateConfigInDatabase('iMaxDownloadsCount', \"2\", 'str');\n        $I->updateConfigInDatabase('iLinkExpirationTime', \"240\", 'str');\n        $I->updateConfigInDatabase('blEnableIntangibleProdAgreement', true, 'bool');\n\n        $I->updateInDatabase('oxarticles', ['oxisdownloadable' => 1], ['oxartnum' => '1002-1']);\n    }\n\n    public function downloadableFiles(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Product downloadable files');\n\n        $I->clearShopCache();\n        $startPage = $I->loginShopWithExistingUser();\n        $this->makePurchaseComplete($I);\n\n        $this->orderId = $I->grabFromDatabase('oxorder', 'OXID', ['oxuserid' => 'testuser']);\n        $articleId = $I->grabFromDatabase('oxorderarticles', 'OXID', ['OXORDERID' => $this->orderId]);\n\n        $I->haveInDatabase(\n            'oxorderfiles',\n            [\n                'OXID' => \"testdownloadProductCest\",\n                'OXORDERID' => $this->orderId,\n                'OXFILENAME' => 'testFile3',\n                'OXFILEID' => '1000l',\n                'OXSHOPID' => 1,\n                'OXORDERARTICLEID' => $articleId,\n                'OXDOWNLOADCOUNT' => '0',\n                'OXMAXDOWNLOADCOUNT' => 2,\n                'OXDOWNLOADEXPIRATIONTIME' => 24,\n                'OXLINKEXPIRATIONTIME' => 240,\n                'OXRESETCOUNT' => 0,\n                'OXVALIDUNTIL' => (new DateTime())->modify('+1 week')->format('Y-m-d H:i:s'),\n                'OXTIMESTAMP' => (new DateTime())->format('Y-m-d H:i:s')\n            ]\n        );\n\n        $I->haveInDatabase(\n            'oxfiles',\n            [\n                'OXID' => '1000l',\n                'OXARTID' => '1002-1',\n                'OXFILENAME' => 'testFile3',\n                'OXPURCHASEDONLY' => 1,\n                'OXSTOREHASH' => 'e48a1b571bd2d2e60fb2d9b1b76b35d5',\n            ]\n        );\n\n        $this->checkMyDownloads($I, $startPage);\n        $this->makeOrderComplete($I);\n        $this->checkFileInMyDownloads($I, $startPage);\n    }\n\n    private function makePurchaseComplete(AcceptanceTester $I): void\n    {\n        $basket = new Basket($I);\n        $basket->addProductToBasketAndOpenUserCheckout('1002-1', 1)\n            ->goToNextStep()\n            ->goToNextStep()\n            ->confirmDownloadableProductsAgreement()\n            ->submitOrderSuccessfully();\n    }\n\n    private function checkMyDownloads(AcceptanceTester $I, Home $startPage): void\n    {\n        $accountPage = $startPage->openAccountPage();\n        $accountPage->openMyDownloadsPage();\n        $I->seeText(Translator::translate('DOWNLOADS_PAYMENT_PENDING'));\n    }\n\n    private function makeOrderComplete(AcceptanceTester $I): void\n    {\n        $currentTime = date('Y-m-d H:i:s');\n        $I->updateInDatabase('oxorder', ['oxpaid' => $currentTime], ['OXID' => $this->orderId]);\n    }\n\n    private function checkFileInMyDownloads(AcceptanceTester $I, Home $startPage): void\n    {\n        $accountPage = $startPage->openAccountPage();\n        $accountPage->openMyDownloadsPage();\n        $I->dontSee(Translator::translate('DOWNLOADS_PAYMENT_PENDING'));\n        $I->clickAndWait(\".downloadList a\");\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/DynamicImageGenerationCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('images')]\nfinal class DynamicImageGenerationCest\n{\n    private string $pathToGeneratedImages = '/out/pictures/generated/product/1/500_500_75';\n\n    public function fetchGeneratedImages(AcceptanceTester $I): void\n    {\n        $I->wantToTest('availability of dynamically generated images');\n\n        $I->amGoingTo('fetch a generated image for an existing product picture');\n        $existingImageFixture = 'test.png';\n        $I->amOnPage(\"$this->pathToGeneratedImages/$existingImageFixture\");\n        $this->dontSeeAnyErrorsOnPage($I);\n\n        $I->amGoingTo('check that a missing product picture will be replaced with a placeholder image');\n        $someMissingImage = 'some-missing-image.png';\n        $I->amOnPage(\"$this->pathToGeneratedImages/$someMissingImage\");\n        $this->dontSeeAnyErrorsOnPage($I);\n    }\n\n    private function dontSeeAnyErrorsOnPage(AcceptanceTester $I): void\n    {\n        $I->dontSee('Error');\n        $I->dontSee('Not found');\n        $I->dontSee('404');\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/FlowThemeCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\Codeception\\Step\\ProductNavigation;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\nfinal class FlowThemeCest\n{\n    #[Group('flow_theme')]\n    public function selectMultidimensionalVariantsInLists(AcceptanceTester $I): void\n    {\n        $I->markTestSkipped('make it work with APEX or remove');\n        $I->wantToTest('multidimensional variants functionality in lists');\n\n        $I->updateConfigInDatabase('blUseMultidimensionVariants', true, 'bool');\n        $I->updateConfigInDatabase('bl_perfLoadSelectListsInAList', true, 'bool');\n        $I->updateConfigInDatabase('bl_perfLoadSelectLists', true, 'bool');\n\n        $productData = [\n            'id' => '10014',\n            'title' => '14 EN product šÄßüл',\n            'description' => '13 EN description šÄßüл',\n            'price' => 'from 15,00 €'\n        ];\n\n        $searchListPage = $I->openShop()\n            ->searchFor($productData['id']);\n\n        $searchListPage->seeProductData($productData, 1);\n\n        $detailsPage = $searchListPage->selectVariant(1, 'M');\n        $detailsPage->seeProductData($productData);\n    }\n\n    #[Group('flow_theme', 'product', 'priceAlarm')]\n    public function sendProductPriceAlert(AcceptanceTester $I): void\n    {\n        $I->markTestSkipped('make it work with APEX or remove');\n\n        $productNavigation = new ProductNavigation($I);\n        $I->wantToTest('product price alert functionality');\n\n        $I->updateConfigInDatabase('sProductListNavigation', true);\n        $I->updateConfigInDatabase('bl_showPriceAlarm', true, 'bool');\n\n        $I->updateConfigInDatabase('blAllowSuggestArticle', true, 'bool');\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n        ];\n\n        //open details page\n        $detailsPage = $productNavigation->openProductDetailsPage($productData['id']);\n        $I->seeText($productData['title']);\n        $I->seeText(Translator::translate('PRICE_ALERT'));\n\n        $detailsPage->sendPriceAlert('example_test@oxid-esales.dev', 99.99);\n        $thankYouMessage = Translator::translate('PAGE_DETAILS_THANKYOUMESSAGE3')\n            . ' 99,99 € ' . Translator::translate('PAGE_DETAILS_THANKYOUMESSAGE4');\n        $I->seeText($thankYouMessage);\n        $I->seeText($productData['title']);\n    }\n\n    #[Group('flow_theme', 'product', 'priceAlarm')]\n    public function disableProductPriceAlert(AcceptanceTester $I): void\n    {\n        $I->markTestSkipped('make it work with APEX or remove');\n\n        $productNavigation = new ProductNavigation($I);\n        $I->wantToTest('product price alert functionality is disabled');\n\n        $I->updateConfigInDatabase('sProductListNavigation', true);\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n        ];\n\n        //disabling price alert for product(1000)\n        $I->updateInDatabase('oxarticles', ['oxblfixedprice' => 1], ['OXID' => '1000']);\n\n        //open details page\n        $productNavigation->openProductDetailsPage($productData['id']);\n        $I->seeText($productData['title']);\n        $I->dontSee(Translator::translate('PRICE_ALERT'));\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/GiftRegistryCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse Codeception\\Util\\Fixtures;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\Codeception\\Step\\ProductNavigation;\nuse OxidEsales\\Codeception\\Step\\Start;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\nuse function sprintf;\n\n#[Group('myAccount', 'giftRegistry')]\nfinal class GiftRegistryCest\n{\n    public function addProductToUserGiftRegistry(AcceptanceTester $I): void\n    {\n        $I->wantToTest('if product gift registry functionality is enabled');\n\n        $I->updateConfigInDatabase('bl_showWishlist', true);\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 € *'\n        ];\n        $userData = $this->getExistingUserData();\n\n        $I->amGoingTo('open details page');\n        $detailsPage = (new ProductNavigation($I))->openProductDetailsPage($productData['id']);\n        $I->seeText($productData['title']);\n\n        $detailsPage = $detailsPage->loginUser($userData['userLoginName'], $userData['userPassword']);\n\n        $detailsPage = $detailsPage->openAccountMenu()\n            ->checkGiftRegistryItemCount(0)\n            ->closeAccountMenu();\n\n        $detailsPage = $detailsPage->addProductToGiftRegistryList()\n            ->openAccountMenu()\n            ->checkGiftRegistryItemCount(1)\n            ->closeAccountMenu();\n\n        $giftRegistryPage = $detailsPage\n            ->openAccountPage()\n            ->seeItemNumberOnGiftRegistryPanel('1')\n            ->logoutUserInAccountPage()\n            ->login($userData['userLoginName'], $userData['userPassword'])\n            ->seeItemNumberOnGiftRegistryPanel('1')\n            ->openGiftRegistryPage()\n            ->seeProductData($productData);\n\n        $I->amGoingTo('open details page');\n        $detailsPage = $giftRegistryPage->openProductDetailsPage(1);\n        $I->seeText($productData['title'], $detailsPage->productTitle);\n\n        $giftRegistryPage = $detailsPage->openUserGiftRegistryPage()\n            ->addProductToBasket(1, 2);\n        $I->seeText('2', $giftRegistryPage->miniBasketMenuElement);\n\n        $giftRegistryPage->removeFromGiftRegistry(1);\n        $I->seeText(Translator::translate('GIFT_REGISTRY_EMPTY'));\n        $giftRegistryPage->openAccountMenu()\n            ->checkGiftRegistryItemCount(0)\n            ->closeAccountMenu();\n    }\n\n    public function makeUserGiftRegistryPublic(AcceptanceTester $I): void\n    {\n        $productNavigation = new ProductNavigation($I);\n        $I->wantToTest('user gift registry functionality setting it as searchable and public');\n\n        //(Use gift registry) is enabled again\n        $I->updateConfigInDatabase('bl_showWishlist', true);\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 € *'\n        ];\n        $userData = $this->getExistingUserData();\n        $adminUserData = $this->getAdminUserData();\n\n        //open details page\n        $detailsPage = $productNavigation->openProductDetailsPage($productData['id']);\n        $I->seeText($productData['title']);\n\n        //add to gift registry and open the page of it\n        $giftRegistryPage = $detailsPage->loginUser($userData['userLoginName'], $userData['userPassword'])\n            ->addProductToGiftRegistryList()\n            ->openUserGiftRegistryPage();\n\n        //making gift registry searchable and logout\n        $giftRegistryPage = $giftRegistryPage->makeListSearchable()\n            ->logoutUser();\n        //login with different user and search for this list\n        $giftRegistryPage = $giftRegistryPage->loginUser(\n            $adminUserData['userLoginName'],\n            $adminUserData['userPassword']\n        )->searchForGiftRegistry($userData['userLoginName']);\n        $I->seeText(Translator::translate('GIFT_REGISTRY_SEARCH_RESULTS'));\n        $I->seeText(\n            Translator::translate('GIFT_REGISTRY_OF') . ' ' . $userData['userName'] . ' ' . $userData['userLastName']\n        );\n        $giftRegListPage = $giftRegistryPage->openFoundGiftRegistryList();\n        $I->seeText(\n            Translator::translate('GIFT_REGISTRY_OF') . ' ' . $userData['userName'] . ' ' . $userData['userLastName'],\n            $giftRegListPage->headerTitle\n        );\n        $I->seeText(\n            sprintf(Translator::translate('WISHLIST_PRODUCTS'), $userData['userName'] . ' ' . $userData['userLastName'])\n        );\n        $giftRegListPage->seeProductData($productData, 1);\n\n        //making gift registry not searchable\n        $giftRegistryPage = $giftRegListPage->openUserGiftRegistryPage()\n            ->logoutUser()\n            ->loginUser($userData['userLoginName'], $userData['userPassword']);\n        $I->seeText(Translator::translate('MESSAGE_MAKE_GIFT_REGISTRY_PUBLISH'));\n        $giftRegistryPage = $giftRegistryPage->makeListNotSearchable();\n\n        $giftRegistryPage = $giftRegistryPage->logoutUser()\n            ->loginUser($adminUserData['userLoginName'], $adminUserData['userPassword'])\n            ->searchForGiftRegistry($userData['userLoginName']);\n        $I->seeText(Translator::translate('MESSAGE_SORRY_NO_GIFT_REGISTRY'));\n\n        //send notification about gift registry\n        $giftRegistryPage = $giftRegistryPage->logoutUser()\n            ->loginUser($userData['userLoginName'], $userData['userPassword'])\n            ->sendGiftRegistryEmail(\n                'example@oxid-esales.dev',\n                'recipient',\n                'Hi, I created a Gift Registry at OXID.'\n            );\n        $I->seeText(\n            sprintf(\n                Translator::translate('GIFT_REGISTRY_SENT_SUCCESSFULLY'),\n                'example@oxid-esales.dev'\n            )\n        );\n\n        $giftRegistryPage->removeFromGiftRegistry(1);\n        $I->seeText(Translator::translate('GIFT_REGISTRY_EMPTY'));\n    }\n\n    public function disableUserGiftRegistry(AcceptanceTester $I): void\n    {\n        $productNavigation = new ProductNavigation($I);\n        $start = new Start($I);\n        $I->wantToTest('disabled user gift registry via performance options');\n\n        //(Use gift registry) is disabled\n        $I->updateConfigInDatabase('bl_showWishlist', false, \"bool\");\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 € *'\n        ];\n\n        $userData = $this->getExistingUserData();\n\n        $start->loginOnStartPage($userData['userLoginName'], $userData['userPassword']);\n\n        //open details page\n        $detailsPage = $productNavigation->openProductDetailsPage($productData['id']);\n        $I->seeText($productData['title']);\n\n        $I->dontSeeElement($detailsPage->addToGiftRegistryLink);\n        $detailsPage->openAccountMenu();\n        $I->dontSee(Translator::translate('MY_GIFT_REGISTRY'));\n        $detailsPage->closeAccountMenu();\n\n        $accountPage = $detailsPage->openAccountPage();\n        $accountPage->dontSeeGiftRegistryLink();\n\n        //(Use gift registry) is enabled again\n        $I->updateConfigInDatabase('bl_showWishlist', true, \"bool\");\n    }\n\n    private function getExistingUserData()\n    {\n        return Fixtures::get('existingUser');\n    }\n\n    private function getAdminUserData()\n    {\n        return Fixtures::get('adminUser');\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/LanguageCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\nfinal class LanguageCest\n{\n    public function checkLanguageSwitch(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Check if Language switch works');\n\n        $startPage = $I->openShop();\n        $I->seeText('Test category 0 [EN] šÄßüл');\n        $startPage->switchLanguage('Deutsch');\n        $I->seeText('Test category 0 [DE] šÄßüл');\n\n        $categoryPage = $startPage->openCategoryPage('Test category 0 [DE] šÄßüл');\n        $I->seeText('Test category 0 [DE] šÄßüл');\n        $startPage->switchLanguage('English');\n        $I->seeText('Test category 0 [EN] šÄßüл');\n\n        $categoryPage->openProductDetailsPage(1);\n        $I->seeText('Test product 0 [EN] šÄßüл');\n        $startPage->switchLanguage('Deutsch');\n        $I->seeText('[DE 4] Test product 0 šÄßüл');\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/ManufacturerCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('manufacturer')]\nfinal class ManufacturerCest\n{\n    public function checkManufacturerList(AcceptanceTester $I): void\n    {\n        $I->wantToTest('manufacturer list');\n        $I->updateConfigInDatabase('bl_showManufacturer', true);\n        $I->updateConfigInDatabase('bl_perfLoadManufacturerTree', true);\n\n        $homePage = $I->openShop();\n        $homePage->openManufacturerListPage()\n            ->seeManufacturerData(\n                [\n                    'title' => 'Manufacturer [EN] šÄßüл',\n                    'count' => '3'\n                ],\n                1\n            )\n            ->openManufacturerPage(1)\n            ->seePageInformation([\n                'title' => 'Manufacturer [EN] šÄßüл',\n                'description' => 'Manufacturer description [EN] šÄßüл'\n            ]);\n    }\n\n    #[Group('product_list')]\n    public function checkAndNavigateThroughManufacturerProductList(AcceptanceTester $I): void\n    {\n        $I->wantToTest('manufacturer functionality and product list navigation');\n        $I->updateConfigInDatabase('bl_showManufacturer', true);\n        $I->updateConfigInDatabase('bl_perfLoadManufacturerTree', true);\n        $I->updateConfigInDatabase('aNrofCatArticles', serialize([10, 50, 100, 2, 1]), 'arr');\n        $I->updateConfigInDatabase('aNrofCatArticlesInGrid', serialize([10, 50, 100, 2, 1]), 'arr');\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 €'\n        ];\n\n        $productData2 = [\n            'id' => '1001',\n            'title' => 'Test product 1 [EN] šÄßüл',\n            'description' => 'Test product 1 short desc [EN] šÄßüл',\n            'price' => '100,00 €'\n        ];\n\n        $homePage = $I->openShop();\n        $productList = $homePage->openManufacturerFromStarPage('Manufacturer [EN] šÄßüл')\n        ->seePageInformation([\n            'title' => 'Manufacturer [EN] šÄßüл',\n            'description' => 'Manufacturer description [EN] šÄßüл'\n        ]);\n\n        $productList->selectSorting('oxtitle', 'asc')\n            ->seeProductData($productData, 1)\n            ->seeProductData($productData2, 2)\n            ->selectSorting('oxprice', 'desc')\n            ->selectProductsPerPage('2')\n            ->seeProductData($productData2, 1)\n            ->openNextListPage()\n            ->seeProductData($productData, 1)\n            ->openPreviousListPage()\n            ->seeProductData($productData2, 1);\n\n        //Dont show manufacturers at all\n        $I->updateConfigInDatabase('bl_perfLoadManufacturerTree', false);\n        $I->updateConfigInDatabase('bl_showManufacturer', false);\n        $I->clearShopCache();\n        $I->openShop();\n        $I->dontSee(Translator::translate('OUR_BRANDS'));\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/NewsletterCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\Codeception\\Page\\Info\\NewsletterSubscription;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('newsletter')]\nfinal class NewsletterCest\n{\n    public function checkEmailValueAfterOpeningNewsletterPage(AcceptanceTester $I): void\n    {\n        $I->wantToTest('if the email value in the newsletter page is correct after opening');\n\n        $email = 'example01@oxid-esales.dev';\n        $newsletterPage = $this->openNewsletterPage($I, $email);\n\n        $I->seeInField($newsletterPage->userEmail, $email);\n    }\n\n    #[Group('subscribe_without_user_name')]\n    public function subscribeWithoutUsername(AcceptanceTester $I): void\n    {\n        $I->wantToTest('subscribe with username missing');\n\n        $this\n            ->openNewsletterPage($I)\n            ->enterUserData()\n            ->subscribe();\n\n        $I->see(Translator::translate('ERROR_MESSAGE_INPUT_NOTALLFIELDS'));\n    }\n\n    public function subscribeWithIncorrectUsername(AcceptanceTester $I): void\n    {\n        $I->wantToTest('invalid email used as a username');\n\n        $newsletterPage = $this->openNewsletterPage($I);\n        $newsletterPage->enterUserData('Test', 'AAA', 'BBB')->subscribe();\n\n        $I->seeText(Translator::translate('DD_FORM_VALIDATION_VALIDEMAIL'));\n    }\n\n    public function subscribeForNewsletter(AcceptanceTester $I): void\n    {\n        $I->wantToTest('subscribe for newsletter');\n\n        $email = 'example01@oxid-esales.dev';\n        $newsletterPage = $this->openNewsletterPage($I, $email);\n        $newsletterPage->enterUserData($email)->subscribe();\n\n        $I->seeText(Translator::translate('MESSAGE_THANKYOU_FOR_SUBSCRIBING_NEWSLETTERS'));\n        $I->seeInDatabase('oxnewssubscribed', ['OXEMAIL' => $email]);\n    }\n\n    public function unsubscribeFromNewsletterWithWrongEmail(AcceptanceTester $I): void\n    {\n        $I->wantToTest('trying to unsubscribe from newsletter when no previous subscription exists');\n\n        $email = 'fake@email.com';\n        $newsletterPage = $this->openNewsletterPage($I, $email);\n        $newsletterPage->enterUserData($email)->unsubscribe();\n\n        $I->seeText(Translator::translate('NEWSLETTER_EMAIL_NOT_EXIST'));\n    }\n\n    public function unsubscribeFromNewsletter(AcceptanceTester $I): void\n    {\n        $I->wantToTest('unsubscribe from newsletter');\n\n        $email = 'example01@oxid-esales.dev';\n        $newsletterPage = $this->openNewsletterPage($I, $email);\n        $newsletterPage->enterUserData($email)->subscribe();\n\n        $newsletterPage = $this->openNewsletterPage($I, $email);\n        $newsletterPage->enterUserData($email)->unsubscribe();\n\n        $I->seeText(Translator::translate('MESSAGE_NEWSLETTER_SUBSCRIPTION_CANCELED'));\n        $I->seeInDatabase('oxnewssubscribed', ['OXEMAIL' => $email, 'OXUNSUBSCRIBED !=' => '0000-00-00 00:00:00']);\n    }\n\n\n    public function subscribeForNewsletterDoubleOptInOff(AcceptanceTester $I): void\n    {\n        $I->wantToTest('subscribe for newsletter with double-opt-in off');\n\n        $I->updateConfigInDatabase('blOrderOptInEmail', false, 'bool');\n\n        $email = 'example01@oxid-esales.dev';\n        $this\n            ->openNewsletterPage($I, $email)\n            ->enterUserData($email)\n            ->subscribe();\n\n        $I->seeText(Translator::translate('MESSAGE_NEWSLETTER_SUBSCRIPTION_ACTIVATED'));\n        $I->seeInDatabase('oxnewssubscribed', ['OXEMAIL' => $email]);\n    }\n\n    private function openNewsletterPage(AcceptanceTester $I, string $email = ''): NewsletterSubscription\n    {\n        return $I\n            ->openShop()\n            ->subscribeForNewsletter($email);\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/PrivateSalesBasketCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\nfinal class PrivateSalesBasketCest\n{\n    public function testIfBasketExcludeEnabledBlocksRootCatChange(AcceptanceTester $I): void\n    {\n        $I->wantToTest('if blBasketExcludeEnabled blocks rootCatChange and continue shopping clears basket.');\n\n        $I->updateConfigInDatabase('blBasketExcludeEnabled', 'true', 'bool');\n\n        $I->updateInDatabase('oxcategories', ['OXACTIVE' => 1], ['OXID' => 'testcategory2']);\n        $I->haveInDatabase(\n            'oxobject2category',\n            [\n                'OXID' => 'testobject2category1002',\n                'OXOBJECTID' => '1002',\n                'OXCATNID' => 'testcategory2',\n                'OXPOS' => 0,\n            ]\n        );\n\n        $I->clearShopCache();\n\n        $homePage = $I->openShop();\n\n        $basketPage = $homePage->openCategoryPage('Test category 0 [EN] šÄßüл')\n            ->openProductDetailsPage(1)\n            ->addProductToBasket(1)\n            ->openBasket();\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'amount' => 1,\n            'totalPrice' => '50,00 €'\n        ];\n\n        $basketPage->seeBasketContains([$productData], '50,00 €');\n\n        $homePage->openCategoryPage('Test category 0 [EN] šÄßüл');\n        $I->dontSee(Translator::translate('ROOT_CATEGORY_CHANGED'));\n\n        $homePage->openCategoryPage('Test category 2 [EN] šÄßüл')\n            ->confirmMainCategoryChanged()\n            ->checkBasketEmpty();\n    }\n\n    public function checkIfBasketExcludeEnabledAlsoClearsByEmptyBasket(AcceptanceTester $I): void\n    {\n        $I->wantToTest('if blBasketExcludeEnabled rootCatChange is no longer blocked by an empty basket.');\n\n        $I->updateConfigInDatabase('blBasketExcludeEnabled', 'true', 'bool');\n\n        $I->updateInDatabase('oxcategories', ['OXACTIVE' => 1], ['OXID' => 'testcategory2']);\n        $I->haveInDatabase(\n            'oxobject2category',\n            [\n                'OXID' => 'testobject2category1002',\n                'OXOBJECTID' => '1002',\n                'OXCATNID' => 'testcategory2',\n                'OXPOS' => 0,\n            ]\n        );\n\n        $I->clearShopCache();\n\n        $homePage = $I->openShop();\n\n        $homePage->openCategoryPage('Test category 0 [EN] šÄßüл')\n            ->openProductDetailsPage(1)\n            ->addProductToBasket()\n            ->openBasket();\n        $homePage->openCategoryPage('Test category 2 [EN] šÄßüл')\n            ->openBasketIfMainCategoryChanged()\n            ->updateProductAmount(0);\n\n        $homePage->openCategoryPage('Test category 2 [EN] šÄßüл');\n        $I->dontSee(Translator::translate('ROOT_CATEGORY_CHANGED'));\n    }\n\n    #[Group('private_shopping_basket_expiration')]\n    public function testPrivateShoppingBasketExpiration(AcceptanceTester $I): void\n    {\n        $I->wantToTest('private basket reservation expiration');\n\n        $basketExpirationTimeout = 5;\n        $I->updateInDatabase('oxarticles', ['oxstock' => '2', 'oxstockflag' => '2'], ['oxid' => '1000']);\n        $I->updateConfigInDatabase('blPsBasketReservationEnabled', 'true', 'bool');\n        $I->updateConfigInDatabase('iPsBasketReservationTimeout', (string)$basketExpirationTimeout, 'str');\n\n        $I->clearShopCache();\n        $homePage = $I->openShop();\n\n        $homePage->openCategoryPage('Test category 0 [EN] šÄßüл')\n            ->openProductDetailsPage(1)\n            ->addProductToBasket(2);\n\n        $homePage->seeCountdownWithinBasket();\n\n        $I\n            ->openShop()\n            ->searchFor('1000');\n        $I->seeText(Translator::translate('NO_ITEMS_FOUND'));\n\n        $I\n            ->amGoingTo('wait for the basket to expire')\n            ->wait($basketExpirationTimeout + 0.1);\n        $I\n            ->openShop()\n            ->checkBasketEmpty()\n            ->searchFor('1000');\n\n        $I->dontSee(Translator::translate('NO_ITEMS_FOUND'));\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/ProductCompareCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse Codeception\\Util\\Fixtures;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\Codeception\\Step\\ProductNavigation;\nuse OxidEsales\\Codeception\\Step\\Start;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('myAccount')]\nfinal class ProductCompareCest\n{\n    public function enableProductCompare(AcceptanceTester $I): void\n    {\n        $productNavigation = new ProductNavigation($I);\n        $I->wantToTest('if product compare functionality is enabled');\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 € *'\n        ];\n\n        $userData = $this->getExistingUserData();\n\n        //open details page\n        $detailsPage = $productNavigation->openProductDetailsPage($productData['id']);\n        $I->seeText($productData['title']);\n\n        $detailsPage->loginUser($userData['userLoginName'], $userData['userPassword'])\n            ->checkCompareListItemCount(0)\n            ->addToCompareList()\n            ->checkCompareListItemCount(1);\n\n        $userAccountPage = $detailsPage->openAccountPage();\n        $I->seeText(Translator::translate('MY_PRODUCT_COMPARISON'));\n        $I->seeText(Translator::translate('PRODUCT') . ' 1');\n\n        $userAccountPage->logoutUserInAccountPage()\n            ->login($userData['userLoginName'], $userData['userPassword']);\n        $I->seeText(Translator::translate('MY_PRODUCT_COMPARISON'));\n        $I->seeText(Translator::translate('PRODUCT') . ' 1');\n\n        //open details page\n        $detailsPage = $productNavigation->openProductDetailsPage($productData['id']);\n        $I->seeText($productData['title']);\n\n        $detailsPage->removeFromCompareList()\n            ->checkCompareListItemCount(0);\n    }\n\n    public function addProductToUserCompareList(AcceptanceTester $I): void\n    {\n        $productNavigation = new ProductNavigation($I);\n        $start = new Start($I);\n        $I->wantToTest('user product compare list functionality');\n\n        $productData1 = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 € *'\n        ];\n\n        $productData2 = [\n            'id' => '1001',\n            'title' => 'Test product 1 [EN] šÄßüл',\n            'description' => 'Test product 1 short desc [EN] šÄßüл',\n            'price' => '100,00 € *'\n        ];\n\n        $productData3 = [\n            'id' => '10014',\n            'title' => '14 EN product šÄßüл',\n            'description' => '13 EN description šÄßüл',\n            'price' => 'from 15,00 €'\n        ];\n\n        $userData = $this->getExistingUserData();\n\n        $start->loginOnStartPage($userData['userLoginName'], $userData['userPassword']);\n\n        //open details page\n        $detailsPage = $productNavigation->openProductDetailsPage($productData1['id']);\n        $I->seeText($productData1['title']);\n        //add to compare list\n        $detailsPage->addToCompareList()\n            ->checkCompareListItemCount(1);\n\n        //open details page\n        $detailsPage = $productNavigation->openProductDetailsPage($productData2['id']);\n        $I->seeText($productData2['title']);\n        //add to compare list\n        $detailsPage->addToCompareList()\n            ->checkCompareListItemCount(2);\n\n        //open details page\n        $detailsPage = $productNavigation->openProductDetailsPage($productData3['id']);\n        $I->seeText($productData3['title']);\n        //add to compare list\n        $detailsPage->addToCompareList()\n            ->checkCompareListItemCount(3);\n\n        //open compare list page\n        $comparePage = $detailsPage->openProductComparePage();\n        $comparePage->seeProductData($productData1, 1);\n        $comparePage->seeProductData($productData2, 2);\n        $comparePage->seeProductData($productData3, 3);\n\n        //open product details page\n        $detailsPage = $comparePage->openProductDetailsPage(1);\n        $I->seeText($productData1['title'], $detailsPage->productTitle);\n        $comparePage = $detailsPage->openProductComparePage();\n        $detailsPage = $comparePage->openProductDetailsPage(2);\n        $I->seeText($productData2['title'], $detailsPage->productTitle);\n        $comparePage = $detailsPage->openProductComparePage();\n\n        $comparePage->seeProductAttributeName('Test attribute 1 [EN] šÄßüл:', 1);\n        $comparePage->seeProductAttributeValue('attr value 1 [EN] šÄßüл', 1);\n        $comparePage->seeProductAttributeValue('attr value 11 [EN] šÄßüл', 1);\n        $comparePage->seeProductAttributeName('Test attribute 3 [EN] šÄßüл:', 2);\n        $comparePage->seeProductAttributeValue('attr value 3 [EN] šÄßüл', 2);\n        $comparePage->seeProductAttributeValue('attr value 3 [EN] šÄßüл', 2);\n        $comparePage->seeProductAttributeName('Test attribute 2 [EN] šÄßüл:', 3);\n        $comparePage->seeProductAttributeValue('attr value 2 [EN] šÄßüл', 3);\n        $comparePage->seeProductAttributeValue('attr value 12 [EN] šÄßüл', 3);\n\n        $comparePage->moveItemToRight($productData1['id']);\n        $comparePage->seeProductData($productData1, 2);\n        $comparePage->seeProductData($productData2, 1);\n\n        $comparePage->moveItemToLeft($productData1['id']);\n        $comparePage->seeProductData($productData1, 1);\n        $comparePage->seeProductData($productData2, 2);\n\n        $comparePage->removeProductFromList($productData1['id']);\n        $comparePage->removeProductFromList($productData2['id']);\n        $comparePage->removeProductFromList($productData3['id']);\n        $I->seeText(Translator::translate('MESSAGE_SELECT_AT_LEAST_ONE_PRODUCT'));\n    }\n\n    public function disableProductCompare(AcceptanceTester $I): void\n    {\n        $productNavigation = new ProductNavigation($I);\n        $start = new Start($I);\n        $I->wantToTest('if product compare functionality is correctly disabled');\n\n        //(Use product compare) is disabled\n        $I->updateConfigInDatabase('bl_showCompareList', false, \"bool\");\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 € *'\n        ];\n\n        $userData = $this->getExistingUserData();\n\n        $start->loginOnStartPage($userData['userLoginName'], $userData['userPassword']);\n\n        //open details page\n        $detailsPage = $productNavigation->openProductDetailsPage($productData['id']);\n        $I->seeText($productData['title']);\n\n        $I->dontSeeElement($detailsPage->addToCompareListLink);\n        $detailsPage->openAccountMenu();\n        $I->dontSee(Translator::translate('MY_PRODUCT_COMPARISON'));\n        $detailsPage->closeAccountMenu();\n\n        $accountPage = $detailsPage->openAccountPage();\n        $I->dontSee(Translator::translate('MY_PRODUCT_COMPARISON'), $accountPage->dashboardCompareListPanelHeader);\n\n        $I->cleanUp();\n        //(Use product compare) is enabled\n        $I->updateConfigInDatabase('bl_showCompareList', true, \"bool\");\n    }\n\n    public function _failed(AcceptanceTester $I): void\n    {\n        $I->cleanUp();\n        $I->clearShopCache();\n    }\n\n    private function getExistingUserData()\n    {\n        return Fixtures::get('existingUser');\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/ProductDetailsPageCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse Codeception\\Util\\Fixtures;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\Codeception\\Step\\ProductNavigation;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('product')]\nfinal class ProductDetailsPageCest\n{\n    private string $productId = '1000';\n    private string $productVariantId = '1001432';\n\n    #[Group('main', 'product', 'productVariants')]\n    public function selectMultidimensionalVariantsInDetailsPage(AcceptanceTester $I): void\n    {\n        $productNavigation = new ProductNavigation($I);\n        $I->wantToTest('multidimensional variants functionality in details page');\n\n        $I->updateConfigInDatabase('blUseMultidimensionVariants', true, 'bool');\n        $I->updateConfigInDatabase('bl_showPriceAlarm', true, 'bool');\n\n        $data = [\n            'OXID' => '1001411',\n            'OXLONGDESC' => 'Test description',\n            'OXLONGDESC_1' => 'Test description',\n            'OXLONGDESC_2' => '',\n            'OXLONGDESC_3' => '',\n        ];\n        $I->haveInDatabase('oxartextends', $data);\n\n        $data = [\n            'OXID' => 'testattributes1',\n            'OXOBJECTID' => '1001411',\n            'OXATTRID' => 'testattribute1',\n            'OXVALUE' => 'attr value 1 [DE]',\n            'OXVALUE_1' => 'attr value 1 [EN]',\n        ];\n        $I->haveInDatabase('oxobject2attribute', $data);\n\n        $productData = [\n            'id' => '10014',\n            'title' => '14 EN product šÄßüл',\n            'description' => '13 EN description šÄßüл',\n            'price' => 'from 15,00 €'\n        ];\n\n        //open details page\n        $detailsPage = $productNavigation->openProductDetailsPage($productData['id']);\n\n        //assert product\n        $detailsPage->seeProductData($productData)->checkIfProductIsNotBuyable();\n\n        //select a variant of the product\n        $detailsPage = $detailsPage->selectVariant(1, 'S')\n            ->checkIfProductIsNotBuyable()\n            ->selectVariant(2, 'white');\n\n        //assert product\n        $productData3 = [\n            'id' => '10014-1-3',\n            'title' => '14 EN product šÄßüл S | white',\n            'description' => '',\n            'price' => '15,00 €'\n        ];\n        $detailsPage->seeProductData($productData3)\n            ->checkIfProductIsBuyable();\n\n        //open details page\n        $detailsPage = $productNavigation->openProductDetailsPage($productData['id']);\n\n        //assert product\n        $detailsPage->seeProductData($productData);\n\n        //select a variant of the product\n        $detailsPage->selectVariant(1, 'S')\n            ->checkIfProductIsNotBuyable()\n            ->selectVariant(2, 'black')\n            ->checkIfProductIsNotBuyable()\n            ->selectVariant(3, 'lether');\n\n        //assert product\n        $productData2 = [\n            'id' => '10014-1-1',\n            'title' => '14 EN product šÄßüл S | black | lether',\n            'description' => '',\n            'price' => '25,00 €'\n        ];\n        $detailsPage->seeProductData($productData2)\n            ->checkIfProductIsBuyable();\n\n        $detailsPage = $detailsPage->openAttributes();\n\n        $I->seeText('attr value 1 [EN]');\n\n        $detailsPage = $detailsPage->openDescription();\n\n        $I->seeText('Test description');\n\n        $detailsPage = $detailsPage->addProductToBasket(2);\n\n        //assert product in basket\n        $basketItem = [\n            'title' => '14 EN product šÄßüл, S | black | lether',\n            'price' => '50,00 €',\n            'amount' => 2\n        ];\n        $detailsPage->seeMiniBasketContains([$basketItem], '50,00 €', '2');\n    }\n\n    #[Group('product', 'search')]\n    public function navigateInDetailsPage(AcceptanceTester $I): void\n    {\n        $I->wantToTest('product navigation in details page');\n\n        $productData = [\n            'id' => '1001',\n            'title' => 'Test product 1 [EN] šÄßüл',\n            'description' => 'Test product 1 short desc [EN] šÄßüл',\n            'price' => '100,00 €'\n        ];\n\n        $I->updateConfigInDatabase('aSortCols', 'a:2:{i:0;s:7:\"oxtitle\";i:1;s:13:\"oxvarminprice\";}', 'arr');\n        $I->updateConfigInDatabase('sProductListNavigation', true);\n\n        $searchListPage = $I->openShop()\n            ->searchFor('100')\n            ->selectSorting('oxtitle', 'asc')\n            ->seeProductData($productData, 3);\n        $detailsPage = $searchListPage->openProductDetailsPage(2);\n        $breadCrumb = \\sprintf(Translator::translate('SEARCH_RESULT'), '100');\n        $detailsPage->seeOnBreadCrumb($breadCrumb);\n        $navigationText = Translator::translate('PRODUCT') . ' 2 ' . Translator::translate('OF') . ' 4';\n        $I->seeText($navigationText);\n        $detailsPage = $detailsPage->openNextProduct();\n        $navigationText = Translator::translate('PRODUCT') . ' 3 ' . Translator::translate('OF') . ' 4';\n        $I->seeText($navigationText);\n        $detailsPage = $detailsPage->openPreviousProduct();\n        $navigationText = Translator::translate('PRODUCT') . ' 2 ' . Translator::translate('OF') . ' 4';\n        $I->seeText($navigationText);\n        $detailsPage->openProductSearchList()\n            ->seeProductData($productData, 3);\n        $breadCrumb = Translator::translate('SEARCH');\n        $detailsPage->seeOnBreadCrumb($breadCrumb);\n    }\n\n    #[Group('product')]\n    public function detailsPageInformation(AcceptanceTester $I): void\n    {\n        $I->wantToTest('product information in details page');\n        $I->updateConfigInDatabase('blStockOffDefaultMessage', true, 'bool');\n        $I->updateConfigInDatabase('bl_perfLoadSelectLists', true, 'bool');\n\n        $productNavigation = new ProductNavigation($I);\n        $productData = [\n            'id' => '1001',\n            'title' => 'Test product 1 [EN] šÄßüл',\n            'description' => 'Test product 1 short desc [EN] šÄßüл',\n            'price' => '100,00 €'\n        ];\n\n        $data = [\n            'OXID' => 'testattributes1',\n            'OXOBJECTID' => '1001',\n            'OXSELNID' => 'testsellist',\n            'OXSORT' => 0,\n        ];\n        $I->haveInDatabase('oxobject2selectlist', $data);\n\n        //open details page\n        $detailsPage = $productNavigation->openProductDetailsPage($productData['id'])\n            ->seeProductData($productData)\n            ->seeProductOldPrice('150,00 €');\n        $I->seeText(Translator::translate('MESSAGE_NOT_ON_STOCK'));\n        $I->seeText(Translator::translate('AVAILABLE_ON') . ' 2030-01-01');\n        $detailsPage = $detailsPage->selectSelectionListItem('selvar1 [EN] šÄßüл')\n            ->selectSelectionListItem('selvar2 [EN] šÄßüл')\n            ->openDescription();\n        $I->seeText('Test product 1 long description [EN] šÄßüл');\n        $detailsPage->openAttributes()\n            ->seeAttributeName('Test attribute 1 [EN] šÄßüл', 1)\n            ->seeAttributeValue('attr value 11 [EN] šÄßüл', 1)\n            ->seeAttributeName('Test attribute 3 [EN] šÄßüл', 2)\n            ->seeAttributeValue('attr value 3 [EN] šÄßüл', 2)\n            ->seeAttributeName('Test attribute 2 [EN] šÄßüл', 3)\n            ->seeAttributeValue('attr value 12 [EN] šÄßüл', 3);\n    }\n\n    #[Group('product', 'productVariants')]\n    public function selectProductVariant(AcceptanceTester $I): void\n    {\n        $productNavigation = new ProductNavigation($I);\n        $I->wantToTest('product simple variant selection and order in details page');\n\n        $productData = [\n            'id' => '1002',\n            'title' => 'Test product 2 [EN] šÄßüл',\n            'description' => 'Test product 2 short desc [EN] šÄßüл',\n            'price' => 'from 55,00 €'\n        ];\n\n        $variantData1 = [\n            'id' => '1002-1',\n            'title' => 'Test product 2 [EN] šÄßüл var1 [EN] šÄßüл',\n            'variantName' => 'var1 [EN] šÄßüл',\n            'description' => '',\n            'price' => '55,00 €'\n        ];\n\n        //open details page\n        $detailsPage = $productNavigation->openProductDetailsPage($productData['id']);\n        $I->seeText($productData['title']);\n        $detailsPage->seeProductData($productData);\n\n        // select variant\n        $detailsPage = $detailsPage->selectVariant(1, $variantData1['variantName'])\n            ->seeProductData($variantData1);\n\n        $basketItemToCheck1 = [\n            'title' => 'Test product 2 [EN] šÄßüл, var1 [EN] šÄßüл',\n            'price' => '165,00 €',\n            'amount' => 3\n        ];\n\n        $detailsPage = $detailsPage->addProductToBasket(3)\n            ->seeMiniBasketContains([$basketItemToCheck1], '165,00 €', '3')\n            ->closeMiniBasket();\n\n        // select second variant\n        $variantData2 = [\n            'id' => '1002-2',\n            'title' => 'Test product 2 [EN] šÄßüл var2 [EN] šÄßüл',\n            'variantName' => 'var2 [EN] šÄßüл',\n            'description' => '',\n            'price' => '67,00 €'\n        ];\n\n        $detailsPage = $detailsPage->selectVariant(1, $variantData2['variantName'])\n            ->seeProductData($variantData2);\n\n        $basketItemToCheck2 = [\n            'title' => 'Test product 2 [EN] šÄßüл, var2 [EN] šÄßüл',\n            'price' => '201,00 €',\n            'amount' => 3\n        ];\n        $detailsPage->addProductToBasket(3)\n            ->seeMiniBasketContains([$basketItemToCheck1, $basketItemToCheck2], '366,00 €', '6');\n    }\n\n    #[Group('product', 'productVariants')]\n    public function selectProductVariantsWithSelectionLists(AcceptanceTester $I): void\n    {\n        $I->wantToTest('add to cart with product variant/selection list combinations');\n\n        $productId = '1002';\n        $variant1Name = 'var1 [EN] šÄßüл';\n        $variant2Name = 'var2 [EN] šÄßüл';\n        $selectionListsTitle = 'test selection list [EN] šÄßüл';\n        $selectionList2Value = 'selvar2 [EN] šÄßüл';\n        $selectionList3Value = 'selvar3 [EN] šÄßüл';\n\n        $data = [\n            'OXID' => 'testattributes1',\n            'OXOBJECTID' => $productId,\n            'OXSELNID' => 'testsellist',\n            'OXSORT' => 0,\n        ];\n        $I->haveInDatabase('oxobject2selectlist', $data);\n\n        $productNavigation = new ProductNavigation($I);\n        $detailsPage = $productNavigation->openProductDetailsPage($productId);\n\n        $detailsPage = $detailsPage->selectVariant(1, $variant1Name);\n        $detailsPage->selectSelectionListItem($selectionList2Value);\n        $detailsPage->addProductToBasket(1);\n\n        $detailsPage = $detailsPage->selectVariant(1, $variant2Name);\n        $detailsPage->selectSelectionListItem($selectionList3Value);\n        $detailsPage->addProductToBasket(2);\n\n        $basketPage = $detailsPage->openBasket();\n\n        $basketPage->seeBasketContainsSelectionList($selectionListsTitle, $selectionList2Value, 1)\n        ->seeBasketContainsSelectionList($selectionListsTitle, $selectionList3Value, 2);\n    }\n\n    #[Group('product', 'accessories')]\n    public function checkProductAccessories(AcceptanceTester $I): void\n    {\n        $productNavigation = new ProductNavigation($I);\n        $I->wantToTest('Product\\'s accessories');\n\n        $I->updateConfigInDatabase('bl_perfLoadAccessoires', true, 'bool');\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 €'\n        ];\n\n        $accessoryData = [\n            'id' => '1002',\n            'title' => 'Test product 2 [EN] šÄßüл',\n            'description' => 'Test product 2 short desc [EN] šÄßüл',\n            'price' => 'from 55,00 €'\n        ];\n\n        $this->prepareAccessoriesDataForProduct($I, $productData['id'], $accessoryData['id']);\n\n        //open details page\n        $detailsPage = $productNavigation->openProductDetailsPage($productData['id']);\n        $I->seeText($productData['title']);\n        $I->seeText(Translator::translate('ACCESSORIES'));\n        $detailsPage->seeAccessoryData($accessoryData, 1)\n            ->openAccessoryDetailsPage(1)\n            ->seeProductData($accessoryData);\n    }\n\n    #[Group('product', 'similarProduct')]\n    public function checkSimilarProducts(AcceptanceTester $I): void\n    {\n        $productNavigation = new ProductNavigation($I);\n        $I->wantToTest('similar products on details page');\n\n        $I->updateConfigInDatabase('bl_perfLoadSimilar', true, 'bool');\n        $I->updateConfigInDatabase('iNrofSimilarArticles', '5', 'str');\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 €'\n        ];\n\n        $similarProductData = [\n            'id' => '1001',\n            'title' => 'Test product 1 [EN] šÄßüл',\n            'description' => 'Test product 1 short desc [EN] šÄßüл',\n            'price' => '100,00 €'\n        ];\n\n        //open details page\n        $detailsPage = $productNavigation->openProductDetailsPage($productData['id']);\n        $I->seeText($productData['title']);\n\n        $I->seeText(Translator::translate('SIMILAR_PRODUCTS'));\n        $detailsPage->seeSimilarProductData($similarProductData, 1)\n            ->openSimilarProductDetailsPage(1)\n            ->seeProductData($similarProductData)\n            ->seeSimilarProductData($productData, 1);\n    }\n\n    #[Group('product', 'crossSelling')]\n    public function checkProductCrossSelling(AcceptanceTester $I): void\n    {\n        $productNavigation = new ProductNavigation($I);\n        $I->wantToTest('Product\\'s crossselling on details page');\n\n        $I->updateConfigInDatabase('bl_perfLoadCrossselling', true, 'bool');\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 €'\n        ];\n\n        $crossSellingProductData = [\n            'id' => '1002',\n            'title' => 'Test product 2 [EN] šÄßüл',\n            'description' => 'Test product 2 short desc [EN] šÄßüл',\n            'price' => 'from 55,00 €'\n        ];\n\n        $this->prepareCrossSellingDataForProduct($I, $productData['id'], $crossSellingProductData['id']);\n\n        //open details page\n        $detailsPage = $productNavigation->openProductDetailsPage($productData['id']);\n        $I->seeText($productData['title']);\n\n        $I->seeText(Translator::translate('HAVE_YOU_SEEN'));\n        $detailsPage->seeCrossSellingData($crossSellingProductData, 1)\n            ->openCrossSellingDetailsPage(1)\n            ->seeProductData($crossSellingProductData);\n    }\n\n    #[Group('product', 'productPrice')]\n    public function checkProductPriceA(AcceptanceTester $I): void\n    {\n        $I->wantToTest('product price A');\n\n        $I->updateConfigInDatabase('blOverrideZeroABCPrices', '1');\n\n        $userData = $this->getExistingUserData();\n\n        $this->preparePriceGroupDataForUser($I, $userData['userId'], 'oxidpricea');\n\n        $productData1 = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 €'\n        ];\n\n        $productData2 = [\n            'id' => '1001',\n            'title' => 'Test product 1 [EN] šÄßüл',\n            'description' => 'Test product 1 short desc [EN] šÄßüл',\n            'price' => '100,00 €'\n        ];\n\n        //option \"Use normal article price instead of zero A, B, C price\" is ON\n        $productListPage = $I->openShop()\n            ->openCategoryPage('Test category 0 [EN] šÄßüл')\n            ->seeProductData($productData1, 1)\n            ->seeProductData($productData2, 2);\n\n        $productData1 = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '35,00 €'\n        ];\n\n        $productDetailsPage = $productListPage->loginUser($userData['userLoginName'], $userData['userPassword'])\n            ->seeProductData($productData1, 1)\n            ->openProductDetailsPage(1)\n            ->seeProductData($productData1)\n            ->seeProductUnitPrice('17,50 €/kg')\n            ->addProductToBasket(3);\n\n        $basketPage = $productDetailsPage->openCategoryPage('Test category 0 [EN] šÄßüл')\n            ->seeProductData($productData2, 2)\n            ->openProductDetailsPage(2)\n            ->seeProductData($productData2)\n            ->addProductToBasket(1)\n            ->openBasket();\n\n        $productData1 = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'amount' => 3,\n            'totalPrice' => '105,00 €'\n        ];\n\n        $productData2 = [\n            'id' => '1001',\n            'title' => 'Test product 1 [EN] šÄßüл',\n            'amount' => 1,\n            'totalPrice' => '100,00 €'\n        ];\n\n        $basketPage->seeBasketContains([$productData1, $productData2], '205,00 €');\n\n        $I->updateConfigInDatabase('blOverrideZeroABCPrices', '');\n        $I->clearShopCache();\n    }\n\n    #[Group('product', 'productPrice')]\n    public function checkProductPriceC(AcceptanceTester $I): void\n    {\n        $I->wantToTest('product price C and amount price discount added to this price');\n\n        $userData = $this->getExistingUserData();\n\n        $this->preparePriceGroupDataForUser($I, $userData['userId'], 'oxidpricec');\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 €'\n        ];\n        $amountPrices = [\n            'priceCase1' => [\n                'amountFrom' => 4,\n                'amountTo' => 9_999_999,\n                'discount' => 20,\n            ]\n        ];\n        $this->prepareAmountPriceDataForProduct($I, $productData['id'], $amountPrices['priceCase1']);\n\n        $productListPage = $I->openShop()\n            ->openCategoryPage('Test category 0 [EN] šÄßüл')\n            ->seeProductData($productData, 1);\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '55,00 €'\n        ];\n\n        $basketPage = $productListPage->loginUser($userData['userLoginName'], $userData['userPassword'])\n            ->seeProductData($productData, 1)\n            ->openProductDetailsPage(1)\n            ->seeProductData($productData)\n            ->seeProductUnitPrice('27,50 €/kg')\n            ->addProductToBasket(5)\n            ->openBasket();\n\n        //amount price discount added to the C price\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'amount' => 5,\n            'totalPrice' => '220,00 €'\n        ];\n\n        $basketPage->seeBasketContains([$productData], '220,00 €');\n        $I->clearShopCache();\n    }\n\n    #[Group('product', 'productPrice')]\n    public function checkProductPriceB(AcceptanceTester $I): void\n    {\n        $I->wantToTest('product price B');\n\n        $userData = $this->getExistingUserData();\n\n        $this->preparePriceGroupDataForUser($I, $userData['userId'], 'oxidpriceb');\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 €'\n        ];\n\n        $productListPage = $I->openShop()\n            ->openCategoryPage('Test category 0 [EN] šÄßüл')\n            ->seeProductData($productData, 1);\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '45,00 €'\n        ];\n\n        $basketPage = $productListPage->loginUser($userData['userLoginName'], $userData['userPassword'])\n            ->seeProductData($productData, 1)\n            ->openProductDetailsPage(1)\n            ->seeProductData($productData)\n            ->seeProductUnitPrice('22,50 €/kg')\n            ->addProductToBasket(2)\n            ->openBasket();\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'amount' => 2,\n            'totalPrice' => '90,00 €'\n        ];\n\n        $basketPage->seeBasketContains([$productData], '90,00 €');\n        $I->clearShopCache();\n    }\n\n    #[Group('product', 'productPrice', 'ProductAmountPrice')]\n    public function checkProductAmountPrice(AcceptanceTester $I): void\n    {\n        $productNavigation = new ProductNavigation($I);\n        $I->wantToTest('product amount price');\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 €'\n        ];\n        $amountPrices = [\n            'priceCase1' => [\n                'amountFrom' => 2,\n                'amountTo' => 3,\n                'discount' => 10,\n            ],\n            'priceCase2' => [\n                'amountFrom' => 4,\n                'amountTo' => 9_999_999,\n                'discount' => 20,\n            ]\n        ];\n\n        $this->prepareAmountPriceDataForProduct($I, $productData['id'], $amountPrices['priceCase1']);\n        $this->prepareAmountPriceDataForProduct($I, $productData['id'], $amountPrices['priceCase2']);\n\n        $productNavigation->openProductDetailsPage($productData['id'])\n            ->seeProductData($productData)\n            ->seeAmountPrices($amountPrices);\n    }\n\n    #[Group('stock')]\n    public function lowStockProductTests(AcceptanceTester $I): void\n    {\n        $I->wantToTest('product low stock label');\n\n        $I->updateInDatabase(\n            'oxarticles',\n            [\n                'OXSTOCK' => 1\n            ],\n            [\n                'OXID' => $this->productId\n            ]\n        );\n\n        $productListPage = $I->openShop()->searchFor($this->productId);\n        $productListPage->openProductDetailsPage(1);\n\n        $I->seeText(Translator::translate('LOW_STOCK'));\n\n\n        $I->amGoingTo('Test product low stock label with deactivated default option');\n\n        $I->updateConfigInDatabase('blStockLowDefaultMessage', '0', 'bool');\n        $I->updateInDatabase(\n            'oxarticles',\n            [\n                'OXLOWSTOCKACTIVE' => 0\n            ],\n            [\n                'OXID' => $this->productId\n            ]\n        );\n        $I->reloadPage();\n        $product = $this->getProductData($this->productId);\n\n        $I->dontSee($product['OXSTOCKTEXT_1']);\n\n\n        $I->amGoingTo('Test product low stock label with product flag enabled');\n\n        $I->updateConfigInDatabase('blStockLowDefaultMessage', '1', 'bool');\n        $lowStockMessage = 'product has low stock';\n        $I->updateInDatabase(\n            'oxarticles',\n            [\n                'OXREMINDAMOUNT' => 20,\n                'OXLOWSTOCKTEXT_1' => $lowStockMessage,\n                'OXLOWSTOCKACTIVE' => 1\n            ],\n            [\n                'OXID' => $this->productId\n            ]\n        );\n        $I->reloadPage();\n\n        $I->seeText($lowStockMessage);\n    }\n\n    #[Group('stock', 'productVariants')]\n    public function lowStockProductVariantTests(AcceptanceTester $I): void\n    {\n        $I->wantToTest('product variant low stock label');\n\n        $productVariant = $this->getProductData($this->productVariantId);\n\n        $productListPage = $I->openShop()->searchFor($productVariant['OXPARENTID']);\n        $detailsPage = $productListPage->openProductDetailsPage(1);\n\n        $I->dontSee(Translator::translate('LOW_STOCK'));\n\n        $I->amGoingTo('Test product low stock label with deactivated default option');\n\n        $detailsPage->selectVariant(1, $productVariant['OXVARSELECT_1']);\n        $I->updateInDatabase(\n            'oxarticles',\n            [\n                'OXLOWSTOCKACTIVE' => 0\n            ],\n            [\n                'OXID' => $this->productVariantId\n            ]\n        );\n        $I->seeText(Translator::translate('LOW_STOCK'));\n\n\n        $I->amGoingTo('Test product low stock label with product flag enabled');\n\n        $I->updateConfigInDatabase('blStockLowDefaultMessage', '1', 'bool');\n        $lowStockMessage = 'product has low stock';\n        $I->updateInDatabase(\n            'oxarticles',\n            [\n                'OXREMINDAMOUNT' => 20,\n                'OXLOWSTOCKTEXT_1' => $lowStockMessage,\n                'OXLOWSTOCKACTIVE' => 1\n            ],\n            [\n                'OXID' => $this->productVariantId\n            ]\n        );\n        $I->reloadPage();\n        $detailsPage->selectVariant(1, $productVariant['OXVARSELECT_1']);\n\n        $I->seeText($lowStockMessage);\n    }\n\n    #[Group('product')]\n    public function popupWhenExceedingStockAvailability(AcceptanceTester $I): void\n    {\n        $I->wantToTest('popup is show when exceeding stock availability');\n\n        $productNavigation = new ProductNavigation($I);\n\n        $productId = '1000';\n\n        $I->updateConfigInDatabase('iNewBasketItemMessage', '2', 'str');\n\n        // making product out of stock now\n        $I->updateInDatabase('oxarticles', ['oxstock' => '1', 'oxstockflag' => '2'], ['oxid' => $productId]);\n\n        //open details page\n        $productNavigation\n            ->openProductDetailsPage($productId)\n            ->addProductToBasket(2);\n        $I->seeText(Translator::translate('ERROR_MESSAGE_OUTOFSTOCK_OUTOFSTOCK'));\n\n        //assert product in basket\n        $basketItem = [\n            'id' => $productId,\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'totalPrice' => '50,00 €',\n            'amount' => 1\n        ];\n\n        $basketPage = $I->openShop()->openBasket();\n        $basketPage->seeBasketContains([$basketItem], '50,00 €');\n    }\n\n    private function getProductData(string $productID): array\n    {\n        return Fixtures::get('product-' . $productID);\n    }\n\n    private function getExistingUserData()\n    {\n        return Fixtures::get('existingUser');\n    }\n\n    private function prepareAccessoriesDataForProduct(AcceptanceTester $I, string $productId, string $accessoryProductId): void\n    {\n        $data = [\n            'OXID' => 'testaccessories1',\n            'OXOBJECTID' => $accessoryProductId,\n            'OXARTICLENID' => $productId,\n        ];\n        $I->haveInDatabase('oxaccessoire2article', $data);\n    }\n\n    private function prepareCrossSellingDataForProduct(AcceptanceTester $I, string $productId, string $crossSellingProductId): void\n    {\n        $data = [\n            'OXID' => 'testcrossselling1',\n            'OXOBJECTID' => $crossSellingProductId,\n            'OXARTICLENID' => $productId,\n        ];\n        $I->haveInDatabase('oxobject2article', $data);\n    }\n\n    private function preparePriceGroupDataForUser(AcceptanceTester $I, string $userId, string $priceGroupId): void\n    {\n        $data = [\n            'OXID' => 'obj2group' . $priceGroupId,\n            'OXOBJECTID' => $userId,\n            'OXGROUPSID' => $priceGroupId,\n        ];\n        $I->haveInDatabase('oxobject2group', $data);\n    }\n\n    private function prepareAmountPriceDataForProduct(AcceptanceTester $I, string $productId, array $amountPrice): void\n    {\n        $data = [\n            'OXID' => 'price2article' . $amountPrice['discount'],\n            'OXARTID' => $productId,\n            'OXADDPERC' => $amountPrice['discount'],\n            'OXAMOUNT' => $amountPrice['amountFrom'],\n            'OXAMOUNTTO' => $amountPrice['amountTo'],\n        ];\n        $I->haveInDatabase('oxprice2article', $data);\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/ProductLabelCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('productLabel')]\nfinal class ProductLabelCest\n{\n    public function addingProductLabel(AcceptanceTester $I): void\n    {\n        $I->wantToTest('product customization (product labels AKA `persparam`) functionality');\n        $label1 = uniqid('product-label', true);\n        $label2 = uniqid('product-label', true);\n\n        $I->amGoingTo('enable products labels for 2 products via admin');\n        $productList = $I\n            ->loginAdmin()\n            ->openProducts();\n        $productList\n            ->findByProductNumber('1000')\n            ->openExtendedTab()\n            ->enableProductCustomization();\n        $productList\n            ->findByProductNumber('1001')\n            ->openExtendedTab()\n            ->enableProductCustomization();\n\n        $I->amGoingTo('add these 2 and any other product to the cart');\n        $I\n            ->openShop()\n            ->switchLanguage('English');\n        $shop = $I->loginShopWithExistingUser();\n\n        $I->amGoingTo('add label for product 1 on its details page');\n        $shop\n            ->searchFor('1000')\n            ->openFirstProductInSearchResults()\n            ->addProductLabel($label1)\n            ->addProductToBasket();\n\n        $I->amGoingTo('check the input for product 2 is there but add label later');\n        $shop\n            ->searchFor('1001')\n            ->openFirstProductInSearchResults()\n            ->seeProductLabelInput()\n            ->addProductToBasket();\n\n        $I->amGoingTo('check there is no input for product 3');\n        $shop\n            ->searchFor('1002')\n            ->openFirstProductInSearchResults()\n            ->dontSeeProductLabelInput()\n            ->selectVariant(1, 'var1 [EN] šÄßüл')\n            ->dontSeeProductLabelInput()\n            ->addProductToBasket();\n\n        $I->amGoingTo('check the inputs and add label for product 2 on the shopping cart page');\n        $basket = $shop->openBasket();\n        $basket->seeProductLabel($label1, 1);\n        $basket->seeProductLabel('', 2);\n        $basket->addProductLabel($label2, 2);\n        $basket->seeProductLabel($label2, 2);\n        $basket->dontSeeProductLabelInput(3);\n\n        $I->amGoingTo('click-through the checkout steps');\n        $orderCheckout = $basket\n            ->goToNextStep()\n            ->goToNextStep()\n            ->goToNextStep();\n\n        $I->amGoingTo('check the labels on the order checkout page and submit the order');\n        $orderCheckout\n            ->seeOrderItemLabel($label1, 1)\n            ->seeOrderItemLabel($label2, 2)\n            ->dontSeeOrderItemHasLabel(3)\n            ->submitOrder();\n\n        $I->amGoingTo('make sure that labels are visible in the user order history');\n        $shop\n            ->openUserAccountPage()\n            ->openOrderHistory()\n            ->seeOrderItemsLabel($label1, 1, 1)\n            ->seeOrderItemsLabel($label2, 1, 2)\n            ->dontSeeOrderItemHasLabel(1, 3);\n\n        $I->amGoingTo('make sure that labels are visible in the admin order history');\n\n        $I->amGoingTo('check labels on order overview tab');\n        $orderOverviewPage = $I\n            ->loginAdmin()\n            ->openOrders()\n            ->findByOrderNumber('1')\n            ->seeOrderProductLabel($label1, 1)\n            ->seeOrderProductLabel($label2, 2)\n            ->dontSeeOrderProductHasLabel(3);\n\n        $I->amGoingTo('check labels on order products tab');\n        $orderOverviewPage\n            ->openProductsTab()\n            ->seeOrderProductLabel($label1, 1)\n            ->seeOrderProductLabel($label2, 2)\n            ->dontSeeOrderProductHasLabel(3);\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/ProductPromotionCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\Codeception\\Page\\Home;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\nfinal class ProductPromotionCest\n{\n    #[Group('testFrontendPromotion', 'testFrontendPromotionNew')]\n    public function testFrontendPromotionNew(AcceptanceTester $I): void\n    {\n        $I->wantToTest('the newest products');\n        $I->updateConfigInDatabase('sShowNewestArticles', true, 'bool');\n        $I->clearShopCache();\n        $I->haveInDatabase(\n            'oxactions2article',\n            [\n                'OXID' => '97483faa8165859ed57e26f75fb6449d',\n                'OXSHOPID' => 1,\n                'OXACTIONID' => 'oxnewest',\n                'OXARTID' => '1001',\n                'OXSORT' => 2,\n                'OXTIMESTAMP' => '2023-01-12 11:45:28'\n            ]\n        );\n\n        $I->haveInDatabase(\n            'oxactions2article',\n            [\n                'OXID' => '66683faa8165859ed57e26f75fb6449d',\n                'OXSHOPID' => 1,\n                'OXACTIONID' => 'oxnewest',\n                'OXARTID' => '1000',\n                'OXSORT' => 10,\n                'OXTIMESTAMP' => '2023-01-12 11:45:28'\n            ]\n        );\n\n        $homePage = $I->openShop();\n        $productsWidget = $homePage->getNewestArticles();\n        $productsWidget\n            ->getProduct(1)\n            ->productHasTitle('Test product 1 [EN] šÄßüл')\n            ->addProductToCart();\n\n        $productsWidget\n            ->getProduct(2)\n            ->productHasTitle('Test product 0 [EN] šÄßüл')\n            ->setProductAmount(3)\n            ->addProductToCart();\n\n        $basketItemToCheck1 = [\n            'title' => 'Test product 1 [EN] šÄßüл',\n            'price' => '100,00 €',\n            'amount' => 1\n        ];\n\n        $basketItemToCheck2 = [\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'price' => '50,00 €',\n            'amount' => 3\n        ];\n\n        $this->checkMiniBasket(\n            $I,\n            $homePage,\n            [\n                $basketItemToCheck1,\n                $basketItemToCheck2\n            ],\n            '250,00 €',\n            '4'\n        );\n    }\n\n    #[Group('testFrontendPromotion', 'testFrontendPromotionTop')]\n    public function testFrontendPromotionTop(AcceptanceTester $I): void\n    {\n        $I->wantToTest('the Top5 products');\n        $I->updateConfigInDatabase('sShowTopArticles', true, 'bool');\n        $I->clearShopCache();\n        $I->haveInDatabase(\n            'oxactions2article',\n            [\n                'OXID' => '87483faa8165859ed57e26f75fb6449d',\n                'OXSHOPID' => 1,\n                'OXACTIONID' => 'oxtop5',\n                'OXARTID' => '1002',\n                'OXSORT' => 1,\n                'OXTIMESTAMP' => '2023-01-12 11:45:28'\n            ]\n        );\n        $I->clearShopCache();\n        $homePage = $I->openShop();\n        $homePage->getPromotionTop5()\n            ->getProduct(1)\n            ->productHasTitle('Test product 2 [EN] šÄßüл')\n            ->openProductDetails();\n\n        $productToCheck = [\n            'title' => 'Test product 2 [EN] šÄßüл',\n            'price' => '55,00 €',\n            'amount' => 2\n        ];\n        $this->checkDetails($I, $productToCheck);\n    }\n\n    #[Group('testFrontendPromotion', 'testFrontendPromotionBargainItems')]\n    public function testFrontendPromotionBargainItems(AcceptanceTester $I): void\n    {\n        $I->wantToTest('the bargain items');\n        $I->updateConfigInDatabase('sShowBargainArticles', true, 'bool');\n        $I->clearShopCache();\n        $I->haveInDatabase(\n            'oxactions2article',\n            [\n                'OXID' => '97483faa8165859ed57e26f75fb6449d',\n                'OXSHOPID' => 1,\n                'OXACTIONID' => 'oxbargain',\n                'OXARTID' => '1001',\n                'OXSORT' => 1,\n                'OXTIMESTAMP' => '2023-01-12 11:45:28'\n            ]\n        );\n\n        $homePage = $I->openShop();\n\n        $productToCheck = [\n            'title' => 'Test product 1 [EN] šÄßüл',\n            'price' => '100,00 €',\n            'amount' => 1\n        ];\n\n        $productsWidget = $homePage->getBargainArticleList();\n        $productsWidget->getProduct(1)\n            ->productHasTitle($productToCheck['title'])\n            ->addProductToCart();\n\n        $this->checkMiniBasket(\n            $I,\n            $homePage,\n            [\n                $productToCheck\n            ],\n            '100,00 €',\n            '1'\n        );\n    }\n\n    private function checkMiniBasket(\n        AcceptanceTester $I,\n        Home $homepage,\n        array $productsToCheck,\n        string $price,\n        string $amount\n    ): void {\n        $I->wantToTest('if the basket contains correct products and price');\n        $homepage->seeMiniBasketContains($productsToCheck, $price, $amount);\n        $homepage->closeMiniBasket();\n    }\n\n    private function checkDetails(AcceptanceTester $I, array $productToCheck): void\n    {\n        $I->wantToTest('if the correct product details page is opened');\n        $I->seeText($productToCheck['title']);\n        $I->seeText($productToCheck['price']);\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/ProductStatusTestCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse Codeception\\Util\\Fixtures;\nuse DateTime;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\nuse function sprintf;\n\n#[Group('product')]\nfinal class ProductStatusTestCest\n{\n    private string $productID = '1000';\n    private array $productData;\n\n    public function _before(AcceptanceTester $I): void\n    {\n        $I->updateConfigInDatabase('blUseTimeCheck', true, 'bool');\n        $product = $this->getProductData($this->productID);\n        $this->productData = [\n            'title' => $product['OXTITLE_1'],\n            'description' => $product['OXSHORTDESC_1'],\n            'price' => $product['OXPRICE']\n        ];\n    }\n\n    public function checkProductsTemporaryActiveStatus(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Product active in list');\n\n        $homePage = $I->openShop();\n\n        $I->amGoingTo('Test product temporary active in the list with empty activeto');\n\n        $I->updateInDatabase(\n            'oxarticles',\n            [\n                'OXACTIVE' => false,\n                'OXACTIVEFROM' => (new DateTime())->modify('-1 day')->format('Y-m-d H:i:s'),\n                'OXACTIVETO' => '0000-00-00 00:00:00'\n            ],\n            [\n                'OXID' => $this->productID\n            ]\n        );\n\n        $I->expect('Product is shown');\n        $productList = $homePage->searchFor($this->productID);\n        $productList->seeProductData($this->productData);\n\n\n        $I->amGoingTo('Test product temporary active in the list with empty activefrom');\n\n        $I->updateInDatabase(\n            'oxarticles',\n            [\n                'OXACTIVE' => false,\n                'OXACTIVEFROM' => '0000-00-00 00:00:00',\n                'OXACTIVETO' => (new DateTime())->modify('+1 day')->format('Y-m-d H:i:s')\n            ],\n            [\n                'OXID' => $this->productID\n            ]\n        );\n\n        $I->expect('Product is shown');\n        $productList = $homePage->searchFor($this->productID);\n        $productList->seeProductData($this->productData);\n    }\n\n    public function checkProductsTemporaryNotActiveStatus(AcceptanceTester $I): void\n    {\n        $I->wantToTest('Product not active in list');\n\n        $homePage = $I->openShop();\n\n        $I->amGoingTo('Test product temporary inactive in the list with empty activeto');\n\n        $I->updateInDatabase(\n            'oxarticles',\n            [\n                'OXACTIVE' => false,\n                'OXACTIVEFROM' => (new DateTime())->modify('+1 day')->format('Y-m-d H:i:s'),\n                'OXACTIVETO' => '0000-00-00 00:00:00'\n            ],\n            [\n                'OXID' => $this->productID\n            ]\n        );\n\n        $I->expect('Product is not shown in case 1');\n        $productList = $homePage->searchFor($this->productID);\n        $productList->dontSeeProductData($this->productData);\n\n\n        $I->amGoingTo('Test product temporary inactive in the list with empty activefrom');\n\n        $I->updateInDatabase(\n            'oxarticles',\n            [\n                'OXACTIVE' => false,\n                'OXACTIVEFROM' => '0000-00-00 00:00:00',\n                'OXACTIVETO' => (new DateTime())->modify('-1 day')->format('Y-m-d H:i:s')\n            ],\n            [\n                'OXID' => $this->productID\n            ]\n        );\n\n        $I->expect('Product is not shown in case 2');\n        $productList = $homePage->searchFor($this->productID);\n        $productList->dontSeeProductData($this->productData);\n    }\n\n    private function getProductData(string $productID): array\n    {\n        return Fixtures::get(sprintf('product-%s', $productID));\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/ReviewAndRatingCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse Codeception\\Util\\Fixtures;\nuse OxidEsales\\Codeception\\Page\\Account\\MyReviews;\nuse OxidEsales\\Codeception\\Step\\ProductNavigation;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('myAccount', 'reviewAndRatings')]\nfinal class ReviewAndRatingCest\n{\n    public function addUserReviewAndRatingForProduct(AcceptanceTester $I): void\n    {\n        $productNavigation = new ProductNavigation($I);\n        $I->wantToTest('user account top menu (popup in top of the page)');\n        $I->updateConfigInDatabase('bl_perfLoadReviews', true, 'bool');\n\n        $userData = $this->getExistingUserData();\n        $userReviewText = 'user review [EN] šÄßüл for product 1000';\n        $userRating = 3;\n\n        $detailsPage = $productNavigation->openProductDetailsPage('1000');\n        $detailsPage->loginUserForReview($userData['userLoginName'], $userData['userPassword'])\n            ->addReviewAndRating($userReviewText, $userRating)\n            ->seeUserProductReviewAndRating(1, $userData['userName'], $userReviewText, $userRating);\n    }\n\n    public function addUserReviewAndRatingForProductWithVariants(AcceptanceTester $I): void\n    {\n        $productNavigation = new ProductNavigation($I);\n        $I->wantToTest('if parent reviews are shown correctly for variant product');\n        $I->updateConfigInDatabase('bl_perfLoadReviews', true, 'bool');\n\n        $parentReview = [\n            'text' => 'review for parent product šÄßüл',\n            'rating' => 3,\n            'created' => '2020-12-12 12:12:12',\n        ];\n        $variantReview = [\n            'text' => 'review for var1 šÄßüл',\n            'rating' => 3,\n            'created' => '2020-11-11 11:11:11',\n        ];\n\n        $userData = $this->getExistingUserData();\n        $this->prepareReviewDataForProduct($I, '1002', 'testUser', $parentReview);\n        $this->prepareReviewDataForProduct($I, '1002-1', 'testUser', $variantReview);\n\n        $detailsPage = $productNavigation->openProductDetailsPage('1002');\n        $detailsPage->seeUserProductReviewAndRating(\n            1,\n            $userData['userName'],\n            $parentReview['text'],\n            $parentReview['rating']\n        );\n        $detailsPage->selectVariant(1, 'var1 [EN] šÄßüл');\n        /** reviews with bigger OXCREATE go to the top of the list */\n        $detailsPage->seeUserProductReviewAndRating(\n            1,\n            $userData['userName'],\n            $parentReview['text'],\n            $parentReview['rating']\n        );\n        $detailsPage->seeUserProductReviewAndRating(\n            2,\n            $userData['userName'],\n            $variantReview['text'],\n            $variantReview['rating']\n        );\n    }\n\n    public function manageUserReviewsInAccountMenu(AcceptanceTester $I): void\n    {\n        $I->wantToTest('review-rating management via account page');\n\n        $userData = $this->getExistingUserData();\n        $numberOfReviewsOnNextPage = 1;\n        $numberOfReviewsTotal = $this->getReviewPaginationSize() + $numberOfReviewsOnNextPage;\n        $this->insertReviewsIntoDb($I, $userData['userId'], $numberOfReviewsTotal);\n\n        $I->amGoingTo('test review link is not visible if config is off');\n        $I->updateConfigInDatabase('blAllowUsersToManageTheirReviews', false);\n        $accountPage = $I->openShop()\n            ->loginUser($userData['userLoginName'], $userData['userPassword'])\n            ->openAccountPage();\n        $accountPage->dontSeeMyReviewsLink();\n\n        $I->amGoingTo('test review page can\\'t be accessed via URL');\n        $I->amOnPage(MyReviews::URL);\n        $accountPage->dontSeeMyReviewsPageTitle();\n\n        $I->amGoingTo('test review page after config change');\n        $I->updateConfigInDatabase('blAllowUsersToManageTheirReviews', true);\n        $I->reloadPage();\n\n        $I->amGoingTo('test badge with count in navigation');\n        $accountPage->seeItemNumberOnReviewPanel($numberOfReviewsTotal);\n\n        $I->amGoingTo('test number of reviews on page');\n        $reviewsPage = $accountPage->openMyReviewsPage();\n        $reviewsPage->seeNumberOfReviews($this->getReviewPaginationSize());\n\n        $I->amGoingTo('test pagination is present and functioning');\n        $reviewsPage->goToNextPage();\n        $reviewsPage->seeNumberOfReviews($numberOfReviewsOnNextPage);\n\n        $I->amGoingTo('test review delete');\n        $reviewsPage->deleteFirstReviewInList();\n\n        $I->amGoingTo('test pagination is not displayed after delete');\n        $reviewsPage->seeNumberOfReviews($this->getReviewPaginationSize());\n        $reviewsPage->dontSeeBottomPaginationElements();\n    }\n\n    private function prepareReviewDataForProduct(\n        AcceptanceTester $I,\n        string $productId,\n        string $userId,\n        array $review\n    ): void {\n        $reviewData = [\n            'OXID' => uniqid('test', true),\n            'OXOBJECTID' => $productId,\n            'OXTYPE' => 'oxarticle',\n            'OXTEXT' => $review['text'],\n            'OXUSERID' => $userId,\n            'OXLANG' => '1',\n            'OXRATING' => $review['rating'],\n            'OXCREATE' => $review['created'],\n        ];\n\n        $I->haveInDatabase('oxreviews', $reviewData);\n    }\n\n    private function getExistingUserData(): array\n    {\n        return Fixtures::get('existingUser');\n    }\n\n    private function insertReviewsIntoDb(AcceptanceTester $I, string $userId, int $cnt): void\n    {\n        $review = [\n            'text' => 'some-text',\n            'rating' => 123,\n            'created' => date('Y-m-d H:i:s'),\n        ];\n        while ($cnt > 0) {\n            $this->prepareReviewDataForProduct($I, '1000', $userId, $review);\n            $cnt--;\n        }\n    }\n\n    private function getReviewPaginationSize(): int\n    {\n        /**\n         * Value is hardcoded in controller\n         * @see OxidEsales\\EshopCommunity\\Application\\Controller\\AccountReviewController\n         */\n        return 10;\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/SearchCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('search')]\nfinal class SearchCest\n{\n    public function searchAndNavigateInProductList(AcceptanceTester $I): void\n    {\n        $I->wantToTest('if sorting, paging and navigation is working correctly in search list');\n\n        $I->updateConfigInDatabase('aNrofCatArticles', serialize([\"1\", \"2\", \"10\", \"20\", \"50\", \"100\"]), \"arr\");\n        $I->updateConfigInDatabase('aNrofCatArticlesInGrid', serialize([\"1\", \"2\", \"10\", \"20\", \"50\", \"100\"]), \"arr\");\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 €'\n        ];\n        $productData2 = [\n            'id' => '1001',\n            'title' => 'Test product 1 [EN] šÄßüл',\n            'description' => 'Test product 1 short desc [EN] šÄßüл',\n            'price' => '100,00 €'\n        ];\n\n        $productData3 = [\n            'id' => '10014',\n            'title' => '14 EN product šÄßüл',\n            'description' => '13 EN description šÄßüл',\n            'price' => 'from 15,00 €'\n        ];\n\n        $searchListPage = $I->openShop()\n            ->searchFor('notExisting')\n            ->seeSearchCount(0);\n        $I->seeText(Translator::translate('NO_ITEMS_FOUND'));\n\n        $searchListPage = $searchListPage->searchFor('100')\n            ->seeSearchCount(4)\n            ->selectSorting('oxtitle', 'asc')\n            ->seeProductData($productData3, 1)\n            ->selectProductsPerPage('2');\n\n        $I->seeText(Translator::translate('PRODUCTS_PER_PAGE') . ' 2');\n\n        $searchListPage = $searchListPage->seeProductData($productData3, 1)\n            ->seeProductData($productData, 2)\n            ->openNextListPage()\n            ->seeProductData($productData2, 1)\n            ->openPreviousListPage()\n            ->seeProductData($productData3, 1)\n            ->selectSorting('oxprice', 'desc')\n            ->seeProductData($productData2, 1)\n            ->openListPageNumber(2)\n            ->seeProductData($productData, 1)\n            ->seeProductData($productData3, 2);\n        $searchListPage->searchFor('[EN] šÄßüл')\n            ->seeSearchCount(4)\n            ->selectListDisplayType(Translator::translate('line'))\n            ->selectSorting('oxtitle', 'asc')\n            ->seeProductDataInDisplayTypeList($productData3, 1);\n\n        $I->updateConfigInDatabase('blUseTimeCheck', true, 'bool');\n        $searchListPage->searchFor('100')\n            ->seeSearchCount(4);\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/SessionHandlingCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse Codeception\\Util\\Fixtures;\nuse OxidEsales\\Codeception\\Module\\Context;\nuse OxidEsales\\Codeception\\Page\\Account\\UserAccount;\nuse OxidEsales\\Codeception\\Step\\Basket;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\nuse function random_int;\n\n#[Group('session')]\nfinal class SessionHandlingCest\n{\n    public function checkForceSidWithDefaultConfig(AcceptanceTester $I): void\n    {\n        $I->wantToTest('that force_sid allows to access the current users session');\n        $userData = Fixtures::get('existingUser');\n\n        $I->amGoingTo('login to existing user account and grab active session ID');\n        $this->createUserSession(\n            $I,\n            $userData['userLoginName'],\n            $userData['userPassword']\n        )\n            ->seePageOpened()\n            ->seeUserAccount($userData);\n        $sessionId = $I->grabCookie('sid');\n\n        $I->amGoingTo('clear the cookie data and see that the session is not accessible');\n        $this->resetSessionCookie($I);\n        $I->openShop()\n            ->openUserAccountPage()\n            ->seePageOpened()\n            ->seeLoginForm();\n\n        $I->amGoingTo('add force_sid to URL and see that the session is accessible');\n        $this->sendRequestToSwitchSessionId($I, $sessionId);\n        $I->openShop()\n            ->openAccountPage()\n            ->seePageOpened()\n            ->seeUserAccount($userData);\n    }\n\n    public function checkForceSidWithDisabledForceSid(AcceptanceTester $I): void\n    {\n        $I->wantToTest('that force_sid is not working after configuration update');\n        $userData = Fixtures::get('existingUser');\n\n        $I->amGoingTo('disable force_sid via configuration');\n        $I->updateProjectConfigurations(['oxid_esales.disallow_force_session_id' => true], []);\n\n        $I->amGoingTo('login to existing user account and grab active session ID');\n        $this->createUserSession(\n            $I,\n            $userData['userLoginName'],\n            $userData['userPassword']\n        )\n            ->seeUserAccount($userData);\n        $sessionId = $I->grabCookie('sid');\n\n        $I->amGoingTo('clear the cookie data and see that the session is not accessible');\n        $this->resetSessionCookie($I);\n        $I->openShop()\n            ->openUserAccountPage()\n            ->seePageOpened()\n            ->seeLoginForm();\n\n        $I->amGoingTo('add force_sid to URL and see that the session is not accessible');\n        $this->sendRequestToSwitchSessionId($I, $sessionId);\n        $I->expect('to see user login form instead of user account page');\n        $I->openShop()\n            ->openUserAccountPage()\n            ->seePageOpened()\n            ->seeLoginForm();\n\n        $I->amGoingTo('do cleanup');\n        $I->restoreProjectConfigurations();\n    }\n\n    public function userSessionAfterPasswordChange(AcceptanceTester $I): void\n    {\n        $I->wantToTest('that user will be logged out if someone changes his password from another active session');\n        $userData = Fixtures::get('existingUser');\n        $I->amGoingTo('log the existing user in');\n        $home = $I\n            ->openShop()\n            ->loginUser($userData['userLoginName'], $userData['userPassword'])\n            ->seeUserLoggedIn();\n\n        $I->amGoingTo('add some product to the shopping cart');\n        (new Basket($I))->addProductToBasket('1001', 3);\n        $home->seeItemCountBadge('3');\n\n        $I->amGoingTo('mock password change for this user from admin/another browser session');\n        $I->updateInDatabase(\n            'oxuser',\n            ['OXPASSWORD' => 'some-new-password-hash'],\n            ['OXUSERNAME' => $userData['userLoginName']]\n        );\n\n        $I->amGoingTo('send any page request after password change');\n        $home->openAccountPage();\n        $I->expect('that user was logged out');\n        $home->seeUserLoggedOut();\n\n        $I->expect('that the basket is retained, despite the user being logged out');\n        $home->seeItemCountBadge('3');\n    }\n\n    public function registerStandardUserInFrontend(AcceptanceTester $I): void\n    {\n        $I->wantToTest('simple user account opening');\n        $username = 'some-user-email@oxid-esales.dev';\n        $userRegistration = $I\n            ->openShop()\n            ->openUserRegistrationPage();\n\n        $homePage = $I->openShop();\n        $homePage->openUserRegistrationPage();\n\n        $userRegistration\n            ->enterUserLoginData([\n                'userLoginNameField' => $username,\n                'userPasswordField' => 'user1user1',\n            ])\n            ->enterUserData([\n                'userUstIDField' => '',\n                'userMobFonField' => '111-111111-1',\n                'userPrivateFonField' => '111111111',\n                'userBirthDateDayField' => random_int(1, 28),\n                'userBirthDateMonthField' => random_int(1, 12),\n                'userBirthDateYearField' => random_int(1960, 2000),\n            ])\n            ->enterAddressData([\n                'userSalutation' => 'Mrs',\n                'userFirstName' => 'John',\n                'userLastName' => 'Doe',\n                'companyName' => 'Unemployed',\n                'street' => 'Main Str.',\n                'streetNr' => 123,\n                'ZIP' => '12341',\n                'city' => 'Big City',\n                'additionalInfo' => 'Something additional',\n                'fonNr' => '111-111-1',\n                'faxNr' => '111-111-111-1',\n                'countryId' => 'Germany',\n                'stateId' => 'Berlin',\n            ])\n            ->registerUser();\n\n        $I->openShop()\n            ->seeUserLoggedIn();\n\n        $I->amGoingTo('mock password change for this user from admin/another browser session');\n        $I->updateInDatabase(\n            'oxuser',\n            ['OXPASSWORD' => 'some-new-password-hash'],\n            ['OXUSERNAME' => $username]\n        );\n\n        $I->openShop()\n            ->seeUserLoggedOut();\n    }\n\n    private function createUserSession(AcceptanceTester $I, string $userName, string $password): UserAccount\n    {\n        return $I->openShop()\n            ->loginUser($userName, $password)\n            ->openAccountPage();\n    }\n\n    private function resetSessionCookie(AcceptanceTester $I): void\n    {\n        $I->resetCookie('sid');\n        Context::resetActiveUser();\n    }\n\n    private function sendRequestToSwitchSessionId(AcceptanceTester $I, string $sessionId): void\n    {\n        $I->amOnUrl(\"{$I->getCurrentURL()}?force_sid=$sessionId\");\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/ShopNavigationCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Util\\Fixtures;\nuse OxidEsales\\Codeception\\Step\\Basket;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\nfinal class ShopNavigationCest\n{\n    public function checkBadgeCountWhenProductIsAddedToCart(AcceptanceTester $I): void\n    {\n        $I->wantToTest('that the badge next to the cart icon shows the number of items in the cart');\n\n        $homePage = $I->openShop();\n        $userData = $this->getExistingUserData();\n        $homePage->loginUser($userData['userLoginName'], $userData['userPassword']);\n\n        $basket = new Basket($I);\n        $basket->dontSeeItemCountBadge();\n        $basket->addProductToBasket('1001', 1);\n        $basket->seeItemCountBadge('1');\n        $basket->addProductToBasket('1001', 3);\n        $basket->seeItemCountBadge('4');\n        $basket->addProductToBasket('1000', 7);\n        $basket->seeItemCountBadge('11');\n\n        $basket->openMiniBasket()->openCheckout()->goToNextStep()->submitOrder();\n        $basket->dontSeeItemCountBadge();\n    }\n\n    private function getExistingUserData(): array\n    {\n        return Fixtures::get('existingUser');\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/UserAccountCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse Codeception\\Util\\Fixtures;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\Codeception\\Step\\Start;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('myAccount')]\nfinal class UserAccountCest\n{\n    public function loginUserInFrontend(AcceptanceTester $I): void\n    {\n        $I->wantToTest('user login (popup in top of the page)');\n\n        $startPage = $I->openShop();\n\n        //login when username/pass are incorrect. error msg should be in place etc.\n        $startPage->loginUser('non-existing-user@oxid-esales.dev', '')\n            ->seeUserLoggedOut();\n        $I->seeText(Translator::translate('ERROR_MESSAGE_USER_NOVALIDLOGIN'), $startPage->badLoginError);\n\n        //login with correct user name/pass\n        $userData = $this->getExistingUserData();\n        $startPage->loginUser($userData['userLoginName'], $userData['userPassword'])\n            ->seeUserLoggedIn();\n\n        $startPage\n            ->openAccountPage()\n            ->seePageOpened()\n            ->seeUserAccount($userData);\n    }\n\n    public function changeUserAccountPassword(AcceptanceTester $I): void\n    {\n        $I->wantTo('change user password in my account navigation');\n\n        $userData = $this->getExistingUserData();\n        $userName = $userData['userLoginName'];\n        $oldPass = $userData['userPassword'];\n        $newPass = 'someNewPassword123';\n        $invalidPass = 'pass';\n\n        $startPage = $I->openShop()\n            ->loginUser($userName, $oldPass);\n        $I->dontSee(Translator::translate('LOGIN'));\n\n        $changePasswordPage = $startPage\n            ->openAccountPage()\n            ->seePageOpened()\n            ->seeUserAccount($userData)\n            ->openChangePasswordPage();\n\n        //entered not matching new passwords\n        $changePasswordPage->fillPasswordFields($oldPass, $newPass, $oldPass);\n        $I->seeText(Translator::translate('ERROR_MESSAGE_PASSWORD_DO_NOT_MATCH'));\n\n        //new pass is too short\n        $changePasswordPage->changePassword($oldPass, $invalidPass, $invalidPass);\n        $I->seeText(Translator::translate('ERROR_MESSAGE_PASSWORD_TOO_SHORT'));\n\n        //correct new pass\n        $changePasswordPage->changePassword($oldPass, $newPass, $newPass);\n        $I->seeText(Translator::translate('MESSAGE_PASSWORD_CHANGED'));\n\n        $I->reloadPage();\n        $startPage->seeUserLoggedOut();\n\n        // try to login with old password\n        $I->openShop()\n            ->loginUser($userName, $oldPass);\n        $I->seeText(Translator::translate('ERROR_MESSAGE_USER_NOVALIDLOGIN'));\n\n        // try to login with new password\n        $changePasswordPage = $I->openShop()\n            ->loginUser($userName, $newPass)\n            ->openUserAccountPage()\n            ->seeUserLoggedIn()\n            ->openChangePasswordPage();\n        $I->dontSee(Translator::translate('LOGIN'));\n\n        //reset new pass to old one\n        $changePasswordPage->changePassword($newPass, $oldPass, $oldPass);\n        $I->seeText(Translator::translate('MESSAGE_PASSWORD_CHANGED'));\n    }\n\n    public function sendUserPasswordReminder(AcceptanceTester $I): void\n    {\n        $I->wantToTest('user password reminder in my account navigation');\n\n        $userData = $this->getExistingUserData();\n        $startPage = $I->openShop();\n\n        $passwordReminderPage = $startPage->openUserPasswordReminderPage();\n        $I->seeText(Translator::translate('HAVE_YOU_FORGOTTEN_PASSWORD'));\n\n        $I->amGoingTo('reset password with invalid email format');\n        $passwordReminderPage->resetPassword('wrongEmail');\n        $I->seeText(Translator::translate('DD_FORM_VALIDATION_VALIDEMAIL'));\n\n        $I->amGoingTo('reset password with existing user email');\n        $passwordReminderPage->resetPassword($userData['userLoginName']);\n        $I->seeText(Translator::translate('PASSWORD_WAS_SEND_TO') . ' ' . $userData['userLoginName']);\n\n        $I->amGoingTo('reset password with non-existing user email');\n        $nonExistingEmail = 'not_existing_user@oxid-esales.dev';\n        $startPage->openUserPasswordReminderPage()\n            ->resetPassword($nonExistingEmail);\n        $I->seeText(Translator::translate('PASSWORD_WAS_SEND_TO') . ' ' . $nonExistingEmail);\n    }\n\n    public function changeUserEmailInBillingAddress(AcceptanceTester $I): void\n    {\n        $I->wantToTest('changing user email address in my account billing information');\n\n        $userData = $this->getExistingUserData();\n        $guestUserData = $this->getExistingGuestUserData();\n        $adminUserData = $this->getAdminUserData();\n        $newEmail = 'example02@oxid-esales.dev';\n\n        $I->amGoingTo('login to shop and navigate to billing address form');\n        $userAddressPage = $I->openShop()\n            ->loginUser($userData['userLoginName'], $userData['userPassword'])\n            ->openAccountPage()\n            ->openUserAddressPage()\n            ->openUserBillingAddressForm();\n\n        $I->expect('to see default country and state selections');\n        $I->seeText('Germany', $userAddressPage->billCountryId);\n        $I->seeText(Translator::translate('PLEASE_SELECT_STATE'), $userAddressPage->billStateId);\n\n        $I->amGoingTo('try to change email to an existing guest user email');\n        $userAddressPage->changeEmail($guestUserData['userLoginName'], $userData['userPassword']);\n        $I->expect('to see an error message about existing user');\n        $I->seeText(Translator::translate('ERROR_MESSAGE_USER_USEREXISTS'));\n\n        $I->amGoingTo('try to change email to an existing admin email');\n        $userAddressPage->openUserBillingAddressForm()\n            ->changeEmail($adminUserData['userLoginName'], $userData['userPassword']);\n        $I->expect('to see an error message about existing user');\n        $I->seeText(Translator::translate('ERROR_MESSAGE_USER_USEREXISTS'));\n\n        $I->amGoingTo('change user email to a new valid email address');\n        $userAddressPage = $userAddressPage->openUserBillingAddressForm()\n            ->changeEmail($newEmail, $userData['userPassword']);\n        $I->expect('not to see any error messages');\n        $I->dontSee(Translator::translate('ERROR_MESSAGE_USER_USEREXISTS'));\n        $I->dontSee(Translator::translate('COMPLETE_MARKED_FIELDS'));\n\n        $I->amGoingTo('logout and try to login with the old email address');\n        $userAddressPage = $userAddressPage->logoutUser();\n        $userAddressPage->loginUser($userData['userLoginName'], $userData['userPassword']);\n        $I->expect('to see login form and error message');\n        $I->seeText(Translator::translate('LOGIN'));\n        $I->seeText(Translator::translate('ERROR_MESSAGE_USER_NOVALIDLOGIN'), $userAddressPage->badLoginError);\n\n        $I->amGoingTo('login with the new email address');\n        $userAddressPage->loginUser($newEmail, $userData['userPassword']);\n        $I->expect('to be logged in successfully');\n        $I->dontSee(Translator::translate('LOGIN'));\n\n        $I->amGoingTo('change the email back to the original one');\n        $userAddressPage->openUserBillingAddressForm()\n            ->changeEmail('example_test@oxid-esales.dev', $userData['userPassword'])\n            ->logoutUser();\n        $I->expect('to be logged out');\n        $I->seeText(Translator::translate('LOGIN'));\n    }\n\n    public function subscribeNewsletterInUserAccount(AcceptanceTester $I): void\n    {\n        $start = new Start($I);\n        $I->wantToTest('newsletter subscription in my account navigation');\n\n        $userData = $this->getExistingUserData();\n\n        $newsletterSettingsPage = $start->loginOnStartPage($userData['userLoginName'], $userData['userPassword'])\n            ->openAccountPage()\n            ->openNewsletterSettingsPage();\n        $I->seeText(Translator::translate('MESSAGE_NEWSLETTER_SUBSCRIPTION'));\n        $newsletterSettingsPage->seeNewsletterUnSubscribed();\n\n        //subscribe for a newsletter\n        $newsletterSettingsPage->subscribeNewsletter()\n            ->seeNewsletterSubscribed();\n\n        //unsubscribe a newsletter\n        $newsletterSettingsPage->unSubscribeNewsletter()\n            ->seeNewsletterUnSubscribed();\n    }\n\n    public function changeUserBillingAddress(AcceptanceTester $I): void\n    {\n        $start = new Start($I);\n        $I->wantToTest('user billing address in my account');\n\n        $I->updateConfigInDatabase('blShowBirthdayFields', true, 'bool');\n        $I->updateConfigInDatabase('blVatIdCheckDisabled', true, 'bool');\n        /** Change Germany and Belgium to non EU country to skip online VAT validation. */\n        $I->updateInDatabase('oxcountry', ['oxvatstatus' => 0], ['OXID' => 'a7c40f632e04633c9.47194042']);\n        $I->updateInDatabase('oxcountry', ['oxvatstatus' => 0], ['OXID' => 'a7c40f631fc920687.20179984']);\n\n        $existingUserData = $this->getExistingUserData();\n\n        $userAddressPage = $start\n            ->loginOnStartPage($existingUserData['userLoginName'], $existingUserData['userPassword'])\n            ->openAccountPage()\n            ->openUserAddressPage()\n            ->openUserBillingAddressForm();\n        $I->seeText('Germany', $userAddressPage->billCountryId);\n        $I->seeText(Translator::translate('PLEASE_SELECT_STATE'), $userAddressPage->billStateId);\n\n        $userLoginData['userLoginNameField'] = $existingUserData['userLoginName'];\n        $addressData = $this->getUserAddressData('1', 'Belgium');\n        $userData = $this->getUserData('1');\n        $userData['userUstIDField'] = 'BE0410521222';\n        $userAddressPage = $userAddressPage\n            ->enterUserData($userData)\n            ->enterAddressData($addressData)\n            ->saveAddress()\n            ->validateUserBillingAddress(array_merge($addressData, $userData, $userLoginData));\n\n        $userData['userUstIDField'] = '';\n        $addressData['UserFirstName'] = $existingUserData['userName'];\n        $addressData['UserLastName'] = $existingUserData['userLastName'];\n        $userAddressPage = $userAddressPage->openUserBillingAddressForm()\n            ->enterUserData($userData)\n            ->enterAddressData($addressData)\n            ->selectBillingCountry('Germany')\n            ->saveAddress();\n        $I->seeText('Germany', $userAddressPage->billingAddress);\n    }\n\n    #[Group('user_account_address', 'exclude_from_compilation')]\n    public function modifyUserShippingAddress(AcceptanceTester $I): void\n    {\n        $start = new Start($I);\n        $I->wantToTest('user shipping address in my account');\n\n        $userData = $this->getExistingUserData();\n\n        $userAddressPage = $start->loginOnStartPage($userData['userLoginName'], $userData['userPassword'])\n            ->openAccountPage()\n            ->openUserAddressPage()\n            ->seeNumberOfShippingAddresses(0)\n            ->openUserBillingAddressForm();\n        $I->seeText('Germany', $userAddressPage->billCountryId);\n        $I->seeText(Translator::translate('PLEASE_SELECT_STATE'), $userAddressPage->billStateId);\n\n        $deliveryAddressData = $this->getUserAddressData('1_2');\n\n        $userAddressPage = $userAddressPage\n            ->openShippingAddressForm()\n            ->enterShippingAddressData($deliveryAddressData)\n            ->saveAddress()\n            ->validateUserDeliveryAddress($deliveryAddressData);\n\n        $deliveryAddressData = $this->getUserAddressData('1_4');\n\n        $userAddressPage->selectShippingAddress(1)\n            ->enterShippingAddressData($deliveryAddressData)\n            ->saveAddress()\n            ->validateUserDeliveryAddress($deliveryAddressData);\n    }\n\n    #[Group('user_account_address', 'exclude_from_compilation')]\n    public function createAndDeleteUserShippingAddress(AcceptanceTester $I): void\n    {\n        $start = new Start($I);\n        $I->wantToTest('user shipping address create and delete');\n\n        $userData = $this->getExistingUserData();\n\n        $userAddressPage = $start->loginOnStartPage($userData['userLoginName'], $userData['userPassword'])\n            ->openAccountPage()\n            ->openUserAddressPage()\n            ->seeNumberOfShippingAddresses(0)\n            ->openUserBillingAddressForm();\n        $I->seeText('Germany', $userAddressPage->billCountryId);\n        $I->seeText(Translator::translate('PLEASE_SELECT_STATE'), $userAddressPage->billStateId);\n\n        $deliveryAddressData = $this->getUserAddressData('1_2');\n\n        $userAddressPage = $userAddressPage\n            ->openShippingAddressForm()\n            ->enterShippingAddressData($deliveryAddressData)\n            ->saveAddress()\n            ->validateUserDeliveryAddress($deliveryAddressData);\n\n        $userAddressPage->seeNumberOfShippingAddresses(1)\n            ->selectShippingAddress(1)\n            ->deleteShippingAddress(1)\n            ->seeNumberOfShippingAddresses(0);\n    }\n\n    private function getExistingUserData()\n    {\n        return Fixtures::get('existingUser');\n    }\n\n    private function getExistingGuestUserData(): array\n    {\n        return Fixtures::get('existingGuestUser');\n    }\n\n    private function getAdminUserData(): array\n    {\n        return Fixtures::get('adminUser');\n    }\n\n    private function getUserData(string $userId): array\n    {\n        return [\n            'userUstIDField' => '',\n            'userMobFonField' => '111-111111-' . $userId,  //still needed?\n            'userPrivateFonField' => '11111111' . $userId,\n            'userBirthDateDayField' => random_int(10, 28),\n            'userBirthDateMonthField' => random_int(8, 10),\n            'userBirthDateYearField' => random_int(1960, 2000),\n        ];\n    }\n\n    private function getUserAddressData(string $userId, string $userCountry = 'Germany'): array\n    {\n        $addressData = [\n            'userSalutation' => 'Mrs',\n            'userFirstName' => 'user' . $userId . ' name_šÄßüл',\n            'userLastName' => 'user' . $userId . ' last name_šÄßüл',\n            'companyName' => 'user' . $userId . ' company_šÄßüл',\n            'street' => 'user' . $userId . ' street_šÄßüл',\n            'streetNr' => $userId . '-' . $userId,\n            'ZIP' => '1234' . $userId,\n            'city' => 'user' . $userId . ' city_šÄßüл',\n            'additionalInfo' => 'user' . $userId . ' additional info_šÄßüл',\n            'fonNr' => '111-111-' . $userId,\n            'faxNr' => '111-111-111-' . $userId,\n            'countryId' => $userCountry,\n        ];\n        if ($userCountry === 'Germany') {\n            $addressData['stateId'] = 'Berlin';\n        }\n        return $addressData;\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/UserRegistrationCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\Codeception\\Step\\Basket;\nuse OxidEsales\\Codeception\\Step\\Start;\nuse OxidEsales\\Codeception\\Step\\UserRegistration;\nuse OxidEsales\\Codeception\\Step\\UserRegistrationInCheckout;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('registration')]\nfinal class UserRegistrationCest\n{\n    #[Group('main')]\n    public function registerStandardUserInFrontend(AcceptanceTester $I): void\n    {\n        $userRegistration = new UserRegistration($I);\n        $I->wantToTest('simple user account opening');\n\n        $I->updateConfigInDatabase('blShowBirthdayFields', true, 'bool');\n        // prepare user data\n        $userId = '1';\n        $userLoginData = $this->getUserLoginData($userId);\n        $userData = $this->getUserData($userId);\n        $addressData = $this->getUserAddressData($userId);\n\n        $homePage = $I->openShop();\n        $homePage->openUserRegistrationPage();\n\n        $userRegistration->registerUser($userLoginData, $userData, $addressData);\n\n        $this->checkUserBillingData($I, $userLoginData, $userData, $addressData);\n    }\n\n    public function registerUserForNewsletterAndShop(AcceptanceTester $I): void\n    {\n        $userRegistration = new UserRegistration($I);\n        $start = new Start($I);\n        $I->wantToTest('the user standard registration and the newsletter subscription with the same email');\n\n        $I->updateConfigInDatabase('blOrderOptInEmail', true, 'bool');\n        // prepare user data\n        $userId = '7';\n        $userLoginData = $this->getUserLoginData($userId);\n        $userData = $this->getUserData($userId);\n        $addressData = $this->getUserAddressData($userId);\n\n        $homePage = $I->openShop();\n        $homePage->openUserRegistrationPage();\n\n        $userRegistration->registerUser($userLoginData, $userData, $addressData);\n\n        $I->clearShopCache();\n\n        $I->openShop();\n        $start->registerUserForNewsletter(\n            $userLoginData['userLoginNameField'],\n            'user7_3 name_šÄßüл',\n            'user7_3 last name_šÄßüл'\n        );\n\n        $this->checkUserBillingData($I, $userLoginData, $userData, $addressData);\n    }\n\n    public function createBasketUserAccountWithoutRegistration(AcceptanceTester $I): void\n    {\n        $basket = new Basket($I);\n        $start = new Start($I);\n        $checkout = new UserRegistrationInCheckout($I);\n        $I->wantToTest('the user newsletter subscription and the user account creation'\n                       . ' without registration with the same email in checkout');\n        $I->openShop();\n\n        $I->updateConfigInDatabase('blShowBirthdayFields', true, 'bool');\n        $I->updateConfigInDatabase('blOrderOptInEmail', true, 'bool');\n        // prepare user data\n        $userId = '2';\n        $userLoginData = $this->getUserLoginData($userId);\n        $userData = $this->getUserData($userId);\n        $addressData = $this->getUserAddressData($userId);\n        $userId = '2_2';\n        $deliveryAddressData = $this->getUserAddressData($userId);\n\n        $start->registerUserForNewsletter(\n            $userLoginData['userLoginNameField'],\n            $addressData['userFirstName'],\n            $addressData['userLastName']\n        );\n\n        //add Product to basket\n        $basket->addProductToBasketAndOpenUserCheckout('1000', 1);\n\n        $paymentPage = $checkout->createNotRegisteredUserInCheckout(\n            $userLoginData['userLoginNameField'],\n            $userData,\n            $addressData,\n            $deliveryAddressData\n        );\n\n        $paymentPage->selectPayment('oxidcashondel');\n        $orderPage = $paymentPage->goToNextStep();\n\n        $orderPage->validateUserBillingAddress(array_merge($addressData, $userData, $userLoginData));\n        $orderPage->validateUserDeliveryAddress($deliveryAddressData);\n\n        $this->checkUserBillingData($I, $userLoginData, $userData, $addressData);\n        $this->checkUserDeliveryData($I, $deliveryAddressData);\n    }\n\n    public function registerBasketUserAccount(AcceptanceTester $I): void\n    {\n        $basket = new Basket($I);\n        $I->wantTo('register user account in the checkout process');\n\n        $I->updateConfigInDatabase('blShowBirthdayFields', true, 'bool');\n        // prepare user data\n        $userId = '3';\n        $userPassword = 'user33';\n        $userLoginData = $this->getUserLoginData($userId, $userPassword);\n        $userData = $this->getUserData($userId);\n        $addressData = $this->getUserAddressData($userId);\n        $userId = '3_2';\n        $deliveryAddressData = $this->getUserAddressData($userId);\n\n        //add Product to basket\n        $userCheckout = $basket->addProductToBasketAndOpenUserCheckout('1000', 1);\n\n        $paymentPage = $userCheckout->selectOptionRegisterNewAccount()\n        ->enterUserLoginData($userLoginData)\n            ->enterUserData($userData)\n            ->enterAddressData($addressData)\n            ->enterOrderRemark('remark text')\n            ->openShippingAddressForm()\n            ->enterShippingAddressData($deliveryAddressData)\n            ->goToNextStep();\n\n        $paymentPage->selectPayment('oxidcashondel');\n        $orderPage = $paymentPage->goToNextStep();\n\n        $orderPage->validateUserBillingAddress(array_merge($addressData, $userData, $userLoginData));\n        $orderPage->validateUserDeliveryAddress($deliveryAddressData);\n        $orderPage->validateRemarkText('remark text');\n\n        $this->checkUserBillingData($I, $userLoginData, $userData, $addressData);\n        $this->checkUserDeliveryData($I, $deliveryAddressData);\n    }\n\n    public function createBasketUserAccountWithoutRegistrationTwice(AcceptanceTester $I): void\n    {\n        $basket = new Basket($I);\n        $checkout = new UserRegistrationInCheckout($I);\n        $I->wantTo('create user account without registration twice in the checkout process');\n\n        $I->updateConfigInDatabase('blShowBirthdayFields', true, 'bool');\n        // prepare user data\n        $userId = '4';\n        $userLoginData = $this->getUserLoginData($userId);\n        $userData = $this->getUserData($userId);\n        $addressData = $this->getUserAddressData($userId);\n        $userId = '4_2';\n        $userCountry = 'Belgium';\n        $deliveryAddressData = $this->getUserAddressData($userId, $userCountry);\n\n        //add Product to basket\n        $basket->addProductToBasketAndOpenUserCheckout('1000', 1);\n\n        $paymentPage = $checkout->createNotRegisteredUserInCheckout(\n            $userLoginData['userLoginNameField'],\n            $userData,\n            $addressData,\n            $deliveryAddressData\n        );\n\n        $I->seeText('Currently we have no shipping method set up for this country.');\n        $paymentPage->goToNextStep();\n\n        // prepare user data second data\n        $userId = '4_3';\n        $userLoginData = $this->getUserLoginData('4');\n        $userData = $this->getUserData($userId);\n        $addressData = $this->getUserAddressData($userId);\n        $userId = '4_4';\n        $deliveryAddressData = $this->getUserAddressData($userId);\n\n        $I->clearShopCache();\n\n        //add Product to basket\n        $basket->addProductToBasketAndOpenUserCheckout('1000', 1);\n\n        $checkout->createNotRegisteredUserInCheckout(\n            $userLoginData['userLoginNameField'],\n            $userData,\n            $addressData,\n            $deliveryAddressData\n        );\n\n        $this->checkUserBillingData($I, $userLoginData, $userData, $addressData);\n        $this->checkUserDeliveryData($I, $deliveryAddressData);\n    }\n\n    public function createBasketUserAccountWithoutAndWithRegistration(AcceptanceTester $I): void\n    {\n        $basket = new Basket($I);\n        $checkout = new UserRegistrationInCheckout($I);\n        $I->wantTo('create user account without registration and later with registration in checkout process');\n\n        $I->updateConfigInDatabase('blShowBirthdayFields', true, 'bool');\n        // prepare user data\n        $userId = '5';\n        $userLoginData = $this->getUserLoginData($userId);\n        $userData = $this->getUserData($userId);\n        $addressData = $this->getUserAddressData($userId);\n        $userId = '5_2';\n        $userCountry = 'Belgium';\n        $deliveryAddressData = $this->getUserAddressData($userId, $userCountry);\n\n        //add Product to basket\n        $basket->addProductToBasketAndOpenUserCheckout('1000', 1);\n\n        $paymentStep = $checkout->createNotRegisteredUserInCheckout(\n            $userLoginData['userLoginNameField'],\n            $userData,\n            $addressData,\n            $deliveryAddressData\n        );\n\n        $I->seeText('Currently we have no shipping method set up for this country.');\n        $paymentStep->goToNextStep();\n\n        // prepare user data second step\n        $userId = '5_3';\n        $userPassword = 'user55';\n        $userCountry = 'Germany';\n        $userLoginData = $this->getUserLoginData('5', $userPassword);\n        $userData = $this->getUserData($userId);\n        $addressData = $this->getUserAddressData($userId, $userCountry);\n        $userId = '5_4';\n        $deliveryAddressData = $this->getUserAddressData($userId, $userCountry);\n\n        $I->clearShopCache();\n\n        //add Product to basket\n        $basket->addProductToBasketAndOpenUserCheckout('1000', 1);\n\n        $checkout->createRegisteredUserInCheckout(\n            $userLoginData,\n            $userData,\n            $addressData,\n            $deliveryAddressData\n        );\n\n        $this->checkUserBillingData($I, $userLoginData, $userData, $addressData);\n        $this->checkUserDeliveryData($I, $deliveryAddressData);\n    }\n\n    public function registerBasketUserAccountTwiceWithWrongPassword(AcceptanceTester $I): void\n    {\n        $basket = new Basket($I);\n        $checkout = new UserRegistrationInCheckout($I);\n        $I->wantTo('create registered user account twice by using wrong password second time');\n\n        $I->updateConfigInDatabase('blShowBirthdayFields', true, 'bool');\n        // prepare user data\n        $userId = '6';\n        $userPassword = 'user66';\n        $userCountry = 'Belgium';\n        $userLoginData = $this->getUserLoginData($userId, $userPassword);\n        $userData = $this->getUserData($userId);\n        $addressData = $this->getUserAddressData($userId, $userCountry);\n        $userId = '6_2';\n        $deliveryAddressData = $this->getUserAddressData($userId, $userCountry);\n\n        //add Product to basket\n        $basket->addProductToBasketAndOpenUserCheckout('1000', 1);\n\n        $checkout->createRegisteredUserInCheckout(\n            $userLoginData,\n            $userData,\n            $addressData,\n            $deliveryAddressData\n        );\n\n        // prepare user data for second step\n        $userId = '6_3';\n        $userPassword = 'aaaaaa';\n        $userLoginData2 = $this->getUserLoginData(6, $userPassword);\n        $userData2 = $this->getUserData($userId);\n        $addressData2 = $this->getUserAddressData($userId, $userCountry);\n\n        $I->clearShopCache();\n\n        //add Product to basket\n        $basket->addProductToBasketAndOpenUserCheckout('1000', 1);\n\n        $checkout->createNotValidRegisteredUserInCheckout($userLoginData2, $userData2, $addressData2);\n\n        $this->checkUserBillingData($I, $userLoginData, $userData, $addressData);\n        $this->checkUserDeliveryData($I, $deliveryAddressData);\n    }\n\n    public function registerBasketUserAccountAndNewsletter(AcceptanceTester $I): void\n    {\n        $basket = new Basket($I);\n        $start = new Start($I);\n        $checkout = new UserRegistrationInCheckout($I);\n        $I->wantTo('create not registered user account in the checkout and subscribe newsletter with the same email');\n\n        $I->updateConfigInDatabase('blShowBirthdayFields', true, 'bool');\n        $I->updateConfigInDatabase('blFooterShowNewsletterForm', true, 'bool');\n        $I->updateConfigInDatabase('blOrderOptInEmail', true, 'bool');\n        // prepare user data\n        $userId = '8';\n        $userLoginData = $this->getUserLoginData($userId);\n        $userData = $this->getUserData($userId);\n        $addressData = $this->getUserAddressData($userId);\n        $userId = '8_2';\n        $deliveryAddressData = $this->getUserAddressData($userId);\n\n        //add Product to basket\n        $basket->addProductToBasketAndOpenUserCheckout('1000', 1);\n\n        $checkout->createNotRegisteredUserInCheckout(\n            $userLoginData['userLoginNameField'],\n            $userData,\n            $addressData,\n            $deliveryAddressData\n        );\n\n        $I->clearShopCache();\n\n        $I->openShop();\n        $start->registerUserForNewsletter(\n            $userLoginData['userLoginNameField'],\n            'user8_3 name_šÄßüл',\n            'user8_3 last name_šÄßüл'\n        );\n\n        $this->checkUserBillingData($I, $userLoginData, $userData, $addressData);\n        $this->checkUserDeliveryData($I, $deliveryAddressData);\n    }\n\n    public function registerBasketUserAccountTwice(AcceptanceTester $I): void\n    {\n        $basket = new Basket($I);\n        $checkout = new UserRegistrationInCheckout($I);\n        $I->wantToTest('user performs order with option3 twice, both time using good email and pass');\n\n        $I->updateConfigInDatabase('blShowBirthdayFields', true, 'bool');\n        // prepare user data\n        $userId = '9';\n        $userPassword = 'user66';\n        $userCountry = 'Belgium';\n        $userLoginData = $this->getUserLoginData($userId, $userPassword);\n        $userData = $this->getUserData($userId);\n        $addressData = $this->getUserAddressData($userId, $userCountry);\n        $userId = '9_2';\n        $deliveryAddressData = $this->getUserAddressData($userId, $userCountry);\n\n        //add Product to basket\n        $basket->addProductToBasketAndOpenUserCheckout('1000', 1);\n\n        $checkout->createRegisteredUserInCheckout(\n            $userLoginData,\n            $userData,\n            $addressData,\n            $deliveryAddressData\n        );\n\n        // prepare user data for second step\n        $userId = '9_3';\n        $userCountry = 'Germany';\n        $userLoginData2 = $this->getUserLoginData('9', $userPassword);\n        $userData2 = $this->getUserData($userId);\n        $addressData2 = $this->getUserAddressData($userId, $userCountry);\n        $userId = '9_4';\n        $userCountry = 'Belgium';\n        $deliveryAddressData2 = $this->getUserAddressData($userId, $userCountry);\n\n        $I->clearShopCache();\n\n        $basket->addProductToBasketAndOpenUserCheckout('1000', 1);\n\n        $checkout->createNotValidRegisteredUserInCheckout(\n            $userLoginData2,\n            $userData2,\n            $addressData2,\n            $deliveryAddressData2\n        );\n        $errorMessage = Translator::translate('ERROR_MESSAGE_USER_USEREXISTS');\n        $I->seeText(sprintf($errorMessage, $userLoginData['userLoginNameField']));\n\n        $this->checkUserBillingData($I, $userLoginData, $userData, $addressData);\n        $this->checkUserDeliveryData($I, $deliveryAddressData);\n    }\n\n    private function getUserLoginData(string|int $userId, string $userPassword = 'user1user1'): array\n    {\n        return [\n            'userLoginNameField' => 'example' . $userId . '@oxid-esales.dev',\n            'userPasswordField' => $userPassword,\n        ];\n    }\n\n    private function getUserData(string $userId): array\n    {\n        return [\n            'userUstIDField' => '',\n            'userMobFonField' => '111-111111-' . $userId,  //still needed?\n            'userPrivateFonField' => '11111111' . $userId,\n            'userBirthDateDayField' => random_int(10, 28),\n            'userBirthDateMonthField' => random_int(10, 12),\n            'userBirthDateYearField' => random_int(1960, 2000),\n        ];\n    }\n\n    private function getUserAddressData(string $userId, string $userCountry = 'Germany'): array\n    {\n        $addressData = [\n            'userSalutation' => 'Mrs',\n            'userFirstName' => 'user' . $userId . ' name_šÄßüл',\n            'userLastName' => 'user' . $userId . ' last name_šÄßüл',\n            'companyName' => 'user' . $userId . ' company_šÄßüл',\n            'street' => 'user' . $userId . ' street_šÄßüл',\n            'streetNr' => $userId . '-' . $userId,\n            'ZIP' => '1234' . $userId,\n            'city' => 'user' . $userId . ' city_šÄßüл',\n            'additionalInfo' => 'user' . $userId . ' additional info_šÄßüл',\n            'fonNr' => '111-111-' . $userId,\n            'faxNr' => '111-111-111-' . $userId,\n            'countryId' => $userCountry,\n        ];\n        if ($userCountry === 'Germany') {\n            $addressData['stateId'] = 'Berlin';\n        }\n        return $addressData;\n    }\n\n    private function checkUserBillingData(\n        AcceptanceTester $I,\n        array $userLoginData,\n        array $userData,\n        array $addressData\n    ): void {\n        $I->seeInDatabase(\n            'oxuser',\n            [\n                'oxusername' => $userLoginData['userLoginNameField'],\n                'oxmobfon' => $userData['userMobFonField'],\n                'oxprivfon' => $userData['userPrivateFonField'],\n                'oxbirthdate' => $userData['userBirthDateYearField']\n                    . '-' . $userData['userBirthDateMonthField']\n                    . '-' . $userData['userBirthDateDayField'],\n                'oxfname' => $addressData['userFirstName'],\n                'oxlname' => $addressData['userLastName'],\n                'oxcompany' => $addressData['companyName'],\n                'oxstreet' => $addressData['street'],\n                'oxstreetnr' => $addressData['streetNr'],\n                'oxzip' => $addressData['ZIP'],\n                'oxcity' => $addressData['city'],\n                'oxaddinfo' => $addressData['additionalInfo'],\n                'oxfon' => $addressData['fonNr'],\n                'oxfax' => $addressData['faxNr'],\n            ]\n        );\n    }\n\n    private function checkUserDeliveryData(AcceptanceTester $I, array $deliveryAddressData): void\n    {\n        $I->seeInDatabase(\n            'oxaddress',\n            [\n                'oxfname' => $deliveryAddressData['userFirstName'],\n                'oxlname' => $deliveryAddressData['userLastName'],\n                'oxcompany' => $deliveryAddressData['companyName'],\n                'oxstreet' => $deliveryAddressData['street'],\n                'oxstreetnr' => $deliveryAddressData['streetNr'],\n                'oxzip' => $deliveryAddressData['ZIP'],\n                'oxcity' => $deliveryAddressData['city'],\n                'oxaddinfo' => $deliveryAddressData['additionalInfo'],\n                'oxfon' => $deliveryAddressData['fonNr'],\n                'oxfax' => $deliveryAddressData['faxNr'],\n                'oxcountry' => $deliveryAddressData['countryId'],\n            ]\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/WishListCest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Acceptance;\n\nuse Codeception\\Attribute\\Group;\nuse Codeception\\Util\\Fixtures;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\Codeception\\Step\\ProductNavigation;\nuse OxidEsales\\Codeception\\Step\\Start;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\AcceptanceTester;\n\n#[Group('myAccount', 'wishList')]\nfinal class WishListCest\n{\n    public function addProductToUserWishList(AcceptanceTester $I): void\n    {\n        $productNavigation = new ProductNavigation($I);\n        $I->wantToTest('if product compare functionality is enabled');\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 € *'\n        ];\n\n        $userData = $this->getExistingUserData();\n\n        $I->openShop()->loginUser($userData['userLoginName'], $userData['userPassword']);\n\n        //open details page\n        $detailsPage = $productNavigation->openProductDetailsPage($productData['id']);\n        $I->seeText($productData['title']);\n\n        $detailsPage->checkWishListItemCount(0)\n            ->addToWishList()\n            ->checkWishListItemCount(1);\n\n        $userAccountPage = $detailsPage->openAccountPage();\n        $I->seeText(Translator::translate('MY_WISH_LIST'));\n        $I->seeText(Translator::translate('PRODUCT') . ' 1');\n\n        $userAccountPage->logoutUserInAccountPage()->login($userData['userLoginName'], $userData['userPassword']);\n        $I->seeText(Translator::translate('MY_WISH_LIST'));\n        $I->seeText(Translator::translate('PRODUCT') . ' 1');\n\n        $userAccountPage->openWishListPage()\n            ->seeProductData($productData)\n            ->openProductDetailsPage(1);\n        $I->seeText($productData['title'], $detailsPage->productTitle);\n\n        $wishListPage = $detailsPage->openUserWishListPage()\n            ->addProductToBasket(1, 2);\n        $I->seeText('2', $wishListPage->miniBasketMenuElement);\n        $wishListPage = $wishListPage->removeProductFromList(1);\n\n        $I->seeText(Translator::translate('PAGE_TITLE_ACCOUNT_NOTICELIST'), $wishListPage->headerTitle);\n        $I->seeText(Translator::translate('WISH_LIST_EMPTY'));\n\n        $wishListPage->checkWishListItemCount(0);\n    }\n\n    public function addVariantToUserWishList(AcceptanceTester $I): void\n    {\n        $productNavigation = new ProductNavigation($I);\n        $start = new Start($I);\n        $I->wantToTest('user wish list functionality, if a variant of product was added');\n\n        $I->updateConfigInDatabase('blUseMultidimensionVariants', true, 'bool');\n\n        $productData = [\n            'id' => '10014',\n            'title' => '14 EN product šÄßüл',\n            'description' => '13 EN description šÄßüл',\n            'price' => 'from 15,00 €'\n        ];\n\n        $userData = $this->getExistingUserData();\n\n        $start->loginOnStartPage($userData['userLoginName'], $userData['userPassword']);\n\n        //open details page\n        $detailsPage = $productNavigation->openProductDetailsPage($productData['id']);\n        $I->seeText('14 EN product šÄßüл');\n        //add parent to wish list\n        $wishListPage = $detailsPage->addToWishList()\n            ->selectVariant(1, 'S')\n            ->selectVariant(2, 'black')\n            ->selectVariant(3, 'lether')\n            ->addToWishList()\n            ->checkWishListItemCount(2)\n            ->openUserWishListPage()\n            ->seeProductData($productData);\n\n        //assert variant\n        $productData = [\n            'id' => '10014-1-1',\n            'title' => '14 EN product šÄßüл S | black | lether',\n            'description' => '',\n            'price' => '25,00 €'\n        ];\n        $wishListPage->seeProductData($productData, 2);\n\n        $wishListPage->removeProductFromList(2)\n            ->removeProductFromList(1);\n\n        $I->seeText(Translator::translate('PAGE_TITLE_ACCOUNT_NOTICELIST'), $wishListPage->headerTitle);\n        $I->seeText(Translator::translate('WISH_LIST_EMPTY'));\n    }\n\n    public function testWishlistInTheCartForALoggedInUser(AcceptanceTester $I): void\n    {\n        $I->wantToTest('if a logged-in user can move a product from the basket to the wishlist');\n\n        $start = $I->loginShopWithExistingUser();\n        $productNavigation = new ProductNavigation($I);\n        $I->updateConfigInDatabase('bl_showWishlist', true);\n\n        $productData = [\n            'id' => '1000',\n            'title' => 'Test product 0 [EN] šÄßüл',\n            'description' => 'Test product 0 short desc [EN] šÄßüл',\n            'price' => '50,00 € *'\n        ];\n\n        $productNavigation\n            ->openProductDetailsPage($productData['id'])\n            ->addProductToBasket()\n            ->openMiniBasket()\n            ->openBasketDisplay()\n            ->seeAddToTheWishlistStar(1)\n            ->addProductToTheWishList(1);\n\n        $I->retrySee(Translator::translate('BASKET_EMPTY'));\n\n        $start\n            ->openUserWishListPage()\n            ->seeProductData($productData);\n    }\n\n    public function testWishlistInTheCartForANonLoggedInUser(AcceptanceTester $I): void\n    {\n        $I->wantToTest('if a non-logged-in user redirected to the login page after click on the star');\n        $I->updateConfigInDatabase('bl_showWishlist', true);\n\n        $productData = [\n            'id' => '1000',\n        ];\n\n        $productNavigation = new ProductNavigation($I);\n\n        $productNavigation\n            ->openProductDetailsPage($productData['id'])\n            ->addProductToBasket()\n            ->openMiniBasket()\n            ->openBasketDisplay()\n            ->seeAddToTheWishlistStar(1)\n            ->addProductToTheWishList(1);\n\n        $I->seeText(Translator::translate('LOGIN'));\n    }\n\n    private function getExistingUserData()\n    {\n        return Fixtures::get('existingUser');\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Acceptance/_bootstrap.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\Codeception\\Module\\FixturesHelper;\n\n$helper = new FixturesHelper();\n$helper->loadRuntimeFixtures(codecept_data_dir('user.php'));\n$helper->loadRuntimeFixtures(codecept_data_dir('voucher.php'));\n$helper->loadRuntimeFixtures(codecept_data_dir('order.php'));\n$helper->loadRuntimeFixtures(codecept_data_dir('product.php'));\n$helper->loadRuntimeFixtures(codecept_data_dir('shop.php'));\n$helper->loadRuntimeFixtures(codecept_data_dir('category.php'));\n\ndate_default_timezone_set(getenv('OXID_DEFAULT_TIMEZONE') ?: 'Europe/Berlin');\n"
  },
  {
    "path": "tests/Codeception/Acceptance.suite.yml",
    "content": "actor: AcceptanceTester\nbootstrap: _bootstrap.php\nmodules:\n    enabled:\n        - Asserts\n        - OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\Helper\\Acceptance\n        - WebDriver:\n            url: '%SHOP_URL%'\n            browser: '%BROWSER%'\n            port: '%SELENIUM_SERVER_PORT%'\n            host: '%SELENIUM_SERVER_HOST%'\n            window_size: 1920x1080\n            clear_cookies: true\n            restart: true\n            capabilities:\n                chromeOptions:\n                    prefs:\n                        'credentials_enable_service': false\n        - OxidEsales\\Codeception\\Module\\ShopSetup:\n            dump: '%DUMP_PATH%'\n            fixtures: '%FIXTURES_PATH%'\n            mysql_config: '%MYSQL_CONFIG_PATH%'\n            db_name: '%DB_NAME%'\n            out_directory_fixtures: '%OUT_DIRECTORY_FIXTURES%'\n            out_directory: '%OUT_DIRECTORY%'\n            theme_id: '%THEME_ID%'\n        - Db:\n            dsn: 'mysql:host=%DB_HOST%;dbname=%DB_NAME%;charset=utf8'\n            user: '%DB_USERNAME%'\n            password: '%DB_PASSWORD%'\n            port: '%DB_PORT%'\n            dump: '%DUMP_PATH%'\n            mysql_config: '%MYSQL_CONFIG_PATH%'\n            populate: true # run populator before all tests\n            cleanup: true # run populator before each test\n            populator: 'mysql --defaults-file=$mysql_config --default-character-set=utf8 $dbname < $dump'\n            initial_queries:\n                - 'SET @@SESSION.sql_mode=\"\"'\n        - OxidEsales\\Codeception\\Module\\Oxideshop:\n            screen_shot_url: '%SCREEN_SHOT_URL%'\n            page_load_timeout: '%PAGE_LOAD_TIMEOUT%'\n            depends:\n              - WebDriver\n              - Db\n        - OxidEsales\\Codeception\\Module\\OxideshopAdmin:\n              screen_shot_url: '%SCREEN_SHOT_URL%'\n              depends:\n                  - WebDriver\n        - OxidEsales\\Codeception\\Module\\Database:\n            depends: Db\n        - OxidEsales\\Codeception\\Module\\Translation\\TranslationsModule:\n            shop_path: '%PROJECT_ROOT%/source'\n            paths:\n                - 'Application/views/%THEME_ID%'\n            paths_admin:\n                - 'Application/views/admin_twig'\n        - OxidEsales\\Codeception\\Module\\OxideshopModules:\n        - Mailpit:\n            url: '%MAIL_HOST%'\n            port: '%MAIL_WEB_PORT%'\n        - OxidEsales\\Codeception\\Module\\Email:\n              depends:\n                  - Mailpit\n              deleteEmailsAfterTest: false\n        - OxidEsales\\Codeception\\Module\\ProjectConfiguration:\n              config_path: '%PROJECT_ROOT%/var/configuration'\n              parameters: []\n              services: []\nstep_decorators:\n    - Codeception\\Step\\Retry\n"
  },
  {
    "path": "tests/Codeception/Config/CodeceptionParametersProvider.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Config;\n\nuse OxidEsales\\Codeception\\Module\\Database;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\DataObject\\DatabaseConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition\\Edition;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition\\EditionDirectoriesLocator;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition\\EditionResolver;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Env\\DotenvLoader;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\DirectoryNotExistentException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\ProjectDirectoriesLocator;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\ProjectRootLocator;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass CodeceptionParametersProvider\n{\n    private DatabaseConfiguration $dbConfig;\n\n    public function getParameters(): array\n    {\n        $this->loadEnvironmentVariables();\n\n        $this->dbConfig = (new DatabaseConfiguration(getenv('OXID_DB_URL')));\n\n        return [\n            'SHOP_URL' => getenv('OXID_SHOP_BASE_URL'),\n            'PROJECT_ROOT' => $this->getProjectRoot(),\n            'VENDOR_PATH' => (new ProjectDirectoriesLocator())->getVendorPath(),\n            'DB_NAME' => $this->getDbName(),\n            'DB_USERNAME' => $this->getDbUser(),\n            'DB_PASSWORD' => $this->getDbPass(),\n            'DB_HOST' => $this->getDbHost(),\n            'DB_PORT' => $this->getDbPort(),\n            'DUMP_PATH' => $this->getTestDataDumpFilePath(),\n            'FIXTURES_PATH' => $this->getTestFixtureSqlFilePath(),\n            'OUT_DIRECTORY' => (new ProjectDirectoriesLocator())->getOutPath(),\n            'OUT_DIRECTORY_FIXTURES' => $this->getOutDirectoryFixturesPath(),\n            'MYSQL_CONFIG_PATH' => $this->generateMysqlStarUpConfigurationFile(),\n            'SELENIUM_SERVER_PORT' => getenv('SELENIUM_SERVER_PORT') ?: '4444',\n            'SELENIUM_SERVER_HOST' => getenv('SELENIUM_SERVER_HOST') ?: 'selenium',\n            'PHP_BIN' => (getenv('PHPBIN')) ?: 'php',\n            'SCREEN_SHOT_URL' => getenv('CC_SCREEN_SHOTS_URL') ?: '',\n            'BROWSER' => getenv('BROWSER_NAME') ?: 'chrome',\n            'THEME_ID' => getenv('THEME_ID') ?: 'apex',\n            'MAIL_HOST' => getenv('MAIL_HOST') ?: 'mailpit',\n            'MAIL_WEB_PORT' => getenv('MAIL_WEB_PORT') ?: '8025',\n            'PAGE_LOAD_TIMEOUT' => getenv('PAGE_LOAD_TIMEOUT') ?: 0.25,\n        ];\n    }\n\n    private function getTestDataDumpFilePath(): string\n    {\n        return Path::join(\n            $this->getShopTestPath(),\n            '/Codeception/Support/_generated/shop-dump.sql'\n        );\n    }\n\n    private function getTestFixtureSqlFilePath(): string\n    {\n        return Path::join(\n            $this->getShopTestPath(),\n            '/Codeception/Support/Data/test-fixtures.sql',\n        );\n    }\n\n    private function getOutDirectoryFixturesPath(): string\n    {\n        return Path::join(\n            $this->getShopTestPath(),\n            '/Codeception/Support/Data/out',\n        );\n    }\n\n    private function getShopTestPath(): string\n    {\n        $edition = (new EditionResolver())->getEdition();\n        $loader = new EditionDirectoriesLocator();\n\n        try {\n            return $edition->value === Edition::Enterprise->value\n                ? Path::join($loader->getEditionRootPath($edition), 'Tests')\n                : Path::join($loader->getEditionRootPath(Edition::Community), 'tests');\n        } catch (DirectoryNotExistentException) {\n            return Path::join($this->getProjectRoot(), 'tests');\n        }\n    }\n\n    private function generateMysqlStarUpConfigurationFile(): string\n    {\n        return Database::generateStartupOptionsFile(\n            $this->getDbUser(),\n            $this->getDbPass(),\n            $this->getDbHost(),\n            $this->getDbPort(),\n        );\n    }\n\n    private function getDbName(): string\n    {\n        return getenv('DB_NAME') ?: $this->dbConfig->getName();\n    }\n\n    private function getDbUser(): string\n    {\n        return getenv('DB_USERNAME') ?: $this->dbConfig->getUser();\n    }\n\n    private function getDbPass(): string\n    {\n        return getenv('DB_PASSWORD') ?: $this->dbConfig->getPass();\n    }\n\n    private function getDbHost(): string\n    {\n        return getenv('DB_HOST') ?: $this->dbConfig->getHost();\n    }\n\n    private function getDbPort(): int\n    {\n        return (int)getenv('DB_PORT') ?: $this->dbConfig->getPort();\n    }\n\n    private function loadEnvironmentVariables(): void\n    {\n        (new DotenvLoader($this->getProjectRoot()))->loadEnvironmentVariables();\n    }\n\n    private function getProjectRoot(): string\n    {\n        return (new ProjectRootLocator())->getProjectRoot();\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Config/params.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Config;\n\nreturn (new CodeceptionParametersProvider())->getParameters();\n"
  },
  {
    "path": "tests/Codeception/Support/AcceptanceActor.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support;\n\nuse Codeception\\Actor;\nuse Codeception\\Lib\\Actor\\Shared\\Retry;\nuse Codeception\\Scenario;\n\nclass AcceptanceActor extends Actor\n{\n    use Retry;\n\n    public function __construct(Scenario $scenario)\n    {\n        parent::__construct($scenario);\n        $this->retryNum = 5;\n        $this->retryInterval = 350;\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Support/AcceptanceTester.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support;\n\nuse Codeception\\Util\\Fixtures;\nuse OxidEsales\\Codeception\\Admin\\AdminLoginPage;\nuse OxidEsales\\Codeception\\Admin\\AdminPanel;\nuse OxidEsales\\Codeception\\Module\\Translation\\Translator;\nuse OxidEsales\\Codeception\\Page\\Home;\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\_generated\\AcceptanceTesterActions;\n\nclass AcceptanceTester extends AcceptanceActor\n{\n    use AcceptanceTesterActions;\n\n    public function openShop(): Home\n    {\n        Translator::switchTranslationDomain(\n            Translator::TRANSLATION_DOMAIN_SHOP\n        );\n        $I = $this;\n        $homePage = new Home($I);\n        $I->amOnPage($homePage->URL);\n        $I->waitForPageLoad();\n        return $homePage;\n    }\n\n    public function loginShopWithExistingUser(): Home\n    {\n        $homePage = $this->openShop();\n        $user = Fixtures::get('existingUser');\n        return $homePage->loginUser($user['userLoginName'], $user['userPassword']);\n    }\n\n    public function openAdmin(): AdminLoginPage\n    {\n        Translator::switchTranslationDomain(\n            Translator::TRANSLATION_DOMAIN_ADMIN\n        );\n        $I = $this;\n        $adminLogin = new AdminLoginPage($I);\n        $I->amOnPage($adminLogin->URL);\n        return $adminLogin;\n    }\n\n    public function loginAdmin(): AdminPanel\n    {\n        $adminPage = $this->openAdmin();\n        $admin = Fixtures::get('adminUser');\n        return $adminPage->login($admin['userLoginName'], $admin['userPassword']);\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/Support/Data/category.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nreturn [\n\t'testCategory0' => [\n\t\t'id' => 'testcategory0',\n\t\t'title' => 'Test category 0 [EN] šÄßüл',\n\t\t'description' => 'Test category 0 desc [EN] šÄßüл',\n\t\t'longDescription' => 'Category 0 long desc [EN] šÄßüл',\n\t],\n\t'testCategory1' => [\n\t\t'id' => 'testcategory1',\n\t\t'title' => 'Test category 1 [EN] šÄßüл',\n\t\t'description' => 'Test category 1 desc [EN] šÄßüл',\n\t\t'longDescription' => 'Category 1 long desc [EN] šÄßüл',\n\t],\n];\n"
  },
  {
    "path": "tests/Codeception/Support/Data/genericImport/oxartextends_with_header.csv",
    "content": "\"OXID\";\"OXLONGDESC\";\"OXLONGDESC_1\"\n\"1001\";\"long desc DE with header\";\"long desc EN with header\"\n"
  },
  {
    "path": "tests/Codeception/Support/Data/genericImport/oxartextends_without_header.csv",
    "content": "'1001','long desc DE no header','long desc EN no header'\n"
  },
  {
    "path": "tests/Codeception/Support/Data/modules/test-module-problems/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse Codeception\\TestModule\\Problems\\Model\\NonExistentFile;\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\n\n$sMetadataVersion = '2.1';\n\n$aModule = [\n    'id' => 'codeception_test-module-problems',\n    'title' => 'Module with problems (Namespaced)',\n    'description' => 'Test module validation for modules, which use namespaces',\n    'thumbnail' => '',\n    'version' => '1.0',\n    'author' => 'OXID',\n    'extend' => [\n        /** The class file does not exist at all and thus the class cannot be loaded */\n        Article::class => NonExistentFile::class\n    ],\n];\n"
  },
  {
    "path": "tests/Codeception/Support/Data/modules/testModule/Controller/ContentController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\Data\\modules\\testModule\\Controller;\n\nfinal class ContentController extends \\OxidEsales\\Eshop\\Application\\Controller\\ContentController\n{\n}\n"
  },
  {
    "path": "tests/Codeception/Support/Data/modules/testModule/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\Data\\modules\\testModule\\Controller\\ContentController;\n\n$sMetadataVersion = '2.1';\n\n$aModule = [\n    'id' => 'codeception_testModule',\n    'title' => 'Codeception test module #1',\n    'description' => 'Working module configuration with possible data types in settings',\n    'thumbnail' => '',\n    'version' => '1.0',\n    'author' => 'OXID',\n    'extend' => [\n        'content' => ContentController::class\n    ],\n    'settings' => [\n        /** Group of empty values */\n        [\n            'group' => 'settingsEmpty',\n            'name' => 'testEmptyBoolConfig',\n            'type' => 'bool',\n            'value' => 'false',\n        ],\n        [\n            'group' => 'settingsEmpty',\n            'name' => 'testEmptyStrConfig',\n            'type' => 'str',\n            'value' => '',\n        ],\n        [\n            'group' => 'settingsEmpty',\n            'name' => 'testEmptyArrConfig',\n            'type' => 'arr',\n            'value' => '',\n        ],\n        [\n            'group' => 'settingsEmpty',\n            'name' => 'testEmptyAArrConfig',\n            'type' => 'aarr',\n            'value' => '',\n        ],\n        [\n            'group' => 'settingsEmpty',\n            'name' => 'testEmptySelectConfig',\n            'type' => 'select',\n            'value' => '',\n            'constraints' => '0|1|2',\n        ],\n        [\n            'group' => 'settingsEmpty',\n            'name' => 'testEmptyPasswordConfig',\n            'type' => 'password',\n            'value' => '',\n        ],\n        /** Group of non-empty values */\n        [\n            'group' => 'settingsFilled',\n            'name' => 'testFilledBoolConfig',\n            'type' => 'bool',\n            'value' => 'true',\n        ],\n        [\n            'group' => 'settingsFilled',\n            'name' => 'testFilledStrConfig',\n            'type' => 'str',\n            'value' => 'testStr',\n        ],\n        [\n            'group' => 'settingsFilled',\n            'name' => 'testFilledArrConfig',\n            'type' => 'arr',\n            'value' => [\n                'option1',\n                'option2',\n            ],\n        ],\n        [\n            'group' => 'settingsFilled',\n            'name' => 'testFilledAArrConfig',\n            'type' => 'aarr',\n            'value' => [\n                'key1' => 'option1',\n                'key2' => 'option2',\n            ],\n        ],\n        [\n            'group' => 'settingsFilled',\n            'name' => 'testFilledSelectConfig',\n            'type' => 'select',\n            'value' => '2',\n            'constraints' => '0|1|2',\n            'position' => 3,\n        ],\n        [\n            'group' => 'settingsFilled',\n            'name' => 'testFilledPasswordConfig',\n            'type' => 'password',\n            'value' => 'testPassword',\n        ],\n    ]\n];\n"
  },
  {
    "path": "tests/Codeception/Support/Data/modules/testModule/views/admin_smarty/de/module_options.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$aLang = [\n    'charset' => 'UTF-8',\n\n    'SHOP_MODULE_GROUP_settingsEmpty' => 'Empty Settings Group',\n    'SHOP_MODULE_testEmptyBoolConfig' => 'Empty Bool Config',\n    'SHOP_MODULE_testEmptyStrConfig' => 'Empty String Config',\n    'SHOP_MODULE_testEmptyArrConfig' => 'Empty Array Config',\n    'SHOP_MODULE_testEmptyAArrConfig' => 'Empty Assoc Array Config',\n    'SHOP_MODULE_testEmptySelectConfig' => 'Empty Select Config',\n    'SHOP_MODULE_testEmptySelectConfig_0' => 'Option 0',\n    'SHOP_MODULE_testEmptySelectConfig_1' => 'Option 1',\n    'SHOP_MODULE_testEmptySelectConfig_2' => 'Option 2',\n    'SHOP_MODULE_testEmptyPasswordConfig' => 'Empty Password Config',\n\n    'SHOP_MODULE_GROUP_settingsFilled' => 'Filled Settings Group',\n    'SHOP_MODULE_testFilledBoolConfig' => 'Filled Bool Config',\n    'SHOP_MODULE_testFilledStrConfig' => 'Filled String Config',\n    'SHOP_MODULE_testFilledArrConfig' => 'Filled Array Config',\n    'SHOP_MODULE_testFilledAArrConfig' => 'Filled Assoc Array Config',\n    'SHOP_MODULE_testFilledSelectConfig' => 'Filled Select Config',\n    'SHOP_MODULE_testFilledSelectConfig_0' => 'Option 0',\n    'SHOP_MODULE_testFilledSelectConfig_1' => 'Option 1',\n    'SHOP_MODULE_testFilledSelectConfig_2' => 'Option 2',\n    'SHOP_MODULE_testFilledPasswordConfig' => 'Filled Password Config',\n];\n"
  },
  {
    "path": "tests/Codeception/Support/Data/modules/testModule/views/admin_smarty/en/module_options.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$aLang = [\n    'charset' => 'UTF-8',\n\n    'SHOP_MODULE_GROUP_settingsEmpty' => 'Empty Settings Group',\n    'SHOP_MODULE_testEmptyBoolConfig' => 'Empty Bool Config',\n    'SHOP_MODULE_testEmptyStrConfig' => 'Empty String Config',\n    'SHOP_MODULE_testEmptyArrConfig' => 'Empty Array Config',\n    'SHOP_MODULE_testEmptyAArrConfig' => 'Empty Assoc Array Config',\n    'SHOP_MODULE_testEmptySelectConfig' => 'Empty Select Config',\n    'SHOP_MODULE_testEmptySelectConfig_0' => 'Option 0',\n    'SHOP_MODULE_testEmptySelectConfig_1' => 'Option 1',\n    'SHOP_MODULE_testEmptySelectConfig_2' => 'Option 2',\n    'SHOP_MODULE_testEmptyPasswordConfig' => 'Empty Password Config',\n\n    'SHOP_MODULE_GROUP_settingsFilled' => 'Filled Settings Group',\n    'SHOP_MODULE_testFilledBoolConfig' => 'Filled Bool Config',\n    'SHOP_MODULE_testFilledStrConfig' => 'Filled String Config',\n    'SHOP_MODULE_testFilledArrConfig' => 'Filled Array Config',\n    'SHOP_MODULE_testFilledAArrConfig' => 'Filled Assoc Array Config',\n    'SHOP_MODULE_testFilledSelectConfig' => 'Filled Select Config',\n    'SHOP_MODULE_testFilledSelectConfig_0' => 'Option 0',\n    'SHOP_MODULE_testFilledSelectConfig_1' => 'Option 1',\n    'SHOP_MODULE_testFilledSelectConfig_2' => 'Option 2',\n    'SHOP_MODULE_testFilledPasswordConfig' => 'Filled Password Config',\n];\n"
  },
  {
    "path": "tests/Codeception/Support/Data/modules/testModule/views/admin_twig/de/module_options.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$aLang = [\n    'charset' => 'UTF-8',\n\n    'SHOP_MODULE_GROUP_settingsEmpty' => 'Empty Settings Group',\n    'SHOP_MODULE_testEmptyBoolConfig' => 'Empty Bool Config',\n    'SHOP_MODULE_testEmptyStrConfig' => 'Empty String Config',\n    'SHOP_MODULE_testEmptyArrConfig' => 'Empty Array Config',\n    'SHOP_MODULE_testEmptyAArrConfig' => 'Empty Assoc Array Config',\n    'SHOP_MODULE_testEmptySelectConfig' => 'Empty Select Config',\n    'SHOP_MODULE_testEmptySelectConfig_0' => 'Option 0',\n    'SHOP_MODULE_testEmptySelectConfig_1' => 'Option 1',\n    'SHOP_MODULE_testEmptySelectConfig_2' => 'Option 2',\n    'SHOP_MODULE_testEmptyPasswordConfig' => 'Empty Password Config',\n\n    'SHOP_MODULE_GROUP_settingsFilled' => 'Filled Settings Group',\n    'SHOP_MODULE_testFilledBoolConfig' => 'Filled Bool Config',\n    'SHOP_MODULE_testFilledStrConfig' => 'Filled String Config',\n    'SHOP_MODULE_testFilledArrConfig' => 'Filled Array Config',\n    'SHOP_MODULE_testFilledAArrConfig' => 'Filled Assoc Array Config',\n    'SHOP_MODULE_testFilledSelectConfig' => 'Filled Select Config',\n    'SHOP_MODULE_testFilledSelectConfig_0' => 'Option 0',\n    'SHOP_MODULE_testFilledSelectConfig_1' => 'Option 1',\n    'SHOP_MODULE_testFilledSelectConfig_2' => 'Option 2',\n    'SHOP_MODULE_testFilledPasswordConfig' => 'Filled Password Config',\n];\n"
  },
  {
    "path": "tests/Codeception/Support/Data/modules/testModule/views/admin_twig/en/module_options.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$aLang = [\n    'charset' => 'UTF-8',\n\n    'SHOP_MODULE_GROUP_settingsEmpty' => 'Empty Settings Group',\n    'SHOP_MODULE_testEmptyBoolConfig' => 'Empty Bool Config',\n    'SHOP_MODULE_testEmptyStrConfig' => 'Empty String Config',\n    'SHOP_MODULE_testEmptyArrConfig' => 'Empty Array Config',\n    'SHOP_MODULE_testEmptyAArrConfig' => 'Empty Assoc Array Config',\n    'SHOP_MODULE_testEmptySelectConfig' => 'Empty Select Config',\n    'SHOP_MODULE_testEmptySelectConfig_0' => 'Option 0',\n    'SHOP_MODULE_testEmptySelectConfig_1' => 'Option 1',\n    'SHOP_MODULE_testEmptySelectConfig_2' => 'Option 2',\n    'SHOP_MODULE_testEmptyPasswordConfig' => 'Empty Password Config',\n\n    'SHOP_MODULE_GROUP_settingsFilled' => 'Filled Settings Group',\n    'SHOP_MODULE_testFilledBoolConfig' => 'Filled Bool Config',\n    'SHOP_MODULE_testFilledStrConfig' => 'Filled String Config',\n    'SHOP_MODULE_testFilledArrConfig' => 'Filled Array Config',\n    'SHOP_MODULE_testFilledAArrConfig' => 'Filled Assoc Array Config',\n    'SHOP_MODULE_testFilledSelectConfig' => 'Filled Select Config',\n    'SHOP_MODULE_testFilledSelectConfig_0' => 'Option 0',\n    'SHOP_MODULE_testFilledSelectConfig_1' => 'Option 1',\n    'SHOP_MODULE_testFilledSelectConfig_2' => 'Option 2',\n    'SHOP_MODULE_testFilledPasswordConfig' => 'Filled Password Config',\n];\n"
  },
  {
    "path": "tests/Codeception/Support/Data/order.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nreturn [\n    'testorder' =>\n        [\n            'OXID' => 'testorder',\n            'OXSHOPID' => 1,\n            'OXUSERID' => 'testuser',\n            'OXORDERDATE' => '2023-03-30 11:00:04',\n            'OXORDERNR' => '123',\n            'OXBILLCOMPANY' => 'test bill company_name',\n            'OXBILLEMAIL' => 'test@billemail.com',\n            'OXBILLFNAME' => 'test bill fname',\n            'OXBILLLNAME' => 'test bill lname',\n            'OXBILLSTREET' => 'test address street',\n            'OXBILLSTREETNR' => 'streetNBR',\n            'OXBILLADDINFO' => 'test address info',\n            'OXBILLCITY' => 'test address city',\n            'OXBILLCOUNTRYID' => 'testcountry_de',\n            'OXBILLZIP' => '55555',\n            'OXREMARK' => 'custom user order remark',\n            'OXFOLDER' => 'ORDERFOLDER_NEW',\n            'OXBILLSTATEID' => 'BB',\n            'OXCARDTEXT' => '',\n            'PRODUCTS' => [\n                [\n                    'OXID' => '919edbc539f414bdefc7f6975bbdf2a1',\n                    'OXAMOUNT' => '100',\n                    'OXARTID' => '1000',\n                    'OXARTNUM' => '1000',\n                    'OXTITLE' => '[DE 4] Test product 0 šÄßüл'\n                ],\n                [\n                    'OXID' => '919edbc539f414bdefc7f6975bbdf2b6',\n                    'OXAMOUNT' => '150',\n                    'OXARTID' => '1001',\n                    'OXARTNUM' => '1001',\n                    'OXTITLE' => '[DE 1] Test product 1 šÄßüл'\n                ],\n            ]\n        ]\n];\n"
  },
  {
    "path": "tests/Codeception/Support/Data/product.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nreturn [\n    'product-10014' => [\n        'OXID' => '10014',\n        'OXSHOPID' => 1,\n        'OXPARENTID' => '',\n        'OXACTIVE' => 1,\n        'OXARTNUM' => '10014',\n        'OXTITLE' => '13 DE product šÄßüл',\n        'OXSHORTDESC' => '14 DE description',\n        'OXPRICE' => '1.6',\n        'OXPRICEA' => 0,\n        'OXPRICEB' => 0,\n        'OXPRICEC' => 0,\n        'OXTPRICE' => 0,\n        'OXUNITNAME' => '',\n        'OXUNITQUANTITY' => 0,\n        'OXVAT' => null,\n        'OXWEIGHT' => 0,\n        'OXSTOCK' => 0,\n        'OXSTOCKFLAG' => 1,\n        'OXSTOCKTEXT' => '',\n        'OXNOSTOCKTEXT' => '',\n        'OXDELIVERY' => '0000-00-00',\n        'OXINSERT' => '2008-04-03',\n        'OXLENGTH' => 0,\n        'OXWIDTH' => 0,\n        'OXHEIGHT' => 0,\n        'OXSEARCHKEYS' => '',\n        'OXISSEARCH' => 1,\n        'OXVARNAME' => 'size[DE] | color | type',\n        'OXVARSTOCK' => 0,\n        'OXVARCOUNT' => 12,\n        'OXVARSELECT' => '',\n        'OXVARMINPRICE' => 15,\n        'OXVARMAXPRICE' => 25,\n        'OXVARNAME_1' => 'size[EN] | color | type',\n        'OXVARSELECT_1' => '',\n        'OXTITLE_1' => '14 EN product šÄßüл',\n        'OXSHORTDESC_1' => '13 EN description šÄßüл',\n        'OXSEARCHKEYS_1' => '',\n        'OXBUNDLEID' => '',\n        'OXSTOCKTEXT_1' => '',\n        'OXNOSTOCKTEXT_1' => '',\n        'OXSORT' => 0,\n        'OXVENDORID' => '',\n        'OXMANUFACTURERID' => '',\n        'OXMINDELTIME' => 0,\n        'OXMAXDELTIME' => 0,\n        'OXDELTIMEUNIT' => '',\n    ],\n    'product-1001432' => [\n        'OXID' => '1001432',\n        'OXSHOPID' => 1,\n        'OXPARENTID' => '10014',\n        'OXACTIVE' => 1,\n        'OXARTNUM' => '10014-3-2',\n        'OXPRICE' => '15',\n        'OXSTOCK' => 3,\n        'OXSTOCKFLAG' => 1,\n        'OXINSERT' => '2008-04-03',\n        'OXVARSELECT' => 'M | black | material [DE]',\n        'OXVARSELECT_1' => 'L | black | material',\n        'OXSUBCLASS' => 'oxarticle',\n        'OXSORT' => 3002,\n    ],\n    'product-1001424' => [\n        'OXID' => '1001424',\n        'OXSHOPID' => 1,\n        'OXPARENTID' => '10014',\n        'OXACTIVE' => 1,\n        'OXARTNUM' => '10014-2-4',\n        'OXPRICE' => '15',\n        'OXSTOCK' => 0,\n        'OXSTOCKFLAG' => 1,\n        'OXINSERT' => '2008-04-03',\n        'OXVARSELECT' => 'M | red [DE]',\n        'OXVARSELECT_1' => 'M | red',\n        'OXSUBCLASS' => 'oxarticle',\n        'OXSORT' => 2004,\n    ],\n    'product-1001422' => [\n        'OXID' => '1001422',\n        'OXSHOPID' => 1,\n        'OXPARENTID' => '10014',\n        'OXACTIVE' => 1,\n        'OXARTNUM' => '10014-2-2',\n        'OXPRICE' => '15',\n        'OXSTOCK' => 0,\n        'OXSTOCKFLAG' => 3,\n        'OXINSERT' => '2008-04-03',\n        'OXVARSELECT' => 'M | black | material [DE]',\n        'OXVARSELECT_1' => 'M | black | material',\n        'OXSUBCLASS' => 'oxarticle',\n        'OXSORT' => 2002,\n    ],\n    'product-1001421' => [\n        'OXID' => '1001421',\n        'OXSHOPID' => 1,\n        'OXPARENTID' => '10014',\n        'OXACTIVE' => 1,\n        'OXARTNUM' => '10014-2-1',\n        'OXPRICE' => '25',\n        'OXSTOCK' => 0,\n        'OXSTOCKFLAG' => 2,\n        'OXINSERT' => '2008-04-03',\n        'OXVARSELECT' => 'M | black | lether [DE]',\n        'OXVARSELECT_1' => 'M | black | lether',\n        'OXSUBCLASS' => 'oxarticle',\n        'OXSORT' => 2001,\n    ],\n    'product-1001411' => [\n        'OXID' => '1001411',\n        'OXSHOPID' => 1,\n        'OXPARENTID' => '10014',\n        'OXACTIVE' => 1,\n        'OXARTNUM' => '10014-1-1',\n        'OXPRICE' => '25',\n        'OXSTOCK' => 3,\n        'OXSTOCKFLAG' => 1,\n        'OXINSERT' => '2008-04-03',\n        'OXVARSELECT' => 'S | black | lether [DE]',\n        'OXVARSELECT_1' => 'S | black | lether',\n        'OXSUBCLASS' => 'oxarticle',\n        'OXSORT' => 1001,\n    ],\n    'product-1001413' => [\n        'OXID' => '1001413',\n        'OXSHOPID' => 1,\n        'OXPARENTID' => '10014',\n        'OXACTIVE' => 1,\n        'OXARTNUM' => '10014-1-3',\n        'OXPRICE' => '15',\n        'OXSTOCK' => 3,\n        'OXSTOCKFLAG' => 1,\n        'OXINSERT' => '2008-04-03',\n        'OXVARSELECT' => 'S | white [DE]',\n        'OXVARSELECT_1' => 'S | white',\n        'OXSUBCLASS' => 'oxarticle',\n        'OXSORT' => 1003,\n    ],\n    'product-1001412' => [\n        'OXID' => '1001412',\n        'OXSHOPID' => 1,\n        'OXPARENTID' => '10014',\n        'OXACTIVE' => 1,\n        'OXARTNUM' => '10014-1-2',\n        'OXPRICE' => '15',\n        'OXSTOCK' => 3,\n        'OXSTOCKFLAG' => 1,\n        'OXINSERT' => '2008-04-03',\n        'OXVARSELECT' => 'S | black | material [DE]',\n        'OXVARSELECT_1' => 'S | black | material',\n        'OXSUBCLASS' => 'oxarticle',\n        'OXSORT' => 1002,\n    ],\n    'product-1001434' => [\n        'OXID' => '1001434',\n        'OXSHOPID' => 1,\n        'OXPARENTID' => '10014',\n        'OXACTIVE' => 0,\n        'OXARTNUM' => '10014-3-4',\n        'OXPRICE' => '15',\n        'OXSTOCK' => 3,\n        'OXSTOCKFLAG' => 1,\n        'OXINSERT' => '2008-04-03',\n        'OXVARSELECT' => 'L | red [DE]',\n        'OXVARSELECT_1' => 'L | red',\n        'OXSUBCLASS' => 'oxarticle',\n        'OXSORT' => 3004,\n    ],\n    'product-1001423' => [\n        'OXID' => '1001423',\n        'OXSHOPID' => 1,\n        'OXPARENTID' => '10014',\n        'OXACTIVE' => 1,\n        'OXARTNUM' => '10014-2-3',\n        'OXPRICE' => '15',\n        'OXSTOCK' => 0,\n        'OXSTOCKFLAG' => 1,\n        'OXINSERT' => '2008-04-03',\n        'OXVARSELECT' => 'M | white [DE]',\n        'OXVARSELECT_1' => 'M | white',\n        'OXSUBCLASS' => 'oxarticle',\n        'OXSORT' => 2003,\n    ],\n    'product-1001414' => [\n        'OXID' => '1001414',\n        'OXSHOPID' => 1,\n        'OXPARENTID' => '10014',\n        'OXACTIVE' => 1,\n        'OXARTNUM' => '10014-1-4',\n        'OXPRICE' => '15',\n        'OXSTOCK' => 3,\n        'OXSTOCKFLAG' => 1,\n        'OXINSERT' => '2008-04-03',\n        'OXVARSELECT' => 'S | red [DE]',\n        'OXVARSELECT_1' => 'S | red',\n        'OXSUBCLASS' => 'oxarticle',\n        'OXSORT' => 1004,\n    ],\n    'product-1001431' => [\n        'OXID' => '1001431',\n        'OXSHOPID' => 1,\n        'OXPARENTID' => '10014',\n        'OXACTIVE' => 1,\n        'OXARTNUM' => '10014-3-1',\n        'OXPRICE' => '15',\n        'OXSTOCK' => 3,\n        'OXSTOCKFLAG' => 1,\n        'OXINSERT' => '2008-04-03',\n        'OXVARSELECT' => 'L | black | lether [DE]',\n        'OXVARSELECT_1' => 'L | black | lether',\n        'OXSUBCLASS' => 'oxarticle',\n        'OXSORT' => 3001,\n    ],\n    'product-1001433' => [\n        'OXID' => '1001433',\n        'OXSHOPID' => 1,\n        'OXPARENTID' => '10014',\n        'OXACTIVE' => 1,\n        'OXARTNUM' => '10014-3-3',\n        'OXPRICE' => '15',\n        'OXSTOCK' => 3,\n        'OXSTOCKFLAG' => 1,\n        'OXINSERT' => '2008-04-03',\n        'OXVARSELECT' => 'L | white [DE]',\n        'OXVARSELECT_1' => 'L | white',\n        'OXSUBCLASS' => 'oxarticle',\n        'OXSORT' => 3003,\n    ],\n    'product-1000' => [\n        'OXID' => '1000',\n        'OXSHOPID' => 1,\n        'OXPARENTID' => '',\n        'OXACTIVE' => 1,\n        'OXARTNUM' => '1000',\n        'OXTITLE' => '[DE 4] Test product 0 šÄßüл',\n        'OXSHORTDESC' => 'Test product 0 short desc [DE]',\n        'OXPRICE' => '50',\n        'OXPRICEA' => 35,\n        'OXPRICEB' => 45,\n        'OXPRICEC' => 55,\n        'OXTPRICE' => 0,\n        'OXUNITNAME' => 'kg',\n        'OXUNITQUANTITY' => 2,\n        'OXVAT' => null,\n        'OXWEIGHT' => 2,\n        'OXSTOCK' => 15,\n        'OXSTOCKFLAG' => 1,\n        'OXSTOCKTEXT' => 'In stock [DE]',\n        'OXNOSTOCKTEXT' => 'Out of stock [DE]',\n        'OXDELIVERY' => '0000-00-00',\n        'OXINSERT' => '2008-04-03',\n        'OXLENGTH' => 1,\n        'OXWIDTH' => 2,\n        'OXHEIGHT' => 2,\n        'OXSEARCHKEYS' => 'search1000',\n        'OXISSEARCH' => 1,\n        'OXVARNAME' => '',\n        'OXVARSTOCK' => 0,\n        'OXVARCOUNT' => 0,\n        'OXVARSELECT' => '',\n        'OXVARMINPRICE' => 50,\n        'OXVARMAXPRICE' => 0,\n        'OXVARNAME_1' => '',\n        'OXVARSELECT_1' => '',\n        'OXTITLE_1' => 'Test product 0 [EN] šÄßüл',\n        'OXSHORTDESC_1' => 'Test product 0 short desc [EN] šÄßüл',\n        'OXSEARCHKEYS_1' => 'šÄßüл1000',\n        'OXBUNDLEID' => '',\n        'OXSTOCKTEXT_1' => 'In stock [EN] šÄßüл',\n        'OXNOSTOCKTEXT_1' => 'Out of stock [EN] šÄßüл',\n        'OXSORT' => 0,\n        'OXVENDORID' => 'testdistributor',\n        'OXMANUFACTURERID' => 'testmanufacturer',\n        'OXMINDELTIME' => 1,\n        'OXMAXDELTIME' => 1,\n        'OXDELTIMEUNIT' => 'DAY',\n    ],\n    'product-1002' => [\n        'OXID' => '1002',\n        'OXSHOPID' => 1,\n        'OXPARENTID' => '',\n        'OXACTIVE' => 1,\n        'OXARTNUM' => '1002',\n        'OXTITLE' => '[DE 2] Test product 2 šÄßüл',\n        'OXSHORTDESC' => 'Test product 2 short desc [DE]',\n        'OXPRICE' => '55',\n        'OXPRICEA' => 0,\n        'OXPRICEB' => 0,\n        'OXPRICEC' => 0,\n        'OXTPRICE' => 0,\n        'OXUNITNAME' => '',\n        'OXUNITQUANTITY' => 0,\n        'OXVAT' => null,\n        'OXWEIGHT' => 0,\n        'OXSTOCK' => 0,\n        'OXSTOCKFLAG' => 1,\n        'OXSTOCKTEXT' => 'In stock [DE]',\n        'OXNOSTOCKTEXT' => 'Out of stock [DE]',\n        'OXDELIVERY' => '0000-00-00',\n        'OXINSERT' => '2008-04-03',\n        'OXLENGTH' => 0,\n        'OXWIDTH' => 0,\n        'OXHEIGHT' => 0,\n        'OXSEARCHKEYS' => 'search1002',\n        'OXISSEARCH' => 1,\n        'OXVARNAME' => 'variants [DE]',\n        'OXVARSTOCK' => 10,\n        'OXVARCOUNT' => 2,\n        'OXVARSELECT' => '',\n        'OXVARMINPRICE' => 55,\n        'OXVARMAXPRICE' => 67,\n        'OXVARNAME_1' => 'variants [EN] šÄßüл',\n        'OXVARSELECT_1' => '',\n        'OXTITLE_1' => 'Test product 2 [EN] šÄßüл',\n        'OXSHORTDESC_1' => 'Test product 2 short desc [EN] šÄßüл',\n        'OXSEARCHKEYS_1' => 'šÄßüл1002',\n        'OXBUNDLEID' => '',\n        'OXSTOCKTEXT_1' => 'In stock [EN] šÄßüл',\n        'OXNOSTOCKTEXT_1' => 'Out of stock [EN] šÄßüл',\n        'OXSORT' => 0,\n        'OXVENDORID' => 'testdistributor',\n        'OXMANUFACTURERID' => 'testmanufacturer',\n        'OXMINDELTIME' => 1,\n        'OXMAXDELTIME' => 1,\n        'OXDELTIMEUNIT' => 'MONTH',\n    ],\n    'product-1002-1' => [\n        'OXID' => '1002-1',\n        'OXSHOPID' => 1,\n        'OXPARENTID' => '1002',\n        'OXACTIVE' => 1,\n        'OXARTNUM' => '1002-1',\n        'OXTITLE' => '',\n        'OXSHORTDESC' => '',\n        'OXPRICE' => '55',\n        'OXPRICEA' => 45,\n        'OXPRICEB' => 0,\n        'OXPRICEC' => 0,\n        'OXTPRICE' => 0,\n        'OXUNITNAME' => '',\n        'OXUNITQUANTITY' => 0,\n        'OXVAT' => null,\n        'OXWEIGHT' => 0,\n        'OXSTOCK' => 5,\n        'OXSTOCKFLAG' => 1,\n        'OXSTOCKTEXT' => 'In stock [DE]',\n        'OXNOSTOCKTEXT' => 'Out of stock [DE]',\n        'OXDELIVERY' => '0000-00-00',\n        'OXINSERT' => '2008-04-03',\n        'OXLENGTH' => 0,\n        'OXWIDTH' => 0,\n        'OXHEIGHT' => 0,\n        'OXSEARCHKEYS' => '',\n        'OXISSEARCH' => 1,\n        'OXVARNAME' => '',\n        'OXVARSTOCK' => 0,\n        'OXVARCOUNT' => 0,\n        'OXVARSELECT' => 'var1 [DE]',\n        'OXVARMINPRICE' => 0,\n        'OXVARMAXPRICE' => 0,\n        'OXVARNAME_1' => '',\n        'OXVARSELECT_1' => 'var1 [EN] šÄßüл',\n        'OXTITLE_1' => '',\n        'OXSHORTDESC_1' => '',\n        'OXSEARCHKEYS_1' => '',\n        'OXBUNDLEID' => '',\n        'OXSTOCKTEXT_1' => 'In stock [EN] šÄßüл',\n        'OXNOSTOCKTEXT_1' => 'Out of stock [EN] šÄßüл',\n        'OXSORT' => 1,\n        'OXVENDORID' => '',\n        'OXMANUFACTURERID' => '',\n        'OXMINDELTIME' => 0,\n        'OXMAXDELTIME' => 0,\n        'OXDELTIMEUNIT' => '',\n    ],\n    'product-1002-2' => [\n        'OXID' => '1002-2',\n        'OXSHOPID' => 1,\n        'OXPARENTID' => '1002',\n        'OXACTIVE' => 1,\n        'OXARTNUM' => '1002-2',\n        'OXTITLE' => '',\n        'OXSHORTDESC' => '',\n        'OXPRICE' => '67',\n        'OXPRICEA' => 47,\n        'OXPRICEB' => 0,\n        'OXPRICEC' => 0,\n        'OXTPRICE' => 0,\n        'OXUNITNAME' => '',\n        'OXUNITQUANTITY' => 0,\n        'OXVAT' => null,\n        'OXWEIGHT' => 0,\n        'OXSTOCK' => 5,\n        'OXSTOCKFLAG' => 1,\n        'OXSTOCKTEXT' => 'In stock [DE]',\n        'OXNOSTOCKTEXT' => 'Out of stock [DE]',\n        'OXDELIVERY' => '0000-00-00',\n        'OXINSERT' => '2008-04-03',\n        'OXLENGTH' => 0,\n        'OXWIDTH' => 0,\n        'OXHEIGHT' => 0,\n        'OXSEARCHKEYS' => '',\n        'OXISSEARCH' => 1,\n        'OXVARNAME' => '',\n        'OXVARSTOCK' => 0,\n        'OXVARCOUNT' => 0,\n        'OXVARSELECT' => 'var2 [DE]',\n        'OXVARMINPRICE' => 0,\n        'OXVARMAXPRICE' => 0,\n        'OXVARNAME_1' => '',\n        'OXVARSELECT_1' => 'var2 [EN] šÄßüл',\n        'OXTITLE_1' => '',\n        'OXSHORTDESC_1' => '',\n        'OXSEARCHKEYS_1' => '',\n        'OXBUNDLEID' => '',\n        'OXSTOCKTEXT_1' => 'In stock [EN] šÄßüл',\n        'OXNOSTOCKTEXT_1' => 'Out of stock [EN] šÄßüл',\n        'OXSORT' => 2,\n        'OXVENDORID' => '',\n        'OXMANUFACTURERID' => '',\n        'OXMINDELTIME' => 0,\n        'OXMAXDELTIME' => 0,\n        'OXDELTIMEUNIT' => '',\n    ],\n    'product-1001' => [\n        'OXID' => '1001',\n        'OXSHOPID' => 1,\n        'OXPARENTID' => '',\n        'OXACTIVE' => 1,\n        'OXARTNUM' => '1001',\n        'OXTITLE' => '[DE 1] Test product 1 šÄßüл',\n        'OXSHORTDESC' => 'Test product 1 short desc [DE]',\n        'OXPRICE' => '100',\n        'OXPRICEA' => 0,\n        'OXPRICEB' => 0,\n        'OXPRICEC' => 0,\n        'OXTPRICE' => 150,\n        'OXUNITNAME' => '',\n        'OXUNITQUANTITY' => 0,\n        'OXVAT' => 10,\n        'OXWEIGHT' => 0,\n        'OXSTOCK' => 0,\n        'OXSTOCKFLAG' => 1,\n        'OXSTOCKTEXT' => '',\n        'OXNOSTOCKTEXT' => '',\n        'OXDELIVERY' => '0000-00-00',\n        'OXINSERT' => '2008-04-03',\n        'OXLENGTH' => 0,\n        'OXWIDTH' => 0,\n        'OXHEIGHT' => 0,\n        'OXSEARCHKEYS' => 'search1001',\n        'OXISSEARCH' => 1,\n        'OXVARNAME' => '',\n        'OXVARSTOCK' => 0,\n        'OXVARCOUNT' => 0,\n        'OXVARSELECT' => '',\n        'OXVARMINPRICE' => 100,\n        'OXVARMAXPRICE' => 0,\n        'OXVARNAME_1' => '',\n        'OXVARSELECT_1' => '',\n        'OXTITLE_1' => 'Test product 1 [EN] šÄßüл',\n        'OXSHORTDESC_1' => 'Test product 1 short desc [EN] šÄßüл',\n        'OXSEARCHKEYS_1' => 'šÄßüл1001',\n        'OXBUNDLEID' => '',\n        'OXSTOCKTEXT_1' => '',\n        'OXNOSTOCKTEXT_1' => '',\n        'OXSORT' => 0,\n        'OXVENDORID' => 'testdistributor',\n        'OXMANUFACTURERID' => 'testmanufacturer',\n        'OXMINDELTIME' => 0,\n        'OXMAXDELTIME' => 1,\n        'OXDELTIMEUNIT' => 'WEEK',\n    ],\n];\n"
  },
  {
    "path": "tests/Codeception/Support/Data/product_image.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n"
  },
  {
    "path": "tests/Codeception/Support/Data/shop.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nreturn [\n    'shop-1' =>\n        [\n            'OXID' => 1,\n            'OXACTIVE' => 1,\n            'OXPRODUCTIVE' => 0,\n            'OXDEFCURRENCY' => '',\n            'OXDEFLANGUAGE' => 0,\n            'OXNAME' => 'OXID eShop',\n            'OXTITLEPREFIX' => 'OXID eShop',\n            'OXTITLEPREFIX_1' => 'OXID eShop',\n            'OXTITLEPREFIX_2' => '',\n            'OXTITLEPREFIX_3' => '',\n            'OXTITLESUFFIX' => 'online kaufen',\n            'OXTITLESUFFIX_1' => 'purchase online',\n            'OXTITLESUFFIX_2' => '',\n            'OXTITLESUFFIX_3' => '',\n            'OXSTARTTITLE' => 'Der Onlineshop',\n            'OXSTARTTITLE_1' => 'Online Shop',\n            'OXSTARTTITLE_2' => '',\n            'OXSTARTTITLE_3' => '',\n            'OXINFOEMAIL' => 'info@myoxideshop.com',\n            'OXORDEREMAIL' => 'order@myoxideshop.com',\n            'OXOWNEREMAIL' => 'reply@myoxideshop.com',\n            'OXORDERSUBJECT' => 'Ihre Bestellung bei OXID eSales',\n            'OXREGISTERSUBJECT' => 'Vielen Dank für Ihre Registrierung im OXID eShop',\n            'OXFORGOTPWDSUBJECT' => 'Ihr Passwort im OXID eShop',\n            'OXSENDEDNOWSUBJECT' => 'Ihre OXID eSales Bestellung wurde versandt',\n            'OXORDERSUBJECT_1' => 'Your order at OXID eShop',\n            'OXREGISTERSUBJECT_1' => 'Thank you for your registration at OXID eShop',\n            'OXFORGOTPWDSUBJECT_1' => 'Your OXID eShop password',\n            'OXSENDEDNOWSUBJECT_1' => 'Your OXID eSales Order has been shipped',\n            'OXORDERSUBJECT_2' => '',\n            'OXREGISTERSUBJECT_2' => '',\n            'OXFORGOTPWDSUBJECT_2' => '',\n            'OXSENDEDNOWSUBJECT_2' => '',\n            'OXORDERSUBJECT_3' => '',\n            'OXREGISTERSUBJECT_3' => '',\n            'OXFORGOTPWDSUBJECT_3' => '',\n            'OXSENDEDNOWSUBJECT_3' => '',\n            'OXSMTP' => '',\n            'OXSMTPUSER' => '',\n            'OXSMTPPWD' => '',\n            'OXCOMPANY' => 'Your Company Name',\n            'OXSTREET' => '2425 Maple Street',\n            'OXZIP' => '9041',\n            'OXCITY' => 'Any City, CA',\n            'OXCOUNTRY' => 'United States',\n            'OXBANKNAME' => 'Bank of America',\n            'OXBANKNUMBER' => '1234567890',\n            'OXBANKCODE' => '900 1234567',\n            'OXVATNUMBER' => '',\n            'OXTAXNUMBER' => '',\n            'OXBICCODE' => '',\n            'OXIBANNUMBER' => '',\n            'OXFNAME' => 'John',\n            'OXLNAME' => 'Doe',\n            'OXTELEFON' => '217-8918712',\n            'OXTELEFAX' => '217-8918713',\n            'OXURL' => 'www.myoxideshop.com',\n            'OXDEFCAT' => '',\n            'OXHRBNR' => '',\n            'OXCOURT' => '',\n            'OXADBUTLERID' => '',\n            'OXAFFILINETID' => '',\n            'OXSUPERCLICKSID' => '',\n            'OXAFFILIWELTID' => '',\n            'OXAFFILI24ID' => '',\n            'OXEDITION' => 'CE',\n            'OXVERSION' => '6.0.0',\n            'OXSEOACTIVE' => 1,\n            'OXSEOACTIVE_1' => 1,\n            'OXSEOACTIVE_2' => 0,\n            'OXSEOACTIVE_3' => 0,\n        ]\n];\n"
  },
  {
    "path": "tests/Codeception/Support/Data/test-fixtures.sql",
    "content": "SET @@session.sql_mode = '';\n\nREPLACE INTO `oxarticles` (`OXID`, `OXSHOPID`, `OXPARENTID`, `OXACTIVE`, `OXARTNUM`, `OXTITLE`, `OXSHORTDESC`, `OXPRICE`, `OXPRICEA`, `OXPRICEB`, `OXPRICEC`, `OXTPRICE`, `OXUNITNAME`, `OXUNITQUANTITY`, `OXVAT`, `OXWEIGHT`, `OXSTOCK`, `OXSTOCKFLAG`, `OXSTOCKTEXT`, `OXNOSTOCKTEXT`, `OXDELIVERY`, `OXINSERT`, `OXTIMESTAMP`, `OXLENGTH`, `OXWIDTH`, `OXHEIGHT`, `OXSEARCHKEYS`, `OXISSEARCH`, `OXVARNAME`, `OXVARSTOCK`, `OXVARCOUNT`, `OXVARSELECT`, `OXVARMINPRICE`, `OXVARMAXPRICE`, `OXVARNAME_1`, `OXVARSELECT_1`, `OXTITLE_1`, `OXSHORTDESC_1`, `OXSEARCHKEYS_1`, `OXBUNDLEID`, `OXSTOCKTEXT_1`, `OXNOSTOCKTEXT_1`, `OXSORT`, `OXVENDORID`, `OXMANUFACTURERID`, `OXMINDELTIME`, `OXMAXDELTIME`, `OXDELTIMEUNIT`)\nVALUES ('1000', 1, '', 1, '1000', '[DE 4] Test product 0 šÄßüл', 'Test product 0 short desc [DE]', 50, 35, 45, 55, 0, 'kg', 2, NULL, 2, 15, 1, 'In stock [DE]', 'Out of stock [DE]', '0000-00-00', '2008-02-04', '2008-02-04 17:07:48', 1, 2, 2, 'search1000', 1, '', 0, 0, '', 50, 0, '', '', 'Test product 0 [EN] šÄßüл', 'Test product 0 short desc [EN] šÄßüл', 'šÄßüл1000', '', 'In stock [EN] šÄßüл', 'Out of stock [EN] šÄßüл', 0, 'testdistributor', 'testmanufacturer', 1, 1, 'DAY'),\n       ('1002', 1, '', 1, '1002', '[DE 2] Test product 2 šÄßüл', 'Test product 2 short desc [DE]', 55, 0, 0, 0, 0, '', 0, NULL, 0, 0, 1, 'In stock [DE]', 'Out of stock [DE]', '0000-00-00', '2008-02-04', '2008-02-04 17:18:18', 0, 0, 0, 'search1002', 1, 'variants [DE]', 10, 2, '', 55, 67, 'variants [EN] šÄßüл', '', 'Test product 2 [EN] šÄßüл', 'Test product 2 short desc [EN] šÄßüл', 'šÄßüл1002', '', 'In stock [EN] šÄßüл', 'Out of stock [EN] šÄßüл', 0, 'testdistributor', 'testmanufacturer', 1, 1, 'MONTH'),\n       ('1002-1', 1, '1002', 1, '1002-1', '', '', 55, 45, 0, 0, 0, '', 0, NULL, 0, 5, 1, 'In stock [DE]', 'Out of stock [DE]', '0000-00-00', '2008-02-04', '2008-02-04 17:34:10', 0, 0, 0, '', 1, '', 0, 0, 'var1 [DE]', 0, 0, '', 'var1 [EN] šÄßüл', '', '', '', '', 'In stock [EN] šÄßüл', 'Out of stock [EN] šÄßüл', 1, '', '', 0, 0, ''),\n       ('1002-2', 1, '1002', 1, '1002-2', '', '', 67, 47, 0, 0, 0, '', 0, NULL, 0, 5, 1, 'In stock [DE]', 'Out of stock [DE]', '0000-00-00', '2008-02-04', '2008-02-04 17:34:36', 0, 0, 0, '', 1, '', 0, 0, 'var2 [DE]', 0, 0, '', 'var2 [EN] šÄßüл', '', '', '', '', 'In stock [EN] šÄßüл', 'Out of stock [EN] šÄßüл', 2, '', '', 0, 0, ''),\n       ('1001', 1, '', 1, '1001', '[DE 1] Test product 1 šÄßüл', 'Test product 1 short desc [DE]', 100, 0, 0, 0, 150, '', 0, 10, 0, 0, 1, '', '', '2030-01-01', '2008-02-04', '2008-02-04 17:35:49', 0, 0, 0, 'search1001', 1, '', 0, 0, '', 100, 0, '', '', 'Test product 1 [EN] šÄßüл', 'Test product 1 short desc [EN] šÄßüл', 'šÄßüл1001', '', '', '', 0, 'testdistributor', 'testmanufacturer', 0, 1, 'WEEK'),\n       ('10014', 1, '', 1, '10014', '13 DE product šÄßüл', '14 DE description', 1.6, 0, 0, 0, 0, '', 0, NULL, 0, 0, 1, '', '', '0000-00-00', '2008-04-03', '2008-04-03 12:50:20', 0, 0, 0, '', 1, 'size[DE] | color | type', 0, 12, '', 15, 25, 'size[EN] | color | type', '', '14 EN product šÄßüл', '13 EN description šÄßüл', '', '', '', '', 0, '', '', 0, 0, '');\n\nREPLACE INTO `oxarticles` (`OXID`, `OXSHOPID`, `OXPARENTID`, `OXACTIVE`, `OXARTNUM`, `OXPRICE`, `OXSTOCK`, `OXSTOCKFLAG`, `OXINSERT`, `OXTIMESTAMP`, `OXVARSELECT`, `OXVARSELECT_1`, `OXSUBCLASS`, `OXSORT`)\nVALUES ('1001432', 1, '10014', 1, '10014-3-2', 15, 3, 1, '2008-04-03', '2008-04-03 12:50:20', 'M | black | material [DE]', 'L | black | material', 'oxarticle', 3002),\n       ('1001424', 1, '10014', 1, '10014-2-4', 15, 0, 1, '2008-04-03', '2008-04-03 12:50:20', 'M | red [DE]', 'M | red', 'oxarticle', 2004),\n       ('1001422', 1, '10014', 1, '10014-2-2', 15, 0, 3, '2008-04-03', '2008-04-03 12:50:20', 'M | black | material [DE]', 'M | black | material', 'oxarticle', 2002),\n       ('1001421', 1, '10014', 1, '10014-2-1', 25, 0, 2, '2008-04-03', '2008-04-03 12:50:20', 'M | black | lether [DE]', 'M | black | lether', 'oxarticle', 2001),\n       ('1001411', 1, '10014', 1, '10014-1-1', 25, 3, 1, '2008-04-03', '2008-04-03 12:50:20', 'S | black | lether [DE]', 'S | black | lether', 'oxarticle', 1001),\n       ('1001413', 1, '10014', 1, '10014-1-3', 15, 3, 1, '2008-04-03', '2008-04-03 12:50:20', 'S | white [DE]', 'S | white', 'oxarticle', 1003),\n       ('1001412', 1, '10014', 1, '10014-1-2', 15, 3, 1, '2008-04-03', '2008-04-03 12:50:20', 'S | black | material [DE]', 'S | black | material', 'oxarticle', 1002),\n       ('1001434', 1, '10014', 0, '10014-3-4', 15, 3, 1, '2008-04-03', '2008-04-03 12:50:20', 'L | red [DE]', 'L | red', 'oxarticle', 3004),\n       ('1001423', 1, '10014', 1, '10014-2-3', 15, 0, 1, '2008-04-03', '2008-04-03 12:50:20', 'M | white [DE]', 'M | white', 'oxarticle', 2003),\n       ('1001414', 1, '10014', 1, '10014-1-4', 15, 3, 1, '2008-04-03', '2008-04-03 12:50:20', 'S | red [DE]', 'S | red', 'oxarticle', 1004),\n       ('1001431', 1, '10014', 1, '10014-3-1', 15, 3, 1, '2008-04-03', '2008-04-03 12:50:20', 'L | black | lether [DE]', 'L | black | lether', 'oxarticle', 3001),\n       ('1001433', 1, '10014', 1, '10014-3-3', 15, 3, 1, '2008-04-03', '2008-04-03 12:50:20', 'L | white [DE]', 'L | white', 'oxarticle', 3003);\n\nREPLACE INTO `oxartextends` (`OXID`, `OXLONGDESC`, `OXLONGDESC_1`)\nVALUES ('1001', '<p>Test product 1 long description [DE]</p>', '<p>Test product 1 long description [EN] šÄßüл</p>'),\n       ('1002', '<p>Test product 2 long description [DE]</p>', '<p>Test product 2 long description [EN] šÄßüл</p>'),\n       ('1002-1', '', ''),\n       ('1002-2', '', ''),\n       ('1000', '<p>Test product 0 long description [DE]</p>', '<p>Test product 0 long description [EN] šÄßüл</p>'),\n       ('10014', '', '');\n\nREPLACE INTO `oxcategories` (`OXID`, `OXPARENTID`, `OXLEFT`, `OXRIGHT`, `OXROOTID`, `OXSORT`, `OXACTIVE`, `OXSHOPID`, `OXTITLE`, `OXDESC`, `OXLONGDESC`, `OXDEFSORT`, `OXDEFSORTMODE`, `OXPRICEFROM`, `OXPRICETO`, `OXACTIVE_1`, `OXTITLE_1`, `OXDESC_1`, `OXLONGDESC_1`, `OXVAT`, `OXSHOWSUFFIX`)\nVALUES ('testcategory0', 'oxrootid', 1, 4, 'testcategory0', 1, 1, 1, 'Test category 0 [DE] šÄßüл', 'Test category 0 desc [DE]', 'Category 0 long desc [DE]', 'oxartnum', 0, 0, 0, 1, 'Test category 0 [EN] šÄßüл', 'Test category 0 desc [EN] šÄßüл', 'Category 0 long desc [EN] šÄßüл', 5, 1),\n       ('testcategory1', 'testcategory0', 2, 3, 'testcategory0', 2, 1, 1, 'Test category 1 [DE] šÄßüл', 'Test category 1 desc [DE]', 'Category 1 long desc [DE]', 'oxartnum', 1, 0, 0, 1, 'Test category 1 [EN] šÄßüл', 'Test category 1 desc [EN] šÄßüл', 'Category 1 long desc [EN] šÄßüл', NULL, 1),\n       ('testcategory2', 'oxrootid', 1, 2, 'testcategory2', 1, 0, 1, 'Test category 2 [DE] šÄßüл', 'Test category 2 desc [DE]', 'Category 2 long desc [DE]', 'oxartnum', 0, 0, 0, 1, 'Test category 2 [EN] šÄßüл', 'Test category 2 desc [EN] šÄßüл', 'Category 2 long desc [EN] šÄßüл', NULL, 1),\n       ('testpricecat', 'oxrootid', 1, 2, 'testpricecat', 99999, 0, 1, 'price šÄßüл [DE]', 'price category [DE]', '', '', 0, 49, 60, 0, 'price [EN] šÄßüл', 'price category [EN] šÄßüл', '', NULL, 1);\n\nREPLACE INTO `oxobject2category` (`OXID`, `OXOBJECTID`, `OXCATNID`, `OXPOS`, `OXTIME`)\nVALUES ('6f047a71f53e3b6c2.93342239', '1000', 'testcategory0', 0, 1202134867),\n       ('testobject2category', '1001', 'testcategory0', 0, 1202134867);\n\nREPLACE INTO `oxuser` (`OXID`, `OXACTIVE`, `OXRIGHTS`, `OXSHOPID`, `OXUSERNAME`, `OXPASSWORD`, `OXPASSSALT`, `OXCUSTNR`, `OXUSTID`, `OXCOMPANY`, `OXFNAME`, `OXLNAME`, `OXSTREET`, `OXSTREETNR`, `OXADDINFO`, `OXCITY`, `OXCOUNTRYID`, `OXZIP`, `OXFON`, `OXFAX`, `OXSAL`, `OXBONI`, `OXCREATE`, `OXREGISTER`, `OXPRIVFON`, `OXMOBFON`, `OXBIRTHDATE`)\nVALUES ('testuser', 1, 'user', 1, 'example_test@oxid-esales.dev', 'c9dadd994241c9e5fa6469547009328a', '7573657275736572', 8, '', 'UserCompany šÄßüл', 'UserNamešÄßüл', 'UserSurnamešÄßüл', 'Musterstr.šÄßüл', '1', 'User additional info šÄßüл', 'Musterstadt šÄßüл', 'testcountry_de', '79098', '0800 111111', '0800 111112', 'Mr', 500, '2008-02-05 14:42:42', '2008-02-05 14:42:42', '0800 111113', '0800 111114', '1980-01-01'),\n       ('testguest', 1, 'user', 1, 'example_guest@oxid-esales.dev', '', '', 9, '', 'UserCompany šÄßüл', 'UserNamešÄßüл', 'UserSurnamešÄßüл', 'Musterstr.šÄßüл', '1', 'User additional info šÄßüл', 'Musterstadt šÄßüл', 'testcountry_de', '79098', '0800 111111', '0800 111112', 'Mr', 500, '2008-02-05 14:42:42', '2008-02-05 14:42:42', '0800 111113', '0800 111114', '1980-01-01'),\n       ('oxdefaultadmin', 1, 'malladmin', 1, 'admin@myoxideshop.com', '6cb4a34e1b66d3445108cd91b67f98b9','6631386565336161636139613634663766383538633566623662613036636539', 1, '', 'Your Company Name', 'John', 'Doe', 'Maple Street', '2425', '', 'Any City', 'testcountry_de', '9041', '217-8918712', '217-8918713', 'MR', 1000, '2003-01-01 00:00:00', '2003-01-01 00:00:00', '', '', '1970-01-01');\n\nREPLACE INTO `oxobject2group` (`OXID`, `OXSHOPID`, `OXOBJECTID`, `OXGROUPSID`)\nVALUES ('aad47a85a83749c71.33568408', 1, 'testuser', 'oxidnewcustomer'),\n       ('34f5e54f695bf109454aa152d9', 1, 'oxdefaultadmin', 'oxidforeigncustomer'),\n       ('e913fdd8443ed43e1.51222316', 1, 'oxdefaultadmin', 'oxidadmin');\n\nREPLACE INTO `oxstates` (`OXID`, `OXCOUNTRYID`, `OXTITLE`, `OXISOALPHA2`, `OXTITLE_1`, `OXTITLE_2`, `OXTITLE_3`)\nVALUES ('BB', 'testcountry_de', 'Brandenburg', 'BB', 'Brandenburg', '', ''),\n       ('BE', 'testcountry_de', 'Berlin', 'BE', 'Berlin', '', ''),\n       ('BW', 'testcountry_de', 'Baden-Württemberg', 'BW', 'Baden-Wurttemberg', '', ''),\n       ('BY', 'testcountry_de', 'Bayern', 'BY', 'Bavaria', '', ''),\n       ('HB', 'testcountry_de', 'Bremen', 'HB', 'Bremen', '', ''),\n       ('HE', 'testcountry_de', 'Hessen', 'HE', 'Hesse', '', ''),\n       ('HH', 'testcountry_de', 'Hamburg', 'HH', 'Hamburg', '', ''),\n       ('MV', 'testcountry_de', 'Mecklenburg-Vorpommern', 'MV', 'Mecklenburg-Western Pomerania', '', ''),\n       ('NI', 'testcountry_de', 'Niedersachsen', 'NI', 'Lower Saxony', '', ''),\n       ('NW', 'testcountry_de', 'Nordrhein-Westfalen', 'NW', 'North Rhine-Westphalia', '', ''),\n       ('RP', 'testcountry_de', 'Rheinland-Pfalz', 'RP', 'Rhineland-Palatinate', '', ''),\n       ('SH', 'testcountry_de', 'Schleswig-Holstein', 'SH', 'Schleswig-Holstein', '', ''),\n       ('SL', 'testcountry_de', 'Saarland', 'SL', 'Saarland', '', ''),\n       ('SN', 'testcountry_de', 'Sachsen', 'SN', 'Saxony', '', ''),\n       ('ST', 'testcountry_de', 'Sachsen-Anhalt', 'ST', 'Saxony-Anhalt', '', ''),\n       ('TH', 'testcountry_de', 'Thüringen', 'TH', 'Thuringia', '', '');\n\nUPDATE `oxconfig`\nSET `OXVARVALUE` = '0'\nWHERE `OXVARNAME` = 'iNewBasketItemMessage';\n\nUPDATE `oxconfig`\nSET `OXVARVALUE` = ''\nWHERE `OXVARNAME` = 'blDisableNavBars';\n\nUPDATE `oxconfig`\nSET `OXVARVALUE` = '0'\nWHERE `OXVARNAME` = 'blUseMultidimensionVariants';\n\nUPDATE `oxconfig`\nSET `OXVARVALUE` = 'a:2:{s:2:\"de\";a:3:{s:6:\"baseId\";i:0;s:6:\"active\";s:1:\"1\";s:4:\"sort\";s:1:\"1\";}s:2:\"en\";a:3:{s:6:\"baseId\";i:1;s:6:\"active\";s:1:\"1\";s:4:\"sort\";s:1:\"2\";}}'\nWHERE `OXVARNAME` = 'aLanguageParams';\n\nUPDATE `oxconfig`\nSET `OXVARVALUE` = 'a:2:{s:2:\"de\";s:7:\"Deutsch\";s:2:\"en\";s:7:\"English\";}'\nWHERE `OXVARNAME` = 'aLanguages';\n\nREPLACE INTO `oxconfig` (`OXID`, `OXSHOPID`, `OXMODULE`, `OXVARNAME`, `OXVARTYPE`, `OXVARVALUE`)\nVALUES ('4742', 1, '', 'blPerfNoBasketSaving', 'bool', 'true'),\n       ('8563fba1965a219c9.51133344', 1, '', 'blUseStock', 'bool', 'true');\n\nUPDATE `oxcountry`\nSET `OXACTIVE` = 1,\n    `OXID` = 'testcountry_be'\nWHERE `OXISOALPHA2` = 'BE';\n\nUPDATE `oxcountry`\nSET `OXACTIVE` = 1,\n    `OXID` = 'testcountry_de'\nWHERE `OXISOALPHA2` = 'DE';\n\nREPLACE INTO `oxdeliveryset` (`OXID`, `OXSHOPID`, `OXACTIVE`, `OXACTIVEFROM`, `OXACTIVETO`, `OXTITLE`, `OXTITLE_1`, `OXPOS`)\nVALUES ('oxidalternative', 1, 1, '0000-00-00 00:00:00', '0000-00-00 00:00:00', 'Alternative', 'Alternative', 20);\n\nREPLACE INTO `oxdelivery` (`OXID`, `OXSHOPID`, `OXACTIVE`, `OXFIXED`, `OXPARAM`, `OXPARAMEND`)\nVALUES ('testdelivery', 1, 1, 0, 0, 9999999);\n\nREPLACE INTO `oxdel2delset` (`OXID`, `OXDELID`, `OXDELSETID`)\nVALUES ('teststandart', 'testdelivery', 'oxidstandard'),\n       ('testalternative', 'testdelivery', 'oxidalternative');\n\nREPLACE INTO `oxobject2delivery` (`OXID`, `OXDELIVERYID`, `OXOBJECTID`, `OXTYPE`)\nVALUES ('testdelcountry', 'testdelivery', 'testcountry_de', 'oxcountry'),\n       ('standartdelset', 'oxidstandard', 'testcountry_de', 'oxdelset'),\n       ('alternativdelset', 'oxidalternative', 'testcountry_de', 'oxdelset');\n\nREPLACE INTO `oxobject2payment` (`OXID`, `OXPAYMENTID`, `OXOBJECTID`, `OXTYPE`)\nVALUES ('testcodalternative', 'oxidcashondel', 'oxidalternative', 'oxdelset'),\n       ('testcodstandard', 'oxidcashondel', 'oxidstandard', 'oxdelset'),\n       ('testcodcountryde', 'oxidcashondel', 'testcountry_de', 'oxcountry'),\n       ('testpiaalternative', 'oxidpayadvance', 'oxidalternative', 'oxdelset'),\n       ('testpiastandard', 'oxidpayadvance', 'oxidstandard', 'oxdelset'),\n       ('testpiacountryde', 'oxidpayadvance', 'testcountry_de', 'oxcountry'),\n       ('testinvalternative', 'oxidinvoice', 'oxidalternative', 'oxdelset'),\n       ('testinvstandard', 'oxidinvoice', 'oxidstandard', 'oxdelset'),\n       ('testinvcountryde', 'oxidinvoice', 'testcountry_de', 'oxcountry');\n\nREPLACE INTO `oxattribute` (`OXID`, `OXSHOPID`, `OXTITLE`, `OXTITLE_1`, `OXPOS`)\nVALUES ('testattribute1', 1, 'Test attribute 1 [DE] šÄßüл', 'Test attribute 1 [EN] šÄßüл', 1),\n       ('testattribute2', 1, 'Test attribute 2 [DE] šÄßüл', 'Test attribute 2 [EN] šÄßüл', 3),\n       ('testattribute3', 1, 'Test attribute 3 [DE] šÄßüл', 'Test attribute 3 [EN] šÄßüл', 2);\n\nUPDATE `oxattribute`\nSET `OXDISPLAYINBASKET` = 0\nWHERE `OXDISPLAYINBASKET` != 0;\n\nREPLACE INTO `oxobject2attribute` (`OXID`, `OXOBJECTID`, `OXATTRID`, `OXVALUE`, `OXPOS`, `OXVALUE_1`)\nVALUES ('aad47a8511f54e023.54090494', '1000', 'testattribute1', 'attr value 1 [DE]', 0, 'attr value 1 [EN] šÄßüл'),\n       ('aad47a8511f556f17.20889862', '1001', 'testattribute1', 'attr value 11 [DE]', 0, 'attr value 11 [EN] šÄßüл'),\n       ('aad47a85125a41ed7.53096100', '1000', 'testattribute2', 'attr value 2 [DE]', 0, 'attr value 2 [EN] šÄßüл'),\n       ('aad47a85125a4aa05.37412863', '1001', 'testattribute2', 'attr value 12 [DE]', 0, 'attr value 12 [EN] šÄßüл'),\n       ('aad47a8512d783995.31168870', '1000', 'testattribute3', 'attr value 3 [DE]', 0, 'attr value 3 [EN] šÄßüл'),\n       ('aad47a8512d78c354.06494034', '1001', 'testattribute3', 'attr value 3 [DE]', 0, 'attr value 3 [EN] šÄßüл');\n\nINSERT INTO `oxcategory2attribute` (`OXID`, `OXOBJECTID`, `OXATTRID`, `OXSORT`)\nVALUES ('testcategory0attribute3', 'testcategory0', 'testattribute3', 2),\n       ('testcategory0attribute1', 'testcategory0', 'testattribute1', 0),\n       ('testcategory0attribute2', 'testcategory0', 'testattribute2', 1);\n\nREPLACE INTO `oxdiscount` (`OXID`, `OXSHOPID`, `OXACTIVE`, `OXTITLE`, `OXTITLE_1`, `OXAMOUNT`, `OXAMOUNTTO`, `OXPRICETO`, `OXPRICE`, `OXADDSUMTYPE`, `OXADDSUM`, `OXITMARTID`, `OXITMAMOUNT`, `OXITMMULTIPLE`, `OXSORT`)\nVALUES ('testcatdiscount', 1, 0, 'discount for category [DE] šÄßüл', 'discount for category [EN] šÄßüл', 1, 999999, 0, 0, 'abs', 5, '', 0, 0, 100);\n\nREPLACE INTO `oxobject2discount` (`OXID`, `OXDISCOUNTID`, `OXOBJECTID`, `OXTYPE`)\nVALUES ('fa647a823ce118996.58546955', 'testcatdiscount', 'testcountry_de', 'oxcountry'),\n       ('fa647a823d5079104.99115703', 'testcatdiscount', 'testcategory0', 'oxcategories');\n\nREPLACE INTO `oxvoucherseries` (`OXID`, `OXSHOPID`, `OXSERIENR`, `OXSERIEDESCRIPTION`, `OXDISCOUNT`, `OXDISCOUNTTYPE`, `OXBEGINDATE`, `OXENDDATE`, `OXALLOWSAMESERIES`, `OXALLOWOTHERSERIES`, `OXALLOWUSEANOTHER`, `OXMINIMUMVALUE`, `OXCALCULATEONCE`)\nVALUES ('testvoucher4', 1, '4 Coupon šÄßüл', '4 Coupon šÄßüл', 50.00, 'percent', '2008-01-01 00:00:00', now() + interval 1 DAY, 0, 0, 0, 45.00, 1);\n\nREPLACE INTO `oxvouchers` (`OXDATEUSED`, `OXRESERVED`, `OXVOUCHERNR`, `OXVOUCHERSERIEID`, `OXID`)\nVALUES ('0000-00-00', 0, '123123', 'testvoucher4', 'testcoucher011');\n\nREPLACE INTO `oxwrapping` (`OXID`, `OXSHOPID`, `OXACTIVE`, `OXACTIVE_1`, `OXACTIVE_2`, `OXACTIVE_3`, `OXTYPE`, `OXNAME`, `OXNAME_1`, `OXPRICE`)\nVALUES ('testwrapping', 1, 1, 1, 1, 1, 'WRAP', 'Test wrapping [DE] šÄßüл', 'Test wrapping [EN] šÄßüл', 0.9),\n       ('testcard', 1, 1, 1, 1, 1, 'CARD', 'Test card [DE] šÄßüл', 'Test card [EN] šÄßüл', 0.2);\n\nREPLACE INTO `oxselectlist` (`OXID`, `OXSHOPID`, `OXTITLE`, `OXIDENT`, `OXVALDESC`, `OXTITLE_1`, `OXVALDESC_1`)\nVALUES ('testsellist', 1, 'test selection list [DE] šÄßüл', 'test sellist šÄßüл', 'selvar1 [DE]!P!1__@@selvar2 [DE]__@@selvar3 [DE]!P!-2__@@selvar4 [DE]!P!2%__@@', 'test selection list [EN] šÄßüл', 'selvar1 [EN] šÄßüл!P!1__@@selvar2 [EN] šÄßüл__@@selvar3 [EN] šÄßüл!P!-2__@@selvar4 [EN] šÄßüл!P!2%__@@');\n\nREPLACE INTO `oxobject2selectlist` (`OXID`, `OXOBJECTID`, `OXSELNID`, `OXSORT`)\nVALUES ('testsellist.1001', '1001', 'testsellist', 0);\n\nREPLACE INTO `oxmanufacturers` (`OXID`, `OXSHOPID`, `OXACTIVE`, `OXTITLE`, `OXSHORTDESC`, `OXTITLE_1`, `OXSHORTDESC_1`, `OXSHOWSUFFIX`, `OXICON`)\nVALUES ('testmanufacturer', 1, 1, 'Manufacturer [DE] šÄßüл', 'Manufacturer description [DE]', 'Manufacturer [EN] šÄßüл', 'Manufacturer description [EN] šÄßüл', 1, 'test.png');\n\nREPLACE INTO `oxvendor` (`OXID`, `OXSHOPID`, `OXACTIVE`, `OXTITLE`, `OXSHORTDESC`, `OXTITLE_1`, `OXSHORTDESC_1`, `OXSHOWSUFFIX`)\nVALUES ('testdistributor', 1, 1, 'Distributor [DE] šÄßüл', 'Distributor description [DE]', 'Distributor [EN] šÄßüл', 'Distributor description [EN] šÄßüл', 1);\n\nREPLACE INTO `oxorder` (`OXID`, `OXSHOPID`, `OXUSERID`, `OXORDERDATE`, `OXORDERNR`, `OXBILLCOMPANY`, `OXBILLEMAIL`, `OXBILLFNAME`, `OXBILLLNAME`, `OXBILLSTREET`, `OXBILLSTREETNR`, `OXBILLADDINFO`, `OXBILLCITY`, `OXBILLCOUNTRYID`, `OXBILLZIP`, `OXREMARK`, `OXFOLDER`, `OXBILLSTATEID`, `OXCARDTEXT`)\nVALUES ('testorder',1, 'testuser','2023-03-30 11:00:04',123, 'test bill company_name', 'test@billemail.com','test bill fname','test bill lname','test address street','streetNBR', 'test address info','test address city','testcountry_de', '55555', 'custom user order remark','ORDERFOLDER_NEW','BB', '');\n\nREPLACE INTO `oxorderarticles` (`OXID`, `OXORDERID`, `OXAMOUNT`, `OXARTID`, `OXARTNUM`, `OXTITLE`)\nVALUES ('919edbc539f414bdefc7f6975bbdf2a1','testorder', 100, '1000', '1000', '[DE 4] Test product 0 šÄßüл'),\n       ('919edbc539f414bdefc7f6975bbdf2b6','testorder', 150, '1001', '1001', '[DE 1] Test product 1 šÄßüл');\n"
  },
  {
    "path": "tests/Codeception/Support/Data/user.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nreturn [\n    'existingUser' => [\n        'userId' => 'testuser',\n        'userLoginName' => 'example_test@oxid-esales.dev',\n        'userPassword' => 'useruser',\n        'userName' => 'UserNamešÄßüл',\n        'userLastName' => 'UserSurnamešÄßüл',\n    ],\n    'existingGuestUser' => [\n        'userId' => 'testguest',\n        'userLoginName' => 'example_guest@oxid-esales.dev',\n        'userPassword' => '',\n        'userName' => 'UserNamešÄßüл',\n        'userLastName' => 'UserSurnamešÄßüл',\n    ],\n    'adminUser' => [\n        'userId' => 'admin',\n        'userLoginName' => 'admin@myoxideshop.com',\n        'userPassword' => 'admin0303',\n        'userName' => 'John',\n        'userLastName' => 'Doe',\n    ],\n];\n"
  },
  {
    "path": "tests/Codeception/Support/Data/voucher.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nreturn [\n    'testvoucher4' =>\n        [\n            'oxvoucherseries' => [\n                'OXID' => 'testvoucher4',\n                'OXSHOPID' => 1,\n                'OXSERIENR' => '4 Coupon šÄßüл',\n                'OXSERIEDESCRIPTION' => '4 Coupon šÄßüл',\n                'OXDISCOUNT' => 50.00,\n                'OXDISCOUNTTYPE' => 'percent',\n                'OXBEGINDATE' => '2008-01-01 00:00:00',\n                'OXENDDATE' => date('Y-m-d H:i:s', time() + (7 * 24 * 60 * 60)),\n                'OXALLOWSAMESERIES' => 0,\n                'OXALLOWOTHERSERIES' => 0,\n                'OXALLOWUSEANOTHER' => 0,\n                'OXMINIMUMVALUE' => 45.00,\n                'OXCALCULATEONCE' => 1\n            ],\n            'oxvouchers' => [\n                [\n                    'OXDATEUSED' => '0000-00-00',\n                    'OXRESERVED' => 0,\n                    'OXVOUCHERNR' => '123123',\n                    'OXVOUCHERSERIEID' => 'testvoucher4',\n                    'OXID' => 'testcoucher011'\n                ]\n            ]\n        ]\n];\n"
  },
  {
    "path": "tests/Codeception/Support/Helper/Acceptance.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Codeception\\Support\\Helper;\n\nuse Codeception\\Module;\nuse OxidEsales\\Codeception\\Module\\Oxideshop;\nuse OxidEsales\\Codeception\\Module\\ProjectConfiguration;\n\n// here you can define custom actions\n// all public methods declared in helper class will be available in $I\nfinal class Acceptance extends Module\n{\n    public function getCurrentURL(): string\n    {\n        return $this->getModule('WebDriver')->webDriver->getCurrentURL();\n    }\n\n    public function updateProjectConfigurations(array $parameters, array $services): void\n    {\n        $module = $this->getModule(ProjectConfiguration::class);\n        $module->_reconfigure([\n            'parameters' => $parameters,\n            'services' => $services,\n        ]);\n        $module->dumpProjectConfigurations();\n        $this->getModule(Oxideshop::class)->clearShopCache();\n    }\n\n    public function restoreProjectConfigurations(): void\n    {\n        $module = $this->getModule(ProjectConfiguration::class);\n        $module->_resetConfig();\n        $module->dumpProjectConfigurations();\n        $this->getModule(Oxideshop::class)->clearShopCache();\n    }\n}\n"
  },
  {
    "path": "tests/Codeception/_envs/.gitkeep",
    "content": ""
  },
  {
    "path": "tests/Codeception/_output/.gitkeep",
    "content": ""
  },
  {
    "path": "tests/ConsoleRunnerTrait.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\ProjectRootLocator;\nuse RuntimeException;\nuse Symfony\\Component\\Process\\Process;\n\nuse function sprintf;\n\ntrait ConsoleRunnerTrait\n{\n    public function runInConsole(string $command): Process\n    {\n        $process = Process::fromShellCommandline(\n            \"{$this->getPathToConsoleScript()} $command\"\n        );\n        $process->run();\n\n        return $process;\n    }\n\n    public function runInConsoleAndAssertSuccess(string $command): Process\n    {\n        $process = $this->runInConsole($command);\n        if (!$process->isSuccessful()) {\n            $this->fail(\n                sprintf(\n                    'Execution of `oe-console` failed unexpectedly! The error output was: %s %s.',\n                    $process->getOutput(),\n                    $process->getErrorOutput()\n                )\n            );\n        }\n        return $process;\n    }\n\n    private function getPathToConsoleScript(): string\n    {\n        $scriptPath = 'bin/oe-console';\n        $shopRootPath = (new ProjectRootLocator())->getProjectRoot();\n        if (is_file(\"$shopRootPath/vendor/$scriptPath\")) {\n            return \"$shopRootPath/vendor/$scriptPath\";\n        }\n        if (is_file(\"$shopRootPath/$scriptPath\")) {\n            return \"$shopRootPath/$scriptPath\";\n        }\n        throw new RuntimeException(\"Error: $scriptPath is not accessible!\");\n    }\n}\n"
  },
  {
    "path": "tests/ContainerTrait.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests;\n\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse Symfony\\Component\\Config\\FileLocator;\nuse Symfony\\Component\\DependencyInjection\\Container;\nuse Symfony\\Component\\DependencyInjection\\Loader\\YamlFileLoader;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\nuse Symfony\\Component\\Yaml\\Yaml;\nuse UnitEnum;\n\n/**\n * @mixin Container\n */\ntrait ContainerTrait\n{\n    private $container;\n\n    protected function get(string $serviceId)\n    {\n        $this->prepareContainer();\n        return $this->container->get($serviceId);\n    }\n\n    private function getParameter(string $name)\n    {\n        $this->prepareContainer();\n        return $this->container->getParameter($name);\n    }\n\n    private function setParameter(string $name, array|bool|string|int|float|UnitEnum|null $value): void\n    {\n        $this->createContainer();\n        $this->container->setParameter($name, $value);\n        $this->compileContainer();\n        $this->replaceContainerInstance();\n    }\n\n    private function prepareContainer(): void\n    {\n        if ($this->container === null) {\n            $this->container = TestContainerFactory::get();\n        }\n    }\n\n    private function createContainer(): void\n    {\n        $this->container = (new TestContainerFactory())->create();\n    }\n\n    private function compileContainer(): void\n    {\n        $this->container->compile(true);\n    }\n\n    private function replaceContainerInstance(): void\n    {\n        if (!$this->container->isCompiled()) {\n            $this->container->compile(true);\n        }\n        TestContainerFactory::setContainer($this->container);\n    }\n\n    private function loadYamlFixture(string $fixtureDir): void\n    {\n        $loader = new YamlFileLoader($this->container, new FileLocator(__DIR__));\n        $loader->load(Path::join($fixtureDir, 'services.yaml'));\n    }\n\n    private function replaceService(string $id, object $service): void\n    {\n        $this->container->set($id, $service);\n        $this->container->autowire($id, $id);\n    }\n\n    private function rewriteProjectConfiguration(array $config): void\n    {\n        (new Filesystem())->dumpFile(\n            Path::join(\n                ContainerFacade::get(BasicContextInterface::class)->getProjectConfigurationDirectory(),\n                'parameters.yaml'\n            ),\n            Yaml::dump($config)\n        );\n    }\n}\n"
  },
  {
    "path": "tests/DatabaseTrait.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests;\n\nuse Doctrine\\DBAL\\Connection;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition\\Edition;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition\\EditionDirectoriesLocator;\nuse ReflectionClass;\n\ntrait DatabaseTrait\n{\n    public function beginTransaction(?Connection $connection = null): void\n    {\n        $connection ??= $this->getDbConnection();\n        $connection->beginTransaction();\n    }\n\n    public function rollBackTransaction(?Connection $connection = null): void\n    {\n        $connection ??= $this->getDbConnection();\n        if ($connection->isTransactionActive()) {\n            $connection->rollBack();\n        }\n    }\n\n    public function getDbConnection(): Connection\n    {\n        return DatabaseProvider::getDb()->getPublicConnection();\n    }\n\n    public function setupShopDatabase(): void\n    {\n        exec(\n            \\sprintf(\n                '%s/bin/oe-console oe:database:reset --force',\n                (new EditionDirectoriesLocator())->getEditionRootPath(Edition::Community)\n            )\n        );\n    }\n\n    private function resetDatabaseProvider(): void\n    {\n        $reflectionClass = new ReflectionClass(DatabaseProvider::class);\n        $reflectionProperty = $reflectionClass->getProperty('db');\n        $reflectionProperty->setValue($reflectionProperty, null);\n    }\n}\n"
  },
  {
    "path": "tests/EnvTrait.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Env\\DotenvLoader;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\n\n/**\n * Run tests that modify env values in a separate process\n * to avoid discrepancy between getenv('value') and $_ENV['value']\n */\ntrait EnvTrait\n{\n    private function loadEnvFixture(string $fixtureDir, array $envFileLines): void\n    {\n        $filesystem = new Filesystem();\n        $fixtureFile = Path::join($fixtureDir, '.env');\n        $filesystem->dumpFile($fixtureFile, implode(\"\\n\", $envFileLines),);\n        (new DotenvLoader($fixtureDir))->loadEnvironmentVariables();\n        $filesystem->remove($fixtureFile);\n    }\n}\n"
  },
  {
    "path": "tests/FilesystemTrait.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\ProjectRootLocator;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\n\ntrait FilesystemTrait\n{\n    private Filesystem $filesystem;\n    private string $varPath = '';\n    private string $varBackupPath = '';\n\n    public function backupVarDirectory(): void\n    {\n        $this->init();\n        $this->filesystem->mirror($this->varPath, $this->varBackupPath);\n    }\n\n    public function restoreVarDirectory(): void\n    {\n        $this->filesystem->remove($this->varPath);\n        $this->filesystem->mirror($this->varBackupPath, $this->varPath);\n        $this->filesystem->remove($this->varBackupPath);\n    }\n\n    private function init(): void\n    {\n        $shopRootPath = (new ProjectRootLocator())->getProjectRoot();\n        $this->filesystem = new Filesystem();\n        $this->varPath = Path::join($shopRootPath, 'var');\n        $this->varBackupPath = Path::join(\n            $shopRootPath,\n            'var.testing-backup'\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Component/BasketComponentSessionTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Component;\n\nuse OxidEsales\\Eshop\\Application\\Component\\BasketComponent;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Utils;\nuse OxidEsales\\EshopCommunity\\Application\\Model\\Article;\nuse OxidEsales\\EshopCommunity\\Core\\Session;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class BasketComponentSessionTest extends IntegrationTestCase\n{\n    private string $articleId = '1000';\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        Registry::getConfig()->setConfigParam('blUseStock', false);\n        Registry::getSession()->setVariable('aLastcall', []);\n        $this->createTestProduct();\n    }\n\n    public function testChangingBasketWhenSessionChallengeValidationNotPassed(): void\n    {\n        $this->actAsSearchEngine(false);\n        $this->initSessionMock(false);\n\n        oxNew(BasketComponent::class)->changeBasket($this->articleId, 2);\n\n        $this->assertEquals(0, $this->getBasketItemCount());\n        $this->assertFalse($this->isChangeBasketKeyExisted());\n    }\n\n    public function testChangingBasketWhenSessionChallengeValidationPassed(): void\n    {\n        $this->actAsSearchEngine(false);\n        $this->initSessionMock(true);\n\n        $_SESSION['sess_stoken'] = 'randomtoken';\n        $_POST['stoken'] = 'randomtoken';\n\n        oxNew(BasketComponent::class)->changeBasket($this->articleId, 2);\n\n        $this->assertEquals(2, $this->getBasketItemCount());\n        $this->assertTrue($this->isChangeBasketKeyExisted());\n    }\n\n    public function testChangingBasketWhenIsSearchEngine(): void\n    {\n        $this->actAsSearchEngine(true);\n        $this->initSessionMock(true);\n\n        oxNew(BasketComponent::class)->changeBasket($this->articleId, 2);\n\n        $this->assertEquals(0, $this->getBasketItemCount());\n        $this->assertFalse($this->isChangeBasketKeyExisted());\n    }\n\n    private function createTestProduct(): void\n    {\n        $product = oxNew(Article::class);\n        $product->setId($this->articleId);\n        $product->save();\n    }\n\n    private function actAsSearchEngine(bool $isSearchEngine): void\n    {\n        Registry::set(\n            Utils::class,\n            $this->createConfiguredStub(\n                Utils::class,\n                ['isSearchEngine' => $isSearchEngine]\n            )\n        );\n    }\n\n    private function initSessionMock(bool $checkSessionChallenge): void\n    {\n        $session = $this->createConfiguredStub(\n            Session::class,\n            ['checkSessionChallenge' => $checkSessionChallenge]\n        );\n\n        Registry::set(\n            Session::class,\n            $session\n        );\n    }\n\n    private function getBasketItemCount(): float\n    {\n        $articles = Registry::getSession()->getBasket()->getBasketSummary()->aArticles;\n        return $articles[$this->articleId] ?? 0.0;\n    }\n\n    private function isChangeBasketKeyExisted(): bool\n    {\n        $lastCall = Registry::getSession()->getVariable('aLastcall');\n        return isset($lastCall['changebasket'][1000]);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Component/BasketComponentTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Component;\n\nuse OxidEsales\\Eshop\\Application\\Component\\BasketComponent;\nuse OxidEsales\\Eshop\\Application\\Model\\Basket;\nuse OxidEsales\\Eshop\\Application\\Model\\BasketReservation;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Session;\nuse OxidEsales\\Eshop\\Core\\Utils;\nuse OxidEsales\\Eshop\\Core\\UtilsView;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\n\nfinal class BasketComponentTest extends IntegrationTestCase\n{\n    use ProphecyTrait;\n    use ContainerTrait;\n\n    public function testFrontendErrorMsgInToBasket(): void\n    {\n        $utilsView = $this->createMock(UtilsView::class);\n        $utilsView->expects($this->once())\n            ->method('addErrorToDisplay')\n            ->with('ERROR_MESSAGE_NON_MATCHING_CSRF_TOKEN');\n\n        Registry::set(\n            UtilsView::class,\n            $utilsView\n        );\n\n        $this->actAsSearchEngine(false);\n        $this->prepareSessionMock();\n\n        oxNew(BasketComponent::class)->toBasket(1000, 2);\n    }\n\n    public function testInitWitDefaultBasketReservationsCleanupRate(): void\n    {\n        $defaultCleanupRate = 200;\n        Registry::getConfig()->reinitialize();\n        Registry::getConfig()->setConfigParam('blPsBasketReservationEnabled', true);\n        $basketReservations = $this->prophesize(BasketReservation::class);\n        $session = $this->prophesize(Session::class);\n        $session->getBasketReservations()\n            ->willReturn($basketReservations);\n        $session->getBasket()\n            ->willReturn(new Basket());\n        Registry::set(Session::class, $session->reveal());\n\n        oxNew(BasketComponent::class)->init();\n\n        $basketReservations->discardUnusedReservations($defaultCleanupRate)\n            ->shouldHaveBeenCalledOnce();\n    }\n\n    public function testInitWitModifiedBasketReservationsCleanupRate(): void\n    {\n        $cleanupRate = 123;\n        $this->setParameter('oxid_esales.basket_reservation_cleanup_rate', $cleanupRate);\n        Registry::getConfig()->reinitialize();\n        Registry::getConfig()->setConfigParam('blPsBasketReservationEnabled', true);\n        $basketReservations = $this->prophesize(BasketReservation::class);\n        $session = $this->prophesize(Session::class);\n        $session->getBasketReservations()\n            ->willReturn($basketReservations);\n        $session->getBasket()\n            ->willReturn(new Basket());\n        Registry::set(Session::class, $session->reveal());\n\n        oxNew(BasketComponent::class)->init();\n\n        $basketReservations->discardUnusedReservations($cleanupRate)\n            ->shouldHaveBeenCalledOnce();\n    }\n\n    private function prepareSessionMock(): void\n    {\n        $basket = oxNew(Basket::class);\n        $basket->setStockCheckMode(false);\n        $session = $this->createConfiguredStub(\n            Session::class,\n            [\n                'getId' => 'random-string',\n                'isActualSidInCookie' => true,\n                'checkSessionChallenge' => false,\n                'getBasket' => $basket\n            ]\n        );\n\n        Registry::set(\n            Session::class,\n            $session\n        );\n    }\n\n    private function actAsSearchEngine(bool $isSearchEngine): void\n    {\n        Registry::set(\n            Utils::class,\n            $this->createConfiguredStub(\n                Utils::class,\n                ['isSearchEngine' => $isSearchEngine]\n            )\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Component/UserComponentTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Component;\n\nuse OxidEsales\\Eshop\\Application\\Component\\UserComponent;\nuse OxidEsales\\Eshop\\Application\\Controller\\FrontendController;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Session;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\n\nfinal class UserComponentTest extends IntegrationTestCase\n{\n    private string $userName = 'some-users-email@example.com';\n    private string $password = 'password123';\n\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->mockSession();\n        Registry::getConfig()->reinitialize();\n    }\n\n    public function testCreateUserWillActivateUserAutomatically(): void\n    {\n        $_POST = $this->getUserFormData();\n\n        $this->getUserComponent()->createUser();\n\n        $this->assertNotEmpty($this->fetchUserData()['OXACTIVE']);\n    }\n\n    public function testCreateUserWithPrivateSalesWillNotActivateUserAutomatically(): void\n    {\n        Registry::getConfig()->setConfigParam('blPsLoginEnabled', true);\n        $_POST = $this->getUserFormData();\n\n        $this->getUserComponent()->createUser();\n\n        $this->assertEmpty($this->fetchUserData()['OXACTIVE']);\n    }\n\n    public function testCreateUserWithMissingBillingAddressData(): void\n    {\n        $requestData = $this->getUserFormData();\n        unset($requestData['invadr']);\n        $_POST = $requestData;\n\n        $return = $this->getUserComponent()->createUser();\n\n        $this->assertFalse($return);\n    }\n\n    public function testCreateUserWithPrivateSalesAndExtraFormDataWillNotUpdateUserStatus(): void\n    {\n        Registry::getConfig()->setConfigParam('blPsLoginEnabled', true);\n        $requestData = $this->getUserFormData();\n        $requestData['invadr']['oxuser__oxactive'] = 1;\n        $_POST = $requestData;\n\n        $this->getUserComponent()->createUser();\n\n        $this->assertEmpty($this->fetchUserData()['OXACTIVE']);\n    }\n\n    public function testCreateUserWithExtraFormDataWillNotUpdateNonAddressUserFields(): void\n    {\n        $wrongShopId = 123;\n        $wrongUserRights = 'admin';\n        $wrongCustomerNumber = 12345;\n        $wrongPassword = uniqid('some-pass-', true);\n        $wrongPasswordSalt = uniqid('some-pass-salt-', true);\n        $wrongTimestamp = '2001-01-01';\n        $wrongUpdateExpiration = 123;\n        $requestData = $this->getUserFormData();\n        $requestData['invadr']['oxuser__oxshopid'] = $wrongShopId;\n        $requestData['invadr']['oxuser__oxrights'] = $wrongUserRights;\n        $requestData['invadr']['oxuser__oxcustnr'] = $wrongCustomerNumber;\n        $requestData['invadr']['oxuser__oxpassword'] = $wrongPassword;\n        $requestData['invadr']['oxuser__oxpasssalt'] = $wrongPasswordSalt;\n        $requestData['invadr']['oxuser__oxcreate'] = $wrongTimestamp;\n        $requestData['invadr']['oxuser__oxregister'] = $wrongTimestamp;\n        $requestData['invadr']['oxuser__oxupdatekey'] = $wrongTimestamp;\n        $requestData['invadr']['oxuser__oxupdateexp'] = $wrongUpdateExpiration;\n        $_POST = $requestData;\n\n        $this->getUserComponent()->createUser();\n\n        $userData = $this->fetchUserData();\n        $this->assertNotEquals($wrongShopId, $userData['OXSHOPID']);\n        $this->assertNotEquals($wrongUserRights, $userData['OXRIGHTS']);\n        $this->assertNotEquals($wrongUserRights, $userData['OXCUSTNR']);\n        $this->assertNotEquals($wrongPassword, $userData['OXPASSWORD']);\n        $this->assertNotEquals($wrongPasswordSalt, $userData['OXPASSSALT']);\n        $this->assertNotEquals($wrongTimestamp, $userData['OXCREATE']);\n        $this->assertNotEquals($wrongTimestamp, $userData['OXREGISTER']);\n        $this->assertNotEquals($wrongTimestamp, $userData['OXUPDATEKEY']);\n        $this->assertNotEquals($wrongUpdateExpiration, $userData['OXUPDATEEXP']);\n    }\n\n    public function testChangeUserWithMissingBillingAddressData(): void\n    {\n        $_POST = $this->getUserFormData();\n        $this->getUserComponent()->createUser();\n\n        $requestData = $this->getUserFormData();\n        unset($requestData['invadr']);\n        $_POST = $requestData;\n\n        $return = $this->getUserComponent()->changeUser();\n\n        $this->assertFalse($return);\n    }\n\n    public function testChangeUserWithoutOrderRemark(): void\n    {\n        $_POST = $this->getUserFormData();\n        $this->getUserComponent()->createUser();\n\n        $this->getUserComponent()->changeUser();\n\n        $this->assertFalse(Registry::getSession()->hasVariable('ordrem'));\n    }\n\n    public function testChangeUserWithOrderRemark(): void\n    {\n        $_POST = $this->getUserFormData();\n        $this->getUserComponent()->createUser();\n\n        $orderRemark = 'Some order remark';\n        $_POST['order_remark'] = $orderRemark;\n\n        $this->getUserComponent()->changeUser();\n\n        $this->assertEquals($orderRemark, Registry::getSession()->getVariable('ordrem'));\n    }\n\n    public function testChangeUserWithExtraFormDataWillNotUpdateNonAddressUserFields(): void\n    {\n        $_POST = $this->getUserFormData();\n        $this->getUserComponent()->createUser();\n\n        $wrongShopId = 123;\n        $wrongUserRights = 'admin';\n        $wrongCustomerNumber = 12345;\n        $wrongPassword = uniqid('some-pass-', true);\n        $wrongPasswordSalt = uniqid('some-pass-salt-', true);\n        $wrongTimestamp = '2001-01-01';\n        $wrongUpdateExpiration = 123;\n        $requestData = $this->getUserFormData();\n        $requestData['invadr']['oxuser__oxshopid'] = $wrongShopId;\n        $requestData['invadr']['oxuser__oxrights'] = $wrongUserRights;\n        $requestData['invadr']['oxuser__oxcustnr'] = $wrongCustomerNumber;\n        $requestData['invadr']['oxuser__oxpassword'] = $wrongPassword;\n        $requestData['invadr']['oxuser__oxpasssalt'] = $wrongPasswordSalt;\n        $requestData['invadr']['oxuser__oxcreate'] = $wrongTimestamp;\n        $requestData['invadr']['oxuser__oxregister'] = $wrongTimestamp;\n        $requestData['invadr']['oxuser__oxupdatekey'] = $wrongTimestamp;\n        $requestData['invadr']['oxuser__oxupdateexp'] = $wrongUpdateExpiration;\n        $_POST = $requestData;\n\n        $this->getUserComponent()->changeUser();\n\n        $userData = $this->fetchUserData();\n        $this->assertNotEquals($wrongShopId, $userData['OXSHOPID']);\n        $this->assertNotEquals($wrongUserRights, $userData['OXRIGHTS']);\n        $this->assertNotEquals($wrongUserRights, $userData['OXCUSTNR']);\n        $this->assertNotEquals($wrongPassword, $userData['OXPASSWORD']);\n        $this->assertNotEquals($wrongPasswordSalt, $userData['OXPASSSALT']);\n        $this->assertNotEquals($wrongTimestamp, $userData['OXCREATE']);\n        $this->assertNotEquals($wrongTimestamp, $userData['OXREGISTER']);\n        $this->assertNotEquals($wrongTimestamp, $userData['OXUPDATEKEY']);\n        $this->assertNotEquals($wrongUpdateExpiration, $userData['OXUPDATEEXP']);\n    }\n\n    public function testChangeUserEmailValidation(): void\n    {\n        $existingUser = $this->createUser($this->userName);\n        $guestUser = $this->createUser('guest@example.com', true);\n        $secondUser = $this->createUser('second-user@example.com');\n\n        $this->assertEmailChangeRejected($secondUser['email'], $existingUser['email']);\n        $this->assertEmailChangeRejected($secondUser['email'], $guestUser['email']);\n        $this->assertEmailChangeAccepted($secondUser['email'], 'new-unique-email@example.com');\n    }\n\n    private function createUser(string $email, bool $isGuest = false): array\n    {\n        $userData = $this->getUserFormData();\n\n        if ($isGuest) {\n            unset($userData['lgn_pwd'], $userData['lgn_pwd2']);\n        }\n\n        $userData['oxuser__oxusername'] = $email;\n        $userData['lgn_usr'] = $email;\n        $_POST = $userData;\n\n        $this->getUserComponent()->createUser();\n\n        return [\n            'email' => $email\n        ];\n    }\n\n    private function assertEmailChangeRejected(string $currentEmail, string $newEmail): void\n    {\n        $this->userName = $currentEmail;\n\n        $requestData = $this->prepareChangeUserRequest($newEmail);\n        $_POST = $requestData;\n\n        $result = $this->getUserComponent()->changeuser_testvalues();\n\n        $this->assertNull($result);\n        $userData = $this->fetchUserData();\n        $this->assertEquals($currentEmail, $userData['OXUSERNAME']);\n    }\n\n    private function assertEmailChangeAccepted(string $currentEmail, string $newEmail): void\n    {\n        $this->userName = $currentEmail;\n\n        $requestData = $this->prepareChangeUserRequest($newEmail);\n        $_POST = $requestData;\n\n        $result = $this->getUserComponent()->changeuser_testvalues();\n\n        $this->assertEquals('account_user', $result);\n        $this->userName = $newEmail;\n        $userData = $this->fetchUserData();\n        $this->assertEquals($newEmail, $userData['OXUSERNAME']);\n    }\n\n    private function prepareChangeUserRequest(string $newEmail): array\n    {\n        $requestData = $this->getUserFormData();\n        $requestData['invadr']['oxuser__oxusername'] = $newEmail;\n        return $requestData;\n    }\n\n    private function mockSession(): void\n    {\n        $sessionMock = $this->createPartialMock(Session::class, ['checkSessionChallenge']);\n        $sessionMock\n            ->expects($this->atLeastOnce())\n            ->method('checkSessionChallenge')\n            ->willReturn(true);\n        Registry::set(Session::class, $sessionMock);\n    }\n\n    private function getUserFormData(): array\n    {\n        return [\n            'oxuser__oxfname' => uniqid('first-name-', true),\n            'oxuser__oxlname' => uniqid('last-name-', true),\n            'oxuser__oxusername' => $this->userName,\n            'lgn_usr' => $this->userName,\n            'lgn_pwd' => $this->password,\n            'lgn_pwd2' => $this->password,\n            'user_password' => $this->password,\n            'invadr' => [\n                'oxuser__oxfname' => uniqid('first-name-', true),\n                'oxuser__oxlname' => uniqid('last-name-', true),\n                'oxuser__oxstreet' => uniqid('street-', true),\n                'oxuser__oxstreetnr' => 123,\n                'oxuser__oxzip' => 123,\n                'oxuser__oxcity' => 'Freiburg',\n                'oxuser__oxcountryid' => 'a7c40f631fc920687.20179984',\n            ],\n            'deladr' => [\n                'oxaddress__oxfname' => uniqid('del-first-name-', true),\n                'oxaddress__oxlname' => uniqid('del-last-name-', true),\n                'oxaddress__oxstreet' => uniqid('del-street-', true),\n                'oxaddress__oxstreetnr' => 123,\n                'oxaddress__oxzip' => 123,\n                'oxaddress__oxcity' => 'Freiburg',\n                'oxaddress__oxcountryid' => 'a7c40f631fc920687.20179984',\n            ]\n        ];\n    }\n\n\n    private function getUserComponent(): UserComponent\n    {\n        $userComponent = oxNew(UserComponent::class);\n        $userComponent->setParent(oxNew(FrontendController::class));\n        return $userComponent;\n    }\n\n    private function fetchUserData(): array\n    {\n        return $this\n            ->getDbConnection()\n            ->createQueryBuilder()\n            ->select('*')\n            ->from('oxuser')\n            ->where('oxusername = :oxusername')\n            ->setParameters([\n                'oxusername' => $this->userName,\n            ])\n            ->executeQuery()\n            ->fetchAssociative();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Controller/Admin/ArticlePicturesAjaxTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ArticlePicturesAjax;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaType;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\MediaUploaderInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Dao\\ProductMediaDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRole;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRoleSet;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Symfony\\Component\\Filesystem\\Path;\nuse Symfony\\Component\\HttpFoundation\\File\\UploadedFile;\nuse Symfony\\Component\\HttpFoundation\\FileBag;\nuse Symfony\\Component\\HttpFoundation\\Request;\n\nfinal class ArticlePicturesAjaxTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private const VALID_IMAGE = 'valid_image.jpg';\n    private const FIXTURES_PATH = '/Fixtures/';\n\n    public function testRemoveMedia(): void\n    {\n        $productMediaId = Id::generate();\n        $productId = Id::generate();\n\n        $this->setupContainerWithRequest([\n            'productMediaId' => (string) $productMediaId,\n            'role' => ProductMediaRole::DETAIL,\n        ]);\n\n        $this->createArticleWithId((string) $productId);\n        $this->addProductMediaWithId($productMediaId, $productId, ProductMediaRole::DETAIL);\n\n        $controller = oxNew(ArticlePicturesAjax::class);\n        $controller->removeMedia();\n\n        $this->assertCount(0, $this->get(ProductMediaDaoInterface::class)->getAll($productId));\n    }\n\n    public function testRemoveMediaRemovesOnlyRoleWhenMultipleRolesExist(): void\n    {\n        $productMediaId = Id::generate();\n        $productId = Id::generate();\n\n        $this->setupContainerWithRequest([\n            'productMediaId' => (string) $productMediaId,\n            'role' => ProductMediaRole::ICON,\n        ]);\n\n        $this->createArticleWithId((string) $productId);\n        $this->addProductMediaWithId($productMediaId, $productId, ProductMediaRole::ICON, ProductMediaRole::THUMBNAIL);\n\n        $controller = oxNew(ArticlePicturesAjax::class);\n        $controller->removeMedia();\n\n        $allMedia = $this->get(ProductMediaDaoInterface::class)->getAll($productId);\n        $this->assertCount(1, $allMedia);\n\n        $existingMedia = $this->get(ProductMediaDaoInterface::class)->get($productMediaId);\n        $this->assertFalse($existingMedia->getRoleSet()->has(ProductMediaRole::from(ProductMediaRole::ICON)));\n        $this->assertTrue($existingMedia->getRoleSet()->has(ProductMediaRole::from(ProductMediaRole::THUMBNAIL)));\n    }\n\n    public function testToggleMediaActiveStateDeactivates(): void\n    {\n        $productMediaId = Id::generate();\n        $productId = Id::generate();\n\n        $this->setupContainerWithRequest([\n            'productMediaId' => (string) $productMediaId,\n        ]);\n\n        $this->createArticleWithId((string) $productId);\n        $productMedia = $this->addProductMediaWithId($productMediaId, $productId, ProductMediaRole::DETAIL);\n\n        $this->assertTrue($productMedia->isActive());\n\n        $controller = oxNew(ArticlePicturesAjax::class);\n        $controller->toggleMediaActiveState();\n\n        $updatedMedia = $this->get(ProductMediaDaoInterface::class)->get($productMediaId);\n        $this->assertFalse($updatedMedia->isActive());\n    }\n\n    public function testToggleMediaActiveStateActivates(): void\n    {\n        $productMediaId = Id::generate();\n        $productId = Id::generate();\n\n        $this->setupContainerWithRequest([\n            'productMediaId' => (string) $productMediaId,\n        ]);\n\n        $this->createArticleWithId((string) $productId);\n        $productMedia = $this->addProductMediaWithId($productMediaId, $productId, ProductMediaRole::DETAIL);\n        $this->get(ProductMediaServiceInterface::class)->deactivate($productMedia);\n\n        $controller = oxNew(ArticlePicturesAjax::class);\n        $controller->toggleMediaActiveState();\n\n        $updatedMedia = $this->get(ProductMediaDaoInterface::class)->get($productMediaId);\n        $this->assertTrue($updatedMedia->isActive());\n    }\n\n    public function testSortMedia(): void\n    {\n        $productId = Id::generate();\n        $mediaId1 = Id::generate();\n        $mediaId2 = Id::generate();\n        $mediaId3 = Id::generate();\n\n        $newOrder = [\n            (string) $mediaId3,\n            (string) $mediaId1,\n            (string) $mediaId2,\n        ];\n\n        $this->setupContainerWithRequest([\n            'sorting' => json_encode($newOrder),\n        ]);\n\n        $this->createArticleWithId((string) $productId);\n        $this->addProductMediaWithId($mediaId1, $productId, ProductMediaRole::DETAIL);\n        $this->addProductMediaWithId($mediaId2, $productId, ProductMediaRole::DETAIL);\n        $this->addProductMediaWithId($mediaId3, $productId, ProductMediaRole::DETAIL);\n\n        $controller = oxNew(ArticlePicturesAjax::class);\n        $controller->sortMedia();\n\n        $allMedia = $this->get(ProductMediaDaoInterface::class)->getAll($productId);\n\n        $positions = [];\n        foreach ($allMedia as $m) {\n            $positions[(string) $m->getId()] = $m->getPosition();\n        }\n\n        $this->assertLessThan($positions[(string) $mediaId1], $positions[(string) $mediaId3]);\n        $this->assertLessThan($positions[(string) $mediaId2], $positions[(string) $mediaId1]);\n    }\n\n    public function testAddMedia(): void\n    {\n        $fixture = Path::join(__DIR__, self::FIXTURES_PATH, self::VALID_IMAGE);\n\n        $uploadedFile = new UploadedFile(\n            $fixture,\n            self::VALID_IMAGE,\n            'image/jpeg',\n            null,\n            true\n        );\n\n        $productId = Id::generate();\n\n        $this->setupContainerWithRequestAndMocks(\n            [\n                'productId' => (string) $productId,\n                'role' => ProductMediaRole::DETAIL,\n            ],\n            [$uploadedFile]\n        );\n\n        $this->createArticleWithId((string) $productId);\n\n        $controller = oxNew(ArticlePicturesAjax::class);\n        $controller->addMedia();\n\n        $allMedia = $this->get(ProductMediaDaoInterface::class)->getAll($productId);\n        $this->assertCount(1, $allMedia);\n        $this->assertTrue($allMedia->first()->getRoleSet()->has(ProductMediaRole::from(ProductMediaRole::DETAIL)));\n    }\n\n    public function testReplaceMedia(): void\n    {\n        $fixture = Path::join(__DIR__, self::FIXTURES_PATH, self::VALID_IMAGE);\n\n        $uploadedFile = new UploadedFile(\n            $fixture,\n            self::VALID_IMAGE,\n            'image/jpeg',\n            null,\n            true\n        );\n\n        $productId = Id::generate();\n        $existingMediaId = Id::generate();\n\n        $this->setupContainerWithRequestAndMocks(\n            [\n                'productId' => (string) $productId,\n                'role' => ProductMediaRole::ICON,\n                'productMediaId' => (string) $existingMediaId,\n            ],\n            [$uploadedFile]\n        );\n\n        $this->createArticleWithId((string) $productId);\n        $this->addProductMediaWithId($existingMediaId, $productId, ProductMediaRole::ICON);\n\n        $controller = oxNew(ArticlePicturesAjax::class);\n        $controller->replaceMedia();\n\n        $allMedia = $this->get(ProductMediaDaoInterface::class)->getAll($productId);\n        $this->assertCount(1, $allMedia);\n        $this->assertNotEquals((string) $existingMediaId, (string) $allMedia->first()->getId());\n    }\n\n    public function testReplaceMediaKeepsExistingWhenValidationFails(): void\n    {\n        $fixture = Path::join(__DIR__, self::FIXTURES_PATH, self::VALID_IMAGE);\n        $uploadedFile = new UploadedFile($fixture, self::VALID_IMAGE, 'image/jpeg', null, true);\n        $productId = Id::generate();\n        $existingMediaId = Id::generate();\n\n        $this->rewriteProjectConfiguration([\n            'parameters' => [\n                'oxid_esales.product.media.file.min_size_kb' => '1048576',\n            ]\n        ]);\n\n        $this->setupContainerWithRequest(\n            [\n                'productId' => (string) $productId,\n                'role' => ProductMediaRole::ICON,\n                'productMediaId' => (string) $existingMediaId,\n            ],\n            [$uploadedFile]\n        );\n\n        $this->createArticleWithId((string) $productId);\n        $this->addProductMediaWithId($existingMediaId, $productId, ProductMediaRole::ICON);\n\n        ob_start();\n        $controller = oxNew(ArticlePicturesAjax::class);\n        $controller->replaceMedia();\n        ob_end_clean();\n\n        $allMedia = $this->get(ProductMediaDaoInterface::class)->getAll($productId);\n        $this->assertEquals((string) $existingMediaId, (string) $allMedia->first()->getId());\n    }\n\n    public function testReplaceMediaRemovesOnlyRoleWhenMultipleRolesExist(): void\n    {\n        $fixture = Path::join(__DIR__, self::FIXTURES_PATH, self::VALID_IMAGE);\n        $uploadedFile = new UploadedFile($fixture, self::VALID_IMAGE, 'image/jpeg', null, true);\n        $productId = Id::generate();\n        $existingMediaId = Id::generate();\n\n        $this->setupContainerWithRequestAndMocks(\n            [\n                'productId' => (string) $productId,\n                'role' => ProductMediaRole::ICON,\n                'productMediaId' => (string) $existingMediaId,\n            ],\n            [$uploadedFile]\n        );\n\n        $this->createArticleWithId((string) $productId);\n        $this->addProductMediaWithId($existingMediaId, $productId, ProductMediaRole::ICON, ProductMediaRole::THUMBNAIL);\n\n        $controller = oxNew(ArticlePicturesAjax::class);\n        $controller->replaceMedia();\n\n        $allMedia = $this->get(ProductMediaDaoInterface::class)->getAll($productId);\n        $this->assertCount(2, $allMedia);\n\n        $existingMedia = $this->get(ProductMediaDaoInterface::class)->get($existingMediaId);\n        $this->assertFalse($existingMedia->getRoleSet()->has(ProductMediaRole::from(ProductMediaRole::ICON)));\n        $this->assertTrue($existingMedia->getRoleSet()->has(ProductMediaRole::from(ProductMediaRole::THUMBNAIL)));\n    }\n\n    private function createArticleWithId(string $id): void\n    {\n        $article = oxNew(Article::class);\n        $article->setId($id);\n        $article->oxarticles__oxshopid = new Field(1);\n        $article->oxarticles__oxactive = new Field(1);\n        $article->oxarticles__oxtitle = new Field('Test Article');\n        $article->oxarticles__oxprice = new Field(10.0);\n        $article->save();\n    }\n\n    private function addProductMediaWithId(Id $mediaId, Id $productId, string ...$roles): ProductMedia\n    {\n        $roleSet = new ProductMediaRoleSet(...array_map(\n            fn(string $role) => ProductMediaRole::from($role),\n            $roles\n        ));\n\n        $productMedia = new ProductMedia(\n            $mediaId,\n            $productId,\n            new Media(Id::generate(), new MediaPath('out/pictures/media/test.jpg'), new MediaType('image/jpeg')),\n            $roleSet,\n        );\n        $this->get(ProductMediaServiceInterface::class)->add($productMedia);\n\n        return $productMedia;\n    }\n\n    private function setupContainerWithRequest(array $postParameters, array $files = []): void\n    {\n        $this->createContainer();\n\n        $request = new Request([], $postParameters, [], [], [], [], null);\n        if (!empty($files)) {\n            $request->files = new FileBag(['uploadedFiles' => $files]);\n        }\n\n        $this->container->set(Request::class, $request);\n        $this->compileContainer();\n        $this->replaceContainerInstance();\n    }\n\n    private function setupContainerWithRequestAndMocks(array $postParameters, array $files = []): void\n    {\n        $this->rewriteProjectConfiguration([\n            'parameters' => [\n                'oxid_esales.product.media.file.min_size_kb' => '0',\n            ]\n        ]);\n\n        $this->createContainer();\n\n        $request = new Request([], $postParameters, [], [], [], [], null);\n        if (!empty($files)) {\n            $request->files = new FileBag(['uploadedFiles' => $files]);\n        }\n\n        $mediaUploader = $this->createStub(MediaUploaderInterface::class);\n        $mediaUploader\n            ->method('uploadTo')\n            ->willReturnCallback(function (UploadedFile $file, MediaPath $path) {\n                return new MediaPath(\n                    Path::join('out/pictures/media/products/placeholder', $file->getClientOriginalName())\n                );\n            });\n\n        $this->container->set(Request::class, $request);\n        $this->container->set('oxid_esales.product.media.media_uploader', $mediaUploader);\n        $this->compileContainer();\n        $this->replaceContainerInstance();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Controller/Admin/ArticleVariantTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Controller\\Admin\\ArticleVariant;\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaType;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Dao\\ProductMediaDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRole;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRoleSet;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ArticleVariantTest extends IntegrationTestCase\n{\n    public function testSaveNewVariantCopiesMediaFromParent(): void\n    {\n        $parentId = $this->createParentArticle();\n        $this->addProductMedia(Id::fromString($parentId), ProductMediaRole::DETAIL);\n\n        $controller = oxNew(ArticleVariant::class);\n        $controller->setEditObjectId($parentId);\n        $controller->savevariant('-1', ['oxarticles__oxvarselect' => 'Test Variant']);\n\n        $variantId = $this->getVariantId($parentId);\n        $this->assertCount(1, $this->get(ProductMediaDaoInterface::class)->getAll(Id::fromString($variantId)));\n    }\n\n    public function testSaveNewVariantCopiesMultipleMedia(): void\n    {\n        $parentId = $this->createParentArticle();\n        $this->addProductMedia(Id::fromString($parentId), ProductMediaRole::ICON);\n        $this->addProductMedia(Id::fromString($parentId), ProductMediaRole::THUMBNAIL);\n        $this->addProductMedia(Id::fromString($parentId), ProductMediaRole::DETAIL);\n\n        $controller = oxNew(ArticleVariant::class);\n        $controller->setEditObjectId($parentId);\n        $controller->savevariant('-1', ['oxarticles__oxvarselect' => 'Test Variant']);\n\n        $variantId = $this->getVariantId($parentId);\n        $this->assertCount(3, $this->get(ProductMediaDaoInterface::class)->getAll(Id::fromString($variantId)));\n    }\n\n    public function testSaveNewVariantPreservesMediaRoles(): void\n    {\n        $parentId = $this->createParentArticle();\n        $this->addProductMedia(Id::fromString($parentId), ProductMediaRole::ICON);\n\n        $controller = oxNew(ArticleVariant::class);\n        $controller->setEditObjectId($parentId);\n        $controller->savevariant('-1', ['oxarticles__oxvarselect' => 'Test Variant']);\n\n        $variantId = $this->getVariantId($parentId);\n        $variantMedia = $this->get(ProductMediaDaoInterface::class)->getAll(Id::fromString($variantId))->first();\n        $this->assertTrue($variantMedia->getRoleSet()->has(ProductMediaRole::from(ProductMediaRole::ICON)));\n    }\n\n    public function testSaveExistingVariantDoesNotCopyMedia(): void\n    {\n        $parentId = $this->createParentArticle();\n        $variantId = $this->createVariantArticle($parentId);\n        $this->addProductMedia(Id::fromString($parentId), ProductMediaRole::DETAIL);\n\n        $controller = oxNew(ArticleVariant::class);\n        $controller->setEditObjectId($parentId);\n        $controller->savevariant($variantId, ['oxarticles__oxvarselect' => 'Updated Variant']);\n\n        $this->assertCount(0, $this->get(ProductMediaDaoInterface::class)->getAll(Id::fromString($variantId)));\n    }\n\n    private function createParentArticle(): string\n    {\n        $article = oxNew(Article::class);\n        $article->oxarticles__oxshopid = new Field(1);\n        $article->oxarticles__oxactive = new Field(1);\n        $article->oxarticles__oxtitle = new Field('Parent Article');\n        $article->oxarticles__oxprice = new Field(10.0);\n        $article->save();\n\n        return $article->getId();\n    }\n\n    private function createVariantArticle(string $parentId): string\n    {\n        $article = oxNew(Article::class);\n        $article->oxarticles__oxshopid = new Field(1);\n        $article->oxarticles__oxparentid = new Field($parentId);\n        $article->oxarticles__oxvarselect = new Field('Existing Variant');\n        $article->save();\n\n        return $article->getId();\n    }\n\n    private function getVariantId(string $parentId): string\n    {\n        $parent = oxNew(Article::class);\n        $parent->load($parentId);\n\n        return $parent->getAdminVariants()->current()->getId();\n    }\n\n    private function addProductMedia(Id $productId, string $role): void\n    {\n        $productMedia = new ProductMedia(\n            Id::generate(),\n            $productId,\n            new Media(Id::generate(), new MediaPath('out/pictures/media/test.jpg'), new MediaType('image/jpeg')),\n            new ProductMediaRoleSet(ProductMediaRole::from($role)),\n        );\n        $this->get(ProductMediaServiceInterface::class)->add($productMedia);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Controller/Admin/ContentMainTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace Integration\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Controller\\Admin\\ContentMain;\nuse OxidEsales\\Eshop\\Application\\Model\\Content;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\ContainerFactory;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ContentMainTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private string $contentId = 'testContentId';\n    private string $safeHtml = '<div><a>Click</a><img src=\"x\" /></div>';\n    private string $unsafeHtml = '<div onclick=\"alert(1)\"><script>evil()</script>'\n        . '<a href=\"javascript:alert(1)\">Click</a><img src=\"x\" onerror=\"evil()\"><iframe src=\"evil.html\"></div>';\n\n    private ContentMain $contentMain;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        $this->contentMain = $this->createContentMain();\n        $this->createContainer();\n    }\n\n    public function tearDown(): void\n    {\n        ContainerFactory::resetContainer();\n        parent::tearDown();\n    }\n\n    public function testSanitizerShouldRemoveUnsafeHtmlContent(): void\n    {\n        $this->setParameter('oxid_esales.html_sanitizer_enabled', true);\n        $this->mockContentRequest($this->unsafeHtml);\n\n        $this->contentMain->save();\n\n        $content = $this->loadContent($this->contentId);\n        $this->assertEquals($this->safeHtml, $content->oxcontents__oxcontent->value);\n    }\n\n    public function testSaveShouldPreserveEmptyContent(): void\n    {\n        $this->setParameter('oxid_esales.html_sanitizer_enabled', true);\n        $this->mockContentRequest('');\n\n        $this->contentMain->save();\n\n        $content = $this->loadContent($this->contentId);\n        $this->assertEmpty($content->oxcontents__oxcontent->value);\n    }\n\n    public function testDisabledSanitizerShouldPreserveAllElements(): void\n    {\n        $this->setParameter('oxid_esales.html_sanitizer_enabled', false);\n        $this->mockContentRequest($this->unsafeHtml);\n\n        $this->contentMain->save();\n\n        $content = $this->loadContent($this->contentId);\n        $this->assertEquals($this->unsafeHtml, $content->oxcontents__oxcontent->value);\n    }\n\n    public function testSanitizerShouldHandleMalformedHtml(): void\n    {\n        $this->setParameter('oxid_esales.html_sanitizer_enabled', true);\n        $malformedHtml = '<div><span>missing closing div';\n        $this->mockContentRequest($malformedHtml);\n\n        $this->contentMain->save();\n\n        $content = $this->loadContent($this->contentId);\n        $this->assertStringContainsString($malformedHtml, $content->oxcontents__oxcontent->value);\n    }\n\n    public function testIdentShouldBePreparedAndPersisted(): void\n    {\n        $this->setParameter('oxid_esales.html_sanitizer_enabled', true);\n\n        $this->mockPostRequest([\n            'oxid' => $this->contentId,\n            'editval' => $this->createEditValArray('a-b_c.d!', 'Test content')\n        ]);\n\n        $this->contentMain->save();\n\n        $content = $this->loadContent($this->contentId);\n        $this->assertEquals('ab_cd', $content->oxcontents__oxloadid->value);\n    }\n\n    public function testDuplicateIdentShouldTriggerError(): void\n    {\n        $this->setParameter('oxid_esales.html_sanitizer_enabled', true);\n\n        $ident = 'abc';\n        $this->mockPostRequest([\n            'oxid' => $this->contentId,\n            'editval' => $this->createEditValArray($ident, 'Test content 1')\n        ]);\n        $this->contentMain->setEditObjectId($this->contentId);\n        $this->contentMain->save();\n\n        $this->mockPostRequest([\n            'oxid' => 'other-content-id',\n            'editval' => $this->createEditValArray($ident, 'Test content 2')\n        ]);\n        $this->contentMain->setEditObjectId('other-content-id');\n        $this->contentMain->save();\n\n        $this->assertTrue($this->contentMain->getViewData()['blLoadError']);\n    }\n\n    public function testSaveinnlangShouldRemoveUnsafeHtmlWhenSanitizerEnabled(): void\n    {\n        $this->setParameter('oxid_esales.html_sanitizer_enabled', true);\n\n        $newLang = 1;\n        $this->mockPostRequest([\n            'oxid' => $this->contentId,\n            'new_lang' => $newLang,\n            'editval' => $this->createEditValArray(uniqid(), $this->unsafeHtml)\n        ]);\n\n        $this->contentMain->saveinnlang();\n\n        $content = $this->loadContent($this->contentId);\n        $content->loadInLang($newLang, $this->contentId);\n        $this->assertEquals($this->safeHtml, $content->oxcontents__oxcontent->value);\n    }\n\n    public function testSaveinnlangShouldPreserveUnsafeHtmlWhenSanitizerDisabled(): void\n    {\n        $this->setParameter('oxid_esales.html_sanitizer_enabled', false);\n\n        $newLang = 1;\n        $this->mockPostRequest([\n            'oxid' => $this->contentId,\n            'new_lang' => $newLang,\n            'editval' => $this->createEditValArray(uniqid(), $this->unsafeHtml)\n        ]);\n\n        $this->contentMain->saveinnlang();\n\n        $content = $this->loadContent($this->contentId);\n        $content->loadInLang($newLang, $this->contentId);\n        $this->assertEquals($this->unsafeHtml, $content->oxcontents__oxcontent->value);\n    }\n\n    private function createContentMain(): ContentMain\n    {\n        $contentMain = new ContentMain();\n        Registry::getConfig()->setAdminMode(true);\n        Registry::getSession()->setVariable('blIsAdmin', true);\n\n        return $contentMain;\n    }\n\n    private function mockContentRequest(string $content): void\n    {\n        $this->mockPostRequest([\n            'oxid' => $this->contentId,\n            'editval' => $this->createEditValArray(uniqid(), $content)\n        ]);\n    }\n\n    private function createEditValArray(string $loadId, string $content): array\n    {\n        return [\n            'oxcontents__oxid' => $this->contentId,\n            'oxcontents__oxloadid' => $loadId,\n            'oxcontents__oxcontent' => $content,\n            'oxcontents__oxtype' => '0',\n            'oxcontents__oxfolder' => 'CMSFOLDER_NONE',\n        ];\n    }\n\n    private function mockPostRequest(array $data): void\n    {\n        $_POST = $data;\n    }\n\n    private function loadContent(string $id): Content\n    {\n        $content = new Content();\n        $content->load($id);\n        return $content;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Controller/Admin/CountryMainTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace Integration\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Controller\\Admin\\CountryMain;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class CountryMainTest extends IntegrationTestCase\n{\n    private bool $allowSharedEdit;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->allowSharedEdit = (bool) Registry::getConfig()->getConfigParam('blAllowSharedEdit');\n        Registry::getConfig()->setConfigParam('blAllowSharedEdit', true);\n    }\n\n    public function tearDown(): void\n    {\n        Registry::getConfig()->setConfigParam('blAllowSharedEdit', $this->allowSharedEdit);\n\n        parent::tearDown();\n    }\n\n    public function testSaveWithEmptyVatPrefixAndVatStatusIsActivated(): void\n    {\n        $this->mockSubmitFormData([\n            'oxcountry__oxvatstatus' => '1',\n            'oxcountry__oxvatinprefix' => '',\n        ]);\n\n        $countryMain = new CountryMain();\n        $countryMain->save();\n\n        $this->assertStringContainsString(\n            'ERROR_MESSAGE_INPUT_VAT_PREFIX_EMPTY',\n            Registry::getSession()->getVariable('Errors')['default'][0]\n        );\n    }\n\n    public function testSaveWithEmptyVatPrefixAndVatStatusIsDisabled(): void\n    {\n        $this->mockSubmitFormData([\n            'oxcountry__oxvatstatus' => '0',\n            'oxcountry__oxvatinprefix' => '',\n        ]);\n\n        $countryMain = new CountryMain();\n        $countryMain->save();\n\n        $this->assertEmpty(Registry::getSession()->getVariable('Errors'));\n    }\n\n    private function mockSubmitFormData(array $data): void\n    {\n        $_POST['editval'] = $data;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Controller/Admin/Fixtures/testModule/ModuleSetup.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Controller\\Admin\\Fixtures\\testModule;\n\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\n\nclass ModuleSetup\n{\n    /**\n     * Activation function for the module\n     */\n    public static function onActivate(): void\n    {\n        ContainerFacade::get(RendererInterface::class);\n    }\n\n    /**\n     * Deactivation function for the module\n     */\n    public static function onDeactivate(): void\n    {\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Controller/Admin/Fixtures/testModule/Renderer.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Controller\\Admin\\Fixtures\\testModule;\n\nclass Renderer implements RendererInterface\n{\n    public function formFilesOutput(): string\n    {\n        return 'Output';\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Controller/Admin/Fixtures/testModule/RendererInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Controller\\Admin\\Fixtures\\testModule;\n\ninterface RendererInterface\n{\n    public function formFilesOutput(): string;\n}\n"
  },
  {
    "path": "tests/Integration/Application/Controller/Admin/Fixtures/testModule/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.1';\n\n$aModule = array(\n    'id'          => 'testModuleId',\n    'title'       => 'myTestModule',\n    'description' => 'myTestModule',\n    'thumbnail'   => 'picture.png',\n    'version'     => '1.0',\n    'author'      => 'OXID eSales AG',\n    'settings'    => [\n        [\n            'group' => 'someGroup',\n            'name' => 'stringSetting',\n            'type' => 'str',\n            'value' => 'row'\n        ],\n        [\n            'group' => 'someGroup',\n            'name' => 'testInt',\n            'type' => 'num',\n            'value' => 0\n        ],\n        [\n            'group' => 'someGroup',\n            'name' => 'testFloat',\n            'type' => 'num',\n            'value' => 0.0\n        ]\n    ],\n    'events'      => [\n        'onActivate'   => '\\OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Controller\\Admin\\Fixtures\\testModule\\ModuleSetup::onActivate',\n        'onDeactivate' => '\\OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Controller\\Admin\\Fixtures\\testModule\\ModuleSetup::onDeactivate'\n    ]\n);\n"
  },
  {
    "path": "tests/Integration/Application/Controller/Admin/Fixtures/testModule/services.yaml",
    "content": "services:\n    _defaults:\n        autowire: true\n\n    OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Controller\\Admin\\Fixtures\\testModule\\RendererInterface:\n        class: OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Controller\\Admin\\Fixtures\\testModule\\Renderer\n        public: true"
  },
  {
    "path": "tests/Integration/Application/Controller/Admin/ManufacturerPictureTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace Integration\\Application\\Controller\\Admin;\n\nuse Generator;\nuse OxidEsales\\Eshop\\Core\\Config;\nuse OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ManufacturerPicture;\nuse OxidEsales\\EshopCommunity\\Application\\Model\\Manufacturer;\nuse OxidEsales\\EshopCommunity\\Core\\Exception\\ExceptionToDisplay;\nuse OxidEsales\\EshopCommunity\\Core\\Field;\nuse OxidEsales\\EshopCommunity\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\Attributes\\Group;\n\n#[Group('manufacturer')]\nfinal class ManufacturerPictureTest extends IntegrationTestCase\n{\n    private string $oxid = 'manufacturerId1';\n\n    private array $imageFields = [\n        'oxicon'           => 'test-icon.jpg',\n        'oxicon_alt'       => 'test-icon-alt.jpg',\n        'oxpicture'        => 'test-picture.jpg',\n        'oxthumbnail'      => 'test-thumbnail.jpg',\n        'oxpromotion_icon' => 'test-promotion-icon.jpg',\n    ];\n\n    public function testRender(): void\n    {\n        $view = oxNew(ManufacturerPicture::class);\n\n        $this->assertEquals('manufacturer_picture', $view->render());\n    }\n\n    public function testSaveShouldThrowAnExceptionInDemoShopMode(): void\n    {\n        $config = $this->createPartialMock(Config::class, [\"isDemoShop\"]);\n        $config->expects($this->once())->method('isDemoShop')->willReturn(true);\n\n        Registry::getSession()->deleteVariable('Errors');\n        Registry::set(Config::class, $config);\n\n        $manufacturerPicture = oxNew(ManufacturerPicture::class);\n        $manufacturerPicture->save();\n\n        $errors = Registry::getSession()->getVariable('Errors');\n        $exception = unserialize($errors['default'][0]);\n\n        $this->assertInstanceOf(ExceptionToDisplay::class, $exception);\n    }\n\n    public function testItShouldSaveImages(): void\n    {\n        $manufacturer = oxNew(Manufacturer::class);\n        $manufacturer->setId('testSaveManufacturerId');\n        $manufacturer->oxmanufacturers__oxicon = new Field('test-icon.jpg', Field::T_RAW);\n        $manufacturer->oxmanufacturers__oxicon_alt = new Field('test-icon-alt.jpg', Field::T_RAW);\n        $manufacturer->oxmanufacturers__oxpicture = new Field('test-picture.jpg', Field::T_RAW);\n        $manufacturer->oxmanufacturers__oxthumbnail = new Field('test-thumbnail.jpg', Field::T_RAW);\n        $manufacturer->oxmanufacturers__oxpromotion_icon = new Field('test-promotion-icon.jpg', Field::T_RAW);\n        $manufacturer->save();\n\n        $loadManufacturer = oxNew(Manufacturer::class);\n        $loadManufacturer->load('testSaveManufacturerId');\n\n        $this->assertSame('test-icon.jpg', $loadManufacturer->oxmanufacturers__oxicon->getRawValue());\n        $this->assertSame('test-icon-alt.jpg', $loadManufacturer->oxmanufacturers__oxicon_alt->getRawValue());\n        $this->assertSame('test-picture.jpg', $loadManufacturer->oxmanufacturers__oxpicture->getRawValue());\n        $this->assertSame('test-thumbnail.jpg', $loadManufacturer->oxmanufacturers__oxthumbnail->getRawValue());\n        $this->assertSame(\n            'test-promotion-icon.jpg',\n            $loadManufacturer->oxmanufacturers__oxpromotion_icon->getRawValue()\n        );\n    }\n\n    public function testDeleteShouldThrowAnExceptionInDemoShopMode(): void\n    {\n        $config = $this->createPartialMock(Config::class, [\"isDemoShop\"]);\n        $config->expects($this->once())->method('isDemoShop')->willReturn(true);\n\n        Registry::getSession()->deleteVariable(\"Errors\");\n        Registry::set(Config::class, $config);\n\n        $manufacturerPicture = oxNew(ManufacturerPicture::class);\n        $manufacturerPicture->deletePicture();\n\n        $errors = Registry::getSession()->getVariable('Errors');\n        $exception = unserialize($errors['default'][0]);\n\n        $this->assertInstanceOf(ExceptionToDisplay::class, $exception);\n    }\n\n    #[DataProvider('provideImageData')]\n    public function testDeleteShouldRemoveOnlyOneImageValueFromDb(string $expected, string $imageFieldName): void\n    {\n        $this->setupManufacturer();\n        $this->setupRequest($imageFieldName);\n        unset($this->imageFields[$imageFieldName]);\n\n        $manufacturerPicture = oxNew(ManufacturerPicture::class);\n\n        $manufacturerPicture->deletePicture();\n\n        $this->assertSame($expected, $this->fetchResult($imageFieldName));\n\n        foreach ($this->imageFields as $imageField => $fieldValue) {\n            $this->assertSame($fieldValue, $this->fetchResult($imageField));\n        }\n    }\n\n    public static function provideImageData(): Generator\n    {\n        yield 'Icon should be removed from database' => ['', 'oxicon',];\n        yield 'Icon Alt should be removed from database' => ['', 'oxicon_alt',];\n        yield 'Picture should be removed from database' => ['', 'oxpicture',];\n        yield 'Thumbnail should be removed from database' => ['', 'oxthumbnail',];\n        yield 'Promotion Icon should be removed from database' => ['', 'oxpromotion_icon',];\n    }\n\n    private function setupManufacturer(): void\n    {\n        $manufacturer = oxNew(Manufacturer::class);\n        $manufacturer->setId($this->oxid);\n        $manufacturer->oxmanufacturers__oxicon = new Field('test-icon.jpg', Field::T_RAW);\n        $manufacturer->oxmanufacturers__oxicon_alt = new Field('test-icon-alt.jpg', Field::T_RAW);\n        $manufacturer->oxmanufacturers__oxpicture = new Field('test-picture.jpg', Field::T_RAW);\n        $manufacturer->oxmanufacturers__oxthumbnail = new Field('test-thumbnail.jpg', Field::T_RAW);\n        $manufacturer->oxmanufacturers__oxpromotion_icon = new Field('test-promotion-icon.jpg', Field::T_RAW);\n        $manufacturer->save();\n    }\n\n    private function setupRequest(string $imageFieldName): void\n    {\n        $_POST['masterPictureField'] = $imageFieldName;\n        $_POST['oxid'] = $this->oxid;\n    }\n\n    private function fetchResult(string $imageFieldName): bool|string\n    {\n        $this->get(QueryBuilderFactoryInterface::class)->create();\n\n        return $this\n            ->getDbConnection()\n            ->createQueryBuilder()\n            ->select($imageFieldName)\n            ->from('oxmanufacturers')\n            ->where('oxid = :oxid')\n            ->setParameters([\n                'oxid' => $this->oxid,\n            ])\n            ->executeQuery()\n            ->fetchOne();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Controller/Admin/ModuleConfigurationTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\ModuleConfiguration as ModuleConfigurationController;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\ContainerFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ModuleConfigurationDaoBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ModuleSettingServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ModuleConfigurationTest extends IntegrationTestCase\n{\n    private string $testModuleDir = 'testModule';\n    private string $testModuleId = 'testModuleId';\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        $this->installTestModule();\n    }\n\n    public function tearDown(): void\n    {\n        $this->uninstallTestModule();\n        $this->unsetAdminMode();\n        parent::tearDown();\n    }\n\n    public function testRender(): void\n    {\n        $_POST['oxid'] = $this->testModuleId;\n        $moduleConfigurationController = oxNew(ModuleConfigurationController::class);\n\n        $renderResponse = $moduleConfigurationController->render();\n        $viewData = $moduleConfigurationController->getViewData();\n\n        $this->assertEquals('module_config', $renderResponse);\n        $this->assertSame(\n            $this->testModuleId,\n            $viewData['oModule']->getId()\n        );\n    }\n\n    public function testSaveConfVarsForInactiveModule(): void\n    {\n        $this->saveConfVars(['stringSetting' => 'newValue']);\n        ContainerFactory::resetContainer();\n\n        $this->assertSame(\n            'newValue',\n            $this->getModuleConfiguration()->getModuleSettings()[0]->getValue()\n        );\n    }\n\n    public function testSaveConfVarsForActiveModule(): void\n    {\n        $this->activateTestModule();\n\n        $this->saveConfVars(['stringSetting' => 'newValue']);\n        ContainerFactory::resetContainer();\n\n        $this->assertSame(\n            'newValue',\n            $this->getModuleConfiguration()->getModuleSetting('stringSetting')->getValue()\n        );\n    }\n\n    public function testModuleSettingCacheInvalidatedAfterSave(): void\n    {\n        $this->activateTestModule();\n\n        $oldValueToBeCached = ContainerFacade::get(ModuleSettingServiceInterface::class)\n            ->getString('stringSetting', $this->testModuleId);\n        $this->assertEquals('row', $oldValueToBeCached);\n\n        $this->saveConfVars(['stringSetting' => 'newValue']);\n        ContainerFactory::resetContainer();\n\n        $this->assertSame(\n            'newValue',\n            ContainerFacade::get(ModuleSettingServiceInterface::class)\n                ->getString('stringSetting', $this->testModuleId)\n                ->toString()\n        );\n    }\n\n    public function testSaveConfVarsSavesZeroNumAsInteger(): void\n    {\n        $this->activateTestModule();\n\n        $this->saveConfVars(['testInt' => '0']);\n\n        ContainerFactory::resetContainer();\n\n        $this->assertSame(\n            0,\n            $this->getModuleConfiguration()->getModuleSetting('testInt')->getValue()\n        );\n    }\n\n    public function testSaveConfVarsSavesNumAsInteger(): void\n    {\n        $this->activateTestModule();\n\n        $this->saveConfVars(['testInt' => '321']);\n\n        ContainerFactory::resetContainer();\n\n        $this->assertSame(\n            321,\n            $this->getModuleConfiguration()->getModuleSetting('testInt')->getValue()\n        );\n    }\n\n    public function testSaveConfVarsSavesZeroNumAsFloat(): void\n    {\n        $this->activateTestModule();\n\n        $this->saveConfVars(['testFloat' => '0.0']);\n\n        ContainerFactory::resetContainer();\n\n        $this->assertSame(\n            0.0,\n            $this->getModuleConfiguration()->getModuleSetting('testFloat')->getValue()\n        );\n    }\n\n    public function testSaveConfVarsSavesNumAsFloat(): void\n    {\n        $this->activateTestModule();\n\n        $this->saveConfVars(['testFloat' => '123.321']);\n\n        ContainerFactory::resetContainer();\n\n        $this->assertSame(\n            123.321,\n            $this->getModuleConfiguration()->getModuleSetting('testFloat')->getValue()\n        );\n    }\n\n    private function installTestModule(): void\n    {\n        ContainerFacade::get(ModuleInstallerInterface::class)\n            ->install($this->getOxidEshopPackage());\n    }\n\n    private function activateTestModule(): void\n    {\n        ContainerFacade::get(ModuleActivationBridgeInterface::class)\n            ->activate($this->testModuleId, 1);\n    }\n\n    private function getModuleConfiguration(): ModuleConfiguration\n    {\n        return ContainerFacade::get(ModuleConfigurationDaoBridgeInterface::class)\n            ->get($this->testModuleId);\n    }\n\n    private function uninstallTestModule(): void\n    {\n        ContainerFacade::get(ModuleInstallerInterface::class)\n            ->uninstall($this->getOxidEshopPackage());\n    }\n\n    private function getOxidEshopPackage(): OxidEshopPackage\n    {\n        return new OxidEshopPackage(__DIR__ . '/Fixtures/' . $this->testModuleDir);\n    }\n\n    private function saveConfVars(array $confstrs = []): void\n    {\n        $_POST['oxid'] = $this->testModuleId;\n        $_POST['confstrs'] = $confstrs;\n\n        $moduleConfigurationController = oxNew(ModuleConfigurationController::class);\n        $moduleConfigurationController->saveConfVars();\n    }\n\n    private function unsetAdminMode(): void\n    {\n        Registry::getSession()->setAdminMode(false);\n        Registry::getConfig()->setAdminMode(false);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Controller/Admin/ShopConfigurationTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace Integration\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Controller\\Admin\\ShopConfiguration;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\ContainerFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ModuleConfigurationDaoBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ShopConfigurationDaoBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setting\\Setting;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ShopConfigurationTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private string $testModuleId = 'testShopModuleId';\n\n    public function testSaveConfVars(): void\n    {\n        $this->prepareTestModuleConfiguration();\n\n        $_POST['confstrs'] = ['stringSetting' => 'newValue'];\n\n        $shopConfigurationController = $this\n            ->getStubBuilder(ShopConfiguration::class)\n            ->onlyMethods(['getModuleForConfigVars'])\n            ->disableOriginalConstructor()\n            ->getStub();\n        $shopConfigurationController->method('getModuleForConfigVars')->willReturn('module:testShopModuleId');\n        $shopConfigurationController->saveConfVars();\n\n        $container = ContainerFactory::getInstance()->getContainer();\n        $moduleConfiguration = $container->get(ModuleConfigurationDaoBridgeInterface::class)->get($this->testModuleId);\n\n        $this->assertSame(\n            'newValue',\n            $moduleConfiguration->getModuleSetting('stringSetting')->getValue()\n        );\n    }\n\n    public function testSaveWhenSettingIsMissingInMetadata(): void\n    {\n        $this->prepareTestModuleConfiguration();\n\n        $_POST['confstrs'] = ['nonExisting' => 'newValue'];\n\n        $shopConfigurationController = $this\n            ->getStubBuilder(ShopConfiguration::class)\n            ->onlyMethods(['getModuleForConfigVars'])\n            ->disableOriginalConstructor()\n            ->getStub();\n        $shopConfigurationController->method('getModuleForConfigVars')->willReturn('module:testShopModuleId');\n        $shopConfigurationController->saveConfVars();\n\n        $this->assertSame(\n            'newValue',\n            Registry::getConfig()->getConfigParam('nonExisting')\n        );\n    }\n\n    public function testUnserializeConfVar(): void\n    {\n        $value = ['a' => 'test'];\n        $serializedValue = serialize($value);\n\n        $shopConfig = new ShopConfiguration();\n        $result = $shopConfig->unserializeConfVar(\n            'aarr',\n            'variableName',\n            $serializedValue\n        );\n\n        $this->assertSame(\n            'a =&gt; test',\n            $result\n        );\n    }\n\n    public function testUnserializeConfVarNestedArray(): void\n    {\n        $value = ['a' => ['b' => 'test']];\n        $serializedValue = serialize($value);\n\n        $shopConfig = new ShopConfiguration();\n        $result = $shopConfig->unserializeConfVar(\n            'aarr',\n            'variableName',\n            $serializedValue\n        );\n\n        $this->assertSame(\n            '',\n            $result\n        );\n    }\n\n    private function prepareTestModuleConfiguration(): void\n    {\n        $setting = new Setting();\n        $setting\n            ->setName('stringSetting')\n            ->setValue('row')\n            ->setType('str');\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->setId($this->testModuleId);\n        $moduleConfiguration->setModuleSource($this->testModuleId);\n        $moduleConfiguration->addModuleSetting($setting);\n\n        $container = ContainerFactory::getInstance()->getContainer();\n        $shopConfigurationDao = $container->get(ShopConfigurationDaoBridgeInterface::class);\n\n        $shopConfiguration = $shopConfigurationDao->get();\n        $shopConfiguration->addModuleConfiguration($moduleConfiguration);\n\n        $shopConfigurationDao->save($shopConfiguration);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Controller/OrderControllerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Controller;\n\nuse OxidEsales\\Eshop\\Application\\Controller\\OrderController;\nuse OxidEsales\\Eshop\\Application\\Model\\Basket;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Session;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Psr\\Log\\LoggerInterface;\n\nfinal class OrderControllerTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private string $basketSummaryHashParameter = 'basketSummaryHash';\n    private string $userId;\n    private LoggerInterface $logger;\n    private Basket $basket;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->prepareUserStub();\n        unset($_SESSION['Errors']);\n    }\n\n    public function testExecuteWithWrongBasketSummaryHashParameterAndEmptyBasketWillRedirectAndAddError(): void\n    {\n        $this->prepareBasketMock();\n        $this->stubSession();\n        $_GET[$this->basketSummaryHashParameter] = 'some-invalid-hash';\n        $this->basket->expects($this->once())->method('getProductsCount')->willReturn(0);\n\n        $redirect = oxNew(OrderController::class)->execute();\n\n        $this->assertEquals('basket', $redirect);\n        $this->assertNotEmpty($_SESSION['Errors']);\n    }\n\n    public function testExecuteWithWrongBasketSummaryHashParameterAndNonEmptyBasketWillRedirectAndAddError(): void\n    {\n        $this->prepareBasketMock();\n        $this->stubSession();\n        $_GET[$this->basketSummaryHashParameter] = 'some-invalid-hash';\n        $this->basket->expects($this->once())->method('getProductsCount')->willReturn(123);\n\n        $redirect = oxNew(OrderController::class)->execute();\n\n        $this->assertEquals('order', $redirect);\n        $this->assertNotEmpty($_SESSION['Errors']);\n    }\n\n    public function testRenderWillSetSessionChallenge(): void\n    {\n        $orderController = oxNew(OrderController::class);\n        $orderController->setIsOrderStep(false);\n\n        $orderController->render();\n\n        $this->assertNotEmpty($_SESSION['sess_challenge']);\n    }\n\n    private function prepareUserStub(): void\n    {\n        Registry::getConfig()->setConfigParam('blEnableIntangibleProdAgreement', false);\n        $user = oxNew('oxUser');\n        $user->oxuser__oxusername = new Field('some-user-name', Field::T_RAW);\n        $user->oxuser__oxpassword = new Field('some-user-pass', Field::T_RAW);\n        $user->save();\n\n        $this->userId = $user->getId();\n    }\n\n    private function stubSession(): void\n    {\n        $session = $this->createPartialMock(\n            Session::class,\n            [\n                'checkSessionChallenge',\n                'getVariable',\n                'getBasket',\n            ]\n        );\n        $session->method('checkSessionChallenge')\n            ->willReturn(true);\n        $session->expects($this->atLeastOnce())->method('getBasket')\n            ->willReturn($this->basket);\n        $session->method('getVariable')\n            ->willReturnMap(\n                [\n                    ['login-token', null],\n                    ['usr', $this->userId]\n                ]\n            );\n        Registry::set(Session::class, $session);\n    }\n\n    private function prepareBasketMock(): void\n    {\n        $this->basket = $this->createPartialMock(\n            Basket::class,\n            ['getProductsCount']\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Model/ArticleListTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Application\\Model\\ArticleList;\nuse OxidEsales\\Eshop\\Application\\Model\\Attribute;\nuse OxidEsales\\Eshop\\Application\\Model\\Category;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Model\\BaseModel;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ArticleListTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private int $language = 0;\n    private string $productId;\n    private string $categoryId;\n    private string $attributeId;\n    private string $productPriceUpdateTime;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        Registry::getLang()->setBaseLanguage($this->language);\n        $this->productId = uniqid('product-', true);\n        $this->categoryId = uniqid('category-', true);\n        $this->attributeId = uniqid('product-', true);\n        $this->productPriceUpdateTime = '2000-01-01 00:00:00';\n    }\n\n    public function testLoadCategoryArticlesWithSameValue(): void\n    {\n        $value = 'value';\n        $this->prepareCategories($value);\n        $sessionFilter = [$this->categoryId => [$this->language => [$this->attributeId => $value]]];\n\n        $productCount = oxNew(ArticleList::class)->loadCategoryArticles($this->categoryId, $sessionFilter);\n\n        $this->assertEquals(1, $productCount);\n    }\n\n    public function testLoadCategoryArticlesWithZero(): void\n    {\n        $value = '0';\n        $this->prepareCategories($value);\n        $sessionFilter = [$this->categoryId => [$this->language => [$this->attributeId => $value]]];\n\n        $productCount = oxNew(ArticleList::class)->loadCategoryArticles($this->categoryId, $sessionFilter);\n\n        $this->assertEquals(1, $productCount);\n    }\n\n    public function testLoadCategoryArticlesWithNoResults(): void\n    {\n        $this->prepareCategories('value');\n        $sessionFilter = [$this->categoryId => [$this->language => [$this->attributeId => 'something-different']]];\n\n        $productCount = oxNew(ArticleList::class)->loadCategoryArticles($this->categoryId, $sessionFilter);\n\n        $this->assertEquals(0, $productCount);\n    }\n\n    public function testUpdateUpcomingPricesWithDefaultCronEnabledSetting(): void\n    {\n        $this->prepareCategories('value');\n\n        oxNew(ArticleList::class)->updateUpcomingPrices();\n\n        $product = oxNew(Article::class);\n        $product->load($this->productId);\n        $this->assertEquals('0000-00-00 00:00:00', $product->getFieldData('oxupdatepricetime'));\n    }\n\n    public function testUpdateUpcomingPricesWithModifiedCronEnabledSetting(): void\n    {\n        $this->createContainer();\n        $this->container->setParameter('oxid_esales.cron_enabled', true);\n        $this->container->setParameter('oxid_esales.build_directory', getenv('OXID_BUILD_DIRECTORY'));\n        $this->replaceContainerInstance();\n\n        $this->prepareCategories('value');\n\n        oxNew(ArticleList::class)->updateUpcomingPrices();\n\n        $product = oxNew(Article::class);\n        $product->load($this->productId);\n        $this->assertEquals($this->productPriceUpdateTime, $product->getFieldData('oxupdatepricetime'));\n    }\n\n    public function testLoadIdsRespectsGivenOrder(): void\n    {\n        $id1 = 'aaa_product_1';\n        $id2 = 'bbb_product_2';\n        $id3 = 'ccc_product_3';\n\n        foreach ([$id1, $id2, $id3] as $id) {\n            $product = oxNew(Article::class);\n            $product->setId($id);\n            $product->oxarticles__oxactive = oxNew(Field::class, true);\n            $product->save();\n        }\n\n        $productList = oxNew(ArticleList::class);\n        $productList->loadIds([$id3, $id1, $id2]);\n\n        $ids = array_keys($productList->getArray());\n        $this->assertSame([$id3, $id1, $id2], $ids);\n    }\n\n    private function prepareCategories(string|int $value): void\n    {\n        $this->createCategory();\n        $this->createProduct();\n        $this->createAttribute();\n        $this->linkProductToCategory();\n        $this->linkAttributeToCategory();\n        $this->linkProductToAttribute($value);\n    }\n\n    private function createCategory(): void\n    {\n        $category = oxNew(Category::class);\n        $category->setId($this->categoryId);\n        $category->oxcategories__oxactive = oxNew(Field::class, 1, Field::T_RAW);\n        $category->oxcategories__oxparentid = oxNew(Field::class, 'oxrootid');\n        $category->save();\n    }\n\n    private function createProduct(): void\n    {\n        $product = oxNew(Article::class);\n        $product->setId($this->productId);\n        $product->oxarticles__oxactive = oxNew(Field::class, true);\n        $product->oxarticles__oxunitquantity = oxNew(Field::class, 50);\n        $product->oxarticles__oxstock = oxNew(Field::class, 50);\n        $product->oxarticles__oxupdatepricetime = oxNew(Field::class, $this->productPriceUpdateTime);\n        $product->save();\n    }\n\n    private function createAttribute(): void\n    {\n        $attribute = oxNew(Attribute::class);\n        $attribute->setId($this->attributeId);\n        $attribute->save();\n    }\n\n    private function linkProductToCategory(): void\n    {\n        $link = oxNew(BaseModel::class);\n        $link->init('oxobject2category');\n        $link->oxobject2category__oxobjectid = oxNew(Field::class, $this->productId);\n        $link->oxobject2category__oxcatnid = oxNew(Field::class, $this->categoryId);\n        $link->save();\n    }\n\n    private function linkAttributeToCategory(): void\n    {\n        $link = oxNew(BaseModel::class);\n        $link->init('oxcategory2attribute');\n        $link->oxcategory2attribute__oxobjectid = oxNew(Field::class, $this->categoryId);\n        $link->oxcategory2attribute__oxattrid = oxNew(Field::class, $this->attributeId);\n        $link->save();\n    }\n\n    private function linkProductToAttribute(string $value): void\n    {\n        $link = oxNew(BaseModel::class);\n        $link->init('oxobject2attribute');\n        $link->oxobject2attribute__oxobjectid = oxNew(Field::class, $this->productId);\n        $link->oxobject2attribute__oxattrid = oxNew(Field::class, $this->attributeId);\n        $link->oxobject2attribute__oxvalue = oxNew(Field::class, $value);\n        $link->save();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Model/ArticleTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Model;\n\nuse DateTimeImmutable;\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaType;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRole;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRoleSet;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaViewServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class ArticleTest extends IntegrationTestCase\n{\n    private static string $timeFormat = 'Y-m-d H:i:s';\n    private static string $defaultTimestamp = '0000-00-00 00:00:00';\n\n    private ProductMediaServiceInterface $productMediaService;\n    private ProductMediaViewServiceInterface $productMediaViewService;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        Registry::getConfig()->init();\n        Registry::getConfig()->setConfigParam('blUseStock', false);\n\n        $this->productMediaService = ContainerFacade::get(ProductMediaServiceInterface::class);\n        $this->productMediaViewService = ContainerFacade::get(ProductMediaViewServiceInterface::class);\n    }\n\n    public function testIsVisibleWithInactive(): void\n    {\n        $product = oxNew(Article::class);\n        $product->oxarticles__oxactive = new Field(false);\n\n        $this->assertFalse($product->isVisible());\n    }\n\n    public function testIsVisibleWithAlwaysActive(): void\n    {\n        $product = oxNew(Article::class);\n        $product->oxarticles__oxactive = new Field(true);\n\n        $this->assertTrue($product->isVisible());\n    }\n\n    public function testIsVisibleWithValidTimeRestrictionsAndDisabledConfig(): void\n    {\n        Registry::getConfig()->setConfigParam('blUseTimeCheck', false);\n        $now = new DateTimeImmutable();\n        $past = $now->modify('-1 day');\n        $future = $now->modify('+1 day');\n        $product = oxNew(Article::class);\n        $product->oxarticles__oxactive = new Field(false);\n        $product->oxarticles__oxactivefrom = new Field($past->format(self::$timeFormat));\n        $product->oxarticles__oxactiveto = new Field($future->format(self::$timeFormat));\n\n        $this->assertFalse($product->isVisible());\n    }\n\n    #[DataProvider('validTimeRestrictionsDataProvider')]\n    public function testIsVisibleWithValidTimeRestrictions(string $activeFrom, string $activeTo): void\n    {\n        Registry::getConfig()->setConfigParam('blUseTimeCheck', true);\n\n        $product = oxNew(Article::class);\n        $product->oxarticles__oxactive = new Field(false);\n        $product->oxarticles__oxactivefrom = new Field($activeFrom);\n        $product->oxarticles__oxactiveto = new Field($activeTo);\n\n        $this->assertTrue($product->isVisible());\n    }\n\n    public static function validTimeRestrictionsDataProvider(): array\n    {\n        $now = new DateTimeImmutable();\n        $past = $now->modify('-1 day');\n        $future = $now->modify('+1 day');\n\n        return [\n            [$past->format(self::$timeFormat), $future->format(self::$timeFormat)],\n            [self::$defaultTimestamp, $future->format(self::$timeFormat)],\n            [$now->format(self::$timeFormat), $future->format(self::$timeFormat)]\n        ];\n    }\n\n    #[DataProvider('invalidTimeRestrictionsDataProvider')]\n    public function testIsVisibleWithInvalidTimeRestrictions(string $activeFrom, string $activeTo): void\n    {\n        Registry::getConfig()->setConfigParam('blUseTimeCheck', true);\n\n        $product = oxNew(Article::class);\n        $product->oxarticles__oxactive = new Field(false);\n        $product->oxarticles__oxactivefrom = new Field($activeFrom);\n        $product->oxarticles__oxactiveto = new Field($activeTo);\n\n        $this->assertFalse($product->isVisible());\n    }\n\n    public static function invalidTimeRestrictionsDataProvider(): array\n    {\n        $now = new DateTimeImmutable();\n        $past = $now->modify('-1 day');\n        $future = $now->modify('+1 day');\n\n        return [\n            [self::$defaultTimestamp, self::$defaultTimestamp],\n            [$future->format(self::$timeFormat), self::$defaultTimestamp],\n            [$future->format(self::$timeFormat), $past->format(self::$timeFormat)],\n            [self::$defaultTimestamp, $past->format(self::$timeFormat)]\n        ];\n    }\n\n    #[DataProvider('productActiveFieldStatesDataProvider')]\n    public function testIsProductAlwaysActive(?bool $active, bool $result): void\n    {\n        $product = oxNew(Article::class);\n        $product->oxarticles__oxactive = new Field($active);\n\n        $this->assertEquals($result, $product->isProductAlwaysActive());\n    }\n\n    public static function productActiveFieldStatesDataProvider(): array\n    {\n        return [\n            'NULL value' => [null, false],\n            'false value' => [false, false],\n            'true value' => [true, true],\n        ];\n    }\n\n    #[DataProvider('validityTimeRangesDataProvider')]\n    public function testHasProductValidTimeRange(string $activeFrom, string $activeTo, bool $result): void\n    {\n        $product = oxNew(Article::class);\n        $product->oxarticles__oxactivefrom = new Field($activeFrom);\n        $product->oxarticles__oxactiveto = new Field($activeTo);\n\n        $this->assertEquals($result, $product->hasProductValidTimeRange());\n    }\n\n    public static function validityTimeRangesDataProvider(): array\n    {\n        $now = new DateTimeImmutable();\n        return [\n            'Empty active From/To' => [self::$defaultTimestamp, self::$defaultTimestamp, false],\n            'Empty active From' => [self::$defaultTimestamp, $now->format(self::$timeFormat), true],\n            'Empty active To' => [$now->format(self::$timeFormat), self::$defaultTimestamp, true],\n            'With active From/to' => [$now->format(self::$timeFormat), $now->format(self::$timeFormat), true],\n        ];\n    }\n\n    #[DataProvider('visibilityTimeRangesDataProvider')]\n    public function testIsProductActive(string $activeFrom, string $activeTo, bool $result): void\n    {\n        $product = oxNew(Article::class);\n        $product->oxarticles__oxactivefrom = new Field($activeFrom);\n        $product->oxarticles__oxactiveto = new Field($activeTo);\n\n        $this->assertEquals($result, $product->hasActiveTimeRange());\n    }\n\n    public static function visibilityTimeRangesDataProvider(): array\n    {\n        $now = new DateTimeImmutable();\n        $past = $now->modify('-1 day')->format(self::$timeFormat);\n        $future = $now->modify('+1 day')->format(self::$timeFormat);\n        return [\n            'Empty active From/To' => [self::$defaultTimestamp, self::$defaultTimestamp, false],\n            'Empty activeFrom valid activeTo' => [self::$defaultTimestamp, $future, true],\n            'Empty activeFrom invalid activeTo' => [self::$defaultTimestamp, $past, false],\n            'Empty activeTo valid activeFrom' => [$past, self::$defaultTimestamp, true],\n            'Empty activeTo invalid activeFrom' => [$future, self::$defaultTimestamp, false],\n            'With valid From/to' => [$past, $future, true],\n            'With invalid From/to' => [$future, $past, false],\n        ];\n    }\n\n    public static function productLowStockDataProvider(): array\n    {\n        return [\n            'Product in low stock: Shop limit reached, Product limit undefined' => [5, 0.0, 10, false],\n            'Product in low stock: Shop limit exceeded, Product limit ignored' => [11, 20.0, 10, true],\n            'Product in low stock: Product limit reached' => [5, 10.0, 0, true]\n        ];\n    }\n\n    #[DataProvider('productLowStockDataProvider')]\n    public function testProductLowStock(\n        int $productStock,\n        float $productLowStockLimit,\n        int $shopLowStockLimit,\n        bool $productLowStockActive\n    ): void {\n        Registry::getConfig()->setConfigParam('blUseStock', true);\n        Registry::getConfig()->setConfigParam('sStockWarningLimit', $shopLowStockLimit);\n\n        $product = oxNew(Article::class);\n        $product->assign([\n            'oxarticles__oxstock' => $productStock,\n            'oxarticles__oxremindamount' => $productLowStockLimit,\n            'oxarticles__oxlowstockactive' => $productLowStockActive,\n            'oxarticles__oxparentid' => '',\n            'oxarticles__oxstockflag' => 1,\n            'oxarticles__oxshopid' => 1,\n            'oxarticles__oxvarstock' => $productStock,\n            'oxarticles__oxvarcount' => 0\n        ]);\n\n        $this->assertEquals(1, $product->getStockStatus());\n    }\n\n    public function testProductInStock(): void\n    {\n        Registry::getConfig()->setConfigParam('blUseStock', true);\n        Registry::getConfig()->setConfigParam('sStockWarningLimit', 3);\n\n        $product = oxNew(Article::class);\n        $product->assign([\n            'oxarticles__oxstock' => 5,\n            'oxarticles__oxremindamount' => 0.0,\n            'oxarticles__oxlowstockactive' => false,\n            'oxarticles__oxparentid' => '',\n            'oxarticles__oxstockflag' => 1,\n            'oxarticles__oxshopid' => 1,\n            'oxarticles__oxvarstock' => 5,\n            'oxarticles__oxvarcount' => 0\n        ]);\n\n        $this->assertEquals(0, $product->getStockStatus());\n    }\n\n    public function testProductOutStock(): void\n    {\n        Registry::getConfig()->setConfigParam('blUseStock', true);\n        Registry::getConfig()->setConfigParam('sStockWarningLimit', 0);\n\n        $product = oxNew(Article::class);\n        $product->assign([\n            'oxarticles__oxstock' => -1,\n            'oxarticles__oxremindamount' => 0.0,\n            'oxarticles__oxlowstockactive' => false,\n            'oxarticles__oxparentid' => '',\n            'oxarticles__oxstockflag' => 1,\n            'oxarticles__oxshopid' => 1,\n            'oxarticles__oxvarstock' => -1,\n            'oxarticles__oxvarcount' => 0\n        ]);\n\n        $this->assertEquals(-1, $product->getStockStatus());\n    }\n\n    public function testGetIconReturnsMediaViewFromService(): void\n    {\n        [$article, $productId] = $this->createArticleWithMedia();\n\n        $expectedUrl = $this->productMediaViewService->getByRole(\n            $productId,\n            ProductMediaRole::from(ProductMediaRole::ICON)\n        )->getDetailUrl();\n\n        $this->assertSame($expectedUrl, $article->getIcon()->getDetailUrl());\n    }\n\n    public function testGetThumbnailReturnsMediaViewFromService(): void\n    {\n        [$article, $productId] = $this->createArticleWithMedia();\n\n        $expectedUrl = $this->productMediaViewService->getByRole(\n            $productId,\n            ProductMediaRole::from(ProductMediaRole::THUMBNAIL)\n        )->getDetailUrl();\n\n        $this->assertSame($expectedUrl, $article->getThumbnail()->getDetailUrl());\n    }\n\n    public function testGetMediaReturnsDetailImageForRequestedPosition(): void\n    {\n        [$article, $productId] = $this->createArticleWithMedia();\n\n        $expectedUrl = $this->productMediaViewService->getByPosition($productId, 1)->getDetailUrl();\n\n        $this->assertSame($expectedUrl, $article->getMedia(1)->getDetailUrl());\n    }\n\n\n    private function createArticleWithMedia(): array\n    {\n        $productId = Id::generate();\n        $this->createPersistedArticle($productId);\n        $this->addProductMedia($productId, 'article-icon.jpg', 0, ProductMediaRole::ICON);\n        $this->addProductMedia($productId, 'article-thumb.jpg', 0, ProductMediaRole::THUMBNAIL);\n        $this->addProductMedia($productId, 'article-detail.jpg', 1, ProductMediaRole::DETAIL);\n        $this->addProductMedia($productId, 'article-detail-2.jpg', 2, ProductMediaRole::DETAIL);\n\n        $article = oxNew(Article::class);\n        $article->load((string) $productId);\n\n        return [$article, $productId];\n    }\n\n    private function createPersistedArticle(Id $productId): void\n    {\n        $article = oxNew(Article::class);\n        $article->setId((string) $productId);\n        $article->oxarticles__oxshopid = new Field((string) Registry::getConfig()->getShopId());\n        $article->oxarticles__oxactive = new Field(true);\n        $article->oxarticles__oxtitle = new Field('Article with media');\n        $article->oxarticles__oxprice = new Field(12.5);\n        $article->save();\n    }\n\n    public function testGetPictureGalleryReturnsCorrectActiveMedia(): void\n    {\n        [$article, $productId] = $this->createArticleWithMedia();\n\n        $expectedMediaItems = $this->productMediaViewService->getAllByRole(\n            $productId,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n        $expectedActiveMedia = reset($expectedMediaItems);\n\n        $gallery = $article->getPictureGallery();\n\n        $this->assertSame($expectedActiveMedia->getDetailUrl(), $gallery['activeMedia']->getDetailUrl());\n    }\n\n    public function testGetPictureGalleryReturnsAllMediaItems(): void\n    {\n        [$article, $productId] = $this->createArticleWithMedia();\n\n        $expectedMediaItems = $this->productMediaViewService->getAllByRole(\n            $productId,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n\n        $gallery = $article->getPictureGallery();\n\n        $this->assertCount(count($expectedMediaItems), $gallery['mediaItems']);\n    }\n\n    public function testGetPictureGalleryHasMultipleImagesTrue(): void\n    {\n        [$article] = $this->createArticleWithMedia();\n\n        $gallery = $article->getPictureGallery();\n\n        $this->assertTrue($gallery['hasMultipleImages']);\n    }\n\n    public function testGetPictureGalleryHasMultipleImagesFalse(): void\n    {\n        $productId = Id::generate();\n        $this->createPersistedArticle($productId);\n        $this->addProductMedia($productId, 'single-image.jpg', 1, ProductMediaRole::DETAIL);\n\n        $article = oxNew(Article::class);\n        $article->load((string) $productId);\n\n        $gallery = $article->getPictureGallery();\n\n        $this->assertFalse($gallery['hasMultipleImages']);\n    }\n\n    private function addProductMedia(Id $productId, string $fileName, int $position, string $role): void\n    {\n        $media = new Media(\n            Id::generate(),\n            new MediaPath(Path::join('out', 'pictures', 'media', $fileName)),\n            new MediaType('image/jpeg')\n        );\n\n        $roleSet = new ProductMediaRoleSet(ProductMediaRole::from($role));\n        $productMedia = new ProductMedia(\n            Id::generate(),\n            $productId,\n            $media,\n            $roleSet\n        );\n        $productMedia->setPosition($position);\n        $this->productMediaService->add($productMedia);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Model/BasketReservationTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Application\\Model\\BasketReservation;\nuse OxidEsales\\Eshop\\Application\\Model\\User;\nuse OxidEsales\\Eshop\\Application\\Model\\UserBasket;\nuse OxidEsales\\Eshop\\Application\\Model\\UserBasketItem;\nuse OxidEsales\\Eshop\\Core\\Database\\Adapter\\DatabaseInterface;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nclass BasketReservationTest extends IntegrationTestCase\n{\n    private int $basketReservationTimeout;\n    private DatabaseInterface $database;\n    private BasketReservation $basketReservation;\n    private string $savedBasket = 'savedbasket';\n    private string $reservations = 'reservations';\n    private string $noticeList = 'noticelist';\n    private int $oldTimestamp;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->basketReservationTimeout = (int) Registry::getConfig()->getConfigParam('iPsBasketReservationTimeout');\n        Registry::getConfig()->setConfigParam('iPsBasketReservationTimeout', 10);\n        $this->database = DatabaseProvider::getDb();\n        $this->basketReservation = new BasketReservation();\n        $this->oldTimestamp = Registry::getUtilsDate()->getTime() - 3600;\n    }\n\n    public function tearDown(): void\n    {\n        Registry::getConfig()->setConfigParam('iPsBasketReservationTimeout', $this->basketReservationTimeout);\n        parent::tearDown();\n    }\n\n    public function testReservationTimeoutRemovesExpiredBaskets(): void\n    {\n        $shopId = Registry::getConfig()->getShopId();\n        $user1Id = 'testUser';\n        $user2Id = 'testUser2';\n        $productId = 'testProduct';\n        $noticeListId = 'testNoticeList';\n        $savedBasketUser2 = 'testSavedBasketUser2';\n        $reservationsUser2 = 'testReservationsUser2';\n\n        $this->setupTestUsers([$user1Id, $user2Id], $shopId);\n        $this->createProduct($productId, 'Test Product 1', $shopId);\n        $this->setupOneShopTestData(\n            $shopId,\n            $user1Id,\n            $user2Id,\n            $productId,\n            $noticeListId,\n            $savedBasketUser2,\n            $reservationsUser2\n        );\n\n        $this->basketReservation->discardUnusedReservations(10);\n\n        $remainingBaskets = $this->database->getCol('SELECT oxid FROM oxuserbaskets order by oxid');\n\n        $this->assertCount(3, $remainingBaskets);\n        $this->assertSame([$noticeListId, $reservationsUser2, $savedBasketUser2], $remainingBaskets);\n\n        $remainingBasketItems = $this->database->getCol(\n            'SELECT oxbasketid FROM oxuserbasketitems WHERE OXARTID = :productid order by oxbasketid',\n            ['productid' => $productId]\n        );\n\n        $this->assertCount(3, $remainingBasketItems);\n        $this->assertSame([$noticeListId, $reservationsUser2, $savedBasketUser2], $remainingBasketItems);\n    }\n\n    public function testDiscardExpiredReservationsPreservesOtherShopsBaskets(): void\n    {\n        $shop1Id = Registry::getConfig()->getShopId();\n        $shop2Id = 2;\n        $user1IdShop1 = 'testUser1Shop1';\n        $user2IdShop1 = 'testUser2Shop1';\n        $user1IdShop2 = 'testUser1Shop2';\n        $product1Id = 'testProductShop1';\n        $product2Id = 'testProductShop2';\n\n        $this->setupMultiShopTestData(\n            $shop1Id,\n            $shop2Id,\n            $user1IdShop1,\n            $user2IdShop1,\n            $user1IdShop2,\n            $product1Id,\n            $product2Id\n        );\n\n        $this->basketReservation->discardUnusedReservations(10);\n\n        $baskets = $this->database->select('SELECT oxuserid, oxtitle FROM oxuserbaskets order by oxuserid');\n        $this->assertSame(1, $baskets->count());\n        $this->assertSame([['oxuserid' => $user1IdShop2, 'oxtitle' => $this->savedBasket]], $baskets->fetchAll());\n\n        $basketItemsCount = $this->database->select('SELECT * FROM oxuserbasketitems')->count();\n\n        $this->assertSame(1, $basketItemsCount);\n    }\n\n    private function setupTestUsers(array $userIds, int $shopId): void\n    {\n        foreach ($userIds as $userId) {\n            $this->createUser($userId, $shopId);\n        }\n    }\n\n    private function setupOneShopTestData(\n        int $shopId,\n        string $user1Id,\n        string $user2Id,\n        string $productId,\n        string $noticeListId,\n        string $savedBasketUser2,\n        string $reservationsUser2\n    ): void {\n        $timeNow = Registry::getUtilsDate()->getTime();\n        $this->createBasket($noticeListId, $shopId, $user1Id, $this->noticeList, $this->oldTimestamp, $productId);\n        $this->createBasket(uniqid(), $shopId, $user1Id, $this->savedBasket, $this->oldTimestamp, $productId);\n        $this->createBasket(uniqid(), $shopId, $user1Id, $this->reservations, $this->oldTimestamp, $productId);\n        $this->createBasket($savedBasketUser2, $shopId, $user2Id, $this->savedBasket, $timeNow, $productId);\n        $this->createBasket($reservationsUser2, $shopId, $user2Id, $this->reservations, $timeNow, $productId);\n    }\n\n    private function setupMultiShopTestData(\n        int $shop1Id,\n        int $shop2Id,\n        string $user1IdShop1,\n        string $user2IdShop1,\n        string $user1IdShop2,\n        string $product1Id,\n        string $product2Id\n    ): void {\n        $this->createUser($user1IdShop1, $shop1Id);\n        $this->createUser($user2IdShop1, $shop1Id);\n        $this->createUser($user1IdShop2, $shop2Id);\n\n        $this->createProduct($product1Id, 'Test Product 1', $shop1Id);\n        $this->createProduct($product2Id, 'Test Product 2', $shop2Id);\n\n        $this->createBasket(uniqid(), $shop1Id, $user1IdShop1, $this->savedBasket, $this->oldTimestamp, $product1Id);\n        $this->createBasket(uniqid(), $shop1Id, $user2IdShop1, $this->reservations, $this->oldTimestamp, $product1Id);\n        $this->createBasket(uniqid(), $shop1Id, $user2IdShop1, $this->savedBasket, $this->oldTimestamp, $product1Id);\n        $this->createBasket(uniqid(), $shop1Id, $user2IdShop1, $this->reservations, $this->oldTimestamp, $product1Id);\n        $this->createBasket(uniqid(), $shop2Id, $user1IdShop2, $this->savedBasket, $this->oldTimestamp, $product2Id);\n        $this->createBasket(uniqid(), $shop2Id, $user1IdShop2, $this->reservations, $this->oldTimestamp, $product2Id);\n    }\n\n    private function createUser(string $userId, int $shopId): void\n    {\n        $user = new User();\n        $user->setId($userId);\n        $user->oxuser__oxshopid = new Field($shopId);\n        $user->oxuser__oxusername = new Field(sprintf('%s@test.com', $userId));\n        $user->oxuser__oxpassword = new Field(null);\n        $user->oxuser__oxregister = new Field(0);\n\n        $user->save();\n    }\n\n    private function createProduct(string $articleId, string $articleTitle, int $shopId): void\n    {\n        $article = new Article();\n        $article->setId($articleId);\n        $article->oxarticles__oxshopid = new Field($shopId);\n        $article->oxarticles__oxtitle = new Field($articleTitle);\n        $article->oxarticles__oxstock = new Field(10);\n        $article->save();\n    }\n\n    private function createBasket(\n        string $basketId,\n        int $shopId,\n        string $userId,\n        string $title,\n        int $updated,\n        string $productId\n    ): void {\n        $this->createUserBasket($basketId, $shopId, $userId, $title, $updated);\n        $this->createBasketItem(uniqid(), $basketId, $productId);\n        $this->updateBasketModificationTime($basketId, $updated);\n    }\n\n    private function createUserBasket(\n        string $basketId,\n        int $shopId,\n        string $userId,\n        string $basketTitle,\n        int $updated\n    ): void {\n        $basket = new UserBasket();\n        $basket->setId($basketId);\n        $basket->oxuserbaskets__oxshopid = new Field($shopId);\n        $basket->oxuserbaskets__oxuserid = new Field($userId);\n        $basket->oxuserbaskets__oxtitle = new Field($basketTitle);\n        $basket->oxuserbaskets__oxtimestamp = new Field(date('Y-m-d H:i:s', Registry::getUtilsDate()->getTime()));\n        $basket->oxuserbaskets__oxpublic = new Field(0);\n        $basket->oxuserbaskets__oxupdate = new Field($updated);\n\n        $basket->save();\n    }\n\n    private function createBasketItem(\n        string $basketItemId,\n        string $basketId,\n        string $productId,\n    ): void {\n        $basketItem = new UserBasketItem();\n        $basketItem->setId($basketItemId);\n        $basketItem->oxuserbasketitems__oxbasketid = new Field($basketId);\n        $basketItem->oxuserbasketitems__oxamount = new Field(1);\n        $basketItem->oxuserbasketitems__oxartid = new Field($productId);\n        $basketItem->oxuserbasketitems__oxsellist = new Field('N;');\n        $basketItem->oxuserbasketitems__oxpersparam = new Field('');\n\n        $basketItem->save();\n    }\n\n    private function updateBasketModificationTime(string $basketId, int $updated): void\n    {\n        $basket = new UserBasket();\n        $basket->load($basketId);\n        $basket->oxuserbaskets__oxupdate = new Field($updated);\n\n        $basket->save();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Model/DiagnosticsOutputTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Utils;\nuse OxidEsales\\EshopCommunity\\Application\\Model\\DiagnosticsOutput;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class DiagnosticsOutputTest extends TestCase\n{\n    private string $key = \"diagnostic_tool_result\";\n    private static string $sOutputFileName = \"diagnostic_tool_result.html\";\n\n    public function testDownloadResultFileWillSetCorrectContentLengthHeader(): void\n    {\n        $content = 'some-content-123';\n        $contentLength = strlen($content);\n\n        $utils = $this->getUtils();\n        $utils->toFileCache($this->key, $content);\n        $diagnostics = new DiagnosticsOutput();\n        ob_start();\n        $diagnostics->downloadResultFile($this->key);\n        ob_end_clean();\n\n        $this->assertArrayHasKey(\n            \"Content-Length: $contentLength\",\n            $utils->getHeaders()\n        );\n    }\n\n    #[DataProvider('headerValuesProvider')]\n    public function testItShouldSetCorrectHeaderValue(string $headerValue): void\n    {\n        $utils = $this->getUtils();\n\n        $diagnostics = new DiagnosticsOutput();\n        ob_start();\n        $diagnostics->downloadResultFile($this->key);\n        ob_end_clean();\n\n        $this->assertArrayHasKey(\n            $headerValue,\n            $utils->getHeaders()\n        );\n    }\n\n    public static function headerValuesProvider(): array\n    {\n        return [\n            ['Pragma: public'],\n            ['Expires: 0'],\n            ['Cache-Control: must-revalidate, post-check=0, pre-check=0, private'],\n            ['Content-Type:text/html;charset=utf-8'],\n            ['Content-Disposition: attachment;filename=' . self::$sOutputFileName],\n        ];\n    }\n\n    public function testDownloadResultFilePrintsOutput(): void\n    {\n        $utils = $this->getUtils();\n        $content = 'some-content-123';\n        $utils->toFileCache($this->key, $content);\n\n        $this->expectOutputString($content);\n\n        $diagnostics = new DiagnosticsOutput();\n        $diagnostics->downloadResultFile($this->key);\n    }\n\n    private function getUtils(): UtilsSpy\n    {\n        $utils = new UtilsSpy();\n        Registry::set(Utils::class, $utils);\n\n        return $utils;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Model/GroupsTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Groups;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nclass GroupsTest extends IntegrationTestCase\n{\n    protected $relatedRecords = [\n        'oxobject2delivery' => ['oxobjectid', ''],\n        'oxobject2discount' => ['oxobjectid', ''],\n        'oxobject2group' => ['oxgroupsid', ''],\n        'oxobject2payment' => ['oxobjectid', '']\n    ];\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $groupsModel = oxNew(Groups::class);\n        $groupsModel->setId('testgroup');\n        $groupsModel->oxgroups__oxtitle = new Field('testgroup');\n        $groupsModel->oxgroups__oxactive = new Field(1);\n        $groupsModel->save();\n    }\n\n    public function testDelete()\n    {\n        $db = DatabaseProvider::getDb();\n\n        // selecting count from DB\n        $groupsModel = oxNew(Groups::class);\n        $groupsModel->load('testgroup');\n\n        $result = $groupsModel->delete();\n        $this->assertTrue($result);\n\n        // checking if group is deleted from DB\n        $groupId = $groupsModel->getId();\n        $query = \"select count(*) from oxgroups where oxid = '$groupId' \";\n\n        $this->assertSame(0, $db->getOne($query), 'item from oxgroups are not deleted');\n\n        // checking related records\n        foreach ($this->relatedRecords as $sTable => $aField) {\n            $sField = $aField[0];\n\n            $query = \"select count(*) from $sTable where $sTable.$sField = '$groupId' \";\n            $this->assertSame(0, $db->getOne($query), 'item from ' . $sTable . ' are not deleted');\n        }\n    }\n\n    public function testDeleteNoId()\n    {\n        $groupsModel = oxNew(Groups::class);\n        $this->assertFalse($groupsModel->delete());\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Model/ManufacturerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace Integration\\Application\\Model;\n\nuse Generator;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Application\\Model\\Manufacturer;\nuse OxidEsales\\EshopCommunity\\Core\\Field;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\Attributes\\Group;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\n\n#[Group('manufacturer')]\nfinal class ManufacturerTest extends IntegrationTestCase\n{\n    private string $oxid = 'id1';\n    private Manufacturer $manufacturer;\n    private Filesystem $filesystem;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        $this->filesystem = new Filesystem();\n    }\n\n    #[DataProvider('provideImageTypeData')]\n    public function testItShouldReturnTheCorrectImageType(?string $expected, string $fieldName): void\n    {\n        $manufacturer = oxNew(Manufacturer::class);\n\n        $this->assertSame($expected, $manufacturer->getImageType($fieldName));\n    }\n\n    public static function provideImageTypeData(): Generator\n    {\n        yield 'MICO' => ['MICO', 'oxicon'];\n        yield 'MICO for alt' => ['MICO', 'oxicon_alt'];\n        yield 'MPIC' => ['MPIC', 'oxpicture'];\n        yield 'MTHU' => ['MTHU', 'oxthumbnail'];\n        yield 'Null' => [null, 'none_field'];\n    }\n\n    #[DataProvider('provideImageFileData')]\n    public function testDeleteShouldRemoveTheGeneratedImage(\n        string $imageFieldName,\n        string $imageFileName,\n        string $imageType\n    ): void {\n        $imagePath = $this->createImage(\n            Path::join($this->getImagePath($imageType, true), 'x_y_z'),\n            $imageFileName\n        );\n        $this->setupManufacturer($imageFieldName, $imageFileName);\n\n        $this->manufacturer->delete($this->oxid);\n\n        $this->assertFileDoesNotExist($imagePath);\n    }\n\n    #[DataProvider('provideImageFileData')]\n    public function testDeleteShouldRemoveTheMasterImage(\n        string $imageFieldName,\n        string $imageFileName,\n        string $imageType\n    ): void {\n        $imagePath = $this->createImage(\n            $this->getImagePath($imageType),\n            $imageFileName\n        );\n        $this->setupManufacturer($imageFieldName, $imageFileName);\n\n        $this->manufacturer->delete($this->oxid);\n\n        $this->assertFileDoesNotExist($imagePath);\n    }\n\n    public static function provideImageFileData(): Generator\n    {\n        yield 'Icon should be deleted from filesystem' => [\n            'oxicon',\n            'test-icon.jpg',\n            'MICO',\n        ];\n        yield 'Icon Alt should be deleted from filesystem' => [\n            'oxicon_alt',\n            'test-icon-alt.jpg',\n            'MICO',\n        ];\n        yield 'Picture should be deleted from filesystem' => [\n            'oxpicture',\n            'test-picture.jpg',\n            'MPIC',\n        ];\n        yield 'Thumbnail should be deleted from filesystem' => [\n            'oxthumbnail',\n            'test-thumbnail.jpg',\n            'MTHU',\n        ];\n        yield 'Promotion Icon should be deleted from filesystem' => [\n            'oxpromotion_icon',\n            'test-promotion-icon.jpg',\n            'MPICO',\n            ];\n    }\n\n    public function testIconUrlShouldBeEndedWithTheGeneratedImgPath(): void\n    {\n        $this->setupManufacturer('oxicon', 'test-icon.png');\n        $this->overwriteConfig('sManufacturerIconsize', '80*90');\n        $sizeDirectory = '80_90_75';\n        [$masterImage, $generatedImage] = $this->createImages('MICO', 'test-icon.png', $sizeDirectory);\n\n        $this->assertStringEndsWith(\n            $this->getImagePathFromSource('MICO', $sizeDirectory, 'test-icon.png'),\n            $this->manufacturer->getIconUrl()\n        );\n\n        $this->filesystem->remove($masterImage);\n        $this->filesystem->remove($generatedImage);\n    }\n\n    public function testAltIconUrlShouldBeEndedWithTheGeneratedImgPath(): void\n    {\n        $this->setupManufacturer('oxicon_alt', 'test-icon-alt.png');\n        $this->overwriteConfig('sManufacturerIconsize', '80*90');\n        $sizeDirectory = '80_90_75';\n        [$masterImage, $generatedImage] = $this->createImages('MICO', 'test-icon-alt.png', $sizeDirectory);\n\n        $this->assertStringEndsWith(\n            $this->getImagePathFromSource('MICO', $sizeDirectory, 'test-icon-alt.png'),\n            $this->manufacturer->getIconAltUrl()\n        );\n\n        $this->filesystem->remove($masterImage);\n        $this->filesystem->remove($generatedImage);\n    }\n\n    public function testPictureUrlShouldBeEndedWithTheGeneratedImgPath(): void\n    {\n        $this->setupManufacturer('oxpicture', 'test-picture.png');\n        $this->overwriteConfig('sManufacturerPicturesize', '80*90');\n        $sizeDirectory = '80_90_75';\n        [$masterImage, $generatedImage] = $this->createImages('MPIC', 'test-picture.png', $sizeDirectory);\n\n        $this->assertStringEndsWith(\n            $this->getImagePathFromSource('MPIC', $sizeDirectory, 'test-picture.png'),\n            $this->manufacturer->getPictureUrl()\n        );\n\n        $this->filesystem->remove($masterImage);\n        $this->filesystem->remove($generatedImage);\n    }\n\n    public function testThumbnailUrlShouldBeEndedWithTheGeneratedImgPath(): void\n    {\n        $this->setupManufacturer('oxthumbnail', 'test-thumbnail.png');\n        $this->overwriteConfig('sManufacturerThumbnailsize', '80*90');\n        $sizeDirectory = '80_90_75';\n        [$masterImage, $generatedImage] = $this->createImages('MTHU', 'test-thumbnail.png', $sizeDirectory);\n\n        $this->assertStringEndsWith(\n            $this->getImagePathFromSource('MTHU', $sizeDirectory, 'test-thumbnail.png'),\n            $this->manufacturer->getThumbnailUrl()\n        );\n\n        $this->filesystem->remove($masterImage);\n        $this->filesystem->remove($generatedImage);\n    }\n\n    public function testPromotionIconUrlShouldBeEndedWithTheGeneratedImgPath(): void\n    {\n        $this->setupManufacturer('oxpromotion_icon', 'test-promotion-icon.png');\n        $this->overwriteConfig('sManufacturerPromotionsize', '80*90');\n        $sizeDirectory = '80_90_75';\n        [$masterImage, $generatedImage] = $this->createImages('MPICO', 'test-promotion-icon.png', $sizeDirectory);\n\n        $this->assertStringEndsWith(\n            $this->getImagePathFromSource('MPICO', $sizeDirectory, 'test-promotion-icon.png'),\n            $this->manufacturer->getPromotionIconUrl()\n        );\n\n        $this->filesystem->remove($masterImage);\n        $this->filesystem->remove($generatedImage);\n    }\n\n    private function setupManufacturer(string $imageFieldName, string $imageFileName): void\n    {\n        $propertyName = 'oxmanufacturers__' . $imageFieldName;\n        $this->manufacturer = oxNew(Manufacturer::class);\n        $this->manufacturer->setId($this->oxid);\n        $this->manufacturer->$propertyName = new Field($imageFileName, Field::T_RAW);\n        $this->manufacturer->save();\n    }\n\n    private function createImage(string $path, string $imageFileName): string\n    {\n        if (!$this->filesystem->exists($path)) {\n            $this->filesystem->mkdir($path);\n        }\n\n        $imagePath = Path::join($path, $imageFileName);\n\n        $this->filesystem->touch($imagePath);\n\n        return $imagePath;\n    }\n\n    private function getImagePath(string $imageType, bool $isGenerated = false): string\n    {\n        return Path::join(\n            Registry::getConfig()->getPictureDir(false),\n            Registry::getUtilsFile()->getImageDirByType($imageType, $isGenerated)\n        );\n    }\n\n    private function getImagePathFromSource(string $imageType, string $sizeDirectory, string $imageFileName): string\n    {\n        return Path::join(\n            Registry::getUtilsFile()->getImageDirByType($imageType, true),\n            $sizeDirectory,\n            $imageFileName\n        );\n    }\n\n    private function createImages(string $imageType, string $imageFileName, string $sizeDirectory): array\n    {\n        $masterImage = $this->createImage($this->getImagePath($imageType), $imageFileName);\n        $generatedImage = $this->createImage(\n            Path::join($this->getImagePath($imageType, true), $sizeDirectory),\n            $imageFileName\n        );\n\n        return [$masterImage, $generatedImage];\n    }\n\n    private function overwriteConfig(string $configParam, string $iconSizeValue): void\n    {\n        Registry::getConfig()->setConfigParam($configParam, false);\n        Registry::getConfig()->setConfigParam('sIconsize', $iconSizeValue);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Model/ReviewTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Model;\n\nuse DateTime;\nuse OxidEsales\\Eshop\\Application\\Model\\Rating;\nuse OxidEsales\\Eshop\\Application\\Model\\Review;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\EshopCommunity\\Core\\UtilsDate;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Exception\\ReviewAndRatingObjectTypeException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\ViewDataObject\\ReviewAndRating;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ReviewTest extends IntegrationTestCase\n{\n    public function testReviewAndRatingListByUserId(): void\n    {\n        $review = oxNew(Review::class);\n        $review->setId('id1');\n        $review->oxreviews__oxactive = new Field(1);\n        $review->oxreviews__oxuserid = new Field('testUser');\n        $review->oxreviews__oxobjectid = new Field('xx1');\n        $review->oxreviews__oxtype = new Field('oxarticle');\n        $review->oxreviews__oxtext = new Field('revtext');\n        $review->save();\n\n        $review = oxNew(Review::class);\n        $review->setId('id2');\n        $review->oxreviews__oxactive = new Field(1);\n        $review->oxreviews__oxuserid = new Field('testUser');\n        $review->oxreviews__oxobjectid = new Field('xx2');\n        $review->oxreviews__oxtype = new Field('oxrecommlist');\n        $review->oxreviews__oxtext = new Field('revtext');\n        $review->save();\n\n        $rating = oxNew(Rating::class);\n        $rating->oxratings__oxuserid = new Field('testUser');\n        $rating->oxratings__oxtype = new Field('oxarticle');\n        $rating->save();\n\n        $review = oxNew(Review::class);\n\n        $reviewAndRatingList = $review->getReviewAndRatingListByUserId('testUser');\n\n        $this->assertIsArray(\n            $reviewAndRatingList\n        );\n\n        $this->assertCount(\n            3,\n            $reviewAndRatingList\n        );\n\n        $this->assertContainsOnlyInstancesOf(\n            ReviewAndRating::class,\n            $reviewAndRatingList\n        );\n    }\n\n    public function testGetReviewAndRatingListByUserIdWithWrongRatingType(): void\n    {\n        /** @var  $wrongTypeValue see `oxreview`.`oxtype` enum('oxarticle', 'oxrecommlist') */\n        $wrongTypeValue = 'wrong_type';\n        $review = oxNew(Review::class);\n        $review->oxreviews__oxuserid = new Field('testUser');\n        $review->oxreviews__oxtype = new Field($wrongTypeValue);\n        $review->save();\n\n        $this->expectException(ReviewAndRatingObjectTypeException::class);\n\n        $review->getReviewAndRatingListByUserId('testUser');\n    }\n\n    public function testLoadListFormatsCreateDates(): void\n    {\n        $reviewType = 'oxrecommlist';\n        $objectId = uniqid('id-', true);\n        $createdDate = new DateTime();\n        $formattedDate = (oxNew(UtilsDate::class)->formatDBDate($createdDate->format('Y/m/d H:i:s')));\n        for ($i = 0; $i < 2; $i++) {\n            $review = oxNew(Review::class);\n            $review->oxreviews__oxobjectid = new Field($objectId);\n            $review->oxreviews__oxtype = new Field($reviewType);\n            $review->oxreviews__oxlang = new Field(0);\n            $review->oxcreate = new Field($createdDate);\n            $review->save();\n        }\n\n        $list = (oxNew(Review::class))->loadList($reviewType, $objectId, true, 0);\n\n        foreach ($list as $review) {\n            $this->assertEqualsWithDelta(\n                DateTime::createFromFormat('d.m.Y H:i:s', $formattedDate),\n                DateTime::createFromFormat('d.m.Y H:i:s', $review->getFieldData('oxcreate')),\n                1\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Model/UserTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Application\\Model\\User;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\PasswordServiceBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\n\nfinal class UserTest extends TestCase\n{\n    use ProphecyTrait;\n    use ContainerTrait;\n\n    public function testSetPassword(): void\n    {\n        $password = 'some-pass';\n        $user = oxNew(User::class);\n\n        $user->setPassword($password);\n\n        $this->assertTrue(\n            $this->get(PasswordServiceBridgeInterface::class)\n                ->verifyPassword(\n                    $password,\n                    $user->getFieldData('oxpassword')\n                )\n        );\n        $this->assertEmpty($user->getFieldData('oxpasssalt'));\n    }\n\n    public function testSetPasswordWithEmptyPass(): void\n    {\n        $user = oxNew(User::class);\n\n        $user->setPassword('');\n\n        $this->assertEmpty($user->getFieldData('oxpassword'));\n        $this->assertEmpty($user->getFieldData('oxpasssalt'));\n    }\n\n    public function testGetBoniWithDefaultConfig(): void\n    {\n        $rating = oxNew(User::class)->getBoni();\n\n        $this->assertEquals(1000, $rating);\n    }\n\n    public function testGetBoniWithModifiedConfig(): void\n    {\n        $configValue = 123;\n        $this->createContainer();\n        $this->container->setParameter('oxid_esales.shop_credit_rating', $configValue);\n        $this->container->setParameter('oxid_esales.build_directory', getenv('OXID_BUILD_DIRECTORY'));\n        $this->replaceContainerInstance();\n\n        $rating = oxNew(User::class)->getBoni();\n\n        $this->assertEquals($configValue, $rating);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Model/UtilsSpy.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\Utils;\n\nclass UtilsSpy extends Utils\n{\n    private array $headers = [];\n\n    public function setHeader($header): void\n    {\n        $this->headers[$header] = null;\n    }\n\n    public function getHeaders(): array\n    {\n        return $this->headers;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Application/Model/VariantHandlerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Application\\Model\\SelectList;\nuse OxidEsales\\Eshop\\Application\\Model\\VariantHandler;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaType;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Dao\\ProductMediaDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRole;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRoleSet;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class VariantHandlerTest extends IntegrationTestCase\n{\n    public function testGenVariantFromSellCopiesMediaFromParent(): void\n    {\n        $parentId = $this->createParentArticle();\n        $selectListId = $this->createSelectList();\n        $this->addProductMedia(Id::fromString($parentId), ProductMediaRole::DETAIL);\n\n        $article = oxNew(Article::class);\n        $article->load($parentId);\n\n        $variantHandler = oxNew(VariantHandler::class);\n        $variantHandler->genVariantFromSell([$selectListId], $article);\n\n        $variantId = $this->getVariantId($parentId);\n        $this->assertCount(1, $this->get(ProductMediaDaoInterface::class)->getAll(Id::fromString($variantId)));\n    }\n\n    public function testGenVariantFromSellCopiesMultipleMedia(): void\n    {\n        $parentId = $this->createParentArticle();\n        $selectListId = $this->createSelectList();\n        $this->addProductMedia(Id::fromString($parentId), ProductMediaRole::ICON);\n        $this->addProductMedia(Id::fromString($parentId), ProductMediaRole::THUMBNAIL);\n\n        $article = oxNew(Article::class);\n        $article->load($parentId);\n\n        $variantHandler = oxNew(VariantHandler::class);\n        $variantHandler->genVariantFromSell([$selectListId], $article);\n\n        $variantId = $this->getVariantId($parentId);\n        $this->assertCount(2, $this->get(ProductMediaDaoInterface::class)->getAll(Id::fromString($variantId)));\n    }\n\n    private function createParentArticle(): string\n    {\n        $article = oxNew(Article::class);\n        $article->oxarticles__oxshopid = new Field(1);\n        $article->oxarticles__oxactive = new Field(1);\n        $article->oxarticles__oxtitle = new Field('Parent Article');\n        $article->oxarticles__oxprice = new Field(10.0);\n        $article->save();\n\n        return $article->getId();\n    }\n\n    private function createSelectList(): string\n    {\n        $selectList = oxNew(SelectList::class);\n        $selectList->setEnableMultilang(false);\n        $selectList->oxselectlist__oxshopid = new Field(1);\n        $selectList->oxselectlist__oxtitle = new Field('Size');\n        $selectList->oxselectlist__oxtitle_1 = new Field('Size');\n        $selectList->oxselectlist__oxvaldesc = new Field('S__@@M__@@L__@@');\n        $selectList->oxselectlist__oxvaldesc_1 = new Field('S__@@M__@@L__@@');\n        $selectList->save();\n\n        return $selectList->getId();\n    }\n\n    private function getVariantId(string $parentId): string\n    {\n        $parent = oxNew(Article::class);\n        $parent->load($parentId);\n\n        return $parent->getAdminVariants()->current()->getId();\n    }\n\n    private function addProductMedia(Id $productId, string $role): void\n    {\n        $productMedia = new ProductMedia(\n            Id::generate(),\n            $productId,\n            new Media(Id::generate(), new MediaPath('out/pictures/media/test.jpg'), new MediaType('image/jpeg')),\n            new ProductMediaRoleSet(ProductMediaRole::from($role)),\n        );\n        $this->get(ProductMediaServiceInterface::class)->add($productMedia);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Core/DynamicImageGeneratorTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Core;\n\nuse OxidEsales\\EshopCommunity\\Core\\Curl;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class DynamicImageGeneratorTest extends IntegrationTestCase\n{\n    public function testRequestToNonExistentImageReturns404(): void\n    {\n        $url = '/out/pictures/generated/product/1/385_385_75/nonexistent.jpg';\n\n        $curl = new Curl();\n        $curl->setMethod('GET');\n        $curl->setUrl(rtrim($this->get(ContextInterface::class)->getShopBaseUrl(), '/') . $url);\n\n        $curl->execute();\n        $status = $curl->getStatusCode();\n\n        $this->assertEquals(404, $status);\n    }\n\n    public function testValidImageGeneration(): void\n    {\n        $this->createTestMasterImage();\n\n        $url = '/out/pictures/generated/media/products/100_100_75/test-image.jpg';\n\n        $curl = new Curl();\n        $curl->setMethod('GET');\n        $curl->setUrl(rtrim($this->get(ContextInterface::class)->getShopBaseUrl(), '/') . $url);\n\n        $response = $curl->execute();\n        $status = $curl->getStatusCode();\n\n        $this->assertEquals(200, $status);\n        $this->assertNotEmpty($response);\n\n        $this->cleanupTestFiles();\n    }\n\n    private function createTestMasterImage(): void\n    {\n        $imagePath = $this->getTestImagePath();\n        $imageDirectory = dirname($imagePath);\n        if (!is_dir($imageDirectory)) {\n            mkdir($imageDirectory, 0755, true);\n        }\n\n        $image = imagecreate(200, 200);\n        $textColor = imagecolorallocate($image, 0, 0, 0);\n\n        imagestring($image, 5, 50, 90, 'TEST', $textColor);\n        imagejpeg($image, $imagePath);\n    }\n\n    private function cleanupTestFiles(): void\n    {\n        $imagePath = $this->getTestImagePath();\n        if (file_exists($imagePath)) {\n            unlink($imagePath);\n        }\n    }\n\n    private function getTestImagePath(): string\n    {\n        return Path::join(\n            $this->get(BasicContextInterface::class)->getSourcePath(),\n            'out',\n            'pictures',\n            'media',\n            'products',\n            'test-image.jpg'\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Core/EmailTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Core;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Order;\nuse OxidEsales\\Eshop\\Application\\Model\\User;\nuse OxidEsales\\Eshop\\Core\\Email;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRendererInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Email\\EmailAdapterInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Psr\\Log\\LoggerInterface;\nuse Symfony\\Component\\Mime\\Email as SymfonyEmail;\n\nfinal class EmailTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private LoggerInterface|MockObject $logger;\n    private Email|MockObject $email;\n    private Order|MockObject $order;\n\n    public function testSendOrderEmailToUserWithDefaultConfiguration(): void\n    {\n        $this->getEmailMock();\n        $this->getOrderStub(true);\n        $this->email->expects($this->once())\n            ->method('sendMail');\n        $this->email->expects($this->once())\n            ->method('getRenderer');\n\n        $this->email->sendOrderEmailToUser($this->order);\n    }\n\n    public function testSendOrderEmailToOwnerWithDefaultConfiguration(): void\n    {\n        $this->getEmailMock();\n        $this->getOrderStub(true);\n        $this->email->expects($this->once())\n            ->method('sendMail');\n        $this->email->expects($this->once())\n            ->method('getRenderer');\n\n        $this->email->sendOrderEmailToOwner($this->order);\n    }\n\n    public function testSendOrderEmailToUserWithDisabledEmails(): void\n    {\n        $this->getEmailMock();\n        $this->getOrderStub(false);\n        $this->setParameter('oxid_esales.email.disable_order_emails', true);\n\n        $this->email->expects($this->never())\n            ->method('sendMail');\n        $this->email->expects($this->never())\n            ->method('getRenderer');\n\n        $return = $this->email->sendOrderEmailToUser($this->order);\n\n        $this->assertTrue($return);\n    }\n\n    public function testSendOrderEmailToOwnerWithDisabledEmails(): void\n    {\n        $this->getEmailMock();\n        $this->getOrderStub(false);\n        $this->setParameter('oxid_esales.email.disable_order_emails', true);\n\n        $this->email->expects($this->never())\n            ->method('sendMail');\n        $this->email->expects($this->never())\n            ->method('getRenderer');\n\n        $return = $this->email->sendOrderEmailToOwner($this->order);\n\n        $this->assertTrue($return);\n    }\n\n    public function testSendWithUnconfiguredDsnFallsBackToPhpMailer(): void\n    {\n        $this->createContainer();\n\n        $this->container->setParameter('oxid_esales.mailing.use_symfony_mailer', true);\n        $this->container->setParameter('oxid_esales.mailing.dsn', null);\n\n        $logger = $this->createMock(LoggerInterface::class);\n        $logger->expects($this->once())\n            ->method('error')\n            ->with($this->stringContains('Mailer failed'));\n\n        $adapter = $this->createStub(EmailAdapterInterface::class);\n        $adapter->method('convertToSymfonyEmail')->willReturn(new SymfonyEmail());\n\n        $this->replaceService(LoggerInterface::class, $logger);\n        $this->replaceService(EmailAdapterInterface::class, $adapter);\n        $this->compileContainer();\n        $this->replaceContainerInstance();\n\n        $email = oxNew(Email::class);\n        $email->setRecipient('test@example.com', 'Test User');\n        $email->setFrom('shop@example.com', 'Shop');\n        $email->setSubject('Test');\n        $email->setBody('Body');\n\n        $email->send();\n    }\n\n    public function testSendWithValidDsn(): void\n    {\n        $this->createContainer();\n\n        $this->container->setParameter('oxid_esales.mailing.use_symfony_mailer', true);\n        $this->container->setParameter('oxid_esales.mailing.dsn', 'null://null');\n\n        $symfonyEmail = new SymfonyEmail();\n        $adapter = $this->createStub(EmailAdapterInterface::class);\n        $adapter->method('convertToSymfonyEmail')->willReturn($symfonyEmail);\n\n        $this->replaceService(EmailAdapterInterface::class, $adapter);\n        $this->compileContainer();\n        $this->replaceContainerInstance();\n\n        $email = oxNew(Email::class);\n        $email->setRecipient('test@example.com', 'Test User');\n        $email->setFrom('shop@example.com', 'Shop');\n        $email->setSubject('Test');\n        $email->setBody('Body');\n\n        $result = $email->send();\n\n        $this->assertTrue($result);\n    }\n\n    private function getOrderStub(bool $expectsOrderUser): void\n    {\n        $user = new User();\n        $user->oxuser__oxfname = new Field('user-first-name');\n        $user->oxuser__oxlname = new Field('user-last-name');\n        $user->oxuser__oxusername = new Field('test@example.com');\n        $user->oxshops__oxorderemail = new Field('test@order.com');\n\n        $this->order = $this->createPartialMock(Order::class, ['getOrderUser']);\n        $this->order->oxorder__oxordernr = new Field('order-test-1');\n        $orderUserExpectation = $expectsOrderUser ? $this->atLeastOnce() : $this->never();\n        $this->order->expects($orderUserExpectation)->method('getOrderUser')\n            ->willReturn($user);\n    }\n\n    private function getEmailMock(): void\n    {\n        $templateRenderer = $this->createStub(TemplateRendererInterface::class);\n        $templateRenderer->method('renderTemplate')\n            ->willReturn('some-data');\n        $this->email = $this->createPartialMock(Email::class, ['sendMail', 'getRenderer']);\n        $this->email->method('getRenderer')\n            ->willReturn($templateRenderer);\n        $this->email->method('sendMail')\n            ->willReturn(true);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Core/Fixtures/Modules/ExampleModule/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Core\\Fixtures\\Modules\\ModuleChainExtension\\ExtendedArticle;\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'ExampleModule',\n    'title' => 'Test OXID eShop module',\n    'description' => 'Empty Module For Testing',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n    'url' => 'testurl.com',\n    'email' => 'test@email.com',\n];\n"
  },
  {
    "path": "tests/Integration/Core/Fixtures/Modules/NotActiveModuleWithMissingData/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Core\\Fixtures\\Modules\\ModuleChainExtension\\ExtendedArticle;\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'NotActiveModuleWithMissingData',\n    'thumbnail' => '',\n    'version' => '1.0'\n];\n"
  },
  {
    "path": "tests/Integration/Core/GenericImport/ImportObject/ImportObjectTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Core\\GenericImport\\ImportObject;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article as Product;\nuse OxidEsales\\Eshop\\Core\\Model\\BaseModel;\nuse OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel;\nuse OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject\\Accessories2Article;\nuse OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject\\Article;\nuse OxidEsales\\EshopCommunity\\Core\\GenericImport\\ImportObject\\ArticleExtends;\nuse OxidEsales\\EshopCommunity\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nuse function array_map;\nuse function sort;\n\nfinal class ImportObjectTest extends IntegrationTestCase\n{\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        Registry::getConfig()->setAdminMode(true);\n    }\n\n    public function tearDown(): void\n    {\n        Registry::getConfig()->setAdminMode(false);\n\n        parent::tearDown();\n    }\n\n    public function testGetFields(): void\n    {\n        $model = oxNew(Product::class);\n        $model->setEnableMultilang(false);\n        $model->setLanguage(0);\n        $modelFields = $model->getFieldNames();\n\n        $importFields = oxNew(Article::class)->getFieldList();\n\n        $this->assertSame(\n            $this->sortFields($modelFields),\n            $this->sortFields($importFields)\n        );\n    }\n\n    public function testGetFieldsWithImportObjectAndI18nAsShopObjectName(): void\n    {\n        $model = oxNew(MultiLanguageModel::class);\n        $model->init('oxartextends');\n        $model->setEnableMultilang(false);\n        $model->setLanguage(0);\n        $modelFields = $model->getFieldNames();\n\n        $importFields = oxNew(ArticleExtends::class)->getFieldList();\n\n        $this->assertSame(\n            $this->sortFields($modelFields),\n            $this->sortFields($importFields)\n        );\n    }\n\n    public function testGetFieldsWithImportObjectAndEmptyShopObjectName(): void\n    {\n        $model = oxNew(BaseModel::class);\n        $model->init('oxaccessoire2article');\n        $modelFields = $model->getFieldNames();\n\n        $importFields = oxNew(Accessories2Article::class)->getFieldList();\n\n        $this->assertSame(\n            $this->sortFields($modelFields),\n            $this->sortFields($importFields)\n        );\n    }\n\n    private function sortFields(array $fields): array\n    {\n        sort($fields);\n\n        return array_map('strtolower', $fields);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Core/InputValidatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Core;\n\nuse Generator;\nuse OxidEsales\\Eshop\\Application\\Model\\User;\nuse OxidEsales\\Eshop\\Application\\Model\\Country;\nuse OxidEsales\\Eshop\\Core\\Exception\\InputException;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\InputValidator;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Exception\\UserException;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nfinal class InputValidatorTest extends IntegrationTestCase\n{\n    private string $countryId = 'test_country_id';\n    private string $validUstId = 'DD123456789';\n    private string $invalidUstId = 'DE123456789';\n    private string $oxidDebitNote = 'oxiddebitnote';\n    private bool $configVatIdCheckDisabled;\n    private InputValidator $inputValidator;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->inputValidator = new InputValidator();\n\n        $this->configVatIdCheckDisabled = (bool) Registry::getConfig()->getConfigParam('blVatIdCheckDisabled');\n        Registry::getConfig()->setConfigParam('blVatIdCheckDisabled', true);\n    }\n\n    public function tearDown(): void\n    {\n        Registry::getConfig()->setConfigParam('blVatIdCheckDisabled', $this->configVatIdCheckDisabled);\n\n        parent::tearDown();\n    }\n\n    public function testCheckVatIdValidationPassesForValidEU(): void\n    {\n        $user = $this->createStub(User::class);\n\n        $this->createCountry();\n\n        $invAddress = [\n            'oxuser__oxustid' => $this->validUstId,\n            'oxuser__oxcountryid' => $this->countryId,\n            'oxuser__oxcompany' => 'Test Company',\n        ];\n\n        $this->inputValidator->checkVatId($user, $invAddress);\n\n        $this->assertEmpty($this->inputValidator->getFieldValidationErrors());\n    }\n\n    public function testValidatePaymentInputDataWithCorrectBankCode(): void\n    {\n        $testValues = [\n            'lsbankname' => 'Bank name',\n            'lsblz' => 'DEDEDEFF',\n            'lsktonr' => 'DE55200800000770876200',\n            'lsktoinhaber' => 'Hans Mustermann',\n        ];\n\n        $result = $this->inputValidator->validatePaymentInputData($this->oxidDebitNote, $testValues);\n\n        $this->assertTrue($result);\n    }\n\n    public function testCheckVatIdThrowsExceptionForMissingCompany(): void\n    {\n        $user = $this->createStub(User::class);\n        $invAddress = [\n            'oxuser__oxustid' => $this->invalidUstId,\n            'oxuser__oxcountryid' => $this->countryId,\n            'oxuser__oxcompany' => null\n        ];\n\n        $this->inputValidator->checkVatId($user, $invAddress);\n\n        $this->assertInstanceOf(\n            InputException::class,\n            $this->inputValidator->getFieldValidationErrors()['oxuser__oxcompany'][0]\n        );\n    }\n\n    public function testCheckVatIdThrowsExceptionForInvalidVatId(): void\n    {\n        $user = $this->createStub(User::class);\n        $invAddress = [\n            'oxuser__oxustid' => $this->invalidUstId,\n            'oxuser__oxcountryid' => $this->countryId,\n            'oxuser__oxcompany' => 'Test Company',\n        ];\n        $this->createCountry();\n\n        $this->inputValidator->checkVatId($user, $invAddress);\n\n        $this->assertInstanceOf(\n            InputException::class,\n            $this->inputValidator->getFieldValidationErrors()['oxuser__oxustid'][0]\n        );\n    }\n\n    public function testValidatePaymentInputDataWithSpaceCharacterForBankCode(): void\n    {\n        $testValues = [\n            'lsbankname' => 'Bank name',\n            'lsblz' => ' ',\n            'lsktonr' => '123456',\n            'lsktoinhaber' => 'Hans Mustermann',\n        ];\n\n        $result = $this->inputValidator->validatePaymentInputData($this->oxidDebitNote, $testValues);\n\n        $this->assertEquals(\n            InputValidator::INVALID_BANK_CODE,\n            $result\n        );\n    }\n\n    public function testValidatePaymentInputDataWithBlankBankCode(): void\n    {\n        $testValues = [\n            'lsbankname' => 'Bank name',\n            'lsblz' => '',\n            'lsktonr' => '123456',\n            'lsktoinhaber' => 'Hans Mustermann',\n        ];\n\n        $validationResult = $this->inputValidator->validatePaymentInputData($this->oxidDebitNote, $testValues);\n\n        $this->assertEquals(\n            InputValidator::INVALID_BANK_CODE,\n            $validationResult\n        );\n    }\n\n\n    #[DataProvider('provideCheckLoginReturnsUsername')]\n    public function testCheckLoginReturnsUsername($userLogged, $inputPassword, $exception): void\n    {\n        $userNameLogin = rand();\n        $submittedData = [];\n\n        $user = oxNew(User::class);\n        if ($userLogged) {\n            $user->assign([\n                'oxuser__oxusername' => rand(),\n                'oxuser__oxsalt'     => '',\n                'oxuser__oxpassword' => password_hash(md5(uniqid()), PASSWORD_DEFAULT),\n            ]);\n        }\n\n        //Send password with request\n        if ($inputPassword) {\n            $this->setRequestParameter('user_password', $inputPassword);\n            $submittedData['oxuser__oxpassword'] = $inputPassword;\n        }\n\n        $validationResult = $this->inputValidator->checkLogin(\n            $user,\n            $userNameLogin,\n            $submittedData\n        );\n\n        $this->assertSame($userNameLogin, $validationResult);\n\n        if ($exception) {\n            $this->assertInstanceOf(\n                $exception,\n                $this->inputValidator->getFieldValidationErrors()['oxuser__oxpassword'][0]\n            );\n        }\n\n        $this->assertSame(\n            (bool) $exception,\n            !empty($this->inputValidator->getFieldValidationErrors())\n        );\n    }\n\n    public static function provideCheckLoginReturnsUsername(): Generator\n    {\n        yield 'User not logged in, no password provided' => [\n            'userLogged'    => false,\n            'inputPassword' => null,\n            'exception'     => false\n        ];\n\n        yield 'User logged in, no password passed' => [\n            'userLogged'    => true,\n            'inputPassword' => null,\n            'exception'     => InputException::class\n        ];\n\n        yield 'User logged in, password are different' => [\n            'userLogged'    => true,\n            'inputPassword' => md5(uniqid()),\n            'exception'     => UserException::class\n        ];\n    }\n\n    public function testCheckLoginWithExistingEmail(): void\n    {\n        $userNameLogin = rand();\n\n        $user = $this->createStub(User::class);\n        $user->method('checkIfEmailExists')->willReturn(true);\n\n        $validationResult = $this->inputValidator->checkLogin(\n            $user,\n            $userNameLogin,\n            []\n        );\n\n        $this->assertSame($userNameLogin, $validationResult);\n        $this->assertNotEmpty($this->inputValidator->getFieldValidationErrors());\n        $this->assertInstanceOf(\n            UserException::class,\n            $this->inputValidator->getFieldValidationErrors()['oxuser__oxusername'][0]\n        );\n    }\n\n\n    private function createCountry(): void\n    {\n        $country = new Country();\n        $country->setId($this->countryId);\n        $country->oxcountry__oxactive = new Field(1);\n        $country->oxcountry__oxvatstatus = new Field(1);\n        $country->oxcountry__oxvatinprefix = new Field('DD');\n        $country->save();\n    }\n\n    private function setRequestParameter(string $key, string $value): void\n    {\n        $_POST[$key] = $value;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Core/LanguageTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Language;\nuse OxidEsales\\EshopCommunity\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\ShopCacheCleanerInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Psr\\Log\\LoggerInterface;\nuse Symfony\\Contracts\\Cache\\ItemInterface;\nuse Symfony\\Contracts\\Cache\\TagAwareCacheInterface;\n\nuse function sprintf;\n\nfinal class LanguageTest extends IntegrationTestCase\n{\n    public function tearDown(): void\n    {\n        $this->get(TagAwareCacheInterface::class)->invalidateTags(['oxid_esales.cache.language']);\n\n        parent::tearDown();\n    }\n\n    public function testTranslateStringWithMissingTranslation(): void\n    {\n        $translationKey = uniqid('some-key-', true);\n        $language = new Language();\n        $logger = $this->createMock(LoggerInterface::class);\n        Registry::set('logger', $logger);\n\n        $logger->expects($this->once())\n            ->method('warning')\n            ->with(sprintf('translation for %s not found', $translationKey), $this->anything());\n\n        $translation = $language->translateString($translationKey, $language->getBaseLanguage());\n\n        $this->assertEquals($translationKey, $translation);\n    }\n\n    public function testTranslateStringWithTranslationInCache(): void\n    {\n        $translationKey = uniqid('some-key-', true);\n        $cachedTranslation = 'some-translation';\n        $language = new Language();\n        $cacheKey = sprintf(\n            'langcache_%d_%s_%d_%s_default',\n            Registry::getConfig()->isAdmin(),\n            $language->getBaseLanguage(),\n            Registry::getConfig()->getShopId(),\n            Registry::getConfig()->getConfigParam('sTheme')\n        );\n\n        $this->get(ShopCacheCleanerInterface::class)->clearAll();\n        $this->get(TagAwareCacheInterface::class)\n            ->get($cacheKey, function (ItemInterface $item) use ($translationKey, $cachedTranslation) {\n                $item->tag('oxid_esales.cache.language');\n                return [$translationKey => $cachedTranslation];\n            });\n\n        $translation = $language->translateString($translationKey, $language->getBaseLanguage());\n\n        $this->assertEquals($cachedTranslation, $translation);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Core/Model/CategoryListTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Core\\Model;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Category;\nuse OxidEsales\\Eshop\\Application\\Model\\CategoryList;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class CategoryListTest extends IntegrationTestCase\n{\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->prepareTestData();\n    }\n\n    public function testUpdateNodesWithSimpleTree(): void\n    {\n        $categoryList = new CategoryList();\n\n        $categoryList->updateCategoryTree(false);\n\n        $rootCategory = new Category();\n        $rootCategory->load('test_root');\n        $this->assertEquals(1, $rootCategory->oxcategories__oxleft->value);\n        $this->assertEquals(10, $rootCategory->oxcategories__oxright->value);\n        $this->assertEquals('test_root', $rootCategory->oxcategories__oxrootid->value);\n\n        $childCategory = new Category();\n        $childCategory->load('test_child1');\n        $this->assertEquals(2, $childCategory->oxcategories__oxleft->value);\n        $this->assertEquals(7, $childCategory->oxcategories__oxright->value);\n        $this->assertEquals('test_root', $childCategory->oxcategories__oxrootid->value);\n\n        $childCategory = new Category();\n        $childCategory->load('test_child2');\n        $this->assertEquals(8, $childCategory->oxcategories__oxleft->value);\n        $this->assertEquals(9, $childCategory->oxcategories__oxright->value);\n        $this->assertEquals('test_root', $childCategory->oxcategories__oxrootid->value);\n\n        $childCategory = new Category();\n        $childCategory->load('test_child1_1');\n        $this->assertEquals(3, $childCategory->oxcategories__oxleft->value);\n        $this->assertEquals(4, $childCategory->oxcategories__oxright->value);\n\n        $childCategory = new Category();\n        $childCategory->load('test_child1_2');\n        $this->assertEquals(5, $childCategory->oxcategories__oxleft->value);\n        $this->assertEquals(6, $childCategory->oxcategories__oxright->value);\n    }\n\n    private function prepareTestData(): void\n    {\n        $categories = [\n            [\n                'oxid' => 'test_root',\n                'oxparentid' => 'oxrootid',\n                'oxtitle' => 'Root Category',\n                'oxsort' => 1,\n                'oxleft' => 0,\n                'oxright' => 0,\n                'oxrootid' => 'test_root'\n            ],\n            [\n                'oxid' => 'test_child1',\n                'oxparentid' => 'test_root',\n                'oxtitle' => 'Child 1',\n                'oxsort' => 1,\n                'oxleft' => 0,\n                'oxright' => 0,\n                'oxrootid' => ''\n            ],\n            [\n                'oxid' => 'test_child2',\n                'oxparentid' => 'test_root',\n                'oxtitle' => 'Child 2',\n                'oxsort' => 2,\n                'oxleft' => 0,\n                'oxright' => 0,\n                'oxrootid' => ''\n            ],\n            [\n                'oxid' => 'test_child1_1',\n                'oxparentid' => 'test_child1',\n                'oxtitle' => 'Child 1.1',\n                'oxsort' => 1,\n                'oxleft' => 0,\n                'oxright' => 0,\n                'oxrootid' => ''\n            ],\n            [\n                'oxid' => 'test_child1_2',\n                'oxparentid' => 'test_child1',\n                'oxtitle' => 'Child 1.2',\n                'oxsort' => 2,\n                'oxleft' => 0,\n                'oxright' => 0,\n                'oxrootid' => ''\n            ]\n        ];\n\n        foreach ($categories as $categoryData) {\n            $category = new Category();\n            $category->setId($categoryData['oxid']);\n            $category->oxcategories__oxparentid = new Field($categoryData['oxparentid']);\n            $category->oxcategories__oxtitle = new Field($categoryData['oxtitle']);\n            $category->oxcategories__oxsort = new Field($categoryData['oxsort']);\n            $category->oxcategories__oxleft = new Field($categoryData['oxleft']);\n            $category->oxcategories__oxright = new Field($categoryData['oxright']);\n            $category->oxcategories__oxrootid = new Field($categoryData['oxrootid']);\n            $category->save();\n        }\n    }\n}"
  },
  {
    "path": "tests/Integration/Core/Model/UserTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Core\\Model;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Country;\nuse OxidEsales\\Eshop\\Application\\Model\\User;\nuse OxidEsales\\Eshop\\Core\\Exception\\InputException;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class UserTest extends IntegrationTestCase\n{\n    private bool $onlineVatIdCheckDisabled;\n    private string $countryId = 'test_country_id';\n    private string $validVatId = 'DE12345';\n    private string $invalidVatId = 'DD12345';\n    private User $user;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->onlineVatIdCheckDisabled = (bool) Registry::getConfig()->getConfigParam('blVatIdCheckDisabled');\n        Registry::getConfig()->setConfigParam('blVatIdCheckDisabled', true);\n        $this->user = new User();\n    }\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        Registry::getConfig()->setConfigParam('blVatIdCheckDisabled', $this->onlineVatIdCheckDisabled);\n    }\n\n    public function testCheckValuesValidationPassesForValidData(): void\n    {\n        $this->createCountryWithVatPrefix();\n\n        $invoiceAddress = $this->getValidInvoiceAddress();\n\n        $this->user->checkValues('test@test.com', 'secretPassword', 'secretPassword', $invoiceAddress, []);\n\n        $this->assertEmpty(Registry::getInputValidator()->getFieldValidationErrors());\n    }\n\n    public function testCheckValuesThrowsExceptionForMissingCountryVatPrefix(): void\n    {\n        $this->createCountryWithoutVatPrefix();\n\n        $invoiceAddress = $this->getValidInvoiceAddress();\n\n        $this->expectException(InputException::class);\n        $this->expectExceptionMessage(\n            Registry::getLang()->translateString('VAT_MESSAGE_MISSING_COUNTRY_PREFIX')\n        );\n\n        $this->user->checkValues('test@test.com', 'secretPassword', 'secretPassword', $invoiceAddress, []);\n    }\n\n    public function testCheckValuesThrowsExceptionForInvalidVatId(): void\n    {\n        $this->createCountryWithVatPrefix();\n\n        $invoiceAddress = $this->getValidInvoiceAddress();\n        $invoiceAddress['oxuser__oxustid'] = $this->invalidVatId;\n\n        $this->expectException(InputException::class);\n        $this->expectExceptionMessage(\n            Registry::getLang()->translateString('VAT_MESSAGE_ID_NOT_VALID')\n        );\n\n        $this->user->checkValues('test@test.com', 'secretPassword', 'secretPassword', $invoiceAddress, []);\n    }\n\n    private function createCountryWithVatPrefix(): void\n    {\n        $country = new Country();\n        $country->setId($this->countryId);\n        $country->oxcountry__oxactive = new Field(1);\n        $country->oxcountry__oxvatstatus = new Field(1);\n        $country->oxcountry__oxvatinprefix = new Field('DE');\n        $country->save();\n    }\n\n    private function createCountryWithoutVatPrefix()\n    {\n        $country = new Country();\n        $country->setId($this->countryId);\n        $country->oxcountry__oxactive = new Field(1);\n        $country->oxcountry__oxvatstatus = new Field(1);\n        $country->save();\n    }\n\n    private function getValidInvoiceAddress(): array\n    {\n        return [\n            'oxuser__oxfname' => 'John',\n            'oxuser__oxlname' => 'Doe',\n            'oxuser__oxaddinfo' => 'Test Address Info',\n            'oxuser__oxstreet' => 'Test Street',\n            'oxuser__oxstreetnr' => '123',\n            'oxuser__oxzip' => '12345',\n            'oxuser__oxcity' => 'Testville',\n            'oxuser__oxcountryid' => $this->countryId,\n            'oxuser__oxustid' => $this->validVatId,\n            'oxuser__oxcompany' => 'OXID eSales',\n            'oxuser__oxfon' => '0123456789',\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Core/OnlineModuleVersionNotifierTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Core;\n\nuse OxidEsales\\Eshop\\Core\\OnlineModulesNotifierRequest;\nuse OxidEsales\\Eshop\\Core\\OnlineModuleVersionNotifier;\nuse OxidEsales\\Eshop\\Core\\OnlineModuleVersionNotifierCaller;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\FilesystemTrait;\nuse PHPUnit\\Framework\\TestCase;\nuse stdClass;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class OnlineModuleVersionNotifierTest extends TestCase\n{\n    use FilesystemTrait;\n\n    private string $activeModuleId = 'ExampleModule';\n    private string $inactiveModuleId = 'NotActiveModuleWithMissingData';\n    private string $moduleFixturesPath = '/Fixtures/Modules/';\n    private int $shopId = 1;\n\n    private array $modulesData = [];\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->backupVarDirectory();\n        ContainerFacade::get('oxid_esales.module.install.service.launched_shop_project_configuration_generator')\n            ->generate();\n        $this->installModule($this->activeModuleId);\n        $this->activateModule($this->activeModuleId);\n        $this->installModule($this->inactiveModuleId);\n    }\n\n    public function tearDown(): void\n    {\n        $this->uninstallModule($this->activeModuleId);\n        $this->uninstallModule($this->inactiveModuleId);\n        $this->restoreVarDirectory();\n\n        parent::tearDown();\n    }\n\n    public function testVersionNotifySendsCorrectModuleData(): void\n    {\n        $expectedRequest = $this->createNotifierRequest();\n\n        $notifierCaller = $this->createMock(OnlineModuleVersionNotifierCaller::class);\n        $notifierCaller->expects($this->once())\n            ->method('doRequest')\n            ->with($this->equalTo($expectedRequest));\n\n        $notifier = new OnlineModuleVersionNotifier($notifierCaller);\n        $notifier->versionNotify();\n    }\n\n    private function createNotifierRequest(): OnlineModulesNotifierRequest\n    {\n        $modulesData = [\n            [\n                'id' => 'ExampleModule',\n                'version' => '1.0',\n                'title' => 'Test OXID eShop module',\n                'description' => 'Empty Module For Testing',\n                'author' => 'OXID eSales AG',\n                'url' => 'testurl.com',\n                'email' => 'test@email.com',\n                'classExtensions' => [],\n                'controllers' => [],\n                'activeInShops' => ['activeInShop' => [Registry::getConfig()->getShopUrl()]],\n            ],\n            [\n                'id' => 'NotActiveModuleWithMissingData',\n                'version' => '1.0',\n                'title' => '',\n                'description' => '',\n                'author' => '',\n                'url' => '',\n                'email' => '',\n                'classExtensions' => [],\n                'controllers' => [],\n                'activeInShops' => ['activeInShop' => []],\n            ],\n        ];\n\n        $request = new OnlineModulesNotifierRequest();\n        $request->modules = new stdClass();\n        $request->modules->module = $modulesData;\n\n        return $request;\n    }\n\n    private function installModule(string $moduleId): void\n    {\n        $installService = ContainerFacade::get(ModuleInstallerInterface::class);\n        $installService->install($this->getModulePackage($moduleId));\n    }\n\n    private function activateModule(string $moduleId): void\n    {\n        $activationService = ContainerFacade::get(ModuleActivationBridgeInterface::class);\n        $activationService->activate($moduleId, $this->shopId);\n    }\n\n    private function uninstallModule(string $moduleId): void\n    {\n        $installService = ContainerFacade::get(ModuleInstallerInterface::class);\n        $installService->uninstall($this->getModulePackage($moduleId));\n    }\n\n    private function getModulePackage(string $moduleId): OxidEshopPackage\n    {\n        return new OxidEshopPackage(Path::join(__DIR__, $this->moduleFixturesPath, $moduleId));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Core/PictureHandlerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Core;\n\nuse OxidEsales\\EshopCommunity\\Core\\PictureHandler;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class PictureHandlerTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private PictureHandler $pictureHandler;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->pictureHandler = new PictureHandler();\n    }\n\n    public function testGetAltImageUrlWithEmptyParameter(): void\n    {\n        $this->createContainer();\n        $this->setParameter('oxid_esales.alternative_image_url', '');\n\n        $altImageUrl = $this->pictureHandler->getAltImageUrl('random/file/path', 'file.txt');\n        $this->assertEmpty($altImageUrl);\n    }\n\n    public function testGetAltImageUrlWithNullFile(): void\n    {\n        $altImageUrlParameter = 'somevalue';\n        $this->createContainer();\n        $this->setParameter('oxid_esales.alternative_image_url', $altImageUrlParameter);\n\n        $altImageUrl = $this->pictureHandler->getAltImageUrl('random/file/path', null);\n        $this->assertEquals($altImageUrlParameter, $altImageUrl);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Core/SessionTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Session;\nuse OxidEsales\\Eshop\\Core\\UtilsServer;\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\ContainerFactory;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class SessionTest extends TestCase\n{\n    use ContainerTrait;\n\n    public static function tearDownAfterClass(): void\n    {\n        ContainerFactory::resetContainer();\n\n        parent::tearDownAfterClass();\n    }\n\n    public function testGetSidFromRequestWithForceSidInRequestAndDisabledConfig(): void\n    {\n        $this->setParameter('oxid_esales.disallow_force_session_id', true);\n        $sessionId = uniqid('session-id-', true);\n        $_GET['force_sid'] = $sessionId;\n        $session = oxNew(Session::class);\n        $session->start();\n\n        $sid = $session->getId();\n\n        $this->assertNotEquals($sessionId, $sid);\n    }\n\n    public function testProcessUrlWithDefaultConfig(): void\n    {\n        $sessionId = uniqid('session-id-', true);\n        $url = 'https://myshop.abc';\n        $session = oxNew(Session::class);\n        $session->setId($sessionId);\n\n        $processedUrl = $session->processUrl($url);\n\n        $this->assertStringContainsString(\"force_sid=$sessionId\", $processedUrl);\n    }\n\n    public function testProcessUrlWithDisabledConfig(): void\n    {\n        $this->setParameter('oxid_esales.disallow_force_session_id', true);\n\n        $sessionId = uniqid('session-id-', true);\n        $url = 'https://myshop.abc';\n        $session = oxNew(Session::class);\n        $session->setId($sessionId);\n\n        $processedUrl = $session->processUrl($url);\n\n        $this->assertStringNotContainsString($sessionId, $processedUrl);\n    }\n\n    public function testAllowSessionStartWithSidInRequestAndDefaultConfig(): void\n    {\n        $utilsSever = $this->createMock(UtilsServer::class);\n        Registry::set(UtilsServer::class, $utilsSever);\n        $session = oxNew(Session::class);\n        $utilsSever->expects($this->once())\n            ->method('setOxCookie')\n            ->with('sid', $this->isString(), 0, '/', null, true, false, true);\n        $sessionId = uniqid('session-id-', true);\n        $_GET['sid'] = $sessionId;\n\n        $session->regenerateSessionId();\n    }\n\n    public function testAllowSessionStartWithSidInRequestAndDisabledConfig(): void\n    {\n        $this->setParameter('oxid_esales.disallow_force_session_id', true);\n\n        $utilsSever = $this->createMock(UtilsServer::class);\n        Registry::set(UtilsServer::class, $utilsSever);\n        $session = oxNew(Session::class);\n        $utilsSever->expects($this->once())\n            ->method('setOxCookie')\n            ->with('sid', $this->isString(), 0, '/', null, true, false, true);\n        $sessionId = uniqid('session-id-', true);\n        $_GET['sid'] = $sessionId;\n\n        $session->regenerateSessionId();\n    }\n\n    public function testAllowSessionStartWithForceSidInRequestAndDefaultConfig(): void\n    {\n        $utilsSever = $this->createMock(UtilsServer::class);\n        Registry::set(UtilsServer::class, $utilsSever);\n        $session = oxNew(Session::class);\n        $utilsSever->expects($this->once())\n            ->method('setOxCookie')\n            ->with('sid', $this->isString(), 0, '/', null, true, false, true);\n        $sessionId = uniqid('session-id-', true);\n        $_GET['force_sid'] = $sessionId;\n\n        $session->regenerateSessionId();\n    }\n\n    public function testAllowSessionStartWithForceSidInRequestAndDisabledConfig(): void\n    {\n        $this->setParameter('oxid_esales.disallow_force_session_id', true);\n\n        $utilsSever = $this->createMock(UtilsServer::class);\n        Registry::set(UtilsServer::class, $utilsSever);\n        $session = oxNew(Session::class);\n        $utilsSever->expects($this->once())\n            ->method('setOxCookie')\n            ->with('sid', null, 0, '/', null, true, false, true);\n        $sessionId = uniqid('session-id-', true);\n        $_GET['force_sid'] = $sessionId;\n\n        $session->regenerateSessionId();\n    }\n\n    public function testSidNeededForDifferentUrls(): void\n    {\n        $session = oxNew(Session::class);\n\n        $utilsSever = $this->createStub(UtilsServer::class);\n        Registry::set(UtilsServer::class, $utilsSever);\n        $utilsSever\n            ->method('isCurrentUrl')\n            ->willReturn(false);\n\n        $this->assertTrue($session->isSidNeeded('https://myshop.abc'));\n    }\n\n    public function testSidNotNeededForTheSameUrl(): void\n    {\n        $session = oxNew(Session::class);\n\n        $utilsSever = $this->createStub(UtilsServer::class);\n        Registry::set(UtilsServer::class, $utilsSever);\n        $utilsSever\n            ->method('isCurrentUrl')\n            ->willReturn(true);\n\n        $url = Registry::getConfig()->getCurrentShopUrl();\n\n        $this->assertFalse($session->isSidNeeded($url));\n    }\n\n    public function testSessionChallengeTrue(): void\n    {\n        $session = oxNew(Session::class);\n        $session->start();\n        $token = $session->getSessionChallengeToken();\n        $_POST['stoken'] = $token;\n        $challenge = $session->checkSessionChallenge();\n        $this->assertTrue($challenge);\n    }\n\n    public function testSessionChallengeEmptyToken(): void\n    {\n        $session = oxNew(Session::class);\n        $session->start();\n        $challenge = $session->checkSessionChallenge();\n        $this->assertFalse($challenge);\n    }\n\n    public function testSessionChallengeWrongToken(): void\n    {\n        $session = oxNew(Session::class);\n        $session->start();\n        $_POST['stoken'] = 'dummy-string-value';\n        $challenge = $session->checkSessionChallenge();\n        $this->assertFalse($challenge);\n    }\n\n    public function testIsSidNeededWithForceSessionStartAndDefaultConfiguration(): void\n    {\n        $needSid = oxNew(Session::class)->isSidNeeded();\n\n        $this->assertFalse($needSid);\n    }\n\n    public function testIsSidNeededWithForceSessionStart(): void\n    {\n        $this->assertFalse(oxNew(Session::class)->isSidNeeded());\n\n        $this->setParameter('oxid_esales.force_session_start', true);\n\n        $needSid = oxNew(Session::class)->isSidNeeded();\n\n        $this->assertTrue($needSid);\n    }\n\n    public function testIsSidNeededWithOxidCookiesSession(): void\n    {\n        $this->assertFalse(oxNew(Session::class)->isSidNeeded());\n\n        $this->setParameter('oxid_esales.cookies_session', false);\n\n        $needSid = oxNew(Session::class)->isSidNeeded();\n\n        $this->assertTrue($needSid);\n    }\n\n    public static function isSidNeededDefaultsDataProvider(): array\n    {\n        return [\n            ['cl', 'register'],\n            ['cl', 'account'],\n            ['fnc', 'tobasket'],\n            ['fnc', 'login_noredirect'],\n            ['fnc', 'tocomparelist'],\n            ['fnc', 'tocomparelist'],\n            ['_artperpage', '1'],\n            ['ldtype', 'some-type'],\n            ['listorderby', 'id'],\n        ];\n    }\n\n    #[DataProvider('isSidNeededDefaultsDataProvider')]\n    public function testIsSidNeededWithSessionInitParamsAndDefaults(string $param, string $value): void\n    {\n        $_GET[$param] = $value;\n        $sidNeeded = oxNew(Session::class)->isSidNeeded();\n\n        $this->assertTrue($sidNeeded);\n    }\n\n    public function testIsSidNeededWithSessionInitParamsAndParamNotInDefaults(): void\n    {\n        $_GET['cl'] = 'this-controller-is-not-configured';\n        $sidNeeded = oxNew(Session::class)->isSidNeeded();\n\n        $this->assertFalse($sidNeeded);\n    }\n\n    public function testIsSidNeededWithSessionInitParamsAndNewParam(): void\n    {\n        $this->setParameter('oxid_esales.session_init_params', [\n            'cl' => [\n                'abc' => true,\n            ]\n        ]);\n\n        $_GET['cl'] = 'abc';\n        $sidNeeded = oxNew(Session::class)->isSidNeeded();\n\n        $this->assertTrue($sidNeeded);\n    }\n\n    public function testIsSidNeededWithSessionInitParamsAndOverwrittenDefault(): void\n    {\n        $this->setParameter('oxid_esales.session_init_params', [\n            'cl' => [\n                'abc' => true,\n                'register' => false,\n            ]\n        ]);\n\n        $_GET['cl'] = 'register';\n        $sidNeeded = oxNew(Session::class)->isSidNeeded();\n\n        $this->assertFalse($sidNeeded);\n    }\n\n    #[DataProvider('isSidNeededDefaultsDataProvider')]\n    public function testIsSidNeededWithSessionInitParamsAndDefaultsUnchanged(string $param, string $value): void\n    {\n        $this->setParameter('oxid_esales.session_init_params', [\n            'cl' => [\n                'abc' => true,\n            ],\n            'fnc' => [\n                'def' => true,\n            ],\n            'ghi' => true,\n        ]);\n\n        $_GET[$param] = $value;\n        $sidNeeded = oxNew(Session::class)->isSidNeeded();\n\n        $this->assertTrue($sidNeeded);\n    }\n\n    public function testIsSidNeededWithNonArrayValueWillSetAnyControllerAsRequiringSessionId(): void\n    {\n        $_GET['cl'] = 'some-unconfigured-controller';\n\n        $this->assertFalse(oxNew(Session::class)->isSidNeeded());\n\n        $this->setParameter('oxid_esales.session_init_params', [\n            'cl' => true,\n        ]);\n\n        $sidNeeded = oxNew(Session::class)->isSidNeeded();\n\n        $this->assertTrue($sidNeeded);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Core/ShopControlTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Exception\\SystemComponentException;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\ShopControl;\nuse OxidEsales\\Eshop\\Core\\Utils;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ShopControlTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    public function testStartWithExceptionAndDisabledDebugModeWillRedirect(): void\n    {\n        $this->setParameter('oxid_esales.debug_mode', false);\n\n        $shopControl = $this->getMockBuilder(ShopControl::class)\n            ->onlyMethods(['isAdmin', 'runOnce'])\n            ->getMock();\n        $shopControl->method('isAdmin')\n            ->willReturn(false);\n        $shopControl->expects($this->once())\n            ->method('runOnce')\n            ->willThrowException(new SystemComponentException());\n        $utils = $this->createMock(Utils::class);\n        Registry::set(Utils::class, $utils);\n\n        $utils->expects($this->once())\n            ->method('redirect')\n            ->with(Registry::getConfig()->getShopHomeUrl() . 'cl=start');\n\n        $shopControl->start();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Core/SystemRequirementsTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Core;\n\nuse OxidEsales\\EshopCommunity\\Core\\SystemRequirements;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class SystemRequirementsTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    public function testGetPermissionIssuesList(): void\n    {\n        $this->setParameter('oxid_esales.build_directory', 'some wrong directory');\n\n        $checkResults = (new SystemRequirements())->getPermissionIssuesList();\n\n        $this->assertNotEmpty($checkResults['missing']);\n        $this->assertNotEmpty($checkResults['not_writable']);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Core/Utils/UtilsCacheTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Core\\Utils;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Utils;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class UtilsCacheTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    public function testCacheResetShouldNotRemoveCacheFilesFromSubdirectories(): void\n    {\n        $context = ContainerFacade::get(ContextInterface::class);\n\n        $cachedTestPhpFile = Path::join($context->getCacheDirectory(), 'myTestSubCacheDir', 'test_cache_file.php');\n        $cachedTestTxtFile = Path::join($context->getCacheDirectory(), 'myTestSubCacheDir2', 'test_cache_file.txt');\n\n        $filesystem = ContainerFacade::get('oxid_esales.symfony.file_system');\n\n        $filesystem->dumpFile($cachedTestPhpFile, '');\n        $filesystem->dumpFile($cachedTestTxtFile, '');\n\n        $utils = Registry::getUtils();\n        $utils->oxResetFileCache();\n\n        $this->assertFileExists($cachedTestPhpFile);\n        $this->assertFileExists($cachedTestTxtFile);\n    }\n\n    public function testSeoIsActiveWithDefaultConfig(): void\n    {\n        $isActive = oxNew(Utils::class)->seoIsActive();\n\n        $this->assertTrue($isActive);\n    }\n\n    public function testSeoIsActiveWithModifiedConfig(): void\n    {\n        $this->setParameter('oxid_esales.seo_mode', false);\n        $this->replaceContainerInstance();\n\n        $isActive = oxNew(Utils::class)->seoIsActive();\n\n        $this->assertFalse($isActive);\n    }\n\n    public function testSeoIsActiveWithInvalidSeoModeValue(): void\n    {\n        Registry::getConfig()->setConfigParam('aSeoModes', new \\stdClass());\n\n        $isActive = oxNew(Utils::class)->seoIsActive();\n\n        $this->assertTrue($isActive);\n    }\n\n    public function testSeoIsActiveWithSingleSeoMode(): void\n    {\n        $currentShopId = 1;\n        Registry::getConfig()->setConfigParam('aSeoModes', [$currentShopId => [1 => false]]);\n\n        $isActive = oxNew(Utils::class)->seoIsActive(languageId: 1);\n\n        $this->assertFalse($isActive);\n    }\n\n    public function testSeoIsActiveWithReset(): void\n    {\n        $currentShopId = 1;\n        $utils = oxNew(Utils::class);\n        $utils->seoIsActive();\n        Registry::getConfig()->setConfigParam('aSeoModes', [$currentShopId => [1 => false]]);\n\n        $valueCached = $utils->seoIsActive(languageId: 1);\n        $valueAfterReset = $utils->seoIsActive(reset: true, languageId: 1);\n\n        $this->assertTrue($valueCached);\n        $this->assertFalse($valueAfterReset);\n    }\n\n    public function testSeoIsActiveWithMultipleSeoModes(): void\n    {\n        $currentShopId = 1;\n        Registry::getConfig()->setConfigParam('aSeoModes', [\n            $currentShopId => [1 => true, 2 => false],\n            2 => [1 => 0, 2 => true],\n        ]);\n\n        $this->assertTrue(oxNew(Utils::class)->seoIsActive(languageId: 1));\n        $this->assertFalse(oxNew(Utils::class)->seoIsActive(languageId: 2));\n        $this->assertFalse(oxNew(Utils::class)->seoIsActive(shopId: 2, languageId: 1));\n        $this->assertTrue(oxNew(Utils::class)->seoIsActive(shopId: 2, languageId: 2));\n        $this->assertTrue(oxNew(Utils::class)->seoIsActive(shopId: 2, languageId: 3));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Core/Utils/UtilsSearchEngineTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Core\\Utils;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Utils;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nfinal class UtilsSearchEngineTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    public static function providerSearchEngineNoneAdminMode(): array\n    {\n        return [\n            [true, [], 'googlebot', false],\n            [false, [], 'googlebot', false],\n            [true, ['googlebot', 'xxx'], 'googlebot', true],\n            [false, ['googlebot', 'xxx'], 'googlebot', true],\n        ];\n    }\n\n    #[DataProvider('providerSearchEngineNoneAdminMode')]\n    public function testIsSearchEngineNonAdmin(bool $debug, array $robots, string $searchEngine, bool $expected): void\n    {\n        $this->createContainer();\n        $this->container->setParameter('oxid_esales.debug_mode', $debug);\n        $this->container->setParameter('oxid_esales.search_engine_list', $robots);\n        $this->replaceContainerInstance();\n\n        $this->assertSame($expected, (new Utils())->isSearchEngine($searchEngine));\n    }\n\n    public function testIsSearchEngineAdminAndDebugOn(): void\n    {\n        $this->createContainer();\n        $this->container->setParameter('oxid_esales.debug_mode', true);\n        $this->container->setParameter('oxid_esales.search_engine_list', ['googlebot', 'xxx']);\n        $this->replaceContainerInstance();\n\n        $utils = $this->getUtils();\n\n        $this->assertFalse($utils->isSearchEngine('googlebot'));\n        $this->assertFalse($utils->isSearchEngine('xxx'));\n    }\n\n    private function getUtils(): UtilsSpy\n    {\n        $utils = new UtilsSpy();\n        Registry::set(Utils::class, $utils);\n\n        return $utils;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Core/Utils/UtilsSpy.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Core\\Utils;\n\nuse OxidEsales\\EshopCommunity\\Core\\Utils;\n\nfinal class UtilsSpy extends Utils\n{\n    public function isAdmin(): bool\n    {\n        return true;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Core/UtilsFileTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Exception\\StandardException;\nuse OxidEsales\\Eshop\\Core\\UtilsFile;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\n\nfinal class UtilsFileTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    #[DoesNotPerformAssertions]\n    public function testProcessFileWithDefaultConfiguration(): void\n    {\n        $filename = 'some_file.jpg';\n        $_FILES[$filename]['name'] = $filename;\n        $_FILES[$filename]['tmp_name'] = uniqid('some-file-', true);\n\n        (new UtilsFile())->processFile($filename, '');\n    }\n\n    public function testProcessFileWithDefaultConfigurationAndDisallowedFileExtension(): void\n    {\n        $filename = 'some_file.exe';\n        $_FILES[$filename]['name'] = $filename;\n        $_FILES[$filename]['tmp_name'] = uniqid('some-file-', true);\n\n        $this->expectException(StandardException::class);\n        $this->expectExceptionMessage('EXCEPTION_NOTALLOWEDTYPE');\n\n        (new UtilsFile())->processFile($filename, '');\n    }\n\n    #[DoesNotPerformAssertions]\n    public function testProcessFileWithModifiedConfig(): void\n    {\n        $filename = 'some_file.exe';\n        $_FILES[$filename]['name'] = $filename;\n        $_FILES[$filename]['tmp_name'] = uniqid('some-file-', true);\n        $this->setAllowedFileExtensions(['exe']);\n\n        (new UtilsFile())->processFile($filename, '');\n    }\n\n    public function testProcessFileWithModifiedConfigAndDisallowedFileExtension(): void\n    {\n        $filename = 'some_file.jpg';\n        $_FILES[$filename]['name'] = $filename;\n        $_FILES[$filename]['tmp_name'] = uniqid('some-file-', true);\n        $this->setAllowedFileExtensions([]);\n\n        $this->expectException(StandardException::class);\n        $this->expectExceptionMessage('EXCEPTION_NOTALLOWEDTYPE');\n\n        (new UtilsFile())->processFile($filename, '');\n    }\n\n    private function setAllowedFileExtensions(array $parameter): void\n    {\n        $this->createContainer();\n        $this->setParameter('oxid_esales.allowed_uploaded_types', $parameter);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Core/UtilsServerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Core;\n\nuse OxidEsales\\Eshop\\Application\\Model\\User;\nuse OxidEsales\\Eshop\\Core\\UtilsServer;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\PasswordServiceBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\nuse Prophecy\\Argument;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\n\nfinal class UtilsServerTest extends TestCase\n{\n    use ContainerTrait;\n    use ProphecyTrait;\n\n    public function testSetUserCookieWillCallPasswordHashing(): void\n    {\n        $passwordService = $this->prophesize(PasswordServiceBridgeInterface::class);\n        $password = 'some-password';\n        $passwordService\n            ->hash(Argument::containingString($password))\n            ->willReturn('some-hash');\n        $this->createContainer();\n        $this->replaceService(PasswordServiceBridgeInterface::class, $passwordService->reveal());\n        $this->replaceContainerInstance();\n\n        $utilsServer = oxNew(UtilsServer::class);\n        $utilsServer->setUserCookie('some-user-name', $password);\n\n        $passwordService->hash($password . User::USER_COOKIE_SALT)->shouldHaveBeenCalledOnce();\n    }\n\n    public function testIsTrustedServerWithDefaultConfig(): void\n    {\n        $isTrusted = oxNew(UtilsServer::class)->isTrustedClientIp();\n\n        $this->assertFalse($isTrusted);\n    }\n\n    public function testIsTrustedServerWithConfiguredIp(): void\n    {\n        $someIp = '255.255.255.255';\n        $_SERVER['HTTP_CLIENT_IP'] = $someIp;\n\n        $this->setParameter('oxid_esales.trusted_ips', [$someIp]);\n\n        $isTrusted = oxNew(UtilsServer::class)->isTrustedClientIp();\n\n        $this->assertTrue($isTrusted);\n    }\n\n    public function testIsTrustedServerWithNonTrustedIp(): void\n    {\n        $someIp = '255.255.255.255';\n        $_SERVER['HTTP_CLIENT_IP'] = $someIp;\n        $this->setParameter('oxid_esales.trusted_ips', ['1.2.3.4', '5.6.7.8']);\n\n        $isTrusted = oxNew(UtilsServer::class)->isTrustedClientIp();\n\n        $this->assertFalse($isTrusted);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Core/UtilsTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass UtilsTest extends IntegrationTestCase\n{\n    public function testToFileCache(): void\n    {\n        $utils = Registry::getUtils();\n        $key = \"testCacheKey\";\n        $value = \"testCacheValue\";\n\n        $utils->toFileCache($key, $value);\n\n        $this->assertEquals($value, $utils->fromFileCache($key));\n    }\n\n    public function testToFileCacheOverrideValue(): void\n    {\n        $utils = Registry::getUtils();\n        $Key = \"testCacheKey\";\n        $value1 = \"testCacheFirstValue\";\n        $value2 = \"testCacheSecondValue\";\n\n        $utils->toFileCache($Key, $value1);\n\n        $this->assertEquals($value1, $utils->fromFileCache($Key));\n\n        $utils->toFileCache($Key, $value2);\n\n        $this->assertEquals($value2, $utils->fromFileCache($Key));\n    }\n\n    public function testLangCache(): void\n    {\n        $utils = Registry::getUtils();\n        $langCache = ['TEST' => 'test value'];\n        $cacheName = 'testCacheName';\n\n        $utils->setLangCache($cacheName, $langCache);\n\n        $this->assertEquals($langCache, $utils->getLangCache($cacheName));\n    }\n\n    public function testDeleteLanguageCache(): void\n    {\n        $utils = Registry::getUtils();\n        $keyLang1 = 'lang_1_0_0';\n        $keyLang2 = 'lang_1_0_0';\n        $testLang = ['key1' => 'testVal1', 'key2' => 'testVal2'];\n\n        $utils->setLangCache($keyLang1, $testLang);\n        $utils->setLangCache($keyLang2, $testLang);\n\n        $utils->resetLanguageCache();\n\n        $this->assertEmpty($utils->fromFileCache($keyLang1));\n        $this->assertEmpty($utils->fromFileCache($keyLang2));\n    }\n\n    public function testDeleteMenuCache(): void\n    {\n        $utils = Registry::getUtils();\n        $keyLang1 = 'lang_1_0_0';\n        $keyLang2 = 'lang_1_0_0';\n        $testLang = ['key1' => 'testVal1', 'key2' => 'testVal2'];\n\n        $utils->setLangCache($keyLang1, $testLang);\n        $utils->setLangCache($keyLang2, $testLang);\n\n        $utils->resetLanguageCache();\n\n        $this->assertEmpty($utils->fromFileCache($keyLang1));\n        $this->assertEmpty($utils->fromFileCache($keyLang2));\n    }\n\n    public function testCacheResetShouldNotRemoveCacheFilesFromSubdirectories(): void\n    {\n        $utils = Registry::getUtils();\n        $context = ContainerFacade::get(ContextInterface::class);\n\n        $cachedTestPhpFile = Path::join($context->getCacheDirectory(), 'myTestSubCacheDir', 'test_cache_file.php');\n        $cachedTestTxtFile = Path::join($context->getCacheDirectory(), 'myTestSubCacheDir2', 'test_cache_file.txt');\n        $filesystem = ContainerFacade::get('oxid_esales.symfony.file_system');\n\n        $filesystem->dumpFile($cachedTestPhpFile, '');\n        $filesystem->dumpFile($cachedTestTxtFile, '');\n        $utils->oxResetFileCache();\n\n        $this->assertFileExists($cachedTestPhpFile);\n        $this->assertFileExists($cachedTestTxtFile);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Core/ViewConfigTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Core;\n\nuse OxidEsales\\EshopCommunity\\Core\\PictureHandler;\nuse OxidEsales\\EshopCommunity\\Core\\ViewConfig;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ViewConfigTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private ViewConfig $viewConfig;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->viewConfig = new ViewConfig();\n    }\n\n    public function testIsAltImageServerConfiguredWithEmptyParameter(): void\n    {\n        $this->setParameter('oxid_esales.alternative_image_url', '');\n\n        $altImageServerConfigured = $this->viewConfig->isAltImageServerConfigured();\n\n        $this->assertFalse($altImageServerConfigured);\n    }\n\n    public function testIsAltImageServerConfiguredWithNotEmptyParameter(): void\n    {\n        $this->setParameter('oxid_esales.alternative_image_url', 'someValue');\n\n        $altImageServerConfigured = $this->viewConfig->isAltImageServerConfigured();\n\n        $this->assertTrue($altImageServerConfigured);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/IntegrationTestCase.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration;\n\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\DatabaseTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\FilesystemTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\TestContainerFactory;\nuse PHPUnit\\Framework\\TestCase;\n\nclass IntegrationTestCase extends TestCase\n{\n    use ContainerTrait;\n    use DatabaseTrait;\n    use FilesystemTrait;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        TestContainerFactory::resetContainer();\n        $this->backupVarDirectory();\n        $this->beginTransaction();\n        $this->get('oxid_esales.module.install.service.launched_shop_project_configuration_generator')->generate();\n    }\n\n    public function tearDown(): void\n    {\n        $this->rollBackTransaction();\n        $this->restoreVarDirectory();\n\n        parent::tearDown();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/BootstrapContainer/BootstrapContainerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\BootstrapContainer;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\BootstrapContainerFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge\\ProductRatingBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Container\\ContainerInterface;\nuse Symfony\\Component\\DependencyInjection\\Exception\\ServiceNotFoundException;\n\nfinal class BootstrapContainerTest extends TestCase\n{\n    public function testContainerProvidesBootstrapServices(): void\n    {\n        $container = BootstrapContainerFactory::getBootstrapContainer();\n\n        $this->assertInstanceOf(\n            BasicContextInterface::class,\n            $container->get(BasicContextInterface::class)\n        );\n    }\n\n    public function testContainerDoesNotProvideNotBootstrapServices(): void\n    {\n        $this->expectException(ServiceNotFoundException::class);\n\n        $container = BootstrapContainerFactory::getBootstrapContainer();\n        $container->get(ProductRatingBridgeInterface::class);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/ComposerPlugin/ComponentInstallerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\ComposerPlugin;\n\nuse Composer\\IO\\NullIO;\nuse Composer\\Package\\Package;\nuse OxidEsales\\ComposerPlugin\\Installer\\Package\\ComponentInstaller;\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\BootstrapContainerFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\ProjectRootLocator;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ComponentInstallerTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private string $servicesFilePath = 'Fixtures/services.yaml';\n\n    public function testInstall(): void\n    {\n        $installer = $this->createInstaller();\n        $installer->install(__DIR__ . '/Fixtures');\n\n        $this->assertTrue($this->doesServiceLineExists());\n    }\n\n    public function testUpdate(): void\n    {\n        $installer = $this->createInstaller();\n        $installer->update(__DIR__ . '/Fixtures');\n\n        $this->assertTrue($this->doesServiceLineExists());\n    }\n\n    private function createInstaller(): ComponentInstaller\n    {\n        $packageStub = $this->createStub(Package::class);\n\n        return new ComponentInstaller(\n            new NullIO(),\n            (new ProjectRootLocator())->getProjectRoot(),\n            $packageStub\n        );\n    }\n\n    private function doesServiceLineExists(): bool\n    {\n        $context = BootstrapContainerFactory::getBootstrapContainer()->get(BasicContextInterface::class);\n        $contentsOfProjectFile = file_get_contents(\n            $context->getGeneratedServicesFilePath()\n        );\n\n        return (bool)strpos($contentsOfProjectFile, $this->servicesFilePath);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/ComposerPlugin/Fixtures/test-module-package-installation/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.1';\n\n$aModule = array(\n    'id'           => 'testModule',\n    'thumbnail'    => 'picture.png',\n    'version'      => '1.0',\n    'author'       => 'OXID eSales AG',\n);\n"
  },
  {
    "path": "tests/Integration/Internal/ComposerPlugin/Fixtures/test-module-package-installation/services.yaml",
    "content": "services:\n"
  },
  {
    "path": "tests/Integration/Internal/ComposerPlugin/ModulePackageInstallerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\ComposerPlugin;\n\nuse Composer\\IO\\NullIO;\nuse Composer\\Package\\Package;\nuse OxidEsales\\ComposerPlugin\\Installer\\Package\\ModulePackageInstaller;\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\ContainerFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ModulePackageInstallerTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private string $modulePackagePath = __DIR__ . '/Fixtures/test-module-package-installation';\n    private string $packageName = 'test-module-package-installation';\n    private string $moduleId = 'testModule';\n\n    public function testModuleNotInstalledByDefault(): void\n    {\n        $installer = $this->getPackageInstaller($this->packageName);\n        $this->assertFalse($installer->isInstalled($this->modulePackagePath));\n    }\n\n    public function testModuleIsInstalledAfterInstallProcess(): void\n    {\n        $installer = $this->getPackageInstaller($this->packageName);\n        $installer->install($this->modulePackagePath);\n\n        $this->assertTrue($installer->isInstalled($this->modulePackagePath));\n    }\n\n    public function testModuleUninstall(): void\n    {\n        $package = new OxidEshopPackage(__DIR__ . '/Fixtures/' . $this->packageName);\n\n        $installer = $this->getPackageInstaller($this->packageName);\n\n        $installer->install($this->modulePackagePath);\n        $this->activateTestModule($package);\n        $installer->uninstall($this->modulePackagePath);\n\n        $this->assertFalse($installer->isInstalled($this->modulePackagePath));\n    }\n\n    public function testModuleInstallDoesNotUseMainContainer(): void\n    {\n        $installer = $this->getPackageInstaller($this->packageName);\n\n        ContainerFactory::resetContainer();\n        $installer->install($this->modulePackagePath);\n\n        $this->assertFileDoesNotExist(\n            $this->get(ContextInterface::class)->getContainerCacheFilePath(\n                $this->get(ContextInterface::class)->getCurrentShopId()\n            )\n        );\n    }\n\n    public function testModuleUpdateDoesNotUseMainContainer(): void\n    {\n        $installer = $this->getPackageInstaller($this->packageName);\n\n        ContainerFactory::resetContainer();\n        $installer->update($this->modulePackagePath);\n\n        $this->assertFileDoesNotExist(\n            $this->get(ContextInterface::class)->getContainerCacheFilePath(\n                $this->get(ContextInterface::class)->getCurrentShopId()\n            )\n        );\n    }\n\n\n    private function getPackageInstaller(string $packageName, array $extra = []): ModulePackageInstaller\n    {\n        $package = new Package($packageName, '1.0.0', '1.0.0');\n        $package->setExtra($extra);\n\n        return new ModulePackageInstaller(\n            new NullIO(),\n            $this->get(BasicContextInterface::class)->getSourcePath(),\n            $package\n        );\n    }\n\n    private function activateTestModule(OxidEshopPackage $package): void\n    {\n        $this->get(ModuleInstallerInterface::class)\n            ->install($package);\n        $this\n            ->get(ModuleActivationBridgeInterface::class)\n            ->activate($this->moduleId, 1);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Container/ContainerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Container;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\ContainerFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Dao\\ProjectYamlDao;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Psr\\Container\\ContainerInterface;\nuse Symfony\\Component\\DependencyInjection\\Exception\\ServiceNotFoundException;\n\nfinal class ContainerTest extends IntegrationTestCase\n{\n    public function testResetCacheWorks(): void\n    {\n        $this->expectException(ServiceNotFoundException::class);\n        ContainerFactory::getInstance()->getContainer()->get('test_service');\n\n        $projectYamlDao = new ProjectYamlDao(\n            $this->getContainer()->get(ContextInterface::class),\n            $this->getContainer()->get('oxid_esales.symfony.file_system')\n        );\n\n        $projectConfigurationFile = $projectYamlDao->loadProjectConfigFile();\n        $projectConfigurationFile->addImport(__DIR__ . '/Fixtures/Project/services.yaml');\n\n        $projectYamlDao->saveProjectConfigFile($projectConfigurationFile);\n\n        ContainerFactory::resetContainer();\n\n        $this->assertIsObject(\n            ContainerFactory::getInstance()->getContainer()->get('test_service')\n        );\n    }\n\n    private function getContainer(): ContainerInterface\n    {\n        return ContainerFactory::getInstance()->getContainer();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Container/Fixtures/SomeService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Container\\Fixtures;\n\nclass SomeService\n{\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Container/Fixtures/services.yaml",
    "content": "services:\n  some_test_service:\n    class: OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Container\\Fixtures\\SomeService\n    public: true\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Admin/Command/CreateUserCommandTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Admin\\Command;\n\nuse OxidEsales\\EshopCommunity\\Application\\Model\\User;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Exception\\EmailAlreadyTakenException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Exception\\InvalidEmailException;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Exception\\RuntimeException;\nuse Symfony\\Component\\Console\\Tester\\CommandTester;\n\nfinal class CreateUserCommandTest extends TestCase\n{\n    use ContainerTrait;\n\n    private const ADMIN_EMAIL = 'someone@test.com';\n\n    public function tearDown(): void\n    {\n        $user = new User();\n        $user->delete($user->getIdByUserName(self::ADMIN_EMAIL));\n\n        parent::tearDown();\n    }\n\n    public function testExecuteWithMissingArgument(): void\n    {\n        $this->expectException(RuntimeException::class);\n\n        $this->getCommandTester()\n            ->execute([\n                'admin-email' => self::ADMIN_EMAIL,\n            ]);\n    }\n\n    public function testExecuteWithInvalidAdminEmail(): void\n    {\n        $this->expectException(InvalidEmailException::class);\n\n        $this->getCommandTester()\n            ->execute([\n                'admin-email' => 'admin',\n                'admin-password' => 'admin',\n            ]);\n    }\n\n    public function testExecuteWithCompleteArgs(): void\n    {\n        $exitCode = $this->getCommandTester()\n            ->execute([\n                'admin-email' => self::ADMIN_EMAIL,\n                'admin-password' => 'some-admin-pass',\n            ]);\n\n        $this->assertSame(Command::SUCCESS, $exitCode);\n        $this->assertUserExists();\n    }\n\n    public function testThrowsEmailAlreadyTakenExceptionWhenAdminExists(): void\n    {\n        $this->expectException(EmailAlreadyTakenException::class);\n\n        $this->getCommandTester()\n            ->execute([\n                'admin-email' => $this->createTestAdminUser(),\n                'admin-password' => uniqid(),\n        ]);\n    }\n\n    private function createTestAdminUser(): string\n    {\n        $email = sprintf('%s@%s.com', uniqid(), uniqid());\n        $user = oxNew(User::class);\n        $user->assign([\n            'oxusername' => $email,\n            'oxpassword' => md5(uniqid()),\n            'oxrights'   => uniqid(),\n            'oxactive'   => 1,\n            'oxshopid'   => 1,\n        ]);\n        $user->save();\n\n        return $email;\n    }\n\n\n    private function getCommandTester(): CommandTester\n    {\n        return new CommandTester(\n            $this->get('console.command_loader')->get('oe:admin:create-user')\n        );\n    }\n\n    private function assertUserExists(): void\n    {\n        $this->assertNotFalse((new User())->getIdByUserName(self::ADMIN_EMAIL));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Admin/Dao/AdminDaoTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Admin\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Dao\\AdminDao;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Dao\\AdminDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\DataObject\\Admin;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Exception\\EmailAlreadyTakenException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\Test;\n\nfinal class AdminDaoTest extends IntegrationTestCase\n{\n    #[Test]\n    public function testCreateSucceedsWhenEmailIsUnique(): void\n    {\n        $admin = new Admin(\n            id: uniqid(),\n            email: $this->generateEmail(),\n            passwordHash: md5(uniqid()),\n            rights: uniqid(),\n            shopId: rand(1, 10),\n        );\n\n        $this->getAdminDao()->create($admin);\n\n        $this->assertTrue(true);\n    }\n\n    #[Test]\n    public function testCreateThrowsExceptionIfEmailAlreadyExists(): void\n    {\n        $email = $this->generateEmail();\n        $shopId = rand(1, 10);\n\n        $admin = new Admin(\n            id: uniqid(),\n            email: $email,\n            passwordHash: md5(uniqid()),\n            rights: uniqid(),\n            shopId: $shopId,\n        );\n\n        $this->getAdminDao()->create($admin);\n\n        $this->expectException(EmailAlreadyTakenException::class);\n\n        $this->getAdminDao()->create($admin);\n    }\n\n    private function generateEmail(): string\n    {\n        return sprintf('%s@%s.com', uniqid(), uniqid());\n    }\n\n    private function getAdminDao(): AdminDaoInterface\n    {\n        return new AdminDao(\n            queryBuilderFactory: $this->get(QueryBuilderFactoryInterface::class)\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Admin/Service/AdminUserServiceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Admin\\Service;\n\nuse OxidEsales\\EshopCommunity\\Application\\Model\\User;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\DataObject\\Admin;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Service\\AdminUserServiceInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class AdminUserServiceTest extends TestCase\n{\n    use ContainerTrait;\n\n    private string $email = 'testuser@oxideshop.dev';\n\n    public function tearDown(): void\n    {\n        $testUser = new User();\n        $testUser->load($testUser->getIdByUserName($this->email));\n\n        $testUser->delete($testUser->getId());\n\n        parent::tearDown();\n    }\n\n    public function testCreateMallAdmin(): void\n    {\n        $adminUserService = $this->get(AdminUserServiceInterface::class);\n\n        $adminUserService->createAdmin(\n            $this->email,\n            'test123',\n            Admin::MALL_ADMIN,\n            1\n        );\n\n        $testUser = new User();\n        $testUser->load($testUser->getIdByUserName($this->email));\n        $this->assertTrue($testUser->isMallAdmin());\n    }\n\n    public function testCreateAdmin(): void\n    {\n        $adminUserService = $this->get(AdminUserServiceInterface::class);\n\n        $adminUserService->createAdmin(\n            $this->email,\n            'test123',\n            '1',\n            1\n        );\n\n        $testUser = new User();\n        $testUser->load($testUser->getIdByUserName($this->email));\n        $this->assertFalse($testUser->isMallAdmin());\n        $this->assertEquals(1, $testUser->oxuser__oxrights->value);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Authentication/Bridge/PasswordServiceBridgeTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Password\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\PasswordServiceBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class PasswordServiceBridgeTest extends TestCase\n{\n    use ContainerTrait;\n\n    /**\n     * End-to-end test for the PasswordService bridge\n     */\n    public function testHashWithBcrypt(): void\n    {\n        /** @var PasswordServiceBridgeInterface $passwordServiceBridge */\n        $passwordServiceBridge = $this->get(PasswordServiceBridgeInterface::class);\n        $hash = $passwordServiceBridge->hash('secret');\n        $info = password_get_info($hash);\n\n        $this->assertSame(PASSWORD_BCRYPT, $info['algo']);\n    }\n\n    /**\n     * End-to-end test for the password verification service.\n     */\n    public function testVerifyPassword(): void\n    {\n        /** @var PasswordServiceBridgeInterface $passwordServiceBridge */\n        $passwordServiceBridge = $this->get(PasswordServiceBridgeInterface::class);\n\n        $password = 'secret';\n        $passwordHash = password_hash($password, PASSWORD_BCRYPT);\n\n        $this->assertTrue(\n            $passwordServiceBridge->verifyPassword($password, $passwordHash)\n        );\n    }\n\n    public function testPasswordNeedsRehash(): void\n    {\n        /** @var PasswordServiceBridgeInterface $passwordServiceBridge */\n        $passwordServiceBridge = $this->get(PasswordServiceBridgeInterface::class);\n\n        $cost = ContainerFacade::getParameter('oxid_esales.utility.hash.service.password_hash.bcrypt.cost');\n\n        $passwordHashWithCostFromConfiguration = password_hash('secret', PASSWORD_BCRYPT, ['cost' => $cost]);\n        $passwordHashWithCostChangedCost = password_hash('secret', PASSWORD_BCRYPT, ['cost' => $cost + 1]);\n\n        $this->assertFalse(\n            $passwordServiceBridge->passwordNeedsRehash($passwordHashWithCostFromConfiguration)\n        );\n        $this->assertTrue(\n            $passwordServiceBridge->passwordNeedsRehash($passwordHashWithCostChangedCost)\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Authentication/Bridge/RandomTokenGeneratorBridgeTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Authentication\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\RandomTokenGeneratorBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function strlen;\n\nfinal class RandomTokenGeneratorBridgeTest extends TestCase\n{\n    use ContainerTrait;\n\n    private RandomTokenGeneratorBridgeInterface $bridge;\n\n    public function testGetAlphanumericToken(): void\n    {\n        $length = 32;\n\n        $token = $this->get(RandomTokenGeneratorBridgeInterface::class)->getAlphanumericToken($length);\n\n        $this->assertEquals($length, strlen($token));\n        $this->assertTrue(ctype_alnum($token));\n        $this->assertFalse(ctype_xdigit($token));\n    }\n\n    public function testGetHexToken(): void\n    {\n        $length = 32;\n\n        $token = $this->get(RandomTokenGeneratorBridgeInterface::class)->getHexToken($length);\n\n        $this->assertEquals($length, strlen($token));\n        $this->assertTrue(ctype_alnum($token));\n        $this->assertTrue(ctype_xdigit($token));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Authentication/Service/PasswordHashServiceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Password\\Service;\n\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Authentication\\Exception\\PasswordPolicyException;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Authentication\\Policy\\PasswordPolicyInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Hash\\Service\\Argon2IPasswordHashService;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Hash\\Service\\BcryptPasswordHashService;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Hash\\Service\\PasswordHashServiceInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class PasswordHashServiceTest extends TestCase\n{\n    use ContainerTrait;\n\n    /**\n     */\n    public function testPasswordNeedsRehashReturnsTrueOnChangedAlgorithm(): void\n    {\n        if (!defined('PASSWORD_ARGON2I')) {\n            $this->markTestSkipped(\n                'The password hashing algorithms PASSWORD_ARGON2I and/or PASSWORD_ARGON2I are not available'\n            );\n        }\n\n        $argon2iPasswordHashService = $this->getArgon2IPasswordHashService();\n        $bcryptPasswordHashService = $this->getBcryptPasswordHashService();\n\n        $bcryptHash = $bcryptPasswordHashService->hash('secret');\n        $argon2iHash = $argon2iPasswordHashService->hash('secret');\n\n        $this->assertFalse($bcryptPasswordHashService->passwordNeedsRehash($bcryptHash));\n        $this->assertFalse($argon2iPasswordHashService->passwordNeedsRehash($argon2iHash));\n        $this->assertTrue($bcryptPasswordHashService->passwordNeedsRehash($argon2iHash));\n        $this->assertTrue($argon2iPasswordHashService->passwordNeedsRehash($bcryptHash));\n    }\n\n    /**\n     * End-to-end test to ensure, that the password policy checking is called during password hashing\n     */\n    public function testPasswordHashServiceEnforcesPasswordPolicy(): void\n    {\n        $this->expectException(PasswordPolicyException::class);\n\n        $passwordUtf8 = 'äääääää';\n        $passwordIso = mb_convert_encoding($passwordUtf8, 'ISO-8859-15');\n\n        $passwordHashService = $this->get(PasswordHashServiceInterface::class);\n        $passwordHashService->hash($passwordIso);\n    }\n\n    private function getBcryptPasswordHashService(): PasswordHashServiceInterface\n    {\n        $passwordPolicy = $this->getPasswordPolicyMock();\n\n        return new BcryptPasswordHashService(\n            $passwordPolicy,\n            4\n        );\n    }\n\n    private function getArgon2IPasswordHashService(): PasswordHashServiceInterface\n    {\n        $passwordPolicyMock = $this->getPasswordPolicyMock();\n\n        return new Argon2IPasswordHashService(\n            $passwordPolicyMock,\n            PASSWORD_ARGON2_DEFAULT_MEMORY_COST,\n            PASSWORD_ARGON2_DEFAULT_TIME_COST,\n            PASSWORD_ARGON2_DEFAULT_THREADS\n        );\n    }\n\n\n    /**\n     * @return PasswordPolicyInterface|MockObject\n     */\n    private function getPasswordPolicyMock(): PasswordPolicyInterface\n    {\n        return $this->createStub(PasswordPolicyInterface::class);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Authentication/Service/PasswordVerificationServiceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Password\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Authentication\\Exception\\PasswordPolicyException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Service\\PasswordVerificationServiceInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class PasswordVerificationServiceTest extends TestCase\n{\n    use ContainerTrait;\n\n    /**\n     * End-to-end test to ensure, that the password policy checking is called during password verification\n     */\n    public function testverifyPasswordHashEnforcesPasswordPolicy(): void\n    {\n        $this->expectException(PasswordPolicyException::class);\n\n        $passwordUtf8 = 'äääääää';\n        $passwordIso = mb_convert_encoding($passwordUtf8, 'ISO-8859-15');\n\n        $passwordHash = password_hash($passwordIso, PASSWORD_DEFAULT);\n\n        $passwordVerificationService = $this->get(PasswordVerificationServiceInterface::class);\n        $passwordVerificationService->verifyPassword($passwordIso, $passwordHash);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Contact/Form/ContactFormBridgeTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Contact\\Form;\n\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form\\ContactFormBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FormConfigurationInterface;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ContactFormBridgeTest extends TestCase\n{\n    public function testFormGetter(): void\n    {\n        $this->assertInstanceOf(\n            FormInterface::class,\n            ContainerFacade::get(ContactFormBridgeInterface::class)\n                ->getContactForm()\n        );\n    }\n\n    public function testFormConfigurationGetter(): void\n    {\n        $this->assertInstanceOf(\n            FormConfigurationInterface::class,\n            ContainerFacade::get(ContactFormBridgeInterface::class)\n                ->getContactFormConfiguration()\n        );\n    }\n\n    public function testFormMessageGetter(): void\n    {\n        $bridge = ContainerFacade::get(ContactFormBridgeInterface::class);\n\n        $form = $bridge->getContactForm();\n        $form->handleRequest(['email' => 'marina.ginesta@bcn.cat']);\n\n        $message = $bridge->getContactFormMessage($form);\n\n        $this->assertStringContainsString(\n            'marina.ginesta@bcn.cat',\n            $message\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Media/Dao/MediaDaoTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Media\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Dao\\MediaDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaType;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\ConnectionFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\DatabaseTrait;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class MediaDaoTest extends TestCase\n{\n    use DatabaseTrait;\n    use ContainerTrait;\n\n    private MediaDaoInterface $mediaDao;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        $this->beginTransaction($this->get(ConnectionFactoryInterface::class)->create());\n        $this->mediaDao = $this->get(MediaDaoInterface::class);\n    }\n\n    public function tearDown(): void\n    {\n        $this->rollBackTransaction($this->get(ConnectionFactoryInterface::class)->create());\n        parent::tearDown();\n    }\n\n    public function testCreateAndGetMedia(): void\n    {\n        $mediaId = Id::generate();\n        $mediaPath = new MediaPath('media/test_mdt.png');\n        $mediaType = new MediaType('image/png');\n        $media = new Media(\n            $mediaId,\n            $mediaPath,\n            $mediaType\n        );\n\n        $this->mediaDao->add($media);\n        $fetched = $this->mediaDao->get($mediaId);\n\n        $this->assertEquals($media, $fetched);\n    }\n\n    public function testGetMediaThrowsExceptionForNonExistentId(): void\n    {\n        $this->expectException(EntryDoesNotExistDaoException::class);\n\n        $this->mediaDao->get(Id::generate());\n    }\n\n    public function testDeleteMedia(): void\n    {\n        $mediaId = Id::generate();\n        $mediaPath = new MediaPath('media/test_mdt.png');\n        $mediaType = new MediaType('image/png');\n        $media = new Media(\n            $mediaId,\n            $mediaPath,\n            $mediaType\n        );\n        $this->mediaDao->add($media);\n\n        $this->mediaDao->delete($mediaId);\n\n        $this->expectException(EntryDoesNotExistDaoException::class);\n\n        $this->mediaDao->get($mediaId);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Media/MediaUploaderTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Media;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\MediaUploader;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\ImageHandlerInterface;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\HttpFoundation\\File\\UploadedFile;\n\nfinal class MediaUploaderTest extends TestCase\n{\n    public function testUploadReturnsMediaPath(): void\n    {\n        $fileName = 'file.jpg';\n        $filePath = tempnam(sys_get_temp_dir(), 'upl_');\n        file_put_contents($filePath, 'some content');\n\n        $uploadedFile = new UploadedFile(\n            $filePath,\n            $fileName,\n            'image/jpeg',\n            null,\n            true\n        );\n\n        $imageHandler = $this->createMock(ImageHandlerInterface::class);\n        $imageHandler\n            ->expects($this->once())\n            ->method('upload')\n            ->with($filePath, 'media/' . $fileName);\n\n        $uploader = new MediaUploader($imageHandler);\n\n        $target = new MediaPath('media/' . $fileName);\n        $result = $uploader->uploadTo($uploadedFile, $target);\n\n        $this->assertSame('media/' . $fileName, (string)$result);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Newsletter/Dao/NewsletterRecipientsDaoTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Newsletter\\Dao;\n\nuse DateInterval;\nuse DateTime;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\Dao\\NewsletterRecipientsDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\DataMapper\\NewsletterRecipientsDataMapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\DataMapper\\NewsletterRecipientsDataMapperInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\ConnectionFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\ShopAdapterInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\DatabaseTrait;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class NewsletterRecipientsDaoTest extends TestCase\n{\n    use ContainerTrait;\n    use DatabaseTrait;\n\n    private QueryBuilderFactoryInterface $queryBuilderFactory;\n\n    public function setup(): void\n    {\n        parent::setUp();\n\n        $this->beginTransaction($this->get(ConnectionFactoryInterface::class)->create());\n        $this->queryBuilderFactory = $this->get(QueryBuilderFactoryInterface::class);\n        $this->prepareTestData();\n    }\n\n    public function tearDown(): void\n    {\n        $this->rollBackTransaction($this->get(ConnectionFactoryInterface::class)->create());\n\n        parent::tearDown();\n    }\n\n    public function testGetNewsletterRecipients(): void\n    {\n        $recipientsList = $this->get(NewsletterRecipientsDataMapperInterface::class)->mapRecipientListDataToArray(\n            $this->get(NewsletterRecipientsDaoInterface::class)->getNewsletterRecipients(1)\n        );\n\n        $this->assertEquals(\n            [\n                [\n                    NewsletterRecipientsDataMapper::SALUTATION,\n                    NewsletterRecipientsDataMapper::FIRST_NAME,\n                    NewsletterRecipientsDataMapper::LAST_NAME,\n                    NewsletterRecipientsDataMapper::EMAIL,\n                    NewsletterRecipientsDataMapper::OPT_IN_STATE,\n                    NewsletterRecipientsDataMapper::COUNTRY,\n                    NewsletterRecipientsDataMapper::ASSIGNED_USER_GROUPS,\n                ],\n                [\n                    \"MR\",\n                    \"christine's welt\\\"\",\n                    \"Doe\",\n                    \"test_user@test.com\",\n                    \"subscribed\",\n                    \"Deutschland\",\n                    \"test_group1,test_group2\"\n                ],\n                [\n                    \"MS\",\n                    \"christine's welt\\\"\",\n                    \"Doe\",\n                    \"test_user2@test.com\",\n                    \"subscribed\",\n                    \"Deutschland\",\n                    \"\"\n                ],\n            ],\n            $recipientsList\n        );\n    }\n\n    private function prepareTestData(): void\n    {\n        $queryBuilder = $this->queryBuilderFactory->create();\n        $queryBuilder->delete('oxnewssubscribed');\n        $queryBuilder->executeStatement();\n\n        $this->createSubscribedUserWithGroups();\n        $this->createSubscribedUserWithoutGroupsAndSpacedSalutation();\n        $this->createSubscribedUserInOtherSubshop();\n    }\n\n    private function createSubscribedUserWithGroups(): void\n    {\n        $shopAdapter = $this->get(ShopAdapterInterface::class);\n\n        $testUserId = 'test_user';\n\n        $queryBuilder = $this->queryBuilderFactory->create();\n        $queryBuilder\n            ->insert('oxuser')\n            ->values([\n                'oxid' => ':id',\n                'OXUSERNAME' => ':username',\n                'OXRIGHTS' => ':userRights',\n                'OXCOUNTRYID' => ':countryId',\n                'OXCREATE' => ':create'\n            ])\n            ->setParameters([\n                'id' => $testUserId,\n                'username' => \"test_user@test.com\",\n                'userRights' => \"malladmin\",\n                'countryId' => \"a7c40f631fc920687.20179984\",\n                'create' => (new DateTime())->format('Y-m-d H:i:s')\n            ]);\n\n        $queryBuilder->executeStatement();\n\n        $queryBuilder\n            ->insert('oxgroups')\n            ->values([\n                'oxid' => ':id',\n                'OXTITLE' => ':title',\n                'OXTITLE_1' => ':title'\n            ])\n            ->setParameters([\n                'id' => 'test_group1',\n                'title' => \"test_group1\"\n            ]);\n\n        $queryBuilder->executeStatement();\n\n        $queryBuilder\n            ->insert('oxgroups')\n            ->values([\n                'oxid' => ':id',\n                'OXTITLE' => ':title',\n                'OXTITLE_1' => ':title'\n            ])\n            ->setParameters([\n                'id' => 'test_group2',\n                'title' => \"test_group2\"\n            ]);\n\n        $queryBuilder->executeStatement();\n\n        $queryBuilder\n            ->insert('oxobject2group')\n            ->values([\n                'oxid' => ':id',\n                'OXSHOPID' => '1',\n                'OXOBJECTID' => ':userId',\n                'OXGROUPSID' => ':groupId',\n            ])\n            ->setParameters([\n                'id' => $shopAdapter->generateUniqueId(),\n                'userId' => $testUserId,\n                'groupId' => 'test_group1'\n            ]);\n\n        $queryBuilder->executeStatement();\n\n        $queryBuilder\n            ->insert('oxobject2group')\n            ->values([\n                'oxid' => ':id',\n                'OXSHOPID' => '1',\n                'OXOBJECTID' => ':userId',\n                'OXGROUPSID' => ':groupId',\n            ])\n            ->setParameters([\n                'id' => $shopAdapter->generateUniqueId(),\n                'userId' => $testUserId,\n                'groupId' => 'test_group2'\n            ]);\n\n        $queryBuilder->executeStatement();\n\n        $queryBuilder\n            ->insert('oxnewssubscribed')\n            ->values([\n                'oxid' => ':id',\n                'OXUSERID' => ':userId',\n                'OXSAL' => ':sal',\n                'OXFNAME' => ':fistname',\n                'OXLNAME' => ':lastname',\n                'OXEMAIL' => ':email',\n                'OXDBOPTIN' => ':otpInState',\n                'OXSHOPID' => ':shopId',\n            ])\n            ->setParameters([\n                'id' => $shopAdapter->generateUniqueId(),\n                'userId' => $testUserId,\n                'sal' => \"MR\",\n                'fistname' => \"christine&#039;s welt&quot;\",\n                'lastname' => \"Doe\",\n                'email' => \"test_user@test.com\",\n                'otpInState' => \"1\",\n                'shopId' => \"1\"\n            ]);\n\n        $queryBuilder->executeStatement();\n    }\n\n    private function createSubscribedUserWithoutGroupsAndSpacedSalutation(): void\n    {\n        $shopAdapter = $this->get(ShopAdapterInterface::class);\n\n        $testUserId = 'test_user2';\n\n        $queryBuilder = $this->queryBuilderFactory->create();\n        $queryBuilder\n            ->insert('oxuser')\n            ->values([\n                'oxid' => ':id',\n                'OXUSERNAME' => ':username',\n                'OXRIGHTS' => ':userRights',\n                'OXCOUNTRYID' => ':countryId',\n                'OXCREATE' => ':create'\n            ])\n            ->setParameters([\n                'id' => $testUserId,\n                'username' => \"test_user2@test.com\",\n                'userRights' => \"malladmin\",\n                'countryId' => \"a7c40f631fc920687.20179984\",\n                'create' => (new DateTime())->add(new DateInterval('P1D'))->format('Y-m-d H:i:s')\n            ]);\n\n        $queryBuilder->executeStatement();\n\n        $queryBuilder\n            ->insert('oxnewssubscribed')\n            ->values([\n                'oxid' => ':id',\n                'OXUSERID' => ':userId',\n                'OXSAL' => ':sal',\n                'OXFNAME' => ':fistname',\n                'OXLNAME' => ':lastname',\n                'OXEMAIL' => ':email',\n                'OXDBOPTIN' => ':otpInState',\n                'OXSHOPID' => ':shopId',\n            ])\n            ->setParameters([\n                'id' => $shopAdapter->generateUniqueId(),\n                'userId' => $testUserId,\n                'sal' => \"MS \",\n                'fistname' => \"christine&#039;s welt&quot;\",\n                'lastname' => \"Doe\",\n                'email' => \"test_user2@test.com\",\n                'otpInState' => \"1\",\n                'shopId' => \"1\"\n            ]);\n\n        $queryBuilder->executeStatement();\n    }\n\n    private function createSubscribedUserInOtherSubshop(): void\n    {\n        $shopAdapter = $this->get(ShopAdapterInterface::class);\n\n        $testUserId = 'test_user3';\n\n        $queryBuilder = $this->queryBuilderFactory->create();\n        $queryBuilder\n            ->insert('oxuser')\n            ->values([\n                'oxid' => ':id',\n                'OXUSERNAME' => ':username',\n                'OXRIGHTS' => ':userRights',\n                'OXCOUNTRYID' => ':countryId',\n            ])\n            ->setParameters([\n                'id' => $testUserId,\n                'username' => \"test_user3@test.com\",\n                'userRights' => \"malladmin\",\n                'countryId' => \"a7c40f631fc920687.20179984\",\n            ]);\n\n        $queryBuilder->executeStatement();\n\n        $queryBuilder\n            ->insert('oxnewssubscribed')\n            ->values([\n                'oxid' => ':id',\n                'OXUSERID' => ':userId',\n                'OXSAL' => ':sal',\n                'OXFNAME' => ':fistname',\n                'OXLNAME' => ':lastname',\n                'OXEMAIL' => ':email',\n                'OXDBOPTIN' => ':otpInState',\n                'OXSHOPID' => ':shopId',\n            ])\n            ->setParameters([\n                'id' => $shopAdapter->generateUniqueId(),\n                'userId' => $testUserId,\n                'sal' => \"MR\",\n                'fistname' => \"christine&#039;s welt&quot;\",\n                'lastname' => \"Doe\",\n                'email' => \"test_user3@test.com\",\n                'otpInState' => \"1\",\n                'shopId' => \"99\"\n            ]);\n\n        $queryBuilder->executeStatement();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Newsletter/DataMapper/NewsletterRecipientsDataMapperTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Newsletter\\DataMapper;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\DataMapper\\NewsletterRecipientsDataMapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\DataObject\\NewsletterRecipient;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class NewsletterRecipientsDataMapperTest extends TestCase\n{\n    public function testMapRecipientListDataToArray(): void\n    {\n        $recipient = new NewsletterRecipient();\n        $recipient\n            ->setEmail('someMail')\n            ->setFistName('Soca')\n            ->setLastName('Warrior')\n            ->setSalutation('no')\n            ->setCountry('Trinidad und Tobago')\n            ->setOtpInState('1')\n            ->setUserGroups('someString');\n\n        $mappedArray = [\n            [\n                NewsletterRecipientsDataMapper::SALUTATION,\n                NewsletterRecipientsDataMapper::FIRST_NAME,\n                NewsletterRecipientsDataMapper::LAST_NAME,\n                NewsletterRecipientsDataMapper::EMAIL,\n                NewsletterRecipientsDataMapper::OPT_IN_STATE,\n                NewsletterRecipientsDataMapper::COUNTRY,\n                NewsletterRecipientsDataMapper::ASSIGNED_USER_GROUPS,\n            ],\n            [\n                \"no\",\n                \"Soca\",\n                \"Warrior\",\n                \"someMail\",\n                \"subscribed\",\n                \"Trinidad und Tobago\",\n                \"someString\"\n            ]\n        ];\n\n        $this->assertEquals(\n            $mappedArray,\n            (new NewsletterRecipientsDataMapper())->mapRecipientListDataToArray([$recipient])\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Product/Media/Dao/ProductMediaDaoTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Product\\Media\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Dao\\MediaDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaType;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Dao\\ProductMediaDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRole;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRoleSet;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaSorting;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\ConnectionFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\DatabaseTrait;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ProductMediaDaoTest extends TestCase\n{\n    use DatabaseTrait;\n    use ContainerTrait;\n\n    private ProductMediaDaoInterface $productMediaDao;\n    private readonly ID $productId;\n    private readonly Media $media1;\n    private readonly Media $media2;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        $connection = $this\n            ->get(ConnectionFactoryInterface::class)\n            ->create();\n        $this->beginTransaction($connection);\n\n        $this->productMediaDao = $this->get(ProductMediaDaoInterface::class);\n        $this->createTestProduct();\n        $this->media1 = new Media(\n            Id::generate(),\n            new MediaPath('media/pmdt_default.jpg'),\n            new MediaType('image/jpeg')\n        );\n        $this->media2 = new Media(\n            Id::generate(),\n            new MediaPath('media/pmdt_default2.jpg'),\n            new MediaType('image/png')\n        );\n        $this\n            ->get(MediaDaoInterface::class)\n            ->add($this->media1);\n        $this\n            ->get(MediaDaoInterface::class)\n            ->add($this->media2);\n    }\n\n    public function tearDown(): void\n    {\n        $connection = $this\n            ->get(ConnectionFactoryInterface::class)\n            ->create();\n        $this->rollBackTransaction($connection);\n        parent::tearDown();\n    }\n\n    public function testAddAndGet(): void\n    {\n        $productMedia = new ProductMedia(\n            id: Id::generate(),\n            productId: $this->productId,\n            media: $this->media1,\n            roleSet: new ProductMediaRoleSet(\n                ProductMediaRole::from(ProductMediaRole::THUMBNAIL),\n                ProductMediaRole::from(ProductMediaRole::ICON),\n            ),\n        );\n        $productMedia->setPosition(123);\n\n        $this->productMediaDao->add($productMedia);\n\n        $fetched = $this->productMediaDao->get($productMedia->getId());\n        $this->assertEquals(\n            $productMedia->getId(),\n            $fetched->getId()\n        );\n        $this->assertEquals(\n            $productMedia->getProductId(),\n            $fetched->getProductId()\n        );\n        $this->assertEquals(\n            $productMedia->getMedia(),\n            $fetched->getMedia()\n        );\n        $this->assertEquals(\n            $productMedia->getPosition(),\n            $fetched->getPosition()\n        );\n        $this->assertEquals(\n            $productMedia->isActive(),\n            $fetched->isActive()\n        );\n        $this->assertTrue($fetched->getRoleSet()->has(ProductMediaRole::from(ProductMediaRole::THUMBNAIL)));\n        $this->assertTrue($fetched->getRoleSet()->has(ProductMediaRole::from(ProductMediaRole::ICON)));\n    }\n\n    public function testGetWithNonExistentId(): void\n    {\n        $this->expectException(EntryDoesNotExistDaoException::class);\n\n        $this->productMediaDao->get(Id::generate());\n    }\n\n    public function testUpdate(): void\n    {\n        $productMedia = new ProductMedia(\n            id: Id::generate(),\n            productId: $this->productId,\n            media: $this->media1,\n            roleSet: new ProductMediaRoleSet(ProductMediaRole::from(ProductMediaRole::DETAIL)),\n        );\n        $productMedia->setPosition(123);\n        $this->productMediaDao->add($productMedia);\n\n        $productMedia->getRoleSet()->addRole(ProductMediaRole::from(ProductMediaRole::ICON));\n        $productMedia->getRoleSet()->addRole(ProductMediaRole::from(ProductMediaRole::THUMBNAIL));\n        $productMedia->getRoleSet()->removeRole(ProductMediaRole::from(ProductMediaRole::DETAIL));\n        $productMedia->deactivate();\n        $this->productMediaDao->update($productMedia);\n\n        $fetched = $this->productMediaDao->get($productMedia->getId());\n        $this->assertTrue($fetched->getRoleSet()->has(ProductMediaRole::from(ProductMediaRole::ICON)));\n        $this->assertTrue($fetched->getRoleSet()->has(ProductMediaRole::from(ProductMediaRole::THUMBNAIL)));\n        $this->assertFalse($fetched->getRoleSet()->has(ProductMediaRole::from(ProductMediaRole::DETAIL)));\n        $this->assertFalse($fetched->isActive());\n    }\n\n    public function testUpdateWithNonExistent(): void\n    {\n        $productMedia = new ProductMedia(\n            id: Id::generate(),\n            productId: $this->productId,\n            media: $this->media1,\n            roleSet: new ProductMediaRoleSet(ProductMediaRole::from(ProductMediaRole::DETAIL)),\n        );\n        $productMedia->setPosition(123);\n\n        $this->expectException(EntryDoesNotExistDaoException::class);\n\n        $this->productMediaDao->update($productMedia);\n    }\n\n    public function testDelete(): void\n    {\n        $productMedia = new ProductMedia(\n            id: Id::generate(),\n            productId: $this->productId,\n            media: $this->media1,\n            roleSet: new ProductMediaRoleSet(ProductMediaRole::from(ProductMediaRole::DETAIL)),\n        );\n        $productMedia->setPosition(123);\n        $productMedia->deactivate();\n        $this->productMediaDao->add($productMedia);\n\n        $this->productMediaDao->delete($productMedia->getId());\n\n        $this->expectException(EntryDoesNotExistDaoException::class);\n\n        $this->productMediaDao->get($productMedia->getId());\n    }\n\n    public function testGetAllProductMediaWillReturnMultipleRecords(): void\n    {\n        $productMedia1 = new ProductMedia(\n            id: Id::generate(),\n            productId: $this->productId,\n            media: $this->media1,\n            roleSet: new ProductMediaRoleSet(\n                ProductMediaRole::from(ProductMediaRole::ICON),\n                ProductMediaRole::from(ProductMediaRole::THUMBNAIL),\n            ),\n        );\n        $productMedia2 = new ProductMedia(\n            id: Id::generate(),\n            productId: $this->productId,\n            media: $this->media2,\n            roleSet: new ProductMediaRoleSet(\n                ProductMediaRole::from(ProductMediaRole::DETAIL),\n                ProductMediaRole::from('whatever')\n            ),\n        );\n        $productMedia2->deactivate();\n        $this->productMediaDao->add($productMedia1);\n        $this->productMediaDao->add($productMedia2);\n\n        $fetchedList = $this->productMediaDao->getAll(productId: $this->productId);\n\n        $this->assertCount(\n            2,\n            $fetchedList\n        );\n        $this->assertEquals(\n            $productMedia1->getId(),\n            $fetchedList\n                ->get(0)\n                ->getId()\n        );\n        $this->assertEquals(\n            $this->media1->getId(),\n            $fetchedList\n                ->get(0)\n                ->getMedia()\n                ->getId()\n        );\n        $this->assertTrue(\n            $fetchedList\n                ->get(0)\n                ->isActive()\n        );\n        $this->assertTrue(\n            $fetchedList\n                ->get(0)\n                ->getRoleSet()\n                ->has(ProductMediaRole::from(ProductMediaRole::ICON))\n        );\n        $this->assertTrue(\n            $fetchedList\n                ->get(0)\n                ->getRoleSet()\n                ->has(ProductMediaRole::from(ProductMediaRole::THUMBNAIL))\n        );\n        $this->assertEquals(\n            $productMedia2->getProductId(),\n            $fetchedList\n                ->get(0)\n                ->getProductId()\n        );\n        $this->assertEquals(\n            $productMedia2->getId(),\n            $fetchedList\n                ->get(1)\n                ->getId()\n        );\n        $this->assertFalse(\n            $fetchedList\n                ->get(1)\n                ->isActive()\n        );\n        $this->assertTrue($fetchedList->get(1)->getRoleSet()->has(ProductMediaRole::from(ProductMediaRole::DETAIL)));\n        $this->assertTrue(\n            $fetchedList\n                ->get(1)\n                ->getRoleSet()\n                ->has(ProductMediaRole::from('whatever'))\n        );\n    }\n\n    public function testAddWillSetNextPositionsAutomatically(): void\n    {\n        $productMedia1 = new ProductMedia(\n            id: Id::generate(),\n            productId: $this->productId,\n            media: $this->media1,\n            roleSet: new ProductMediaRoleSet(ProductMediaRole::from(ProductMediaRole::DETAIL)),\n        );\n        $productMedia1->setPosition(123);\n        $productMedia2 = new ProductMedia(\n            id: Id::generate(),\n            productId: $this->productId,\n            media: $this->media1,\n            roleSet: new ProductMediaRoleSet(ProductMediaRole::from(ProductMediaRole::DETAIL)),\n        );\n        $productMedia3 = new ProductMedia(\n            id: Id::generate(),\n            productId: $this->productId,\n            media: $this->media1,\n            roleSet: new ProductMediaRoleSet(ProductMediaRole::from(ProductMediaRole::DETAIL)),\n        );\n        $this->productMediaDao->add($productMedia1);\n        $this->productMediaDao->add($productMedia2);\n        $this->productMediaDao->add($productMedia3);\n\n        $list = $this->productMediaDao->getAll(productId: $this->productId);\n\n        $this->assertEquals(\n            123,\n            $list\n                ->get(0)\n                ->getPosition()\n        );\n        $this->assertEquals(\n            124,\n            $list\n                ->get(1)\n                ->getPosition()\n        );\n        $this->assertEquals(\n            125,\n            $list\n                ->get(2)\n                ->getPosition()\n        );\n    }\n\n    public function testSortWillResetAndUpdatePositions(): void\n    {\n        $id1 = Id::generate();\n        $id2 = Id::generate();\n        $id3 = Id::generate();\n\n        $productMedia1 = new ProductMedia(\n            id: $id1,\n            productId: $this->productId,\n            media: $this->media1,\n            roleSet: new ProductMediaRoleSet(ProductMediaRole::from(ProductMediaRole::DETAIL)),\n        );\n        $productMedia1->setPosition(123);\n        $productMedia2 = new ProductMedia(\n            id: $id2,\n            productId: $this->productId,\n            media: $this->media1,\n            roleSet: new ProductMediaRoleSet(ProductMediaRole::from(ProductMediaRole::DETAIL)),\n        );\n        $productMedia2->setPosition(456);\n        $productMedia3 = new ProductMedia(\n            id: $id3,\n            productId: $this->productId,\n            media: $this->media1,\n            roleSet: new ProductMediaRoleSet(ProductMediaRole::from(ProductMediaRole::DETAIL)),\n        );\n        $productMedia3->setPosition(789);\n        $this->productMediaDao->add($productMedia1);\n        $this->productMediaDao->add($productMedia2);\n        $this->productMediaDao->add($productMedia3);\n\n        $this->productMediaDao->sort(\n            new ProductMediaSorting(\n                array_map(\n                    'strval',\n                    [\n                        $id2,\n                        $id1,\n                        $id3\n                    ]\n                )\n            )\n        );\n\n        $list = $this->productMediaDao->getAll(productId: $this->productId);\n\n        $this->assertEquals(\n            $id2,\n            $list\n                ->get(0)\n                ->getId()\n        );\n        $this->assertEquals(\n            $id1,\n            $list\n                ->get(1)\n                ->getId()\n        );\n        $this->assertEquals(\n            $id3,\n            $list\n                ->get(2)\n                ->getId()\n        );\n        $this->assertEquals(\n            0,\n            $list\n                ->get(0)\n                ->getPosition()\n        );\n        $this->assertEquals(\n            1,\n            $list\n                ->get(1)\n                ->getPosition()\n        );\n        $this->assertEquals(\n            2,\n            $list\n                ->get(2)\n                ->getPosition()\n        );\n    }\n\n    public function testGetAllWithEmptyCollection(): void\n    {\n        $listAll = $this->productMediaDao->getAll(Id::generate());\n\n        $this->assertTrue($listAll->isEmpty());\n    }\n\n    private function createTestProduct(): void\n    {\n        $this->productId = Id::generate();\n        $this\n            ->get(QueryBuilderFactoryInterface::class)\n            ->create()\n            ->insert('oxarticles')\n            ->values([\n                'OXID' => ':id',\n                'OXTITLE' => ':title',\n                'OXACTIVE' => ':active',\n            ])\n            ->setParameters([\n                'id' => (string)$this->productId,\n                'title' => 'Test Product ',\n                'active' => 1,\n            ])\n            ->executeQuery();\n    }\n\n    public function testGetAllActive(): void\n    {\n        $productMedia1 = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n        $productMedia1->setPosition(1);\n        $this->productMediaDao->add($productMedia1);\n\n        $productMedia2 = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n        $productMedia2->setPosition(2);\n        $productMedia2->deactivate();\n        $this->productMediaDao->add($productMedia2);\n\n        $activeList = $this->productMediaDao->getAllActive($this->productId);\n\n        $this->assertCount(1, $activeList);\n        $this->assertTrue($activeList->get(0)->isActive());\n    }\n\n    public function testGetAllActiveWithEmptyResult(): void\n    {\n        $productMedia = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n        $productMedia->setPosition(1);\n        $productMedia->deactivate();\n        $this->productMediaDao->add($productMedia);\n\n        $activeList = $this->productMediaDao->getAllActive($this->productId);\n\n        $this->assertTrue($activeList->isEmpty());\n    }\n\n    public function testGetAllByRoleReturnsMediaSortedByPosition(): void\n    {\n        $productMedia1 = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n        $productMedia1->setPosition(2);\n        $this->productMediaDao->add($productMedia1);\n\n        $productMedia2 = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n        $productMedia2->setPosition(1);\n        $productMedia2->deactivate();\n        $this->productMediaDao->add($productMedia2);\n\n        $otherRoleMedia = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::THUMBNAIL)\n        );\n        $otherRoleMedia->setPosition(3);\n        $this->productMediaDao->add($otherRoleMedia);\n\n        $result = $this->productMediaDao->getAllByRole(\n            $this->productId,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n\n        $this->assertCount(2, $result);\n        $this->assertEquals($productMedia2->getId(), $result->get(0)->getId());\n        $this->assertEquals($productMedia1->getId(), $result->get(1)->getId());\n        $this->assertFalse($result->get(0)->isActive());\n        $this->assertTrue($result->get(1)->isActive());\n    }\n\n    public function testGetAllByRoleReturnsEmptyWhenNoMediaWithRole(): void\n    {\n        $productMedia = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n        $productMedia->setPosition(1);\n        $this->productMediaDao->add($productMedia);\n\n        $result = $this->productMediaDao->getAllByRole(\n            $this->productId,\n            ProductMediaRole::from(ProductMediaRole::THUMBNAIL)\n        );\n\n        $this->assertTrue($result->isEmpty());\n    }\n\n    public function testGetByRoleReturnsFirstByPosition(): void\n    {\n        $productMedia1 = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::THUMBNAIL)\n        );\n        $productMedia1->setPosition(2);\n        $this->productMediaDao->add($productMedia1);\n\n        $productMediaId2 = Id::generate();\n        $roleSet = new ProductMediaRoleSet(ProductMediaRole::from(ProductMediaRole::THUMBNAIL));\n        $productMedia2 = new ProductMedia(\n            id: $productMediaId2,\n            productId: $this->productId,\n            media: $this->media1,\n            roleSet: $roleSet\n        );\n        $productMedia2->setPosition(1);\n        $this->productMediaDao->add($productMedia2);\n\n        $result = $this->productMediaDao->getByRole(\n            $this->productId,\n            ProductMediaRole::from(ProductMediaRole::THUMBNAIL)\n        );\n\n        $this->assertEquals($productMediaId2, $result->getId());\n    }\n\n    public function testGetByRoleReturnsInactiveMedia(): void\n    {\n        $productMedia = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::THUMBNAIL)\n        );\n        $productMedia->setPosition(1);\n        $productMedia->deactivate();\n        $this->productMediaDao->add($productMedia);\n\n        $result = $this->productMediaDao->getByRole(\n            $this->productId,\n            ProductMediaRole::from(ProductMediaRole::THUMBNAIL)\n        );\n\n        $this->assertNotNull($result);\n        $this->assertEquals($productMedia->getId(), $result->getId());\n        $this->assertFalse($result->isActive());\n    }\n\n    public function testGetByRoleReturnsNullWhenNotFound(): void\n    {\n        $productMedia = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n        $productMedia->setPosition(1);\n        $this->productMediaDao->add($productMedia);\n\n        $result = $this->productMediaDao->getByRole(\n            $this->productId,\n            ProductMediaRole::from(ProductMediaRole::THUMBNAIL)\n        );\n\n        $this->assertNull($result);\n    }\n\n    public function testGetActiveByRole(): void\n    {\n        $productMedia = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::THUMBNAIL)\n        );\n        $productMedia->setPosition(1);\n        $this->productMediaDao->add($productMedia);\n\n        $fetched = $this->productMediaDao->getActiveByRole(\n            $this->productId,\n            ProductMediaRole::from(ProductMediaRole::THUMBNAIL)\n        );\n\n        $this->assertEquals($productMedia->getId(), $fetched->getId());\n    }\n\n    public function testGetActiveByRoleReturnsNullWhenNotFound(): void\n    {\n        $productMedia = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n        $productMedia->setPosition(1);\n        $this->productMediaDao->add($productMedia);\n\n        $result = $this->productMediaDao->getActiveByRole(\n            $this->productId,\n            ProductMediaRole::from(ProductMediaRole::THUMBNAIL)\n        );\n\n        $this->assertNull($result);\n    }\n\n    public function testGetActiveByRoleIgnoresInactive(): void\n    {\n        $productMedia = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::THUMBNAIL)\n        );\n        $productMedia->setPosition(1);\n        $productMedia->deactivate();\n        $this->productMediaDao->add($productMedia);\n\n        $result = $this->productMediaDao->getActiveByRole(\n            $this->productId,\n            ProductMediaRole::from(ProductMediaRole::THUMBNAIL)\n        );\n\n        $this->assertNull($result);\n    }\n\n    public function testGetActiveByRoleReturnsFirstByPosition(): void\n    {\n        $productMedia1 = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::THUMBNAIL)\n        );\n        $productMedia1->setPosition(2);\n        $this->productMediaDao->add($productMedia1);\n\n        $productMediaId2 = Id::generate();\n        $roleSet = new ProductMediaRoleSet(ProductMediaRole::from(ProductMediaRole::THUMBNAIL));\n        $productMedia2 = new ProductMedia(\n            id: $productMediaId2,\n            productId: $this->productId,\n            media: $this->media1,\n            roleSet: $roleSet\n        );\n        $productMedia2->setPosition(1);\n        $this->productMediaDao->add($productMedia2);\n\n        $result = $this->productMediaDao->getActiveByRole(\n            $this->productId,\n            ProductMediaRole::from(ProductMediaRole::THUMBNAIL)\n        );\n\n        $this->assertEquals($productMediaId2, $result->getId());\n    }\n\n    public function testGetAllActiveByRoleReturnsActiveMediaSortedByPosition(): void\n    {\n        $productMedia1 = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n        $productMedia1->setPosition(2);\n        $this->productMediaDao->add($productMedia1);\n\n        $productMedia2 = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n        $productMedia2->setPosition(1);\n        $this->productMediaDao->add($productMedia2);\n\n        $inactiveProductMedia = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n        $inactiveProductMedia->setPosition(3);\n        $inactiveProductMedia->deactivate();\n        $this->productMediaDao->add($inactiveProductMedia);\n\n        $result = $this->productMediaDao->getAllActiveByRole(\n            $this->productId,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n\n        $this->assertCount(2, $result);\n        $this->assertEquals($productMedia2->getId(), $result->get(0)->getId());\n        $this->assertEquals($productMedia1->getId(), $result->get(1)->getId());\n        $this->assertTrue($result->get(0)->isActive());\n        $this->assertTrue($result->get(1)->isActive());\n    }\n\n    public function testGetFirstActive(): void\n    {\n        $productMedia = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n        $productMedia->setPosition(1);\n        $this->productMediaDao->add($productMedia);\n\n        $fetched = $this->productMediaDao->getFirstActive($this->productId);\n\n        $this->assertEquals($productMedia->getId(), $fetched->getId());\n    }\n\n    public function testGetFirstActiveReturnsNullWhenNoActiveMedia(): void\n    {\n        $productMedia = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n        $productMedia->setPosition(1);\n        $productMedia->deactivate();\n        $this->productMediaDao->add($productMedia);\n\n        $result = $this->productMediaDao->getFirstActive($this->productId);\n\n        $this->assertNull($result);\n    }\n\n    public function testGetFirstActiveReturnsNullForNonExistentProduct(): void\n    {\n        $result = $this->productMediaDao->getFirstActive(Id::generate());\n\n        $this->assertNull($result);\n    }\n\n    public function testGetActiveByPosition(): void\n    {\n        $productMedia = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n        $productMedia->setPosition(5);\n        $this->productMediaDao->add($productMedia);\n\n        $fetched = $this->productMediaDao->getActiveByPosition($this->productId, 5);\n\n        $this->assertEquals($productMedia->getId(), $fetched->getId());\n    }\n\n    public function testGetActiveByPositionReturnsNullWhenNotFound(): void\n    {\n        $productMedia = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n        $productMedia->setPosition(1);\n        $this->productMediaDao->add($productMedia);\n\n        $result = $this->productMediaDao->getActiveByPosition($this->productId, 99);\n\n        $this->assertNull($result);\n    }\n\n    public function testGetActiveByPositionReturnsCorrectMediaForMultipleProducts(): void\n    {\n        // Create another product\n        $anotherProductId = Id::generate();\n        $this->get(QueryBuilderFactoryInterface::class)\n            ->create()\n            ->insert('oxarticles')\n            ->values([\n                'OXID' => ':id',\n                'OXTITLE' => ':title',\n                'OXACTIVE' => ':active',\n            ])\n            ->setParameters([\n                'id' => (string)$anotherProductId,\n                'title' => 'Another Product',\n                'active' => 1,\n            ])\n            ->executeQuery();\n\n        $media1 = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n        $media1->setPosition(1);\n        $this->productMediaDao->add($media1);\n\n        $media2 = $this->createProductMedia(\n            $anotherProductId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n        $media2->setPosition(1);\n        $this->productMediaDao->add($media2);\n\n        $result1 = $this->productMediaDao->getActiveByPosition($this->productId, 1);\n        $result2 = $this->productMediaDao->getActiveByPosition($anotherProductId, 1);\n\n        $this->assertEquals($media1->getId(), $result1->getId());\n        $this->assertEquals($media2->getId(), $result2->getId());\n    }\n\n    public function testGetActiveByPositionReturnsNullForNonExistentProduct(): void\n    {\n        $result = $this->productMediaDao->getActiveByPosition(Id::generate(), 1);\n\n        $this->assertNull($result);\n    }\n\n    public function testGetActiveByPositionSkipsInactive(): void\n    {\n        $productMedia = $this->createProductMedia(\n            $this->productId,\n            $this->media1,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n        $productMedia->setPosition(3);\n        $productMedia->deactivate();\n        $this->productMediaDao->add($productMedia);\n\n        $result = $this->productMediaDao->getActiveByPosition($this->productId, 3);\n        $this->assertNull($result);\n    }\n\n    private function createProductMedia(Id $productId, Media $media, ProductMediaRole $role): ProductMedia\n    {\n        $roleSet = new ProductMediaRoleSet($role);\n        return new ProductMedia(\n            id: Id::generate(),\n            productId: $productId,\n            media: $media,\n            roleSet: $roleSet\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Product/Media/Factory/ProductMediaFactoryTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Product\\Media\\Factory;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaType;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Dao\\ProductMediaDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRole;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRoleSet;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Factory\\ProductMediaFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ProductMediaFactoryTest extends IntegrationTestCase\n{\n    public function testCreate(): void\n    {\n        $productId = Id::generate();\n        $mediaPath = new MediaPath('aaa/bbb/ccc/test.jpg');\n        $mediaType = new MediaType('image/jpeg');\n\n        $productMedia = new ProductMedia(\n            Id::generate(),\n            $productId,\n            new Media(\n                Id::generate(),\n                $mediaPath,\n                $mediaType\n            ),\n            new ProductMediaRoleSet(ProductMediaRole::from(ProductMediaRole::DETAIL)),\n        );\n        $this\n            ->get(ProductMediaDaoInterface::class)\n            ->add($productMedia);\n\n        $productMedia = $this\n            ->get(ProductMediaFactoryInterface::class)\n            ->create(\n                $productId,\n                $mediaPath,\n                $mediaType\n            );\n\n        $this->assertEquals(\n            $productId,\n            $productMedia->getProductId()\n        );\n        $this->assertEquals(\n            $mediaPath,\n            $productMedia\n                ->getMedia()\n                ->getMediaPath()\n        );\n        $this->assertEquals(\n            $mediaType,\n            $productMedia\n                ->getMedia()\n                ->getMediaType()\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Product/Media/Service/ProductMediaResolverTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Product\\Media\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaPathResolverInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class ProductMediaResolverTest extends TestCase\n{\n    use ContainerTrait;\n\n    public function testResolveBuildsExpectedPath(): void\n    {\n        $mediaFile = 'media.jpg';\n\n        $path = (string) $this->get(ProductMediaPathResolverInterface::class)->resolve('product123', $mediaFile);\n\n        $this->assertStringStartsWith(\n            Path::join('out', 'pictures', 'media', 'products', 'product123'),\n            $path\n        );\n        $this->assertStringEndsWith($mediaFile, $path);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Product/Media/Service/ProductMediaServiceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Product\\Media\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Dao\\MediaDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaType;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRole;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRoleSet;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\ConnectionFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\DatabaseTrait;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ProductMediaServiceTest extends TestCase\n{\n    use ContainerTrait;\n    use DatabaseTrait;\n\n    private ProductMedia $productMedia;\n    private readonly ProductMediaServiceInterface $service;\n    private readonly MediaDaoInterface $mediaDao;\n    private readonly Id $productId;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        $this->beginTransaction(\n            $this\n                ->get(ConnectionFactoryInterface::class)\n                ->create()\n        );\n        $this->service = $this->get(ProductMediaServiceInterface::class);\n        $this->mediaDao = $this->get(MediaDaoInterface::class);\n        $this->createTestProductMedia();\n    }\n\n    public function tearDown(): void\n    {\n        $this->rollBackTransaction(\n            $this\n                ->get(ConnectionFactoryInterface::class)\n                ->create()\n        );\n        parent::tearDown();\n    }\n\n    public function testGet(): void\n    {\n        $fetched = $this->service->get($this->productMedia->getId());\n\n        $this->assertEquals(\n            $this->productMedia->getId(),\n            $fetched->getId()\n        );\n        $this->assertEquals(\n            $this->productMedia\n                ->getMedia()\n                ->getId(),\n            $fetched\n                ->getMedia()\n                ->getId()\n        );\n    }\n\n    public function testRemove(): void\n    {\n        $this->service->remove($this->productMedia->getId());\n\n        $this->expectException(EntryDoesNotExistDaoException::class);\n\n        $this->service->get($this->productMedia->getId());\n    }\n\n    public function testRemoveWillNotDeleteMediaRecord(): void\n    {\n        $this->service->remove($this->productMedia->getId());\n\n        $this->mediaDao->get(\n            $this->productMedia\n                ->getMedia()\n                ->getId()\n        );\n\n        $this->expectNotToPerformAssertions();\n    }\n\n    public function testActivate(): void\n    {\n        $this->service->deactivate($this->productMedia);\n\n        $this->service->activate($this->productMedia);\n\n        $fetched = $this->service->get($this->productMedia->getId());\n\n        $this->assertTrue($fetched->isActive());\n    }\n\n    public function testDeactivate(): void\n    {\n        $this->service->deactivate($this->productMedia);\n\n        $fetched = $this->service->get($this->productMedia->getId());\n\n        $this->assertFalse($fetched->isActive());\n    }\n\n    public function testSorting(): void\n    {\n        $this->createTestProductMedia();\n        $media1 = $this->productMedia;\n        $this->createTestProductMedia();\n        $media2 = $this->productMedia;\n        $this->createTestProductMedia();\n        $media3 = $this->productMedia;\n\n        $this->service->sort([\n            (string)$media2->getId(),\n            (string)$media1->getId(),\n            (string)$media3->getId(),\n        ]);\n\n        $this->assertEquals(\n            1,\n            $this->service\n                ->get($media1->getId())\n                ->getPosition()\n        );\n        $this->assertEquals(\n            0,\n            $this->service\n                ->get($media2->getId())\n                ->getPosition()\n        );\n        $this->assertEquals(\n            2,\n            $this->service\n                ->get($media3->getId())\n                ->getPosition()\n        );\n    }\n\n    public function testSortWithMaliciousValues(): void\n    {\n        $this->createTestProductMedia();\n        $media1 = $this->productMedia;\n        $this->createTestProductMedia();\n        $media2 = $this->productMedia;\n        $this->createTestProductMedia();\n        $media3 = $this->productMedia;\n\n        $this->service->sort([\n            $media2->getId() . \"' OR '1'='1\",\n            $media1->getId() . \"') OR SLEEP(5) --\",\n            $media3->getId() . \"'; DROP TABLE oxproduct_media; --\",\n            \"' OR SLEEP(5) OR '\",\n            \"' OR 1=1 --\",\n            \"'; DELETE FROM oxproduct_media WHERE '1'='1\",\n        ]);\n\n        $this->assertNotNull($this->service->get($media1->getId()));\n    }\n\n    private function createTestProductMedia(): void\n    {\n        $media = new Media(\n            Id::fromString(\n                md5(\n                    uniqid(\n                        'media_',\n                        true\n                    )\n                )\n            ),\n            new MediaPath(\n                uniqid(\n                    'img-',\n                    true\n                ) . '.jpg'\n            ),\n            new MediaType('image/jpeg')\n        );\n        $this->productMedia = new ProductMedia(\n            Id::fromString(\n                md5(\n                    uniqid(\n                        'product_media_',\n                        true\n                    )\n                )\n            ),\n            $this->getSameProductIdForAllMedia(),\n            $media,\n            new ProductMediaRoleSet(ProductMediaRole::from(ProductMediaRole::DETAIL)),\n        );\n        $this->service->add($this->productMedia);\n    }\n\n    private function getSameProductIdForAllMedia(): Id\n    {\n        if (!isset($this->productId)) {\n            $this->productId = Id::fromString(\n                md5(\n                    uniqid(\n                        'product_',\n                        true\n                    )\n                )\n            );\n        }\n\n        return $this->productId;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Product/Media/Service/ProductMediaUploadProcessorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Product\\Media\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\MediaUploaderInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\FileExtensionMismatchException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\FileSizeTooLargeException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\FileSizeTooSmallException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\MimeBaseTypeMismatchException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\MimeGuessMismatchException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\UploadInvalidException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaUploadProcessorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Symfony\\Component\\Filesystem\\Path;\nuse Symfony\\Component\\HttpFoundation\\File\\UploadedFile;\n\nuse const UPLOAD_ERR_NO_FILE;\n\nfinal class ProductMediaUploadProcessorTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private const VALID_IMAGE = 'valid_image.jpg';\n    private const INVALID_IMAGE = 'invalid_image.jpg';\n    private const VALID_IMAGE_WRONG_EXTENSION = 'valid_image.png';\n\n    private readonly Id $productId;\n    private readonly string $destinationPath;\n    private readonly ProductMediaUploadProcessorInterface $productMediaUploadProcessor;\n    private MediaUploaderInterface $mediaUploader;\n\n    public function testUploadWithValidImage(): void\n    {\n        $this->allowSmallFilesUploadInConfiguration();\n        $this->replaceMediaUploaderServiceInstanceWithMock();\n        $fixture = Path::join(\n            __DIR__,\n            'Fixtures',\n            self::VALID_IMAGE\n        );\n        $uploadedFile = new UploadedFile(\n            $fixture,\n            self::VALID_IMAGE,\n            'image/jpeg',\n            null,\n            true\n        );\n        $this->mediaUploader\n            ->expects($this->once())\n            ->method('uploadTo')\n            ->with($uploadedFile, $this->isInstanceOf(MediaPath::class))\n            ->willReturn(new MediaPath(\n                Path::join('out/pictures/media/products/placeholder', $uploadedFile->getClientOriginalName())\n            ));\n\n        $productId = Id::generate();\n        $result = $this->get(ProductMediaUploadProcessorInterface::class)\n            ->process(\n                $productId,\n                $uploadedFile\n            );\n\n        $this->assertEquals((string) $productId, (string) $result->getProductId());\n        $this->assertStringEndsWith(self::VALID_IMAGE, (string) $result->getMedia()->getMediaPath());\n        $this->assertEquals($uploadedFile->getClientMimeType(), (string) $result->getMedia()->getMediaType());\n    }\n\n    public function testUploadWithFileTooSmall(): void\n    {\n        $this->rewriteProjectConfiguration([\n            'parameters' => [\n                'oxid_esales.product.media.file.min_size_kb' => '1024',\n            ]\n        ]);\n        $this->replaceMediaUploaderServiceInstance();\n        $fixture = Path::join(\n            __DIR__,\n            'Fixtures',\n            self::VALID_IMAGE\n        );\n        $uploadedFile = new UploadedFile(\n            $fixture,\n            self::VALID_IMAGE,\n            'image/jpeg',\n            null,\n            true\n        );\n\n        $this->expectException(FileSizeTooSmallException::class);\n        $this->get(ProductMediaUploadProcessorInterface::class)->process(Id::generate(), $uploadedFile);\n    }\n\n    public function testUploadWithFileTooBig(): void\n    {\n        $this->rewriteProjectConfiguration([\n            'parameters' => [\n                'oxid_esales.product.media.file.min_size_kb' => '0',\n                'oxid_esales.product.media.file.max_size_kb' => '1',\n            ]\n        ]);\n        $this->replaceMediaUploaderServiceInstance();\n        $fixture = Path::join(\n            __DIR__,\n            'Fixtures',\n            self::VALID_IMAGE\n        );\n        $uploadedFile = new UploadedFile(\n            $fixture,\n            self::VALID_IMAGE,\n            'image/jpeg',\n            null,\n            true\n        );\n\n        $this->expectException(FileSizeTooLargeException::class);\n        $this->get(ProductMediaUploadProcessorInterface::class)->process(Id::generate(), $uploadedFile);\n    }\n\n    public function testUploadWithNonImageMimeTypeFile(): void\n    {\n        $this->allowSmallFilesUploadInConfiguration();\n        $this->replaceMediaUploaderServiceInstance();\n        $uploadedFile = new UploadedFile(\n            Path::join(\n                __DIR__,\n                'Fixtures',\n                self::INVALID_IMAGE\n            ),\n            self::INVALID_IMAGE,\n            'image/jpeg',\n            null,\n            true\n        );\n\n        $this->expectException(MimeBaseTypeMismatchException::class);\n        $this->get(ProductMediaUploadProcessorInterface::class)->process(Id::generate(), $uploadedFile);\n    }\n\n    public function testUploadWitMimeTypeSpoofing(): void\n    {\n        $this->allowSmallFilesUploadInConfiguration();\n        $this->replaceMediaUploaderServiceInstance();\n        $uploadedFile = new UploadedFile(\n            Path::join(\n                __DIR__,\n                'Fixtures',\n                self::VALID_IMAGE\n            ),\n            self::VALID_IMAGE,\n            'image/png',\n            null,\n            true\n        );\n\n        $this->expectException(MimeGuessMismatchException::class);\n        $this->get(ProductMediaUploadProcessorInterface::class)->process(Id::generate(), $uploadedFile);\n    }\n\n    public function testUploadWithImageFileHavingInvalidFileExtension(): void\n    {\n        $this->allowSmallFilesUploadInConfiguration();\n        $this->replaceMediaUploaderServiceInstance();\n        $uploadedFile = new UploadedFile(\n            Path::join(\n                __DIR__,\n                'Fixtures',\n                self::VALID_IMAGE_WRONG_EXTENSION\n            ),\n            self::VALID_IMAGE_WRONG_EXTENSION,\n            'image/jpeg',\n            null,\n            true\n        );\n        $this->expectException(FileExtensionMismatchException::class);\n        $this->get(ProductMediaUploadProcessorInterface::class)->process(Id::generate(), $uploadedFile);\n    }\n\n    public function testUploadWithFileHavingAnErrorSetDuringUploading(): void\n    {\n        $this->allowSmallFilesUploadInConfiguration();\n        $this->replaceMediaUploaderServiceInstance();\n        $uploadedFile = new UploadedFile(\n            Path::join(\n                __DIR__,\n                'Fixtures',\n                self::VALID_IMAGE\n            ),\n            self::VALID_IMAGE,\n            'image/jpeg',\n            UPLOAD_ERR_NO_FILE,\n            true\n        );\n        $this->expectException(UploadInvalidException::class);\n        $this->get(ProductMediaUploadProcessorInterface::class)->process(Id::generate(), $uploadedFile);\n    }\n\n    private function replaceMediaUploaderServiceInstance(): void\n    {\n        $this->createContainer();\n        $this->mediaUploader = $this->createStub(MediaUploaderInterface::class);\n        $this->container->set(\n            'oxid_esales.product.media.media_uploader',\n            $this->mediaUploader\n        );\n        $this->compileContainer();\n    }\n\n    private function replaceMediaUploaderServiceInstanceWithMock(): void\n    {\n        $this->createContainer();\n        $this->mediaUploader = $this->createMock(MediaUploaderInterface::class);\n        $this->container->set(\n            'oxid_esales.product.media.media_uploader',\n            $this->mediaUploader\n        );\n        $this->compileContainer();\n    }\n\n    public function allowSmallFilesUploadInConfiguration(): void\n    {\n        $this->rewriteProjectConfiguration([\n            'parameters' => [\n                'oxid_esales.product.media.file.min_size_kb' => '0',\n            ]\n        ]);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Product/Media/Service/ProductMediaViewServiceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Product\\Media\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaType;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRole;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRoleSet;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaViewServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Dao\\ShopConfigurationSettingDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Config\\Dao\\ThemeSettingDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\DataObject\\ShopConfigurationSetting;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\DataObject\\ShopSettingType;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Config\\DataObject\\ThemeSetting;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\ShopAdapterInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class ProductMediaViewServiceTest extends IntegrationTestCase\n{\n    private const CONFIG_KEY_ICON_SIZE = 'sIconsize';\n    private const CONFIG_KEY_THUMBNAIL_SIZE = 'sThumbnailsize';\n    private const CONFIG_KEY_DETAIL_IMAGE_SIZE = 'sDetailImageSize';\n    private const CONFIG_KEY_ZOOM_IMAGE_SIZE = 'sZoomImageSize';\n    private const CONFIG_KEY_DEFAULT_IMAGE_QUALITY = 'sDefaultImageQuality';\n    private const CONFIG_KEY_CONVERT_IMAGES_TO_WEBP = 'blConvertImagesToWebP';\n\n    private Id $productId;\n    private string $baseUrl;\n    private int $shopId;\n    private string $themeId;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $context = $this->get(ContextInterface::class);\n        $this->baseUrl = $context->getShopBaseUrl();\n        $this->shopId = $context->getCurrentShopId();\n        $this->themeId = $this->get(ShopAdapterInterface::class)->getActiveThemeId();\n\n        $this->productId = Id::generate();\n        $this->configureImageSettings();\n        $this->setupTestData();\n    }\n\n    public function testGetIconReturnsMediaViewWithAllUrls(): void\n    {\n        $result = $this->get(ProductMediaViewServiceInterface::class)->getByRole($this->productId, ProductMediaRole::from(ProductMediaRole::ICON));\n\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_DETAIL_IMAGE_SIZE, 'icon.jpg'),\n            $result->getDetailUrl()\n        );\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_ICON_SIZE, 'icon.jpg'),\n            $result->getIconUrl()\n        );\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_ZOOM_IMAGE_SIZE, 'icon.jpg'),\n            $result->getZoomUrl()\n        );\n    }\n\n    public function testGetIconReturnsFallbackWhenMissing(): void\n    {\n        $result = $this->get(ProductMediaViewServiceInterface::class)->getByRole(Id::generate(), ProductMediaRole::from(ProductMediaRole::ICON));\n\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_DETAIL_IMAGE_SIZE, 'nopic.jpg'),\n            $result->getDetailUrl()\n        );\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_ICON_SIZE, 'nopic.jpg'),\n            $result->getIconUrl()\n        );\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_ZOOM_IMAGE_SIZE, 'nopic.jpg'),\n            $result->getZoomUrl()\n        );\n        $this->assertTrue($result->isFallback());\n    }\n\n    public function testGetThumbnailReturnsMediaViewWithAllUrls(): void\n    {\n        $result = $this->get(ProductMediaViewServiceInterface::class)->getByRole($this->productId, ProductMediaRole::from(ProductMediaRole::THUMBNAIL));\n\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_DETAIL_IMAGE_SIZE, 'thumb.jpg'),\n            $result->getDetailUrl()\n        );\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_ICON_SIZE, 'thumb.jpg'),\n            $result->getIconUrl()\n        );\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_ZOOM_IMAGE_SIZE, 'thumb.jpg'),\n            $result->getZoomUrl()\n        );\n    }\n\n    public function testGetMediaReturnsMediaViewWithAllUrls(): void\n    {\n        $result = $this->get(ProductMediaViewServiceInterface::class)->getByPosition($this->productId, 1);\n\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_DETAIL_IMAGE_SIZE, 'detail.jpg'),\n            $result->getDetailUrl()\n        );\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_ICON_SIZE, 'detail.jpg'),\n            $result->getIconUrl()\n        );\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_ZOOM_IMAGE_SIZE, 'detail.jpg'),\n            $result->getZoomUrl()\n        );\n    }\n\n    public function testGetMediaReturnsFallbackForMissingPosition(): void\n    {\n        $result = $this->get(ProductMediaViewServiceInterface::class)->getByPosition($this->productId, 999);\n\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_DETAIL_IMAGE_SIZE, 'nopic.jpg'),\n            $result->getDetailUrl()\n        );\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_ICON_SIZE, 'nopic.jpg'),\n            $result->getIconUrl()\n        );\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_ZOOM_IMAGE_SIZE, 'nopic.jpg'),\n            $result->getZoomUrl()\n        );\n        $this->assertTrue($result->isFallback());\n    }\n\n    public function testUsesConfiguredQuality(): void\n    {\n        $this->setStringConfigValue(self::CONFIG_KEY_DEFAULT_IMAGE_QUALITY, '95');\n\n        $result = $this->get(ProductMediaViewServiceInterface::class)->getByRole($this->productId, ProductMediaRole::from(ProductMediaRole::ICON));\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_DETAIL_IMAGE_SIZE, 'icon.jpg', '95'),\n            $result->getDetailUrl()\n        );\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_ICON_SIZE, 'icon.jpg', '95'),\n            $result->getIconUrl()\n        );\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_ZOOM_IMAGE_SIZE, 'icon.jpg', '95'),\n            $result->getZoomUrl()\n        );\n    }\n\n    public function testIconFallsBackToFirstActiveWhenIconMissing(): void\n    {\n        $otherProductId = Id::generate();\n\n        $media = new Media(\n            Id::generate(),\n            new MediaPath(Path::join('out', 'pictures', 'media', 'detail.jpg')),\n            new MediaType('image/jpeg')\n        );\n\n        $roleSet = new ProductMediaRoleSet(ProductMediaRole::from(ProductMediaRole::DETAIL));\n        $productMedia = new ProductMedia(\n            Id::generate(),\n            $otherProductId,\n            $media,\n            $roleSet\n        );\n        $productMedia->setPosition(1);\n        $this->get(ProductMediaServiceInterface::class)->add($productMedia);\n\n        $result = $this->get(ProductMediaViewServiceInterface::class)->getByRole($otherProductId, ProductMediaRole::from(ProductMediaRole::ICON));\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_DETAIL_IMAGE_SIZE, 'detail.jpg'),\n            $result->getDetailUrl()\n        );\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_ICON_SIZE, 'detail.jpg'),\n            $result->getIconUrl()\n        );\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_ZOOM_IMAGE_SIZE, 'detail.jpg'),\n            $result->getZoomUrl()\n        );\n    }\n\n    public function testFallbackUsesWebPWhenEnabled(): void\n    {\n        $this->setBooleanConfigValue(self::CONFIG_KEY_CONVERT_IMAGES_TO_WEBP, true);\n        $result = $this->get(ProductMediaViewServiceInterface::class)->getByRole(Id::generate(), ProductMediaRole::from(ProductMediaRole::ICON));\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_DETAIL_IMAGE_SIZE, 'nopic.webp'),\n            $result->getDetailUrl()\n        );\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_ICON_SIZE, 'nopic.webp'),\n            $result->getIconUrl()\n        );\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_ZOOM_IMAGE_SIZE, 'nopic.webp'),\n            $result->getZoomUrl()\n        );\n    }\n\n    public function testGetAllActiveDetailReturnsMediaViews(): void\n    {\n        $results = $this->get(ProductMediaViewServiceInterface::class)->getAllByRole(\n            $this->productId,\n            ProductMediaRole::from(ProductMediaRole::DETAIL)\n        );\n\n        $this->assertCount(3, $results);\n        $urls = array_map(fn($result) => $result->getDetailUrl(), $results);\n\n        $this->assertContains(\n            $this->expectedUrlFor(self::CONFIG_KEY_DETAIL_IMAGE_SIZE, 'detail.jpg'),\n            $urls\n        );\n        $this->assertContains(\n            $this->expectedUrlFor(self::CONFIG_KEY_DETAIL_IMAGE_SIZE, 'detail-2.jpg'),\n            $urls\n        );\n        $this->assertContains(\n            $this->expectedUrlFor(self::CONFIG_KEY_DETAIL_IMAGE_SIZE, 'detail-3.jpg'),\n            $urls\n        );\n        $this->assertNotContains(\n            $this->expectedUrlFor(self::CONFIG_KEY_DETAIL_IMAGE_SIZE, 'icon.jpg'),\n            $urls\n        );\n        $this->assertNotContains(\n            $this->expectedUrlFor(self::CONFIG_KEY_DETAIL_IMAGE_SIZE, 'thumb.jpg'),\n            $urls\n        );\n    }\n\n    public function testGetAllActiveDetailReturnsEmptyForNonExistentProduct(): void\n    {\n        $results = $this->get(ProductMediaViewServiceInterface::class)->getAllByRole(Id::generate(), ProductMediaRole::from(ProductMediaRole::DETAIL));\n\n        $this->assertEmpty($results);\n    }\n\n    public function testGetThumbnailUrl(): void\n    {\n        $result = $this->get(ProductMediaViewServiceInterface::class)->getByRole($this->productId, ProductMediaRole::from(ProductMediaRole::ICON));\n\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_THUMBNAIL_SIZE, 'icon.jpg'),\n            $result->getThumbnailUrl()\n        );\n    }\n\n    public function testFallbackHasThumbnailUrl(): void\n    {\n        $result = $this->get(ProductMediaViewServiceInterface::class)->getByRole(Id::generate(), ProductMediaRole::from(ProductMediaRole::ICON));\n\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_THUMBNAIL_SIZE, 'nopic.jpg'),\n            $result->getThumbnailUrl()\n        );\n        $this->assertTrue($result->isFallback());\n    }\n\n    public function testGetMediaWithFallbackBehavior(): void\n    {\n        $otherProductId = Id::generate();\n        $this->addProductMedia(\n            $otherProductId,\n            Path::join('out', 'pictures', 'media', 'fallback.jpg'),\n            1,\n            ProductMediaRole::DETAIL\n        );\n\n        $iconResult = $this->get(ProductMediaViewServiceInterface::class)->getByRole($otherProductId, ProductMediaRole::from(ProductMediaRole::ICON));\n\n        $this->assertEquals(\n            $this->expectedUrlFor(self::CONFIG_KEY_DETAIL_IMAGE_SIZE, 'fallback.jpg'),\n            $iconResult->getDetailUrl()\n        );\n        $this->assertFalse($iconResult->isFallback());\n    }\n\n    private function setupTestData(): void\n    {\n        $this->addProductMedia(\n            $this->productId,\n            Path::join('out', 'pictures', 'media', 'icon.jpg'),\n            0,\n            ProductMediaRole::ICON\n        );\n        $this->addProductMedia(\n            $this->productId,\n            Path::join('out', 'pictures', 'media', 'thumb.jpg'),\n            0,\n            ProductMediaRole::THUMBNAIL\n        );\n        $this->addProductMedia(\n            $this->productId,\n            Path::join('out', 'pictures', 'media', 'detail.jpg'),\n            1,\n            ProductMediaRole::DETAIL\n        );\n        $this->addProductMedia(\n            $this->productId,\n            Path::join('out', 'pictures', 'media', 'detail-2.jpg'),\n            2,\n            ProductMediaRole::DETAIL\n        );\n        $this->addProductMedia(\n            $this->productId,\n            Path::join('out', 'pictures', 'media', 'detail-3.jpg'),\n            3,\n            ProductMediaRole::DETAIL\n        );\n    }\n\n    private function addProductMedia(Id $productId, string $path, int $position, string $role): void\n    {\n        $media = new Media(Id::generate(), new MediaPath($path), new MediaType('image/jpeg'));\n        $roleSet = new ProductMediaRoleSet(ProductMediaRole::from($role));\n        $productMedia = new ProductMedia(Id::generate(), $productId, $media, $roleSet);\n        $productMedia->setPosition($position);\n        $this->get(ProductMediaServiceInterface::class)->add($productMedia);\n    }\n\n    private function configureImageSettings(): void\n    {\n        $this->setThemeStringConfigValue(self::CONFIG_KEY_ICON_SIZE, '87*87');\n        $this->setThemeStringConfigValue(self::CONFIG_KEY_THUMBNAIL_SIZE, '200*200');\n        $this->setThemeStringConfigValue(self::CONFIG_KEY_DETAIL_IMAGE_SIZE, '600*600');\n        $this->setThemeStringConfigValue(self::CONFIG_KEY_ZOOM_IMAGE_SIZE, '1200*1200');\n        $this->setStringConfigValue(self::CONFIG_KEY_DEFAULT_IMAGE_QUALITY, '75');\n        $this->setBooleanConfigValue(self::CONFIG_KEY_CONVERT_IMAGES_TO_WEBP, false);\n    }\n\n    private function expectedUrlFor(string $sizeConfigKey, string $filename, ?string $qualityOverride = null): string\n    {\n        $size = $this->getSizeFromConfig($sizeConfigKey);\n        $quality = $qualityOverride ?? $this->getQualityFromConfig();\n        $sizePath = $size['width'] . '_' . $size['height'] . '_' . $quality;\n        return Path::join(\n            $this->baseUrl,\n            'out',\n            'pictures',\n            'generated',\n            'media',\n            $sizePath,\n            $filename\n        );\n    }\n\n    private function getSizeFromConfig(string $key): array\n    {\n        $value = (string) $this->get(ThemeSettingDaoInterface::class)->get($key, $this->shopId, $this->themeId)->getValue();\n        [$width, $height] = explode('*', $value);\n        return ['width' => (int) $width, 'height' => (int) $height];\n    }\n\n    private function getQualityFromConfig(): string\n    {\n        return (string) $this->get(ShopConfigurationSettingDaoInterface::class)->get(self::CONFIG_KEY_DEFAULT_IMAGE_QUALITY, $this->shopId)->getValue();\n    }\n\n    private function setThemeStringConfigValue(string $name, string $value): void\n    {\n        $setting = new ThemeSetting();\n        $setting->setName($name);\n        $setting->setValue($value);\n        $setting->setType(ShopSettingType::STRING);\n        $setting->setShopId($this->shopId);\n        $setting->setThemeId($this->themeId);\n        $this->get(ThemeSettingDaoInterface::class)->save($setting);\n    }\n\n    private function setStringConfigValue(string $name, string $value): void\n    {\n        $setting = new ShopConfigurationSetting();\n        $setting->setName($name);\n        $setting->setValue($value);\n        $setting->setType(ShopSettingType::STRING);\n        $setting->setShopId($this->shopId);\n        $this->get(ShopConfigurationSettingDaoInterface::class)->save($setting);\n    }\n\n    private function setBooleanConfigValue(string $name, bool $value): void\n    {\n        $setting = new ShopConfigurationSetting();\n        $setting->setName($name);\n        $setting->setValue($value);\n        $setting->setType(ShopSettingType::BOOLEAN);\n        $setting->setShopId($this->shopId);\n        $this->get(ShopConfigurationSettingDaoInterface::class)->save($setting);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Product/Media/Service/ProductVariantMediaServiceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Product\\Media\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaType;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Dao\\ProductMediaDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRole;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRoleSet;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductMediaServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Service\\ProductVariantMediaServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ProductVariantMediaServiceTest extends IntegrationTestCase\n{\n    private Id $parentId;\n    private Id $variantId;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        $this->parentId = Id::generate();\n        $this->variantId = Id::generate();\n    }\n\n    public function testCopyMediaFromParentToVariant(): void\n    {\n        $parentMedia = $this->createProductMedia($this->parentId);\n        $this->get(ProductMediaServiceInterface::class)->add($parentMedia);\n\n        $this->get(ProductVariantMediaServiceInterface::class)->assignFromParentToVariant($this->parentId, $this->variantId);\n\n        $variantMediaCollection = $this->get(ProductMediaDaoInterface::class)->getAll($this->variantId);\n\n        $this->assertCount(1, $variantMediaCollection);\n        $this->assertEquals(\n            $parentMedia->getMedia()->getId(),\n            $variantMediaCollection->first()->getMedia()->getId()\n        );\n    }\n\n    public function testCopyMediaPreservesRoles(): void\n    {\n        $parentMedia = $this->createProductMedia(\n            $this->parentId,\n            ProductMediaRole::from(ProductMediaRole::ICON),\n            ProductMediaRole::from(ProductMediaRole::THUMBNAIL)\n        );\n        $this->get(ProductMediaServiceInterface::class)->add($parentMedia);\n\n        $this->get(ProductVariantMediaServiceInterface::class)->assignFromParentToVariant($this->parentId, $this->variantId);\n\n        $variantMedia = $this->get(ProductMediaDaoInterface::class)->getAll($this->variantId)->first();\n\n        $this->assertTrue($variantMedia->getRoleSet()->has(ProductMediaRole::from(ProductMediaRole::ICON)));\n        $this->assertTrue($variantMedia->getRoleSet()->has(ProductMediaRole::from(ProductMediaRole::THUMBNAIL)));\n    }\n\n    public function testCopyMediaPreservesPosition(): void\n    {\n        $parentMedia = $this->createProductMedia($this->parentId);\n        $parentMedia->setPosition(5);\n        $this->get(ProductMediaServiceInterface::class)->add($parentMedia);\n\n        $this->get(ProductVariantMediaServiceInterface::class)->assignFromParentToVariant($this->parentId, $this->variantId);\n\n        $this->assertEquals(5, $this->get(ProductMediaDaoInterface::class)->getAll($this->variantId)->first()->getPosition());\n    }\n\n    public function testCopyMediaPreservesInactiveStatus(): void\n    {\n        $parentMedia = $this->createProductMedia($this->parentId);\n        $parentMedia->deactivate();\n        $this->get(ProductMediaServiceInterface::class)->add($parentMedia);\n\n        $this->get(ProductVariantMediaServiceInterface::class)->assignFromParentToVariant($this->parentId, $this->variantId);\n\n        $this->assertFalse($this->get(ProductMediaDaoInterface::class)->getAll($this->variantId)->first()->isActive());\n    }\n\n    public function testCopyMultipleMedia(): void\n    {\n        $this->get(ProductMediaServiceInterface::class)->add($this->createProductMedia($this->parentId));\n        $this->get(ProductMediaServiceInterface::class)->add($this->createProductMedia($this->parentId));\n        $this->get(ProductMediaServiceInterface::class)->add($this->createProductMedia($this->parentId));\n\n        $this->get(ProductVariantMediaServiceInterface::class)->assignFromParentToVariant($this->parentId, $this->variantId);\n\n        $this->assertCount(3, $this->get(ProductMediaDaoInterface::class)->getAll($this->variantId));\n    }\n\n    public function testCopyFromParentWithNoMedia(): void\n    {\n        $this->get(ProductVariantMediaServiceInterface::class)->assignFromParentToVariant($this->parentId, $this->variantId);\n\n        $this->assertCount(0, $this->get(ProductMediaDaoInterface::class)->getAll($this->variantId));\n    }\n\n    public function testVariantMediaHasNewId(): void\n    {\n        $parentMedia = $this->createProductMedia($this->parentId);\n        $this->get(ProductMediaServiceInterface::class)->add($parentMedia);\n\n        $this->get(ProductVariantMediaServiceInterface::class)->assignFromParentToVariant($this->parentId, $this->variantId);\n\n        $this->assertNotEquals(\n            (string) $parentMedia->getId(),\n            (string) $this->get(ProductMediaDaoInterface::class)->getAll($this->variantId)->first()->getId()\n        );\n    }\n\n    private function createProductMedia(Id $productId, ProductMediaRole ...$roles): ProductMedia\n    {\n        if (empty($roles)) {\n            $roles = [ProductMediaRole::from(ProductMediaRole::DETAIL)];\n        }\n\n        return new ProductMedia(\n            Id::generate(),\n            $productId,\n            new Media(\n                Id::generate(),\n                new MediaPath(uniqid('img-', true) . '.jpg'),\n                new MediaType('image/jpeg')\n            ),\n            new ProductMediaRoleSet(...$roles),\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Review/Bridge/ProductRatingBridgeTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Review\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao\\ProductRatingDaoInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\nuse ReflectionProperty;\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Application\\Model\\Rating;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge\\ProductRatingBridge;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge\\ProductRatingBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao\\ProductRatingDao;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\ProductRatingService;\n\nfinal class ProductRatingBridgeTest extends TestCase\n{\n    use ContainerTrait;\n    public function testUpdateProductRating(): void\n    {\n        $this->createTestProduct();\n        $this->createTestRatings();\n\n        $productRatingBridge = $this->get(ProductRatingBridgeInterface::class);\n        $productRatingBridge->updateProductRating('testProduct');\n\n        $productRatingDao = $this->get(ProductRatingDaoInterface::class);\n        $productRating = $productRatingDao->getProductRatingById('testProduct');\n\n        $this->assertEquals(\n            4,\n            $productRating->getRatingAverage()\n        );\n\n        $this->assertEquals(\n            3,\n            $productRating->getRatingCount()\n        );\n    }\n\n    private function createTestProduct(): void\n    {\n        $product = oxNew(Article::class);\n        $product->setId('testProduct');\n        $product->save();\n    }\n\n    private function createTestRatings(): void\n    {\n        $rating = oxNew(Rating::class);\n        $rating->oxratings__oxobjectid = new Field('testProduct');\n        $rating->oxratings__oxtype = new Field('oxarticle');\n        $rating->oxratings__oxrating = new Field(3);\n        $rating->save();\n\n        $rating = oxNew(Rating::class);\n        $rating->oxratings__oxobjectid = new Field('testProduct');\n        $rating->oxratings__oxtype = new Field('oxarticle');\n        $rating->oxratings__oxrating = new Field(4);\n        $rating->save();\n\n        $rating = oxNew(Rating::class);\n        $rating->oxratings__oxobjectid = new Field('testProduct');\n        $rating->oxratings__oxtype = new Field('oxarticle');\n        $rating->oxratings__oxrating = new Field(5);\n        $rating->save();\n    }\n\n    /**\n     * Accessing the dao is difficult, because it is a private service.\n     * In newer versions of the Symfony Container (since 4.1) this may be\n     * done more elegant.\n     *\n     * @return ProductRatingDao\n     */\n    private function getProductRatingDao()\n    {\n        $bridge = ContainerFacade::get(ProductRatingBridgeInterface::class);\n        $serviceProperty = new ReflectionProperty(ProductRatingBridge::class, 'productRatingService');\n        $service = $serviceProperty->getValue($bridge);\n        $daoProperty = new ReflectionProperty(ProductRatingService::class, 'productRatingDao');\n\n        return $daoProperty->getValue($service);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Review/Bridge/UserRatingBridgeTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Review\\Bridge;\n\nuse PHPUnit\\Framework\\TestCase;\nuse OxidEsales\\Eshop\\Application\\Model\\Rating;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge\\UserRatingBridge;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Exception\\RatingPermissionException;\n\n\nfinal class UserRatingBridgeTest extends TestCase\n{\n    public function testDeleteRating(): void\n    {\n        $this->createTestRating();\n\n        $userRatingBridge = $this->getUserRatingBridge();\n        $userRatingBridge->deleteRating('testUserId', 'testRatingId');\n\n        $this->assertFalse(\n            $this->ratingExists('testRatingId')\n        );\n    }\n\n    public function testDeleteRatingForSubShop(): void\n    {\n        $this->createTestRatingForSubShop();\n\n        $userRatingBridge = $this->getUserRatingBridge();\n        $userRatingBridge->deleteRating('testUserId', 'testSubShopRatingId');\n\n        $this->assertFalse(\n            $this->ratingExists('testSubShopRatingId')\n        );\n    }\n\n    public function testDeleteRatingWithNonExistentRatingId(): void\n    {\n        $this->expectException(EntryDoesNotExistDaoException::class);\n\n        $userRatingBridge = $this->getUserRatingBridge();\n        $userRatingBridge->deleteRating('testUserId', 'nonExistentId');\n    }\n\n    public function testDeleteRatingWithWrongUserId(): void\n    {\n        $this->expectException(RatingPermissionException::class);\n\n        $this->createTestRating();\n\n        $userRatingBridge = $this->getUserRatingBridge();\n        $userRatingBridge->deleteRating('userWithWrongId', 'testRatingId');\n    }\n\n    private function ratingExists(string $id): bool\n    {\n        $rating = oxNew(Rating::class);\n\n        return $rating->load($id) !== false;\n    }\n\n    private function getUserRatingBridge(): UserRatingBridge\n    {\n        return new UserRatingBridge();\n    }\n\n    private function createTestRating(): void\n    {\n        $rating = oxNew(Rating::class);\n        $rating->setId('testRatingId');\n        $rating->oxratings__oxuserid = new Field('testUserId');\n        $rating->save();\n    }\n\n    private function createTestRatingForSubShop(): void\n    {\n        $rating = oxNew(Rating::class);\n        $rating->setId('testSubShopRatingId');\n        $rating->oxratings__oxuserid = new Field('testUserId');\n        $rating->oxratings__oxshopid = new Field(5);\n        $rating->save();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Review/Bridge/UserReviewBridgeTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Review\\Bridge;\n\nuse PHPUnit\\Framework\\TestCase;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Application\\Model\\Review;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge\\UserReviewBridge;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Exception\\ReviewPermissionException;\n\n\nfinal class UserReviewBridgeTest extends TestCase\n{\n    public function testDeleteReview(): void\n    {\n        $userReviewBridge = $this->getUserReviewBridge();\n        $database = DatabaseProvider::getDb();\n\n        $sql = \"select oxid from oxreviews where oxid = 'id1'\";\n\n        $this->createTestReview();\n        $this->assertEquals('id1', $database->getOne($sql));\n\n        $userReviewBridge->deleteReview('user1', 'id1');\n        $this->assertFalse($database->getOne($sql));\n    }\n\n    public function testDeleteReviewWithNonExistentReviewId(): void\n    {\n        $this->expectException(EntryDoesNotExistDaoException::class);\n\n        $userReviewBridge = $this->getUserReviewBridge();\n        $userReviewBridge->deleteReview('user1', 'nonExistentId');\n    }\n\n    public function testDeleteRatingWithWrongUserId(): void\n    {\n        $this->expectException(ReviewPermissionException::class);\n\n        $userReviewBridge = $this->getUserReviewBridge();\n        $database = DatabaseProvider::getDb();\n\n        $sql = \"select oxid from oxreviews where oxid = 'id1'\";\n\n        $this->createTestReview();\n        $this->assertEquals('id1', $database->getOne($sql));\n\n        $userReviewBridge->deleteReview('userWithWrongId', 'id1');\n    }\n\n    private function getUserReviewBridge(): UserReviewBridge\n    {\n        return new UserReviewBridge();\n    }\n\n    private function createTestReview(): void\n    {\n        $review = oxNew(Review::class);\n        $review->setId('id1');\n        $review->oxreviews__oxuserid = new Field('user1');\n        $review->save();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Review/Dao/ProductRatingDaoTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Review\\Dao;\n\nuse PHPUnit\\Framework\\TestCase;\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge\\ProductRatingBridge;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Bridge\\ProductRatingBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao\\ProductRatingDao;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\ProductRating;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\ProductRatingService;\nuse ReflectionProperty;\n\nfinal class ProductRatingDaoTest extends TestCase\n{\n    public function testUpdateProductRating(): void\n    {\n        $this->createTestProduct();\n\n        $productRating = new ProductRating();\n        $productRating\n            ->setProductId('testProduct')\n            ->setRatingCount(66)\n            ->setRatingAverage(3.7);\n\n        $productRatingDao = $this->getProductRatingDao();\n        $productRatingDao->update($productRating);\n\n        $this->assertEquals(\n            $productRating,\n            $productRatingDao->getProductRatingById('testProduct')\n        );\n    }\n\n    private function createTestProduct(): void\n    {\n        $product = oxNew(Article::class);\n        $product->setId('testProduct');\n        $product->save();\n    }\n\n    /**\n     * Accessing the dao is difficult, because it is a private service.\n     * In newer versions of the Symfony Container (since 4.1) this may be\n     * done more elegant.\n     *\n     * @return ProductRatingDao\n     */\n    private function getProductRatingDao()\n    {\n        $bridge = ContainerFacade::get(ProductRatingBridgeInterface::class);\n        $serviceProperty = new ReflectionProperty(ProductRatingBridge::class, 'productRatingService');\n        $service = $serviceProperty->getValue($bridge);\n        $daoProperty = new ReflectionProperty(ProductRatingService::class, 'productRatingDao');\n\n        return $daoProperty->getValue($service);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Review/Dao/RatingDaoTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Review\\Dao;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Rating as EshopRating;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao\\RatingDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\Rating;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class RatingDaoTest extends TestCase\n{\n    use ContainerTrait;\n\n    public function testGetRatingsByUserId(): void\n    {\n        $this->createTestRatingsForGetRatingsByUserIdTest();\n\n        $ratings = $this->get(RatingDaoInterface::class)\n            ->getRatingsByUserId('user1');\n\n        $this->assertCount(2, $ratings->toArray());\n        $this->assertInstanceOf(Rating::class, $ratings->first());\n    }\n\n    public function testGetRatingsByProductId(): void\n    {\n        $this->createTestRatingsForGetRatingsByProductIdTest();\n\n        $ratings = $this->get(RatingDaoInterface::class)\n            ->getRatingsByProductId('product1');\n\n        $this->assertCount(2, $ratings->toArray());\n        $this->assertInstanceOf(Rating::class, $ratings->first());\n    }\n\n    public function testDeleteRating(): void\n    {\n        $this->createTestRatingsForDeleteRatingTest();\n\n        $ratingDao = $this->get(RatingDaoInterface::class);\n\n        $ratingsBeforeDeletion = $ratingDao->getRatingsByUserId('user1');\n        $ratingToDelete = $ratingsBeforeDeletion->first();\n\n        $ratingDao->delete($ratingToDelete);\n\n        $ratingsAfterDeletion = $ratingDao->getRatingsByUserId('user1');\n\n        $this->assertNotContains(\n            $ratingToDelete,\n            $ratingsAfterDeletion->toArray()\n        );\n    }\n\n    private function createTestRatingsForDeleteRatingTest(): void\n    {\n        $rating = oxNew(EshopRating::class);\n        $rating->setId('id1');\n        $rating->oxratings__oxuserid = new Field('user1');\n        $rating->save();\n\n        $rating = oxNew(EshopRating::class);\n        $rating->setId('id2');\n        $rating->oxratings__oxuserid = new Field('user1');\n        $rating->save();\n    }\n\n    private function createTestRatingsForGetRatingsByUserIdTest(): void\n    {\n        $rating = oxNew(EshopRating::class);\n        $rating->setId('id1');\n        $rating->oxratings__oxuserid = new Field('user1');\n        $rating->save();\n\n        $rating = oxNew(EshopRating::class);\n        $rating->setId('id2');\n        $rating->oxratings__oxuserid = new Field('user1');\n        $rating->save();\n\n        $rating = oxNew(EshopRating::class);\n        $rating->setId('id3');\n        $rating->oxratings__oxuserid = new Field('userNotMatched');\n        $rating->save();\n    }\n\n    private function createTestRatingsForGetRatingsByProductIdTest(): void\n    {\n        $rating = oxNew(EshopRating::class);\n        $rating->setId('id1');\n        $rating->oxratings__oxobjectid = new Field('product1');\n        $rating->oxratings__oxtype = new Field('oxarticle');\n        $rating->save();\n\n        $rating = oxNew(EshopRating::class);\n        $rating->setId('id2');\n        $rating->oxratings__oxobjectid = new Field('product1');\n        $rating->oxratings__oxtype = new Field('oxarticle');\n        $rating->save();\n\n        $rating = oxNew(EshopRating::class);\n        $rating->setId('id3');\n        $rating->oxratings__oxobjectid = new Field('productNotMatched');\n        $rating->oxratings__oxtype = new Field('oxarticle');\n        $rating->save();\n\n        $rating = oxNew(EshopRating::class);\n        $rating->setId('id4');\n        $rating->oxratings__oxobjectid = new Field('product1');\n        $rating->oxratings__oxtype = new Field('oxrecommlist');\n        $rating->save();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Domain/Review/Dao/ReviewDaoTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Domain\\Review\\Dao;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Review;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao\\ReviewDaoInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ReviewDaoTest extends TestCase\n{\n    use ContainerTrait;\n\n    public function testGetReviewsByUserId(): void\n    {\n        $this->createTestReviewsForGetRatingsByUserIdTest();\n\n        $reviews = $this->get(ReviewDaoInterface::class)\n            ->getReviewsByUserId('user1');\n\n        $this->assertCount(2, $reviews->toArray());\n    }\n\n    public function testDeleteReview(): void\n    {\n        $this->createTestReviewsForDeleteReviewTest();\n\n        $reviewDao = $this->get(ReviewDaoInterface::class);\n\n        $reviewsBeforeDeletion = $reviewDao->getReviewsByUserId('user1');\n        $reviewToDelete = $reviewsBeforeDeletion->first();\n\n        $reviewDao->delete($reviewToDelete);\n\n        $reviewsAfterDeletion = $reviewDao->getReviewsByUserId('user1');\n\n        $this->assertNotContains(\n            $reviewToDelete,\n            $reviewsAfterDeletion->toArray()\n        );\n    }\n\n    private function createTestReviewsForDeleteReviewTest(): void\n    {\n        $review = oxNew(Review::class);\n        $review->setId('id1');\n        $review->oxreviews__oxuserid = new Field('user1');\n        $review->save();\n\n        $review = oxNew(Review::class);\n        $review->setId('id2');\n        $review->oxreviews__oxuserid = new Field('user1');\n        $review->save();\n    }\n\n    private function createTestReviewsForGetRatingsByUserIdTest(): void\n    {\n        $review = oxNew(Review::class);\n        $review->setId('id1');\n        $review->oxreviews__oxuserid = new Field('user1');\n        $review->save();\n\n        $review = oxNew(Review::class);\n        $review->setId('id2');\n        $review->oxreviews__oxuserid = new Field('user1');\n        $review->save();\n\n        $review = oxNew(Review::class);\n        $review->setId('id3');\n        $review->oxreviews__oxuserid = new Field('userNotMatched');\n        $review->save();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Cache/ShopCacheFacadeTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Cache;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\ShopCacheCleanerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Cache\\ModuleCacheInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\Attributes\\Group;\nuse PHPUnit\\Framework\\TestCase;\n\n#[Group('cache')]\nfinal class ShopCacheFacadeTest extends TestCase\n{\n    use ContainerTrait;\n\n    public function testDeleteShopRelatedCache(): void\n    {\n        $moduleCache = $this->getModuleCache();\n        $moduleCache->put('test', ['something']);\n\n        $shopPool = $this->getShopCacheCleaner();\n        $shopPool->clear(1);\n\n        $this->assertFalse(\n            $moduleCache->exists('test')\n        );\n    }\n\n    private function getShopCacheCleaner(): ShopCacheCleanerInterface\n    {\n        return $this->get(ShopCacheCleanerInterface::class);\n    }\n\n    private function getModuleCache(): ModuleCacheInterface\n    {\n        return $this->get(ModuleCacheInterface::class);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Configuration/Dao/SystemConfigurationDaoTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Configuration\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Env\\DotenvLoader;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\ProjectRootLocator;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class SystemConfigurationDaoTest extends TestCase\n{\n    use ContainerTrait;\n\n    public function testGetDatabaseConfigurationWillContainSomeDefaults(): void\n    {\n        new DotenvLoader(\n            (new ProjectRootLocator())->getProjectRoot()\n        );\n\n        $this->assertNotEmpty(getenv('OXID_DB_URL'));\n    }\n\n    public function testGetBootstrapParametersWillContainsDefaults(): void\n    {\n        $this->assertNotEmpty($this->get(ContextInterface::class)->getCacheDirectory());\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Console/ConsoleTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Console;\n\nuse OxidEsales\\EshopCommunity\\Tests\\ConsoleRunnerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\EnvTrait;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ConsoleTest extends TestCase\n{\n    use ConsoleRunnerTrait;\n    use EnvTrait;\n    use ContainerTrait;\n\n    public function testConsoleWithEmptyInput(): void\n    {\n        $process = $this->runInConsoleAndAssertSuccess('');\n\n        $this->assertNotEmpty($process->getOutput());\n    }\n\n    public function testConsoleWillParseEnvVariables(): void\n    {\n        $envValue = 123;\n        $this->loadEnvFixture(__DIR__ . '/Fixtures', [\"OXID_VALUE='$envValue'\"]);\n        $this->createContainer();\n        $this->loadYamlFixture(__DIR__ . '/Fixtures');\n        $this->compileContainer();\n\n        $this->runInConsoleAndAssertSuccess('');\n\n        $this->assertEquals($envValue, $this->getParameter('oxid_esales.value'));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Console/Fixtures/services.yaml",
    "content": "parameters:\n  oxid_esales.value: '%env(defined:OXID_VALUE)%'\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/ContainerBuilderTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\ContainerFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\ContainerBuilder;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition\\Edition;\nuse OxidEsales\\EshopCommunity\\Tests\\EnvTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\Ce\\Internal\\ServiceInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\ContextStub;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ContainerBuilderTest extends TestCase\n{\n    use EnvTrait;\n\n    public function testServiceLoadingOrderWithAllConfigsPresent(): void\n    {\n        $this->loadEnvFixture(__DIR__, ['OXID_ENV=abc']);\n        $context = $this->makeContextStub();\n        $context->setEdition(Edition::Community);\n\n        $container = (new ContainerBuilder($context))->getContainer();\n        $container->compile();\n\n        $decoratorChainOutput =\n            'ce.component.module.project_default_env.project_specific_env';\n        $this->assertSame(\n            $decoratorChainOutput,\n            $container->get(ServiceInterface::class)->getNamespace()\n        );\n    }\n\n    public function testServiceLoadingOrderWithShopAndNoEnvironmentConfig(): void\n    {\n        $context = $this->makeContextStub();\n        $context->setEdition(Edition::Community);\n\n        $container = (new ContainerBuilder($context))->getContainer();\n        $container->compile();\n\n\n        $decoratorChainOutput =\n            'ce.component.module.project_default_env';\n        $this->assertSame(\n            $decoratorChainOutput,\n            $container->get(ServiceInterface::class)->getNamespace()\n        );\n    }\n\n    public function testServiceLoadingOrderWithEnvironmentAndNoShopConfig(): void\n    {\n        $this->loadEnvFixture(__DIR__, ['OXID_ENV=abc']);\n        $context = $this->makeContextStub();\n        $context->setEdition(Edition::Community);\n\n        $container = (new ContainerBuilder($context))->getContainer();\n        $container->compile();\n\n        $decoratorChainOutput =\n            'ce.component.module.project_default_env.project_specific_env';\n        $this->assertSame(\n            $decoratorChainOutput,\n            $container->get(ServiceInterface::class)->getNamespace()\n        );\n    }\n\n    public function testServiceLoadingOrderWithNoShopAndNoEnvironmentConfigs(): void\n    {\n        $this->loadEnvFixture(__DIR__, ['OXID_ENV=xyz']);\n        $context = $this->makeContextStub();\n        $context->setEdition(Edition::Community);\n\n        $container = (new ContainerBuilder($context))->getContainer();\n        $container->compile();\n\n        $decoratorChainOutput = 'ce.component.module.project_default_env';\n        $this->assertSame(\n            $decoratorChainOutput,\n            $container->get(ServiceInterface::class)->getNamespace()\n        );\n    }\n\n    private function makeContextStub(): ContextStub\n    {\n        $context = new ContextStub();\n        $context->setCommunityEditionSourcePath(__DIR__ . '/Fixtures/Ce');\n        $context->setGeneratedServicesFilePath(__DIR__ . '/Fixtures/var/generated_services.yaml');\n        $context->setActiveModuleServicesFilePath(__DIR__ . '/Fixtures/var/active_module_services.yaml');\n        $context->setProjectConfigurationDirectory(__DIR__ . '/Fixtures/var/configuration/');\n\n        return $context;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Dao/ParameterDaoTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Dao\\ParameterDaoInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Symfony\\Component\\DependencyInjection\\Exception\\ParameterNotFoundException;\n\nfinal class ParameterDaoTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    public function testAddWithMultipleParameters(): void\n    {\n        $dao = $this->get(ParameterDaoInterface::class);\n\n        $dao->add('param_1', 'som-val-1', 1);\n        $dao->add('param_2', 'some-val-2', 1);\n\n        $this->assertTrue($dao->has('param_1', 1));\n        $this->assertTrue($dao->has('param_2', 1));\n    }\n\n    public function testAddWithExistingParameterWillOverwriteValue(): void\n    {\n        $dao = ContainerFacade::get(ParameterDaoInterface::class);\n\n        $value1 = uniqid('val-', true);\n        $value2 = uniqid('val-', true);\n\n        $dao->add('param_1', $value1, 1);\n        $dao->add('param_1', $value2, 1);\n\n        $this->assertEquals($value2, ContainerFacade::getParameter('param_1'));\n    }\n\n    public function testAddParameterWithNulValue(): void\n    {\n        $dao = $this->get(ParameterDaoInterface::class);\n        $dao->add('some_null_param', null, 1);\n\n        $this->assertTrue($dao->has('some_null_param', 1));\n    }\n\n    public function testRemoveWithMultipleParameters(): void\n    {\n        $dao = $this->get(ParameterDaoInterface::class);\n        $dao->add('param_1', 'som-val-1', 1);\n        $dao->add('param_2', 'some-val-2', 1);\n\n        $dao->remove('param_1', 1);\n\n        $this->assertFalse($dao->has('param_1', 1));\n        $this->assertTrue($dao->has('param_2', 1));\n    }\n\n    public function testParameterAccessFromContainer(): void\n    {\n        $parameterName = 'test' . time();\n        $dao = ContainerFacade::get(ParameterDaoInterface::class);\n        $dao->add($parameterName, 'value', 1);\n\n        $this->assertEquals(\n            'value',\n            ContainerFacade::getParameter($parameterName)\n        );\n\n        $dao->remove($parameterName, 1);\n\n        $this->expectException(ParameterNotFoundException::class);\n\n        ContainerFacade::getParameter($parameterName);\n    }\n\n    public function testAddParameterAsEnvVar(): void\n    {\n        $dao = ContainerFacade::get(ParameterDaoInterface::class);\n        $dao->add('testEnvVar', '%env(OXID_TEST_ENV_VAR)%', 1);\n\n        putenv('OXID_TEST_ENV_VAR=testValue');\n\n        $this->assertEquals(\n            'testValue',\n            ContainerFacade::getParameter('testEnvVar')\n        );\n    }\n\n    public function testAddParameterToMultipleShops(): void\n    {\n        $dao = $this->get(ParameterDaoInterface::class);\n        $dao->add('testShop1', 'value', 1);\n        $dao->add('testShop2', 'value', 2);\n\n        $this->assertTrue($dao->has('testShop1', 1));\n        $this->assertTrue($dao->has('testShop2', 2));\n\n        $this->assertFalse($dao->has('testShop1', 2));\n        $this->assertFalse($dao->has('testShop2', 1));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Dao/ProjectYamlDaoTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Dao\\ProjectYamlDao;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Dao\\ProjectYamlDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\DataObject\\DIConfigWrapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\BasicContextStub;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class ProjectYamlDaoTest extends TestCase\n{\n    use ContainerTrait;\n\n    private string $tmpFixture = __DIR__ . DIRECTORY_SEPARATOR . 'generated_project.yaml';\n    private ProjectYamlDao $dao;\n\n    public function setup(): void\n    {\n        parent::setUp();\n\n        (new Filesystem())->touch($this->tmpFixture);\n        $context = new BasicContextStub();\n        $context->setGeneratedServicesFilePath($this->tmpFixture);\n\n        $this->dao = new ProjectYamlDao(\n            $context,\n            $this->get('oxid_esales.symfony.file_system')\n        );\n    }\n\n    public function tearDown(): void\n    {\n        (new Filesystem())->remove($this->tmpFixture);\n\n        parent::tearDown();\n    }\n\n    public function testLadProjectConfigFileWillWorkWithEmptyFile(): void\n    {\n        (new Filesystem())->remove($this->tmpFixture);\n\n        $this->assertEmpty($this->dao->loadProjectConfigFile()->getConfigAsArray());\n\n        (new Filesystem())->dumpFile($this->tmpFixture, '');\n\n        $this->assertEmpty($this->dao->loadProjectConfigFile()->getConfigAsArray());\n    }\n\n    public function testSaveProjectConfigFileWillUseRelativePaths(): void\n    {\n        $relativePath = 'some/relative/path/services.yaml';\n        $absolutePath = '/some/absolute/path/services.yaml';\n        $configArray = [\n            'imports' => [\n                ['resource' => $relativePath],\n                ['resource' => $absolutePath],\n            ],\n        ];\n\n        $this->dao->saveProjectConfigFile(new DIConfigWrapper($configArray));\n\n        $projectYaml = $this->dao->loadProjectConfigFile();\n        $this->assertEquals($relativePath, $projectYaml->getImportFileNames()[0]);\n        $this->assertTrue(Path::isRelative($projectYaml->getImportFileNames()[1]));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Fixtures/Ce/Internal/Service.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\Ce\\Internal;\n\nclass Service implements ServiceInterface\n{\n    public function __construct(private readonly string $namespace)\n    {\n    }\n\n    public function getNamespace(): string\n    {\n        return $this->namespace;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Fixtures/Ce/Internal/ServiceInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\Ce\\Internal;\n\ninterface ServiceInterface\n{\n    public function getNamespace(): string;\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Fixtures/Ce/Internal/services.yaml",
    "content": "services:\n  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\Ce\\Internal\\ServiceInterface:\n    class: OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\Ce\\Internal\\Service\n    arguments:\n      $namespace: ce\n    public: true\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Fixtures/Component/Internal/ServiceDecorator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\Component\\Internal;\n\nuse  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\Ce\\Internal\\ServiceInterface;\n\nclass ServiceDecorator implements ServiceInterface\n{\n    public function __construct(\n        private readonly ServiceInterface $decorated,\n        private readonly string $namespace\n    ) {\n    }\n\n    public function getNamespace(): string\n    {\n        return \"{$this->decorated->getNamespace()}.$this->namespace\";\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Fixtures/Component/Internal/services.yaml",
    "content": "services:\n  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\Component\\Internal\\ServiceDecorator:\n    decorates: OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\Ce\\Internal\\ServiceInterface\n    arguments:\n      $decorated: '@.inner'\n      $namespace: component\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Fixtures/Module/Internal/ServiceDecorator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\Module\\Internal;\n\nuse  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\Ce\\Internal\\ServiceInterface;\n\nclass ServiceDecorator implements ServiceInterface\n{\n    public function __construct(\n        private readonly ServiceInterface $decorated,\n        private readonly string $namespace\n    ) {\n    }\n\n    public function getNamespace(): string\n    {\n        return \"{$this->decorated->getNamespace()}.$this->namespace\";\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Fixtures/Module/Internal/services.yaml",
    "content": "services:\n  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\Module\\Internal\\ServiceDecorator:\n    decorates: OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\Ce\\Internal\\ServiceInterface\n    arguments:\n      $decorated: '@.inner'\n      $namespace: module\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Fixtures/Project/Internal/ServiceDecorator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\Project\\Internal;\n\nuse  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\Ce\\Internal\\ServiceInterface;\n\nclass ServiceDecorator implements ServiceInterface\n{\n    public function __construct(\n        private readonly ServiceInterface $decorated,\n        private readonly string $namespace\n    ) {\n    }\n\n    public function getNamespace(): string\n    {\n        return \"{$this->decorated->getNamespace()}.$this->namespace\";\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Fixtures/ProjectEnv/Internal/ServiceDecorator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\ProjectEnv\\Internal;\n\nuse  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\Ce\\Internal\\ServiceInterface;\n\nclass ServiceDecorator implements ServiceInterface\n{\n    public function __construct(\n        private readonly ServiceInterface $decorated,\n        private readonly string $namespace\n    ) {\n    }\n\n    public function getNamespace(): string\n    {\n        return \"{$this->decorated->getNamespace()}.$this->namespace\";\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Fixtures/var/active_module_services.yaml",
    "content": "imports:\n  -\n    resource: ../Module/Internal/services.yaml\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Fixtures/var/configuration/parameters.yaml",
    "content": "parameters:\n  project_default_env: default_env\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Fixtures/var/configuration/services.yaml",
    "content": "services:\n  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\Project\\Internal\\ServiceDecorator:\n    decorates: OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\Ce\\Internal\\ServiceInterface\n    arguments:\n      $decorated: '@.inner'\n      $namespace: project_%project_default_env%\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Fixtures/var/configuration.abc/parameters.yaml",
    "content": "parameters:\n  project_specific_env: specific_env\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Fixtures/var/configuration.abc/services.yaml",
    "content": "services:\n  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\ProjectEnv\\Internal\\ServiceDecorator:\n    decorates: OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Fixtures\\Ce\\Internal\\ServiceInterface\n    arguments:\n      $decorated: '@.inner'\n      $namespace: project_%project_specific_env%\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Fixtures/var/generated_services.yaml",
    "content": "imports:\n  -\n    resource: ../Component/Internal/services.yaml\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Service/Fixtures/generated_services.yaml",
    "content": "imports:\n  -\n    resource: module-1/services.yaml\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Service/Fixtures/module-1/services.yaml",
    "content": "services:\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Service/Fixtures/module-2/services.yaml",
    "content": "services:\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/DIContainer/Service/ProjectYamlImportServiceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\DIContainer\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Dao\\ProjectYamlDao;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\DataObject\\DIConfigWrapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Exception\\NoServiceYamlException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Service\\ProjectYamlImportService;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Service\\ProjectYamlImportServiceInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\BasicContextStub;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class ProjectYamlImportServiceTest extends TestCase\n{\n    private string $generatedServicesFile = __DIR__ . '/Fixtures/generated_services.yaml';\n\n    public function testAddImport(): void\n    {\n        $this->getImportService()->addImport($this->getFixturePath('module-1'));\n\n        $imports = $this->getDao()->loadProjectConfigFile()->getImportFileNames();\n        $this->assertStringEndsWith(\n            'module-1/services.yaml',\n            $imports[0],\n        );\n    }\n\n    public function testAddImportSeveralTimes(): void\n    {\n        $service = $this->getImportService();\n        $service->addImport($this->getFixturePath('module-1'));\n        $service->addImport($this->getFixturePath('module-2'));\n        $service->addImport($this->getFixturePath('module-1'));\n\n        $imports = $this->getDao()->loadProjectConfigFile()->getImportFileNames();\n        $this->assertCount(2, $imports);\n    }\n\n    public function testRemoveImport(): void\n    {\n        $service = $this->getImportService();\n        $service->addImport($this->getFixturePath('module-1'));\n        $service->addImport($this->getFixturePath('module-2'));\n        $service->removeImport($this->getFixturePath('module-1'));\n\n        $imports = $this->getDao()->loadProjectConfigFile()->getImportFileNames();\n        $this->assertCount(1, $imports);\n        $this->assertStringEndsWith(\n            'module-2/services.yaml',\n            $imports[0]\n        );\n    }\n\n    public function testRemoveAllImports(): void\n    {\n        $service = $this->getImportService();\n        $service->addImport($this->getFixturePath('module-1'));\n        $service->addImport($this->getFixturePath('module-2'));\n        $service->removeImport($this->getFixturePath('module-1'));\n        $service->removeImport($this->getFixturePath('module-2'));\n\n        $imports = $this->getDao()->loadProjectConfigFile()->getImportFileNames();\n        $this->assertEmpty($imports);\n    }\n\n    public function testAddNonExistingDirectory(): void\n    {\n        $this->expectException(NoServiceYamlException::class);\n\n        $this->getImportService()->addImport($this->getFixturePath('some-missing-directory'));\n    }\n\n    public function testAddNonExistingServiceYaml(): void\n    {\n        $this->expectException(NoServiceYamlException::class);\n\n        $this->getImportService()->addImport($this->getFixturePath('module-1/missing-services.yaml'));\n    }\n\n    public function testRemovingNonExistingImports(): void\n    {\n        $path = 'module-1/services.yaml';\n        $existingImport = $this->getFixturePath($path);\n        $configuration = [\n            'imports' => [\n                ['resource' => 'some/non/existing/directory/services.yaml'],\n                ['resource' => $existingImport]]\n        ];\n        $this->getDao()->saveProjectConfigFile(\n            new DIConfigWrapper($configuration)\n        );\n\n        $this->getImportService()->removeNonExistingImports();\n\n        $imports = $this->getDao()->loadProjectConfigFile()->getImportFileNames();\n\n        $this->assertCount(1, $imports);\n        $this->assertEquals($path, $imports[0]);\n    }\n\n    private function getFixturePath(string $dir): string\n    {\n        return Path::join(__DIR__, 'Fixtures', $dir);\n    }\n\n    private function getImportService(): ProjectYamlImportServiceInterface\n    {\n        return new ProjectYamlImportService($this->getDao(), $this->getContextStub());\n    }\n\n    private function getContextStub(): BasicContextStub\n    {\n        $context = new BasicContextStub();\n        $context->setGeneratedServicesFilePath($this->generatedServicesFile);\n        return $context;\n    }\n\n    private function getDao(): ProjectYamlDao\n    {\n        return new ProjectYamlDao($this->getContextStub(), (new Filesystem()));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Database/ConnectionFactoryTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Database;\n\nuse OxidEsales\\EshopCommunity\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\ConnectionFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\ContextStub;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Filesystem\\Filesystem;\n\nfinal class ConnectionFactoryTest extends TestCase\n{\n    use ContainerTrait;\n\n    private string $logFile = __DIR__ . DIRECTORY_SEPARATOR . 'some-log.log';\n    private string $id;\n    private string $variable;\n    private string $value;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        (new FileSystem())->touch($this->logFile);\n        $this->id = Registry::getUtilsObject()->generateUId();\n        $this->variable = 'some-variable';\n        $this->value = uniqid('some-value-', true);\n    }\n\n    public function tearDown(): void\n    {\n        (new FileSystem())->remove($this->logFile);\n\n        parent::tearDown();\n    }\n\n    public function testSqlLoggerWillWriteNothingForNonAdmin(): void\n    {\n        $this->executeInsertStatement();\n\n        $this->assertEmpty(file_get_contents($this->logFile));\n    }\n\n    public function testSqlLoggerWillWriteNothingForAdminWithConfigDisabled(): void\n    {\n        $this->setAdminWithLoggingDisabled();\n\n        $this->executeInsertStatement();\n\n        $this->assertEmpty(file_get_contents($this->logFile));\n    }\n\n    public function testSqlLoggerWillWriteQueryData(): void\n    {\n        $this->setAdminWithLoggingEnabled();\n\n        $this->executeInsertStatement();\n\n        $log = file_get_contents($this->logFile);\n        $this->assertStringContainsString('INSERT INTO', $log);\n        $this->assertStringContainsString($this->id, $log);\n        $this->assertStringContainsString($this->variable, $log);\n        $this->assertStringContainsString($this->value, $log);\n    }\n\n    public function testSqlLoggerWillWriteExtendedContextData(): void\n    {\n        $this->setAdminWithLoggingEnabled();\n\n        $this->executeInsertStatement();\n\n        $log = file_get_contents($this->logFile);\n        $this->assertStringContainsString('adminUserId', $log);\n        $this->assertStringContainsString('shopId', $log);\n        $this->assertStringContainsString((new \\ReflectionClass(self::class))->getShortName(), $log);\n        $this->assertStringContainsString('class', $log);\n        $this->assertStringContainsString('function', $log);\n        $this->assertStringContainsString('file', $log);\n        $this->assertStringContainsString('line', $log);\n    }\n\n    public function testSqlLoggerWillWriteNothingForNonLoggedQuery(): void\n    {\n        $this->setAdminWithLoggingEnabled();\n\n        $this->executeSelectQuery();\n\n        $this->assertEmpty(file_get_contents($this->logFile));\n    }\n\n    public function testSqlLoggerWillWriteNothingIfNoQueryExecuted(): void\n    {\n        $this->setAdminWithLoggingEnabled();\n\n        $this->executeNoQuery();\n\n        $this->assertEmpty(file_get_contents($this->logFile));\n    }\n\n    public function testSqlLoggerWillWriteNothingIfQueryIsFiltered(): void\n    {\n        $this->setAdminWithLoggingEnabled();\n\n        $this->executeQueryWithFilteredWord();\n\n        $this->assertEmpty(file_get_contents($this->logFile));\n    }\n\n    private function executeInsertStatement(): void\n    {\n        $statement = $this\n            ->get(ConnectionFactoryInterface::class)\n            ->create()\n            ->prepare(\n                'INSERT INTO `oxconfig` (`OXID`, `OXVARNAME`, `OXVARVALUE`)  VALUES (?, ?, ?);'\n            );\n        $statement->bindValue(1, $this->id);\n        $statement->bindValue(2, $this->variable);\n        $statement->bindValue(3, $this->value);\n\n        $statement->executeStatement();\n    }\n\n    private function executeSelectQuery(): void\n    {\n        $this\n            ->get(ConnectionFactoryInterface::class)\n            ->create()\n            ->executeQuery(\n                'SELECT *  FROM `oxconfig` LIMIT 1;'\n            );\n    }\n\n    private function executeNoQuery(): void\n    {\n        $connection = $this\n            ->get(ConnectionFactoryInterface::class)\n            ->create();\n        $connection->beginTransaction();\n        $connection->rollBack();\n    }\n\n    private function executeQueryWithFilteredWord(): void\n    {\n        $statement = $this\n            ->get(ConnectionFactoryInterface::class)\n            ->create()\n            ->prepare(\n                'UPDATE `oxconfig` SET `OXVARVALUE` = \"123\"  WHERE `OXVARNAME` = \"oxsession\";'\n            );\n\n        $statement->executeStatement();\n    }\n\n    private function setAdminWithLoggingDisabled(): void\n    {\n        $this->createContainer();\n        $this->stubAdminContext();\n        $this->container->setParameter('oxid_esales.log_admin_queries', false);\n        $this->replaceContainerInstance();\n    }\n\n    private function stubAdminContext(): void\n    {\n        /** @var ContextStub $context */\n        $context = $this->container->get(ContextInterface::class);\n        $context->setIsAdmin(true);\n        $context->setAdminLogFilePath($this->logFile);\n    }\n\n    private function setAdminWithLoggingEnabled(): void\n    {\n        $this->createContainer();\n        $this->stubAdminContext();\n        $this->container->setParameter('oxid_esales.log_admin_queries', true);\n        $this->replaceContainerInstance();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Env/EnvLoaderTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Env;\n\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\EnvTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\TestContainerFactory;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class EnvLoaderTest extends TestCase\n{\n    use ContainerTrait;\n    use EnvTrait;\n\n    private string $dotEnvFixture = '';\n    private string $fixtures = __DIR__ . '/Fixtures/EnvLoader';\n    private string $appEnvKey = 'OXID_ENV';\n    private string $serializedEnvKey = 'SERIALIZED_VALUE_KEY';\n    private string $serializedParameterKey = 'serialized_value_key';\n\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        $this->dotEnvFixture = Path::join($this->fixtures, '.env');\n    }\n\n    public function tearDown(): void\n    {\n        (new Filesystem())->remove($this->dotEnvFixture);\n        parent::tearDown();\n    }\n\n    public function testApplicationEnvironmentIsDefined(): void\n    {\n        $currentEnvironment = getenv($this->appEnvKey);\n\n        $this->assertNotEmpty($currentEnvironment);\n    }\n\n    public function testApplicationEnvironmentCanBeRedefined(): void\n    {\n        $someValue = uniqid('some-value', true);\n        $this->loadEnvFixture($this->fixtures, [\"$this->appEnvKey=$someValue\"]);\n\n        $currentEnvironment = getenv($this->appEnvKey);\n\n        $this->assertEquals($someValue, $currentEnvironment);\n    }\n\n    public function testJsonDSNsWithSpecialCharactersWillBeParsedAsArray(): void\n    {\n        $somePassword = '\"\\[(üÄ\\\\\" *123,.;)::\"\"';\n        $dsnString = \"mysql://username:$somePassword@123.255.255.255:3306/db-name?charset=utf8&driverOptions[1002]=\\\"SET @@SESSION.sql_mode=\\\"\\\"\\\"\";\n        $serializedValue = json_encode(\n            [$dsnString, $dsnString, $dsnString],\n            JSON_THROW_ON_ERROR\n        );\n        $this->loadEnvFixture($this->fixtures, [\"$this->serializedEnvKey='$serializedValue'\"]);\n\n        $this->container = (new TestContainerFactory())->create();\n        $this->loadYamlFixture($this->fixtures);\n        $this->container->compile(true);\n        TestContainerFactory::setContainer($this->container);\n\n        $containerParameter = $this->getParameter($this->serializedParameterKey);\n\n        $this->assertEquals($dsnString, $containerParameter[2]);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Env/EnvUrlFormatterTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace Integration\\Internal\\Framework\\Env;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Env\\EnvUrlFormatter;\nuse OxidEsales\\EshopCommunity\\Tests\\EnvTrait;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class EnvUrlFormatterTest extends TestCase\n{\n    use EnvTrait;\n\n    public function testWithPath(): void\n    {\n        $this->loadEnvFixture(__DIR__, ['OXID_ENV=abc']);\n\n        $url = EnvUrlFormatter::toEnvUrl('/path/to/some/directory');\n\n        $this->assertEquals('/path/to/some/directory.abc', $url);\n    }\n\n    public function testWithPathAndTrailingSlash(): void\n    {\n        $this->loadEnvFixture(__DIR__, ['OXID_ENV=abc']);\n\n        $url = EnvUrlFormatter::toEnvUrl('/path/to/some/directory/');\n\n        $this->assertEquals('/path/to/some/directory.abc', $url);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Env/Fixtures/EnvLoader/services.yaml",
    "content": "parameters:\n  serialized_value_key: '%env(json:SERIALIZED_VALUE_KEY)%'\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Event/EventLoggingSubscriberTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Event;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\ContainerBuilder;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Event\\ServicesYamlConfigurationErrorEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\ContextStub;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\n\nfinal class EventLoggingSubscriberTest extends TestCase\n{\n    private \\Symfony\\Component\\DependencyInjection\\ContainerBuilder $container;\n\n    private $testlog = __DIR__ . DIRECTORY_SEPARATOR . 'test.log';\n\n    public function setup(): void\n    {\n        parent::setUp();\n\n        $containerBuilder = new ContainerBuilder(new ContextStub());\n        $this->container = $containerBuilder->getContainer();\n\n        $contextDefinition = $this->container->getDefinition(ContextInterface::class);\n        $contextDefinition->setClass(ContextStub::class);\n        $this->container->compile();\n    }\n\n    public function tearDown(): void\n    {\n        if (file_exists($this->testlog)) {\n            unlink($this->testlog);\n        }\n        parent::tearDown();\n    }\n\n    public function testLoggingOnConfigurationErrorEvent(): void\n    {\n        /** @var ContextStub $context */\n        $context = $this->container->get(ContextInterface::class);\n        $context->setLogFilePath(__dir__ . DIRECTORY_SEPARATOR . 'test.log');\n\n        /** @var EventDispatcherInterface $dispatcher */\n        $dispatcher = $this->container->get(EventDispatcherInterface::class);\n        $dispatcher->dispatch(\n            new ServicesYamlConfigurationErrorEvent('error', 'just/some/path/services.yaml')\n        );\n\n        self::assertFileExists(__DIR__ . DIRECTORY_SEPARATOR . 'test.log');\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Event/TestEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Event;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\nclass TestEvent extends Event\n{\n    private int $counter = 0;\n\n    public function getNumberOfActiveHandlers(): int\n    {\n        return $this->counter;\n    }\n\n    public function handleEvent(): void\n    {\n        $this->counter++;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Event/generated_project.yaml",
    "content": "services:\n\n  OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface:\n    class: OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\ContextStub\n    autowire: true\n    public: true\n    arguments:\n      $currentShopId: 1\n\n  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Event\\TestEventSubscriber1:\n    class: OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Event\\TestEventSubscriber\n    public: true\n    arguments:\n      $stopPropagation: false\n    calls:\n      - [setActiveShops, [[1]]]\n      - [setContext, ['@OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface']]\n    tags:\n      - { name: kernel.event_subscriber }\n\n  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Event\\TestEventSubscriber2:\n    class: OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Event\\TestEventSubscriber\n    public: true\n    arguments:\n      $stopPropagation: true\n    calls:\n      - [setActiveShops, [[1, 2]]]\n      - [setContext, ['@OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface']]\n    tags:\n      - { name: kernel.event_subscriber }\n\n  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Event\\TestEventSubscriber3:\n    class: OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Event\\TestEventSubscriber\n    public: true\n    arguments:\n      $stopPropagation: false\n    calls:\n      - [setActiveShops, [[1, 2]]]\n      - [setContext, ['@OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface']]\n    tags:\n      - { name: kernel.event_subscriber }\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/FileSystem/FileGenerator/CsvFileGeneratorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\FileSystem\\FileGenerator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Newsletter\\DataMapper\\NewsletterRecipientsDataMapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\FileGenerator\\CsvFileGenerator;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Filesystem\\Filesystem;\n\n/**\n * Class CsvFileGeneratorTest\n */\nfinal class CsvFileGeneratorTest extends TestCase\n{\n    use ContainerTrait;\n\n    private $filename = __DIR__ . DIRECTORY_SEPARATOR . 'test.csv';\n\n    /** @var Filesystem  */\n    private $filesystem;\n\n    protected function setUp(): void\n    {\n        parent::setUp();\n\n        $this->filesystem = $this->get('oxid_esales.symfony.file_system');\n    }\n\n    public function tearDown(): void\n    {\n        $this->filesystem->remove($this->filename);\n\n        parent::tearDown();\n    }\n\n    public function testGenerateIfDataExists(): void\n    {\n        $csvGenerator = new CsvFileGenerator();\n\n        $this->filesystem->touch($this->filename);\n\n        $csvGenerator->generate($this->filename, [\n            [\"Salutation\", \"Name\"],\n            [\"MR\", \"John\"]\n        ]);\n\n        $this->assertEquals(\"Salutation,Name\\nMR,John\\n\", file_get_contents($this->filename));\n    }\n\n    public function testGenerateIfDataNotExists(): void\n    {\n        $csvGenerator = new CsvFileGenerator();\n\n        $this->filesystem->touch($this->filename);\n\n        $data = [\n            [\n                NewsletterRecipientsDataMapper::SALUTATION,\n                NewsletterRecipientsDataMapper::FIRST_NAME,\n                NewsletterRecipientsDataMapper::LAST_NAME,\n                NewsletterRecipientsDataMapper::EMAIL,\n                NewsletterRecipientsDataMapper::OPT_IN_STATE,\n                NewsletterRecipientsDataMapper::COUNTRY,\n                NewsletterRecipientsDataMapper::ASSIGNED_USER_GROUPS\n            ]\n        ];\n\n        $csvGenerator->generate($this->filename, $data);\n        $expected = \"Salutation,Firstname,LastName,Email,\\\"Opt-In state\\\",Country,\\\"Assigned user groups\\\"\\n\";\n\n        $this->assertEquals($expected, file_get_contents($this->filename));\n    }\n\n    public function testGenerateEscapesSpecialCharacters(): void\n    {\n        $csvGenerator = new CsvFileGenerator();\n\n        $this->filesystem->touch($this->filename);\n\n        $csvGenerator->generate($this->filename, [\n            [\"Name\", \"Description\"],\n            [\"Test\", \"Value with \\\"quotes\\\" inside\"],\n            [\"Another\", \"Backslash \\\\ test\"]\n        ]);\n\n        $expected = \"Name,Description\\n\"\n            . \"Test,\\\"Value with \\\"\\\"quotes\\\"\\\" inside\\\"\\n\"\n            . \"Another,\\\"Backslash \\\\ test\\\"\\n\";\n\n        $this->assertEquals($expected, file_get_contents($this->filename));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/FileSystem/ProjectRootLocatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\FileSystem;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\ProjectRootLocator;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class ProjectRootLocatorTest extends TestCase\n{\n    public function testGeProjectRootDirectoryContainsDistFile(): void\n    {\n        $rootPath = (new ProjectRootLocator())->getProjectRoot();\n        $envFile = Path::join($rootPath, '.env.dist');\n\n        $this->assertFileExists($envFile);\n    }\n\n    public function testGeProjectRootReturnsAbsolutePath(): void\n    {\n        $rootPath = (new ProjectRootLocator())->getProjectRoot();\n\n        $this->assertTrue(Path::isAbsolute($rootPath));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/FileSystem/Validator/FileValidatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\FileSystem\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\Validator\\FileValidator;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\Validator\\FileValidatorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\Validator\\ImageValidationException;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class FileValidatorTest extends IntegrationTestCase\n{\n    private FileValidatorInterface $fileValidator;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $mimeTypes = $this->get('oxid_esales.symfony.mime_types');\n        $this->fileValidator = new FileValidator($mimeTypes);\n    }\n\n    public function testImageValid(): void\n    {\n        $this->assertTrue(\n            $this->fileValidator->validateImage($this->getFilePath('image.png'))\n        );\n    }\n\n    public function testImageWrongExtension(): void\n    {\n        $this->assertFalse(\n            $this->fileValidator->validateImage($this->getFilePath('fake_image.php'))\n        );\n    }\n\n    public function testImageWrongType(): void\n    {\n        $this->assertFalse(\n            $this->fileValidator->validateImage($this->getFilePath('fake_image.png'))\n        );\n    }\n\n    public function testImageNotExist(): void\n    {\n        $this->expectException(ImageValidationException::class);\n\n        $this->fileValidator->validateImage($this->getFilePath('noimagepath'));\n    }\n\n    private function getFilePath(string $fileName): string\n    {\n        return Path::join(__DIR__, 'Fixtures/images', $fileName);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/FileSystem/Validator/Fixtures/images/fake_image.php",
    "content": "<?php\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Logger/LoggerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Logger;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Logger\\LoggerServiceFactory;\nuse OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\ContextStub;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class LoggerTest extends TestCase\n{\n    private string $logFilePath;\n\n    public function setup(): void\n    {\n        parent::setUp();\n\n        $this->logFilePath = tempnam(sys_get_temp_dir(), 'test_');\n    }\n\n    public function tearDown(): void\n    {\n        unlink($this->logFilePath);\n\n        parent::tearDown();\n    }\n\n    public function testLogging(): void\n    {\n        $loggedMessage = uniqid('message-', true);\n\n        $logger = (new LoggerServiceFactory($this->getContext()))->getLogger();\n        $logger->critical($loggedMessage);\n\n        $this->assertStringContainsString(\n            $loggedMessage,\n            file_get_contents($this->logFilePath)\n        );\n    }\n\n    public function testLoggerDoesNotLogMessagesLowerAsLogLevel(): void\n    {\n        $loggedMessage = uniqid('message-', true);\n\n        $logger = (new LoggerServiceFactory($this->getContext()))->getLogger();\n        $logger->info($loggedMessage);\n\n        $this->assertStringNotContainsString(\n            $loggedMessage,\n            file_get_contents($this->logFilePath)\n        );\n    }\n\n    public function testLoggerWithEnvValueMissingWillUseDefaultLogLevel(): void\n    {\n        putenv('OXID_LOG_LEVEL=');\n        $loggedMessage = uniqid('message-', true);\n\n        $logger = (new LoggerServiceFactory($this->getContext()))->getLogger();\n        $logger->error($loggedMessage);\n\n        $this->assertStringContainsString(\n            $loggedMessage,\n            file_get_contents($this->logFilePath)\n        );\n    }\n\n    private function getContext(): ContextStub\n    {\n        $context = new ContextStub();\n        $context->setLogFilePath($this->logFilePath);\n\n        return $context;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Cache/ModuleCacheTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Cache;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Cache\\CacheNotFoundException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Cache\\ModuleCacheInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\Attributes\\Group;\nuse PHPUnit\\Framework\\TestCase;\n\n#[Group('cache')]\nfinal class ModuleCacheTest extends TestCase\n{\n    use ContainerTrait;\n\n    public function testPut(): void\n    {\n        $cache = $this->getModuleCacheService();\n        $cache->put('test', ['something']);\n\n        $this->assertEquals(\n            ['something'],\n            $cache->get('test')\n        );\n    }\n\n    public function testExists(): void\n    {\n        $cache = $this->getModuleCacheService();\n        $cache->put('test', ['something']);\n\n        $this->assertTrue(\n            $cache->exists('test')\n        );\n    }\n\n    public function testInvalidate(): void\n    {\n        $cache = $this->getModuleCacheService();\n        $cache->put('test_key', ['something']);\n\n        $cache->deleteItem('test_key');\n\n        $this->assertFalse(\n            $cache->exists('test_key')\n        );\n    }\n\n    public function testGetNotExistentCache(): void\n    {\n        $cache = $this->getModuleCacheService();\n\n        $this->expectException(CacheNotFoundException::class);\n        $cache->get('nonExistent');\n    }\n\n    private function getModuleCacheService(): ModuleCacheInterface\n    {\n        return $this->get(ModuleCacheInterface::class);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Command/Fixtures/modules/testmodule/assets/some.css",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Command/Fixtures/modules/testmodule/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.1';\n\n$aModule = array(\n    'id' => 'testmodule',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n    'settings' => [\n        ['group' => 'test', 'name' => 'testSetting', 'type' => 'bool', 'value' => false],\n    ],\n);\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Command/InstallModuleAssetsCommandTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace Integration\\Internal\\Framework\\Module\\Command;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleFilesInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Command\\ModuleCommandsTestCase;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class InstallModuleAssetsCommandTest extends ModuleCommandsTestCase\n{\n    use ContainerTrait;\n\n    public function testInstallModuleAssets(): void\n    {\n        $testModulePackage = new OxidEshopPackage(Path::join($this->modulesPath, $this->moduleId));\n\n        $this->installTestModule();\n\n        $moduleFilesInstaller = $this->get(ModuleFilesInstallerInterface::class);\n        $moduleFilesInstaller->uninstall($testModulePackage);\n\n        $this->executeCommand('oe:module:install-assets');\n\n        $this->assertTrue(\n            $moduleFilesInstaller->isInstalled($testModulePackage)\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Command/ModuleActivateCommandTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Command;\n\nuse OxidEsales\\Eshop\\Core\\Module\\Module;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Command\\ModuleActivateCommand;\n\nfinal class ModuleActivateCommandTest extends ModuleCommandsTestCase\n{\n    private string $commandName = 'oe:module:activate';\n\n    public function testModuleActivation(): void\n    {\n        $this->installTestModule();\n\n        $consoleOutput = $this->executeCommand($this->commandName, ['module-id' => $this->moduleId]);\n\n        $this->assertSame(\n            sprintf(ModuleActivateCommand::MESSAGE_MODULE_ACTIVATED, $this->moduleId) . PHP_EOL,\n            $consoleOutput\n        );\n\n        $module = oxNew(Module::class);\n        $module->load($this->moduleId);\n        $this->assertTrue($module->isActive());\n\n        $this->cleanupTestData();\n    }\n\n    public function testNonExistingModuleActivation(): void\n    {\n        $moduleId = 'test';\n        $consoleOutput = $this->executeCommand($this->commandName, ['module-id' => $moduleId]);\n\n        $this->assertSame(\n            sprintf(ModuleActivateCommand::MESSAGE_MODULE_NOT_FOUND, $moduleId) . PHP_EOL,\n            $consoleOutput\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Command/ModuleCommandsTestCase.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Command;\n\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Symfony\\Component\\Console\\Application;\nuse Symfony\\Component\\Console\\Tester\\CommandTester;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass ModuleCommandsTestCase extends IntegrationTestCase\n{\n    protected string $modulesPath = __DIR__ . '/Fixtures/modules/';\n\n    protected string $moduleId = 'testmodule';\n\n    public function tearDown(): void\n    {\n        $this->cleanupTestData();\n        parent::tearDown();\n    }\n\n    protected function get(string $serviceId)\n    {\n        return ContainerFacade::get($serviceId);\n    }\n\n    protected function getApplication(): Application\n    {\n        $application = $this->get('oxid_esales.console.symfony.component.console.application');\n        $application->setAutoExit(false);\n\n        return $application;\n    }\n\n    protected function cleanupTestData(): void\n    {\n        $this\n            ->get(ModuleInstallerInterface::class)\n            ->uninstall(\n                new OxidEshopPackage(\n                    Path::join($this->modulesPath, $this->moduleId)\n                )\n            );\n    }\n\n    protected function installTestModule(): void\n    {\n        $this\n            ->get(ModuleInstallerInterface::class)\n            ->install(new OxidEshopPackage(Path::join($this->modulesPath, $this->moduleId)));\n    }\n\n    protected function executeCommand(string $command, array $input = []): string\n    {\n        $commandTester = new CommandTester(\n            $this->get('console.command_loader')->get($command)\n        );\n\n        $commandTester->execute($input);\n        return $commandTester->getDisplay();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Command/ModuleDeactivateCommandTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Command;\n\nuse OxidEsales\\Eshop\\Core\\Module\\Module;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Command\\ModuleDeactivateCommand;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridgeInterface;\n\nfinal class ModuleDeactivateCommandTest extends ModuleCommandsTestCase\n{\n    private string $commandName = 'oe:module:deactivate';\n\n    public function testModuleDeactivation(): void\n    {\n        $this->installTestModule();\n        $this->get(ModuleActivationBridgeInterface::class)->activate($this->moduleId, 1);\n\n        $consoleOutput = $this->executeCommand($this->commandName, ['module-id' => $this->moduleId]);\n\n        $this->assertSame(\n            sprintf(ModuleDeactivateCommand::MESSAGE_MODULE_DEACTIVATED, $this->moduleId) . PHP_EOL,\n            $consoleOutput\n        );\n\n        $module = oxNew(Module::class);\n        $module->load($this->moduleId);\n        $this->assertFalse($module->isActive());\n\n        $this->cleanupTestData();\n    }\n\n    public function testNonExistingModuleActivation(): void\n    {\n        $moduleId = 'test';\n        $consoleOutput = $this->executeCommand($this->commandName, ['module-id' => $moduleId]);\n\n        $this->assertSame(\n            sprintf(ModuleDeactivateCommand::MESSAGE_MODULE_NOT_FOUND, $moduleId) . PHP_EOL,\n            $consoleOutput\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Command/ModuleInstallCommandTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Command;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Tester\\CommandTester;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class ModuleInstallCommandTest extends TestCase\n{\n    use ContainerTrait;\n\n    private string $moduleId = 'testmodule';\n    private $workingDirectoryBackup;\n    private $workingDirectory;\n\n    protected function setup(): void\n    {\n        parent::setUp();\n        $this->assertFalse($this->isTestModuleInstalled());\n    }\n\n    protected function tearDown(): void\n    {\n        $this->cleanupTestData();\n        parent::tearDown();\n    }\n\n    public function testInstallWithAbsolutePath(): void\n    {\n        $consoleOutput = $this->executeModuleInstallCommand($this->getTestModulePath());\n\n        $this->assertSame(Command::SUCCESS, $consoleOutput);\n        $this->assertTrue($this->isTestModuleInstalled());\n    }\n\n    public function testInstallWithRelativePath(): void\n    {\n        $relativeModulePath = Path::makeRelative($this->getTestModulePath(), getcwd());\n\n        $consoleOutput = $this->executeModuleInstallCommand($relativeModulePath);\n\n        $this->assertSame(Command::SUCCESS, $consoleOutput);\n        $this->assertTrue($this->isTestModuleInstalled());\n    }\n\n    public function testInstallWithWrongModulePath(): void\n    {\n        $consoleOutput = $this->executeModuleInstallCommand('wrong-path');\n\n        $this->assertSame(Command::FAILURE, $consoleOutput);\n    }\n\n    private function executeModuleInstallCommand(string $moduleSourcePath): int\n    {\n        return (new CommandTester($this->get('console.command_loader')\n            ->get('oe:module:install')))\n            ->execute(['module-path' => $moduleSourcePath]);\n    }\n\n    private function isTestModuleInstalled(): bool\n    {\n        return $this->get(ModuleInstallerInterface::class)\n            ->isInstalled($this->getTestPackage());\n    }\n\n    private function getTestPackage(): OxidEshopPackage\n    {\n        return new OxidEshopPackage($this->getTestModulePath());\n    }\n\n    private function getTestModulePath(): string\n    {\n        return Path::join(__DIR__, 'Fixtures/modules', $this->moduleId);\n    }\n\n    private function cleanupTestData(): void\n    {\n        $this->get(ModuleInstallerInterface::class)\n            ->uninstall($this->getTestPackage());\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Command/ModuleUninstallCommandTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Command;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Tester\\CommandTester;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class ModuleUninstallCommandTest extends TestCase\n{\n    use ContainerTrait;\n\n    private string $moduleId = 'testmodule';\n\n    protected function setUp(): void\n    {\n        parent::setUp();\n        $this->installTestModule();\n        $this->assertTrue($this->isTestModuleInstalled());\n    }\n\n    protected function tearDown(): void\n    {\n        $this->cleanupTestData();\n        parent::tearDown();\n    }\n\n    public function testUninstallModule(): void\n    {\n        $consoleOutput = $this->executeModuleUninstallCommand($this->moduleId);\n\n        $this->assertSame(Command::SUCCESS, $consoleOutput);\n        $this->assertFalse($this->isTestModuleInstalled());\n    }\n\n    public function testUninstallWithWrongId(): void\n    {\n        $consoleOutput = $this->executeModuleUninstallCommand('some/wrong-module-id');\n\n        $this->assertSame(Command::FAILURE, $consoleOutput);\n    }\n\n    private function installTestModule(): void\n    {\n        $this->get(ModuleInstallerInterface::class)\n            ->install($this->getTestPackage());\n    }\n\n    private function cleanupTestData(): void\n    {\n        $this->get(ModuleInstallerInterface::class)\n            ->uninstall($this->getTestPackage());\n    }\n\n    private function executeModuleUninstallCommand(string $moduleId): int\n    {\n        return (new CommandTester($this->get('console.command_loader')\n            ->get('oe:module:uninstall')))\n            ->execute(['module-id' => $moduleId]);\n    }\n\n    private function isTestModuleInstalled(): bool\n    {\n        return $this->get(ModuleInstallerInterface::class)\n            ->isInstalled($this->getTestPackage());\n    }\n\n    private function getTestPackage(): OxidEshopPackage\n    {\n        return new OxidEshopPackage($this->getTestModulePath());\n    }\n\n    private function getTestModulePath(): string\n    {\n        return Path::join(__DIR__, 'Fixtures/modules', $this->moduleId);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Configuration/Bridge/ModuleSettingBridgeTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Configuration\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleConfigurationInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ModuleSettingBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ModuleSettingBridgeTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    public function setup(): void\n    {\n        parent::setUp();\n\n        $modulePath = realpath(__DIR__ . '/../../TestData/TestModule/');\n\n        $configurationInstaller = $this->get(ModuleConfigurationInstallerInterface::class);\n        $configurationInstaller->install($modulePath);\n    }\n\n    public function testSave(): void\n    {\n        $bridge = $this->get(ModuleSettingBridgeInterface::class);\n        $newValue = ['some new setting'];\n\n        $bridge->save('test-setting', $newValue, 'test-module');\n\n        $configurationDao = $this->get(ModuleConfigurationDaoInterface::class);\n        $configuration = $configurationDao->get('test-module', 1);\n        $this->assertSame($newValue, $configuration->getModuleSetting('test-setting')->getValue());\n    }\n\n    public function testGet(): void\n    {\n        $defaultModuleSettingValue = ['Preis', 'Hersteller'];\n        $bridge = $this->get(ModuleSettingBridgeInterface::class);\n        $this->assertSame($defaultModuleSettingValue, $bridge->get('test-setting', 'test-module'));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Configuration/Bridge/ShopConfigurationDaoBridgeTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Configuration\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ShopConfigurationDaoBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ShopConfigurationDaoBridgeTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private string $testModuleId = 'testModuleId';\n\n    public function testSaving(): void\n    {\n        $shopConfigurationDaoBridge = $this->get(ShopConfigurationDaoBridgeInterface::class);\n\n        $someModule = new ModuleConfiguration();\n        $someModule\n            ->setId('someId')\n            ->setModuleSource('test');\n\n        $shopConfiguration = new ShopConfiguration();\n        $shopConfiguration->addModuleConfiguration($someModule);\n\n        $shopConfigurationDaoBridge->save($shopConfiguration);\n\n        $this->assertEquals(\n            $shopConfiguration,\n            $shopConfigurationDaoBridge->get()\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Configuration/Dao/Chain/ClassExtensionsChainDaoTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Configuration\\Dao\\Chain;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\Chain\\ClassExtensionsChainDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ClassExtensionsChain;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ClassExtensionsChainDaoTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    public function testSaving(): void\n    {\n        $chain = new ClassExtensionsChain(['first', 'second']);\n\n        $dao = $this->get(ClassExtensionsChainDaoInterface::class);\n        $dao->saveChain(1, $chain);\n\n        $this->assertEquals($chain, $dao->getChain(1));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Configuration/Dao/Fixtures/TestModuleWithDependencies/dependencies.yaml",
    "content": "modules:\n  - module-1\n  - module-2\n  - module-3\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Configuration/Dao/Fixtures/TestModuleWithEmptyDependencyFile/dependencies.yaml",
    "content": "\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Configuration/Dao/Fixtures/project_configuration/shops/1/template_extension_chain.yaml",
    "content": "template-name-with-hyphens.html.twig:\n  - module1\ntemplate_name_with_underscores.html.twig:\n  - module1\ntemplate_name_with_underscores-and-hyphens.html.twig:\n  - module1\n'@namespace/template-name.html.twig':\n  - module1\ntemplate-extended-by-multiple-modules.html.twig:\n  - module3\n  - module2\n  - module1"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Configuration/Dao/ModuleConfigurationDaoTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Configuration\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage\\FileStorageFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class ModuleConfigurationDaoTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->prepareProjectConfiguration();\n    }\n\n    public function testSaving(): void\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration\n            ->setId('testId')\n            ->setModuleSource('test');\n\n        $dao = $this->get(ModuleConfigurationDaoInterface::class);\n        $dao->save($moduleConfiguration, 1);\n\n        $this->assertEquals(\n            $moduleConfiguration,\n            $dao->get('testId', 1)\n        );\n    }\n\n    public function testGetAllOrdersConfigurationsById(): void\n    {\n        $aModuleConfiguration = new ModuleConfiguration();\n        $aModuleConfiguration\n            ->setId('aTestId')\n            ->setModuleSource('test');\n\n        $bModuleConfiguration = new ModuleConfiguration();\n        $bModuleConfiguration\n            ->setId('bTestId')\n            ->setModuleSource('test');\n\n        $cModuleConfiguration = new ModuleConfiguration();\n        $cModuleConfiguration\n            ->setId('cTestId')\n            ->setModuleSource('test');\n\n        $dao = $this->get(ModuleConfigurationDaoInterface::class);\n        $dao->save($bModuleConfiguration, 1);\n        $dao->save($cModuleConfiguration, 1);\n        $dao->save($aModuleConfiguration, 1);\n\n        $this->assertSame(\n            [\n                'aTestId',\n                'bTestId',\n                'cTestId',\n            ],\n            array_keys($dao->getAll(1))\n        );\n    }\n\n\n\n    public function testDelete(): void\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration\n            ->setId('testDeleteId')\n            ->setModuleSource('test');\n\n        $dao = $this->get(ModuleConfigurationDaoInterface::class);\n        $dao->save($moduleConfiguration, 1);\n\n        $this->assertTrue($dao->exists('testDeleteId', 1));\n\n        $dao->delete('testDeleteId', 1);\n\n        $this->assertFalse($dao->exists('testDeleteId', 1));\n    }\n\n    public function testDeleteAll(): void\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration\n            ->setId('testId')\n            ->setModuleSource('test');\n\n        $dao = $this->get(ModuleConfigurationDaoInterface::class);\n        $dao->save($moduleConfiguration, 1);\n\n        $dao->deleteAll(1);\n\n        $this->assertEquals([], $dao->getAll(1));\n    }\n\n    public function testGetAlwaysReturnsTheSameObjectIfConfigurationWasNotChanged(): void\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration\n            ->setId('testId')\n            ->setModuleSource('test');\n\n        $dao = $this->get(ModuleConfigurationDaoInterface::class);\n        $dao->save($moduleConfiguration, 1);\n\n        $configuration = $dao->get('testId', 1);\n\n        $this->assertSame(\n            $configuration,\n            $dao->get('testId', 1)\n        );\n    }\n\n    public function testExists(): void\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration\n            ->setId('testId')\n            ->setModuleSource('test');\n\n        $dao = $this->get(ModuleConfigurationDaoInterface::class);\n        $dao->save($moduleConfiguration, 1);\n\n        $this->assertTrue(\n            $dao->exists('testId', 1)\n        );\n\n        $this->assertFalse(\n            $dao->exists('nonExistentModule', 1)\n        );\n    }\n\n    public function testWithIncorrectNode(): void\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration\n            ->setId('testId')\n            ->setModuleSource('test');\n\n        $dao = $this->get(ModuleConfigurationDaoInterface::class);\n        $dao->save($moduleConfiguration, 1);\n\n        $yamlStorage = $this->get(FileStorageFactoryInterface::class)->create(\n            Path::join(\n                $this->get(BasicContextInterface::class)->getProjectConfigurationDirectory(),\n                'shops/1/modules/testId.yaml'\n            )\n        );\n\n        $yamlStorage->save(['incorrectKey']);\n\n        $this->expectException(InvalidConfigurationException::class);\n        $dao->get('testId', 1);\n    }\n\n    private function prepareProjectConfiguration(): void\n    {\n        $this->get(ShopConfigurationDaoInterface::class)->save(\n            new ShopConfiguration(),\n            1\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Configuration/Dao/ModuleDependencyDaoTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Configuration\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleDependencyDao;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModulePathResolverInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage\\FileStorageFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\Group;\n\n#[Group('module-dependency')]\nfinal class ModuleDependencyDaoTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private string $testModuleWithDependenciesPath = __DIR__ . '/Fixtures/TestModuleWithDependencies';\n    private string $testModuleWithEmptyDependencyYamlPath = __DIR__ . '/Fixtures/TestModuleWithEmptyDependencyFile';\n\n    public function testReturnEmptyArrayIfNoDependencyYaml(): void\n    {\n        $pathResolverStub = $this->createStub(ModulePathResolverInterface::class);\n        $pathResolverStub\n            ->method('getFullModulePathFromConfiguration')\n            ->willReturn('no-module-path');\n        $moduleDependencyDao = new ModuleDependencyDao(\n            $this->get(FileStorageFactoryInterface::class),\n            $pathResolverStub,\n            $this->get(BasicContextInterface::class)\n        );\n        $moduleDependencies = $moduleDependencyDao->get('module-id');\n\n        $this->assertEmpty($moduleDependencies->getRequiredModuleIds());\n    }\n\n    public function testReturnEmptyArrayIfDependencyYamlEmpty(): void\n    {\n        $pathResolverStub = $this->createStub(ModulePathResolverInterface::class);\n        $pathResolverStub\n            ->method('getFullModulePathFromConfiguration')\n            ->willReturn($this->testModuleWithEmptyDependencyYamlPath);\n        $moduleDependencyDao = new ModuleDependencyDao(\n            $this->get(FileStorageFactoryInterface::class),\n            $pathResolverStub,\n            $this->get(BasicContextInterface::class)\n        );\n        $moduleDependencies = $moduleDependencyDao->get('module-id');\n\n        $this->assertEmpty($moduleDependencies->getRequiredModuleIds());\n    }\n\n    public function testReturnAnArrayOfDependencies(): void\n    {\n        $pathResolverStub = $this->createStub(ModulePathResolverInterface::class);\n        $pathResolverStub\n            ->method('getFullModulePathFromConfiguration')\n            ->willReturn($this->testModuleWithDependenciesPath);\n        $moduleDependencyDao = new ModuleDependencyDao(\n            $this->get(FileStorageFactoryInterface::class),\n            $pathResolverStub,\n            $this->get(BasicContextInterface::class)\n        );\n        $moduleDependencies = $moduleDependencyDao->get('module-id');\n\n        $this->assertCount(3, $moduleDependencies->getRequiredModuleIds());\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Configuration/Dao/ModuleEnvironmentConfigurationDaoTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Configuration\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Env\\EnvUrlFormatter;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleEnvironmentConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\ModuleSettingsDataMapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage\\FileStorageFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class ModuleEnvironmentConfigurationDaoTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    public function testGet(): void\n    {\n        $this->prepareTestEnvironmentShopConfigurationFile();\n        $environmentConfiguration = $this->get(ModuleEnvironmentConfigurationDaoInterface::class)\n            ->get('testModuleId', 1);\n        $expectedEnvironmentConfiguration = [\n            ModuleSettingsDataMapper::MAPPING_KEY => [\n                'settingToOverwrite' => [\n                    'value' => 'overwrittenValue',\n                ]\n            ]\n        ];\n\n        $this->assertEquals($expectedEnvironmentConfiguration, $environmentConfiguration);\n    }\n\n    public function testRemove(): void\n    {\n        $this->prepareTestEnvironmentShopConfigurationFile();\n\n        $this->get(ModuleEnvironmentConfigurationDaoInterface::class)->remove('testModuleId', 1);\n\n        $environmentConfiguration = $this->get(ModuleEnvironmentConfigurationDaoInterface::class)\n            ->get('testModuleId', 1);\n\n        $this->assertEquals([], $environmentConfiguration);\n    }\n\n    #[DoesNotPerformAssertions]\n    public function testRemoveOverwriteAlreadyBackupEnvironmentFile(): void\n    {\n        $this->prepareTestEnvironmentShopConfigurationFile();\n        $this->get(ModuleEnvironmentConfigurationDaoInterface::class)->remove('testModuleId', 1);\n\n        $this->prepareTestEnvironmentShopConfigurationFile();\n        $this->get(ModuleEnvironmentConfigurationDaoInterface::class)->remove('testModuleId', 1);\n    }\n\n    #[DoesNotPerformAssertions]\n    public function testRemoveWithNonExistingEnvironmentFile(): void\n    {\n        $this->get(ModuleEnvironmentConfigurationDaoInterface::class)->remove('testModuleId', 1);\n    }\n\n    private function prepareTestEnvironmentShopConfigurationFile(): void\n    {\n        $fileStorageFactory = $this->get(FileStorageFactoryInterface::class);\n\n        $configPath = Path::join(\n            EnvUrlFormatter::toEnvUrl(\n                $this->get(ContextInterface::class)->getProjectConfigurationDirectory()\n            ),\n            'shops',\n            '1',\n            'modules',\n            'testModuleId.yaml'\n        );\n\n        $storage = $fileStorageFactory->create($configPath);\n\n        $storage->save([\n            ModuleSettingsDataMapper::MAPPING_KEY => [\n                'settingToOverwrite' => [\n                    'value' => 'overwrittenValue',\n                ]\n            ]\n        ]);\n    }\n}"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Configuration/Dao/ModuleTemplateExtensionChainTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Configuration\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse OxidEsales\\EshopCommunity\\Tests\\TestContainerFactory;\nuse OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\BasicContextStub;\nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder;\n\nfinal class ModuleTemplateExtensionChainTest extends IntegrationTestCase\n{\n    private ShopConfigurationDaoInterface $shopConfigurationDao;\n    private int $shopId = 1;\n    private string $projectConfiguration = __DIR__ . '/Fixtures/project_configuration/';\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->shopConfigurationDao = $this->getContainer()->get(ShopConfigurationDaoInterface::class);\n    }\n\n    public function testGetWithNonExistingTemplate(): void\n    {\n        $nonExistingTemplate = uniqid('template_', true);\n        $moduleTemplateExtensions = $this->shopConfigurationDao\n            ->get($this->shopId)\n            ->getModuleTemplateExtensionChain();\n\n        $moduleIds = $moduleTemplateExtensions->getTemplateLoadingPriority($nonExistingTemplate);\n\n        $this->assertEmpty($moduleIds->getIterator()->getArrayCopy());\n    }\n\n    public function testGetWithTemplateNameWithUnderscores(): void\n    {\n        $existingTemplate = 'template_name_with_underscores.html.twig';\n        $moduleTemplateExtensions = $this->shopConfigurationDao\n            ->get($this->shopId)\n            ->getModuleTemplateExtensionChain();\n\n        $moduleIds = $moduleTemplateExtensions->getTemplateLoadingPriority($existingTemplate);\n\n        $this->assertEquals(['module1'], $moduleIds->getIterator()->getArrayCopy());\n    }\n\n    public function testGetWithTemplateNameWithHyphens(): void\n    {\n        $existingTemplate = 'template-name-with-hyphens.html.twig';\n        $moduleTemplateExtensions = $this->shopConfigurationDao\n            ->get($this->shopId)\n            ->getModuleTemplateExtensionChain();\n\n        $moduleIds = $moduleTemplateExtensions->getTemplateLoadingPriority($existingTemplate);\n\n        $this->assertEquals(['module1'], $moduleIds->getIterator()->getArrayCopy());\n    }\n\n    public function testGetWithTemplateNameWithUnderscoresAndHyphens(): void\n    {\n        $existingTemplate = 'template_name_with_underscores-and-hyphens.html.twig';\n        $moduleTemplateExtensions = $this->shopConfigurationDao\n            ->get($this->shopId)\n            ->getModuleTemplateExtensionChain();\n\n        $moduleIds = $moduleTemplateExtensions->getTemplateLoadingPriority($existingTemplate);\n\n        $this->assertEquals(['module1'], $moduleIds->getIterator()->getArrayCopy());\n    }\n\n    public function testGetWithTemplateNameWithNamespace(): void\n    {\n        $existingTemplate = '@namespace/template-name.html.twig';\n        $moduleTemplateExtensions = $this->shopConfigurationDao\n            ->get($this->shopId)\n            ->getModuleTemplateExtensionChain();\n\n        $moduleIds = $moduleTemplateExtensions->getTemplateLoadingPriority($existingTemplate);\n\n        $this->assertEquals(['module1'], $moduleIds->getIterator()->getArrayCopy());\n    }\n\n    public function testGetWithMultipleModuleIds(): void\n    {\n        $existingTemplate = 'template-extended-by-multiple-modules.html.twig';\n        $moduleTemplateExtensions = $this->shopConfigurationDao\n            ->get($this->shopId)\n            ->getModuleTemplateExtensionChain();\n\n        $moduleIds = $moduleTemplateExtensions->getTemplateLoadingPriority($existingTemplate);\n\n        $this->assertEquals(['module3', 'module2', 'module1'], $moduleIds->getIterator()->getArrayCopy());\n    }\n\n    private function getContainer(): ContainerBuilder\n    {\n        $context = new BasicContextStub();\n        $context->setProjectConfigurationDirectory($this->projectConfiguration);\n\n        $container = (new TestContainerFactory())->create();\n        $container->set(BasicContextInterface::class, $context);\n        $container->autowire(BasicContextInterface::class, BasicContextStub::class);\n        $container->compile();\n\n        return $container;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Configuration/Dao/ShopConfigurationDaoTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Configuration\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Env\\EnvUrlFormatter;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\{\n    ModuleConfiguration\\ModuleSettingsDataMapper};\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\ShopConfigurationNotFoundException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setting\\Setting;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage\\FileStorageFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class ShopConfigurationDaoTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private string $testModuleId = 'testModuleId';\n    private string $testedSetting = 'settingToOverwrite';\n    private string $originalValue = 'some-original-value';\n    private string $newValue = 'some-new-value';\n\n    public function testSave(): void\n    {\n        $shopConfigurationDao = $this->get(ShopConfigurationDaoInterface::class);\n\n        $module = new ModuleConfiguration();\n        $module\n            ->setId('test')\n            ->setModuleSource('test');\n\n        $shopConfigurationWithModule = new ShopConfiguration();\n        $shopConfigurationWithModule->addModuleConfiguration($module);\n        $shopConfigurationDao->save($shopConfigurationWithModule, 1);\n\n        $shopConfiguration = new ShopConfiguration();\n        $shopConfigurationDao->save($shopConfiguration, 2);\n\n        $this->assertEquals(\n            $shopConfigurationWithModule,\n            $shopConfigurationDao->get(1)\n        );\n\n        $this->assertEquals(\n            $shopConfiguration,\n            $shopConfigurationDao->get(2)\n        );\n    }\n\n    public function testEnvironmentConfigurationOverwritesShopConfiguration(): void\n    {\n        $this->configureModuleInShopFile();\n        $this->configureModuleInEnvironmentFile();\n\n        $this->assertSame(\n            $this->newValue,\n            $this->get(ShopConfigurationDaoInterface::class)\n                ->get(1)\n                ->getModuleConfiguration($this->testModuleId)\n                ->getModuleSetting($this->testedSetting)\n                ->getValue()\n        );\n    }\n\n    public function testGetAll(): void\n    {\n        $shopConfigurationDao = $this->get(ShopConfigurationDaoInterface::class);\n        $shopConfigurationDao->save(new ShopConfiguration(), 1);\n\n        $this->assertEquals(\n            new ShopConfiguration(),\n            $shopConfigurationDao->get(1)\n        );\n\n        $shopConfigurationDao->save(new ShopConfiguration(), 3);\n\n        $this->assertEquals(\n            [\n                1 => new ShopConfiguration(),\n                3 => new ShopConfiguration(),\n            ],\n            $shopConfigurationDao->getAll()\n        );\n    }\n\n    public function testGetIncorrectShopId(): void\n    {\n        $this->expectException(ShopConfigurationNotFoundException::class);\n        $shopConfigurationDao = $this->get(ShopConfigurationDaoInterface::class);\n        $shopConfigurationDao->save(new ShopConfiguration(), 1);\n        $shopConfigurationDao->save(new ShopConfiguration(), 2);\n        $shopConfigurationDao->save(new ShopConfiguration(), 3);\n\n        $this->expectException(ShopConfigurationNotFoundException::class);\n        $shopConfigurationDao->get(99);\n    }\n\n    public function testGetCorrectShopId(): void\n    {\n        $shopConfigurationDao = $this->get(ShopConfigurationDaoInterface::class);\n        $shopConfigurationDao->save(new ShopConfiguration(), 1);\n\n        $shopConfiguration = $shopConfigurationDao->get(1);\n\n        $this->assertEquals(\n            $shopConfiguration,\n            $shopConfigurationDao->get(1)\n        );\n    }\n\n    public function testRemovingModuleConfiguration(): void\n    {\n        $shopConfigurationDao = $this->get(ShopConfigurationDaoInterface::class);\n\n        $module = new ModuleConfiguration();\n        $module\n            ->setId('test')\n            ->setModuleSource('test');\n\n        $shopConfiguration = new ShopConfiguration();\n        $shopConfiguration->addModuleConfiguration($module);\n        $shopConfigurationDao->save($shopConfiguration, 1);\n\n        $shopConfiguration->deleteModuleConfiguration('test');\n\n        $shopConfigurationDao->save($shopConfiguration, 1);\n\n        $this->assertEquals([], $shopConfigurationDao->get(1)->getModuleConfigurations());\n    }\n\n    public function testDeleteAll(): void\n    {\n        $this->expectException(ShopConfigurationNotFoundException::class);\n        $shopConfigurationDao = $this->get(ShopConfigurationDaoInterface::class);\n        $shopConfigurationDao->save(new ShopConfiguration(), 1);\n        $shopConfigurationDao->save(new ShopConfiguration(), 2);\n        $shopConfigurationDao->save(new ShopConfiguration(), 3);\n\n        $shopConfigurationDao->deleteAll();\n\n        $this->expectException(ShopConfigurationNotFoundException::class);\n        $this->assertEquals(\n            [],\n            $shopConfigurationDao->get(1)\n        );\n    }\n\n    private function configureModuleInEnvironmentFile(): void\n    {\n        $fileStorageFactory = $this->get(FileStorageFactoryInterface::class);\n\n        $configPath = Path::join(\n            EnvUrlFormatter::toEnvUrl(\n                $this->get(ContextInterface::class)->getProjectConfigurationDirectory()\n            ),\n            'shops',\n            '1',\n            'modules',\n            'testModuleId.yaml'\n        );\n\n        $storage = $fileStorageFactory->create($configPath);\n\n        $storage->save([\n            ModuleSettingsDataMapper::MAPPING_KEY => [\n                $this->testedSetting => ['value' => $this->newValue],\n            ]\n        ]);\n    }\n\n    private function configureModuleInShopFile(): void\n    {\n        $originalModuleSetting = new Setting();\n        $originalModuleSetting\n            ->setName($this->testedSetting)\n            ->setValue($this->originalValue)\n            ->setType('int');\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration\n            ->setId($this->testModuleId)\n            ->setModuleSource('test')\n            ->addModuleSetting($originalModuleSetting);\n        $shopConfiguration = new ShopConfiguration();\n        $shopConfiguration->addModuleConfiguration($moduleConfiguration);\n        $this->get(ShopConfigurationDaoInterface::class)->save($shopConfiguration, 1);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Configuration/Dao/ShopEnvironmentMisconfigurationEventSubscriberTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Configuration\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopEnvironmentWithOrphanSettingEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\ContextStub;\nuse Psr\\Log\\LogLevel;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class ShopEnvironmentMisconfigurationEventSubscriberTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private ?string $testLog = null;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        $this->prepareLogger();\n    }\n\n    public function tearDown(): void\n    {\n        $this->cleanupTestLog();\n        parent::tearDown();\n    }\n\n    public function testLogIsCreatedOnEventDispatch(): void\n    {\n        $this->get(EventDispatcherInterface::class)\n            ->dispatch(\n                new ShopEnvironmentWithOrphanSettingEvent(\n                    123,\n                    'some-module',\n                    'some-setting'\n                )\n            );\n\n        $this->assertFileExists($this->testLog);\n    }\n\n    private function prepareLogger(): void\n    {\n        /** @var ContextStub $context */\n        $this->createContainer();\n        $context = $this->get(ContextInterface::class);\n        $logDirectory = Path::getDirectory($context->getLogFilePath());\n        $testLogFile = uniqid('test.log.', true);\n        $this->testLog = Path::join($logDirectory, $testLogFile);\n        $context->setLogFilePath($this->testLog);\n        $context->setLogLevel(LogLevel::WARNING);\n        $this->replaceContainerInstance();\n    }\n\n    private function cleanupTestLog(): void\n    {\n        if (\\file_exists($this->testLog)) {\n            \\unlink($this->testLog);\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Configuration/DataMapper/ModuleConfigurationDataMapperTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Configuration\\DataMapper;\n\nuse MyVendor\\MyController\\Controller1;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\ClassExtensionsDataMapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\ControllersDataMapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\EventsDataMapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\ModuleSettingsDataMapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfigurationDataMapperInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ModuleConfigurationDataMapperTest extends TestCase\n{\n    use ContainerTrait;\n\n    public function testMapping(): void\n    {\n        $configurationData = [\n            'id'          => 'moduleId',\n            'moduleSource' => 'relativePath',\n            'version'     => '7.0',\n            'activated'   => true,\n            'title'       => ['en' => 'title'],\n            'description' => [\n                'de' => 'description de',\n                'en' => 'description en',\n            ],\n            'lang'        => 'en',\n            'thumbnail'   => 'logo.png',\n            'author'      => 'author',\n            'url'         => 'http://example.com',\n            'email'       => 'test@example.com',\n            'keyWithoutDataMapperAssigned' => [\n                'subkey' => 'subvalue'\n            ],\n            ClassExtensionsDataMapper::MAPPING_KEY => [\n                'shopClass' => 'moduleClass',\n            ],\n            ControllersDataMapper::MAPPING_KEY => [\n                'controller1' => Controller1::class,\n            ],\n            EventsDataMapper::MAPPING_KEY => [\n                'onActivate'   => 'MyEvents::onActivate'\n            ],\n            ModuleSettingsDataMapper::MAPPING_KEY => [\n                'name' => [\n                    'group'         => 'name',\n                    'type'          => 'type',\n                    'value'         => true,\n                    'position'      => 4,\n                    'constraints'   => [1, 2],\n                ]\n            ]\n        ];\n\n        $moduleConfigurationDataMapper = $this->get(ModuleConfigurationDataMapperInterface::class);\n\n        $moduleConfiguration = new ModuleConfiguration();\n\n        $moduleConfiguration = $moduleConfigurationDataMapper->fromData($moduleConfiguration, $configurationData);\n\n        $this->assertEquals(\n            $this->removeKeysWithoutAssignedDataMapper($configurationData),\n            $moduleConfigurationDataMapper->toData($moduleConfiguration)\n        );\n    }\n\n    private function removeKeysWithoutAssignedDataMapper(array $configurationData): array\n    {\n        unset($configurationData['keyWithoutDataMapperAssigned']);\n        return $configurationData;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Configuration/Service/ModuleConfigurationMergingServiceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Configuration\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ClassExtensionsChain;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\ClassExtension;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Service\\{\n    ModuleConfigurationMergingServiceInterface};\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setting\\Setting;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ModuleConfigurationMergingServiceTest extends TestCase\n{\n    use ContainerTrait;\n\n    public function testMergeNewModuleConfiguration(): void\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->setId('newModule');\n\n        $moduleConfigurationMergingService = $this->getMergingService();\n        $shopConfiguration = $moduleConfigurationMergingService->merge(new ShopConfiguration(), $moduleConfiguration);\n\n        $this->assertEquals(\n            $moduleConfiguration,\n            $shopConfiguration->getModuleConfiguration('newModule')\n        );\n    }\n\n    public function testNewMergedModuleConfigurationIsCloned(): void\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->setId('newModule');\n\n        $moduleConfigurationMergingService = $this->getMergingService();\n        $shopConfiguration = $moduleConfigurationMergingService->merge(new ShopConfiguration(), $moduleConfiguration);\n\n        $this->assertNotSame(\n            $moduleConfiguration,\n            $shopConfiguration->getModuleConfiguration('newModule')\n        );\n    }\n\n    public function testExtensionClassAppendToChainAfterMergingNewModuleConfiguration(): void\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->setId('newModule');\n        $moduleConfiguration->addClassExtension(\n            new ClassExtension(\n                'shopClass',\n                'testModuleClassExtendsShopClass'\n            )\n        );\n\n        $shopConfigurationWithChain = new ShopConfiguration();\n        $chain = new ClassExtensionsChain();\n        $chain->setChain([\n            'shopClass'             => ['alreadyInstalledShopClass', 'anotherAlreadyInstalledShopClass'],\n            'someAnotherShopClass'  => ['alreadyInstalledShopClass'],\n        ]);\n\n        $shopConfigurationWithChain->setClassExtensionsChain($chain);\n\n        $moduleConfigurationMergingService = $this->getMergingService();\n        $shopConfiguration = $moduleConfigurationMergingService->merge(\n            $shopConfigurationWithChain,\n            $moduleConfiguration\n        );\n\n        $this->assertSame(\n            [\n                'shopClass'             => [\n                    'alreadyInstalledShopClass',\n                    'anotherAlreadyInstalledShopClass',\n                    'testModuleClassExtendsShopClass',\n                ],\n                'someAnotherShopClass'  => ['alreadyInstalledShopClass'],\n            ],\n            $shopConfiguration->getClassExtensionsChain()->getChain()\n        );\n        $this->assertEquals(\n            $moduleConfiguration,\n            $shopConfiguration->getModuleConfiguration('newModule')\n        );\n    }\n\n    public function testMergeModuleConfigurationOfAlreadyInstalledModule(): void\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->setId('installedModule');\n\n        $moduleConfigurationMergingService = $this->getMergingService();\n        $shopConfiguration = $moduleConfigurationMergingService->merge(\n            $this->getShopConfigurationWithAlreadyInstalledModule(),\n            $moduleConfiguration\n        );\n\n        $this->assertEquals(\n            $moduleConfiguration,\n            $shopConfiguration->getModuleConfiguration('installedModule')\n        );\n    }\n\n    public function testMergeSetsModuleConfigurationIfNoExistingModuleConfigurationInstalled(): void\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->setId('installedModule');\n\n        $shopConfiguration = new ShopConfiguration();\n\n        $moduleConfigurationMergingService = $this->getMergingService();\n        $shopConfiguration = $moduleConfigurationMergingService->merge(\n            $shopConfiguration,\n            $moduleConfiguration\n        );\n\n        $this->assertEquals(\n            $moduleConfiguration,\n            $shopConfiguration->getModuleConfiguration('installedModule')\n        );\n    }\n\n    public function testExtensionClassChainUpdatedAfterMergingAlreadyInstalledModule(): void\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->setId('installedModule');\n\n        $classExtension = [\n            'shopClass1' => 'extension1ToStayInNewModuleConfiguration',\n            'shopClass2' => 'extension5',\n            'shopClass5' => 'extension6'\n        ];\n\n        foreach ($classExtension as $namespace => $moduleExtension) {\n            $moduleConfiguration->addClassExtension(\n                new ClassExtension(\n                    $namespace,\n                    $moduleExtension\n                )\n            );\n        }\n\n        $moduleConfigurationMergingService = $this->getMergingService();\n        $shopConfiguration = $moduleConfigurationMergingService->merge(\n            $this->getShopConfigurationWithAlreadyInstalledModule(),\n            $moduleConfiguration\n        );\n\n        $this->assertEquals(\n            [\n                'shopClass1' => ['someOtherExtension1', 'extension1ToStayInNewModuleConfiguration'],\n                'shopClass2' => ['someOtherExtension2', 'extension5', 'someOtherExtension3'],\n                'shopClass3' => ['someOtherExtension4'],\n                'shopClass5' => ['extension6']\n            ],\n            $shopConfiguration->getClassExtensionsChain()->getChain()\n        );\n    }\n\n    public function testSettingUpdatedAfterMergingAlreadyInstalledModule(): void\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->setId('installedModule');\n\n        $moduleSettings = [\n            [\n                'name'     => 'existingValueIsTaken1',\n                'group'    => 'oldGroup',\n                'type'     => 'int',\n                'position' => '100500'\n            ],\n            [\n                'name'     => 'withTypeToChange',\n                'type'     => 'bool',\n                'position' => '100500',\n                'value'    => 'true',\n            ],\n            [\n                'name'     => 'existingValueIsTaken2',\n                'type'     => 'str',\n                'position' => '100500'\n            ],\n            [\n                'name'        => 'existingValueIsTaken3',\n                'type'        => 'select',\n                'constraints' => ['1', '2', '3'],\n                'position'    => '100500',\n            ],\n            [\n                'name'        => 'existingValueNotTaken',\n                'type'        => 'select',\n                'constraints' => ['1', '2'],\n                'position'    => '100500',\n                'value'       => '2'\n            ],\n            [\n                'name'     => 'completeNewOne',\n                'type'     => 'string',\n                'position' => '100500',\n                'value'    => 'myValue'\n            ]\n        ];\n\n        foreach ($moduleSettings as $settingData) {\n            $setting = new Setting();\n\n            $setting->setType($settingData['type']);\n            $setting->setName($settingData['name']);\n\n            if (isset($settingData['value'])) {\n                $setting->setValue($settingData['value']);\n            }\n\n            if (isset($settingData['group'])) {\n                $setting->setGroupName($settingData['group']);\n            }\n\n            if (isset($settingData['position'])) {\n                $setting->setPositionInGroup((int)$settingData['position']);\n            }\n\n            if (isset($settingData['constraints'])) {\n                $setting->setConstraints($settingData['constraints']);\n            }\n\n            $moduleConfiguration->addModuleSetting($setting);\n        }\n\n        $moduleConfigurationMergingService = $this->getMergingService();\n        $shopConfiguration = $moduleConfigurationMergingService->merge(\n            $this->getShopConfigurationWithAlreadyInstalledModule(),\n            $moduleConfiguration\n        );\n\n        $mergedModuleConfiguration = $shopConfiguration->getModuleConfiguration('installedModule');\n\n        $settings = [];\n\n        foreach ($mergedModuleConfiguration->getModuleSettings() as $index => $setting) {\n            if ($setting->getGroupName()) {\n                $settings[$index]['group'] = $setting->getGroupName();\n            }\n\n            if ($setting->getName()) {\n                $settings[$index]['name'] = $setting->getName();\n            }\n\n            if ($setting->getType()) {\n                $settings[$index]['type'] = $setting->getType();\n            }\n\n            $settings[$index]['value'] = $setting->getValue();\n\n            if ($setting->getPositionInGroup()) {\n                $settings[$index]['position'] = $setting->getPositionInGroup();\n            }\n\n            if (!empty($setting->getConstraints())) {\n                $settings[$index]['constraints'] = $setting->getConstraints();\n            }\n        }\n\n        $this->assertEquals(\n            [\n                [\n                    'name'     => 'existingValueIsTaken1',\n                    'group'    => 'oldGroup',\n                    'type'     => 'int',\n                    'position' => '100500',\n                    'value'    => '1'\n                ],\n                [\n                    'name'     => 'withTypeToChange',\n                    'type'     => 'bool',\n                    'position' => '100500',\n                    'value'    => 'true'\n                ],\n                [\n                    'name'     => 'existingValueIsTaken2',\n                    'type'     => 'str',\n                    'position' => '100500',\n                    'value'    => 'keep'\n                ],\n                [\n                    'name'        => 'existingValueIsTaken3',\n                    'type'        => 'select',\n                    'constraints' => ['1', '2', '3'],\n                    'position'    => '100500',\n                    'value'       => '3',\n                ],\n                [\n                    'name'        => 'existingValueNotTaken',\n                    'type'        => 'select',\n                    'constraints' => ['1', '2'],\n                    'position'    => '100500',\n                    'value'       => '2',\n                ],\n                [\n                    'name'     => 'completeNewOne',\n                    'type'     => 'string',\n                    'position' => '100500',\n                    'value'    => 'myValue'\n                ]\n            ],\n            $settings\n        );\n    }\n\n    public function testActivatedOptionValueStaysTheSameAfterMerging(): void\n    {\n        $newModuleConfiguration = new ModuleConfiguration();\n        $newModuleConfiguration->setId('test');\n        $newModuleConfiguration->setActivated(false);\n\n        $oldModuleConfiguration = new ModuleConfiguration();\n        $oldModuleConfiguration->setId('test');\n        $oldModuleConfiguration->setActivated(true);\n\n        $shopConfiguration = new ShopConfiguration();\n        $shopConfiguration->addModuleConfiguration($oldModuleConfiguration);\n\n        $moduleConfigurationMergingService = $this->getMergingService();\n        $shopConfiguration = $moduleConfigurationMergingService->merge(\n            $shopConfiguration,\n            $newModuleConfiguration\n        );\n\n        $this->assertTrue(\n            $shopConfiguration->getModuleConfiguration('test')->isActivated()\n        );\n    }\n\n    private function getShopConfigurationWithAlreadyInstalledModule(): ShopConfiguration\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->setId('installedModule');\n\n        $classExtension = [\n            'shopClass1'            => 'extension1ToStayInNewModuleConfiguration',\n            'shopClass2'            => 'extension2ToBeChanged',\n            'shopClass3'            => 'extension3ToBeDeleted',\n            'shopClass4ToBeDeleted' => 'extension4ToBeDeleted'\n        ];\n\n        foreach ($classExtension as $namespace => $moduleExtension) {\n            $moduleConfiguration->addClassExtension(\n                new ClassExtension(\n                    $namespace,\n                    $moduleExtension\n                )\n            );\n        }\n\n        $moduleSettings =\n            [\n                [\n                    'name'     => 'existingValueIsTaken1',\n                    'group'    => 'oldGroup',\n                    'type'     => 'int',\n                    'position' => '100500',\n                    'value'    => '1',\n                ],\n                [\n                    'name'     => 'withTypeToChange',\n                    'type'     => 'str',\n                    'position' => '100500',\n                    'value'    => 'toDelete',\n                ],\n                [\n                    'name'     => 'existingValueIsTaken2',\n                    'type'     => 'str',\n                    'position' => '100500',\n                    'value'    => 'keep',\n                ],\n                [\n                    'name'        => 'existingValueIsTaken3',\n                    'type'        => 'select',\n                    'constraints' => ['1', '2', '3'],\n                    'position'    => '100500',\n                    'value'       => '3',\n                ],\n                [\n                    'name'        => 'existingValueNotTaken',\n                    'type'        => 'select',\n                    'constraints' => ['1', '2', '3'],\n                    'position'    => '100500',\n                    'value'       => '3',\n                ],\n                [\n                    'name'     => 'willBeDeleted',\n                    'type'     => 'str',\n                    'position' => '100500',\n                    'value'    => 'myValue1',\n                ]\n            ];\n\n        foreach ($moduleSettings as $settingData) {\n            $setting = new Setting();\n\n            $setting->setType($settingData['type']);\n            $setting->setName($settingData['name']);\n            $setting->setValue($settingData['value']);\n\n            if (isset($settingData['group'])) {\n                $setting->setGroupName($settingData['group']);\n            }\n\n            if (isset($settingData['position'])) {\n                $setting->setPositionInGroup((int)$settingData['position']);\n            }\n\n            if (isset($settingData['constraints'])) {\n                $setting->setConstraints($settingData['constraints']);\n            }\n\n            $moduleConfiguration->addModuleSetting($setting);\n        }\n\n        $chain = new ClassExtensionsChain();\n        $chain->setChain([\n            'shopClass1'            => ['someOtherExtension1', 'extension1ToStayInNewModuleConfiguration'],\n            'shopClass2'            => ['someOtherExtension2', 'extension2ToBeChanged', 'someOtherExtension3'],\n            'shopClass3'            => ['extension3ToBeDeleted', 'someOtherExtension4'],\n            'shopClass4ToBeDeleted' => ['extension4ToBeDeleted']\n        ]);\n\n        $shopConfiguration = new ShopConfiguration();\n        $shopConfiguration->addModuleConfiguration($moduleConfiguration);\n        $shopConfiguration->setClassExtensionsChain($chain);\n\n        return $shopConfiguration;\n    }\n\n    private function getMergingService(): ModuleConfigurationMergingServiceInterface\n    {\n        return $this->get(ModuleConfigurationMergingServiceInterface::class);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Controller/Fixtures/module1/composer.json",
    "content": "{\n  \"name\": \"oxid-esales/module1\",\n  \"description\": \"\",\n  \"type\": \"oxideshop-module\",\n  \"license\": [\n    \"GPL-3.0\"\n  ],\n  \"autoload\": {\n    \"psr-4\": {\n      \"OxidEsales\\\\Module1\\\\\": \"src/\"\n    }\n  }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Controller/Fixtures/module1/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Controller\\Fixtures\\module1\\src\\Controller\\ModuleController;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Controller\\Fixtures\\module1\\src\\Controller\\ModuleControllerMissingTemplate;\n\n$sMetadataVersion = '2.1';\n\n$aModule = [\n    'id' => 'module1',\n    'controllers' => [\n        'module1_controller' => ModuleController::class,\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Controller/Fixtures/module1/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Controller\\Fixtures\\module1\\src\\Controller\\ModuleControllerAsService:\n    tags:\n      - { name: 'oxid.view_controller', controller_key: 'test_module_controller_as_service' }\n    public: true\n\n  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Controller\\Fixtures\\module1\\src\\Controller\\ModuleControllerDecorator:\n    decorates: OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Controller\\Fixtures\\module1\\src\\Controller\\ModuleControllerAsService\n    arguments: [ '@.inner' ]\n    public: true\n\n  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Controller\\Fixtures\\module1\\src\\Controller\\ApiTestController:\n    public: true"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Controller/Fixtures/module1/src/Controller/ApiTestController.php",
    "content": "<?php\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Controller\\Fixtures\\module1\\src\\Controller;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse Symfony\\Component\\HttpFoundation\\JsonResponse;\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Symfony\\Component\\Routing\\Attribute\\Route;\nuse Symfony\\Component\\Routing\\Requirement\\Requirement;\n\nreadonly class ApiTestController\n{\n    public function __construct(private ModuleConfigurationDaoInterface $moduleConfigurationDao)\n    {\n    }\n\n    #[Route('/api/{name}/{id}/', requirements: ['id' => Requirement::DIGITS], methods: ['GET'])]\n    public function fly(string $name, int $id): Response\n    {\n        return new JsonResponse(\n            [\n                'name' => $name,\n                'id' => $id,\n                'configParameter' => 'hello',\n            ]\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Controller/Fixtures/module1/src/Controller/ModuleController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Controller\\Fixtures\\module1\\src\\Controller;\n\nuse OxidEsales\\Eshop\\Application\\Controller\\FrontendController;\n\nclass ModuleController extends FrontendController\n{\n    protected $_sThisTemplate = '@module1/module_controller';\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Controller/Fixtures/module1/src/Controller/ModuleControllerAsService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Controller\\Fixtures\\module1\\src\\Controller;\n\nuse OxidEsales\\EshopCommunity\\Core\\Controller\\BaseController;\n\nclass ModuleControllerAsService extends BaseController\n{\n    protected $_sThisTemplate = '@module1/module_controller_as_service';\n\n    public function testFunction(): void\n    {\n        echo 'Function output';\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Controller/Fixtures/module1/src/Controller/ModuleControllerDecorator.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Controller\\Fixtures\\module1\\src\\Controller;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Controller\\AbstractControllerDecorator;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Controller\\ViewControllerInterface;\n\nclass ModuleControllerDecorator extends AbstractControllerDecorator\n{\n    public function __construct(protected readonly ViewControllerInterface $controller)\n    {\n\n    }\n\n    public function init()\n    {\n        echo \"Init Decorator\";\n        $this->controller->init();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Controller/Fixtures/module1/views/twig/module_controller.html.twig",
    "content": "<h1>module1/module_controller</h1>\n{{ 'now' | date('Y-m-d') }}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Controller/Fixtures/module1/views/twig/module_controller_as_service.html.twig",
    "content": "<h1>module1/module_controller_as_service</h1>\n{{ 'now' | date('Y-m-d') }}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Controller/ModuleControllerApiTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Controller;\n\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse PHPUnit\\Framework\\Attributes\\RunTestsInSeparateProcesses;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\BrowserKit\\HttpBrowser;\n\n#[RunTestsInSeparateProcesses]\nfinal class ModuleControllerApiTest extends TestCase\n{\n    protected function setUp(): void\n    {\n        parent::setUp();\n\n        $this->setupModuleFixture('module1');\n    }\n\n    protected function tearDown(): void\n    {\n        $this->uninstallModuleFixture('module1');\n\n        parent::tearDown();\n    }\n\n    public function testApiController(): void\n    {\n        $shopUrl = Registry::getConfig()->getShopUrl();\n        $httpBrowser = new HttpBrowser();\n        $httpBrowser->request('GET', $shopUrl . 'api/oxid/8/');\n        $response = $httpBrowser->getResponse();\n\n        $this->assertSame(200, $response->getStatusCode());\n        $this->assertSame(\n            [\n                'name' => 'oxid',\n                'id' => 8,\n                'configParameter' => 'hello',\n            ],\n            \\json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR)\n        );\n    }\n\n    public function testApiControllerWrongRequest(): void\n    {\n        $shopUrl = Registry::getConfig()->getShopUrl();\n        $httpBrowser = new HttpBrowser();\n        $httpBrowser->request('GET', $shopUrl . 'api/oxid/notInt/');\n        $response = $httpBrowser->getResponse();\n\n        $this->assertSame(404, $response->getStatusCode());\n    }\n\n    private function get(string $serviceId)\n    {\n        return ContainerFacade::get($serviceId);\n    }\n\n    private function setupModuleFixture(string $moduleId): void\n    {\n        $this->installModuleFixture($moduleId);\n        $this->activateModuleFixture($moduleId);\n    }\n\n    private function installModuleFixture(string $moduleId): void\n    {\n        $this->get(ModuleInstallerInterface::class)\n            ->install($this->getPackageFixture($moduleId));\n    }\n\n    private function activateModuleFixture(string $moduleId): void\n    {\n        $this->get(ModuleActivationBridgeInterface::class)\n            ->activate($moduleId, $this->get(BasicContextInterface::class)->getDefaultShopId());\n    }\n\n    private function uninstallModuleFixture(string $moduleId): void\n    {\n        $this->get(ModuleInstallerInterface::class)\n            ->uninstall($this->getPackageFixture($moduleId));\n    }\n\n    private function getPackageFixture(string $moduleId): OxidEshopPackage\n    {\n        return new OxidEshopPackage(\"{$this->getFixturesDirectory()}/$moduleId/\");\n    }\n\n    private function getFixturesDirectory(): string\n    {\n        return __DIR__ . \"/Fixtures\";\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Controller/ModuleControllerRenderTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Controller;\n\nuse OxidEsales\\Eshop\\Core\\ShopControl;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\RunTestsInSeparateProcesses;\n\n#[RunTestsInSeparateProcesses]\nfinal class ModuleControllerRenderTest extends IntegrationTestCase\n{\n\tprivate ShopControl $shopControl;\n\n\tpublic function setUp(): void\n    {\n        parent::setUp();\n\n\t    $_GET['searchparam'] = '';\n\t    $_GET['page'] = '';\n\t    $_GET['tpl'] = '';\n\n        $this->setupModuleFixture('module1');\n\n        $this->shopControl = new ShopControl();\n    }\n\n    public function tearDown(): void\n    {\n        $this->uninstallModuleFixture('module1');\n\n        parent::tearDown();\n    }\n\n    public function testRenderTraditionalController(): void\n    {\n        ob_start();\n\t    $this->shopControl->start('module1_controller', '');\n\t    $output = ob_get_clean();\n\n\t    $this->assertStringContainsString('module1/module_controller', $output);\n    }\n\n    public function testRenderServiceController(): void\n    {\n        ob_start();\n        $this->shopControl->start('test_module_controller_as_service', '');\n        $output = ob_get_clean();\n\n        $this->assertStringContainsString('module1/module_controller_as_service', $output);\n    }\n\n    public function testRenderServiceControllerWithFunction(): void\n    {\n        ob_start();\n        $this->shopControl->start('test_module_controller_as_service', 'testFunction');\n        $output = ob_get_clean();\n\n        $this->assertStringContainsString('module1/module_controller_as_service', $output);\n        $this->assertStringContainsString('Function output', $output);\n    }\n\n    public function testControllerDecorator(): void\n    {\n        ob_start();\n        $this->shopControl->start('test_module_controller_as_service', 'testFunction');\n        $output = ob_get_clean();\n\n        $this->assertStringContainsString('module1/module_controller_as_service', $output);\n        $this->assertStringContainsString('Init Decorator', $output);\n    }\n\n    private function setupModuleFixture(string $moduleId): void\n    {\n        $this->installModuleFixture($moduleId);\n        $this->activateModuleFixture($moduleId);\n    }\n\n    private function installModuleFixture(string $moduleId): void\n    {\n        $this->get(ModuleInstallerInterface::class)\n            ->install($this->getPackageFixture($moduleId));\n    }\n\n    private function activateModuleFixture(string $moduleId): void\n    {\n        $this->get(ModuleActivationBridgeInterface::class)\n            ->activate($moduleId, $this->get(BasicContextInterface::class)->getDefaultShopId());\n    }\n\n    private function uninstallModuleFixture(string $moduleId): void\n    {\n        $this->get(ModuleInstallerInterface::class)\n            ->uninstall($this->getPackageFixture($moduleId));\n    }\n\n    private function getPackageFixture(string $moduleId): OxidEshopPackage\n    {\n        return new OxidEshopPackage(\"{$this->getFixturesDirectory()}/$moduleId/\");\n    }\n\n    private function getFixturesDirectory(): string\n    {\n        return __DIR__ . \"/Fixtures\";\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Facade/ActiveModulesDataProviderTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Facade;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Cache\\ModuleCacheInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ClassExtensionsChain;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\ClassExtension;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\Controller;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ActiveModulesDataProvider;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ActiveModulesDataProviderInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModulePathResolverInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ActiveClassExtensionChainResolverInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ModuleActivationServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContext;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\Group;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class ActiveModulesDataProviderTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private string $activeModuleId = 'activeModuleId';\n    private string $activeModulePath = 'some-path-active';\n    private string $activeModuleSource = 'some-source-active';\n    private string $inactiveModuleId = 'inActiveModuleId';\n    private string $inactiveModulePath = 'some-path-inactive';\n    private string $inactiveModuleSource = 'some-source-inactive';\n\n    private BasicContext $context;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->context = new BasicContext();\n        $this->prepareTestShopConfiguration();\n    }\n\n    public function tearDown(): void\n    {\n        $this->cleanUpTestData();\n\n        parent::tearDown();\n    }\n\n    public function testGetModuleIds(): void\n    {\n        $this->assertSame(\n            [$this->activeModuleId],\n            $this->get(ActiveModulesDataProviderInterface::class)->getModuleIds()\n        );\n    }\n\n    public function testGetModuleIdsUsesCacheIfItExists(): void\n    {\n        $cache = $this->getDummyCache();\n        $cache->put('active_module_ids', ['cachedModuleId']);\n\n        $activeModulesDataProvider = $this->getActiveModulesDataProviderWithCache($cache);\n\n        $this->assertSame(\n            ['cachedModuleId'],\n            $activeModulesDataProvider->getModuleIds()\n        );\n    }\n\n    public function testGetModuleIdsPopulatesCacheIfItDoesNotExist(): void\n    {\n        $cache = $this->getDummyCache();\n        $activeModulesDataProvider = $this->getActiveModulesDataProviderWithCache($cache);\n\n        $this->assertSame(\n            [$this->activeModuleId],\n            $activeModulesDataProvider->getModuleIds()\n        );\n\n        $this->assertTrue($cache->exists('active_module_ids'));\n        $this->assertSame(\n            [$this->activeModuleId],\n            $cache->get('active_module_ids')\n        );\n    }\n\n    public function testGetModulePathsWillReturnSourcePathForActiveModule(): void\n    {\n        $this->assertEquals(\n            [\n                $this->activeModuleId => Path::join($this->context->getShopRootPath(), $this->activeModuleSource),\n            ],\n            $this->get(ActiveModulesDataProviderInterface::class)->getModulePaths()\n        );\n    }\n\n    #[Group('cache')]\n    public function testGetModulePathsUsesCacheIfItExists(): void\n    {\n        $cache = $this->getDummyCache();\n        $cache->put('absolute_module_paths', ['moduleId' => 'somePath']);\n\n        $activeModulesDataProvider = $this->getActiveModulesDataProviderWithCache($cache);\n\n        $this->assertEquals(\n            ['moduleId' => 'somePath'],\n            $activeModulesDataProvider->getModulePaths()\n        );\n    }\n\n    public function testGetModulePathsUsesCacheIfItDoesNotExist(): void\n    {\n        $activeModulesDataProvider = $this->getActiveModulesDataProviderWithCache($this->getDummyCache());\n\n        $this->assertEquals(\n            [\n                $this->activeModuleId => Path::join($this->context->getShopRootPath(), $this->activeModuleSource),\n            ],\n            $activeModulesDataProvider->getModulePaths()\n        );\n    }\n\n    public function testGetControllers(): void\n    {\n        $activeModulesDataProvider =\n            $this->getActiveModulesDataProviderWithCache($this->get(ModuleCacheInterface::class));\n\n        $this->assertEquals(\n            [\n                new Controller('activeController1', 'activeControllerNamespace1'),\n                new Controller('activeController2', 'activeControllerNamespace2'),\n            ],\n            $activeModulesDataProvider->getControllers()\n        );\n    }\n\n    public function testGetModuleClassExtensionsIfCacheDoesNotExist(): void\n    {\n        $activeModulesDataProvider =\n            $this->getActiveModulesDataProviderWithCache($this->get(ModuleCacheInterface::class));\n\n        $this->assertEquals(\n            [\n                'shopClass'        => ['moduleExtensionClassName1'],\n                'anotherShopClass' => ['moduleExtensionClassName2'],\n            ],\n            $activeModulesDataProvider->getClassExtensions()\n        );\n    }\n\n    public function testGetModuleClassExtensionsUsesCacheIfItExists(): void\n    {\n        $cache = $this->getDummyCache();\n        $cache->put(\n            'module_class_extensions',\n            [\n                'shopClassCache'        => ['moduleExtensionClassName1'],\n                'anotherShopClassCache' => ['moduleExtensionClassName2'],\n            ]\n        );\n\n        $activeModulesDataProvider = $this->getActiveModulesDataProviderWithCache($cache);\n\n        $this->assertEquals(\n            [\n                'shopClassCache'        => ['moduleExtensionClassName1'],\n                'anotherShopClassCache' => ['moduleExtensionClassName2'],\n            ],\n            $activeModulesDataProvider->getClassExtensions()\n        );\n    }\n\n    private function prepareTestShopConfiguration(): void\n    {\n        $activeModule = new ModuleConfiguration();\n        $activeModule\n            ->setId($this->activeModuleId)\n            ->setModuleSource($this->activeModuleSource)\n            ->addController(new Controller('activeController1', 'activeControllerNamespace1'))\n            ->addController(new Controller('activeController2', 'activeControllerNamespace2'))\n            ->addClassExtension(new ClassExtension('shopClass', 'moduleExtensionClassName1'))\n            ->addClassExtension(\n                new ClassExtension(\n                    'anotherShopClass',\n                    'moduleExtensionClassName2'\n                )\n            );\n\n        $chain = new ClassExtensionsChain();\n        $chain->addExtension(new ClassExtension('shopClass', 'moduleExtensionClassName1'));\n        $chain->addExtension(new ClassExtension('anotherShopClass', 'moduleExtensionClassName2'));\n        $chain->setChain([\n            'shopClass'        => ['moduleExtensionClassName1'],\n            'anotherShopClass' => ['moduleExtensionClassName2'],\n        ]);\n\n        $inactiveModule = new ModuleConfiguration();\n        $inactiveModule\n            ->setId($this->inactiveModuleId)\n            ->setModuleSource($this->inactiveModuleSource)\n            ->addController(new Controller('inactiveController', 'inactiveControllerNamespace'));\n\n        /** @var ShopConfigurationDaoInterface $dao */\n        $dao = $this->get(ShopConfigurationDaoInterface::class);\n        $shopConfiguration = $dao->get(1);\n        $shopConfiguration\n            ->setClassExtensionsChain($chain)\n            ->addModuleConfiguration($activeModule)\n            ->addModuleConfiguration($inactiveModule);\n\n        $dao->save($shopConfiguration, $this->context->getDefaultShopId());\n\n        $this->get(ModuleActivationServiceInterface::class)\n            ->activate($this->activeModuleId, $this->context->getDefaultShopId());\n    }\n\n    private function cleanUpTestData(): void\n    {\n        $this->get(ModuleActivationServiceInterface::class)\n            ->deactivate($this->activeModuleId, $this->context->getDefaultShopId());\n    }\n\n    private function getActiveModulesDataProviderWithCache(ModuleCacheInterface $cache): ActiveModulesDataProvider\n    {\n        return new ActiveModulesDataProvider(\n            $this->get(ModuleConfigurationDaoInterface::class),\n            $this->get(ModulePathResolverInterface::class),\n            $this->get(ContextInterface::class),\n            $cache,\n            $this->get(ActiveClassExtensionChainResolverInterface::class)\n        );\n    }\n\n    private function getDummyCache(): ModuleCacheInterface\n    {\n        return new class implements ModuleCacheInterface {\n            private array $cache;\n\n            public function deleteItem(string $key): void\n            {\n            }\n\n            public function put(string $key, array $data): void\n            {\n                $this->cache[$key] = $data;\n            }\n\n            public function get(string $key): array\n            {\n                return $this->cache[$key];\n            }\n\n            public function exists(string $key): bool\n            {\n                return isset($this->cache[$key]);\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Facade/ModuleSettingServiceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Facade;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ModuleSettingServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setting\\Setting;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse function Symfony\\Component\\String\\u;\n\nfinal class ModuleSettingServiceTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private string $testModuleId = 'testSettingModuleId';\n    private ModuleSettingServiceInterface $settingFacade;\n\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        $this->settingFacade = $this->get(ModuleSettingServiceInterface::class);\n        $this->prepareTestShopConfiguration();\n    }\n\n    public function testInteger(): void\n    {\n        $this->settingFacade->saveInteger('intSetting', 777, $this->testModuleId);\n\n        $this->assertSame(777, $this->settingFacade->getInteger('intSetting', $this->testModuleId));\n    }\n\n    public function testFloat(): void\n    {\n        $this->settingFacade->saveFloat('floatSetting', 1.1, $this->testModuleId);\n\n        $this->assertSame(1.1, $this->settingFacade->getFloat('floatSetting', $this->testModuleId));\n    }\n\n    public function testBoolean(): void\n    {\n        $this->settingFacade->saveBoolean('boolSetting', true, $this->testModuleId);\n\n        $this->assertTrue($this->settingFacade->getBoolean('boolSetting', $this->testModuleId));\n    }\n\n    public function testString(): void\n    {\n        $this->settingFacade->saveString('stringSetting', 'test', $this->testModuleId);\n\n        $this->assertEquals(u('test'), $this->settingFacade->getString('stringSetting', $this->testModuleId));\n    }\n\n    public function testCollection(): void\n    {\n        $this->settingFacade->saveCollection('arraySetting', [1, 2, 3], $this->testModuleId);\n\n        $this->assertSame([1, 2, 3], $this->settingFacade->getCollection('arraySetting', $this->testModuleId));\n    }\n\n    public function testGetterReturnsValueFromCache(): void\n    {\n        $this->settingFacade->saveString('stringSetting', 'cachedValue', $this->testModuleId);\n        $this->settingFacade->getString('stringSetting', $this->testModuleId);\n\n        $moduleConfiguration = $this->get(ModuleConfigurationDaoInterface::class)->get($this->testModuleId, 1);\n        $setting = $moduleConfiguration->getModuleSetting('stringSetting');\n        $setting->setValue('newValue');\n        $this->get(ModuleConfigurationDaoInterface::class)->save($moduleConfiguration, 1);\n\n        $this->assertEquals('newValue', $this->settingFacade->getString('stringSetting', $this->testModuleId));\n    }\n\n    public function testSaveModuleConfigurationCleansCache(): void\n    {\n        $this->settingFacade->saveString('stringSetting', 'cachedValue', $this->testModuleId);\n        $this->settingFacade->getString('stringSetting', $this->testModuleId);\n\n        $moduleConfiguration = $this->get(ModuleConfigurationDaoInterface::class)->get($this->testModuleId, 1);\n        $setting = $moduleConfiguration->getModuleSetting('stringSetting');\n        $setting->setValue('newValue');\n        $this->get(ModuleConfigurationDaoInterface::class)->save($moduleConfiguration, 1);\n\n        $this->assertEquals('newValue', $this->settingFacade->getString('stringSetting', $this->testModuleId));\n    }\n\n    public function testExists(): void\n    {\n        $this->assertFalse($this->settingFacade->exists('nonExistent', $this->testModuleId));\n        $this->assertTrue($this->settingFacade->exists('stringSetting', $this->testModuleId));\n    }\n\n    private function prepareTestShopConfiguration(): void\n    {\n        $integerSetting = new Setting();\n        $integerSetting\n            ->setName('intSetting')\n            ->setValue(0);\n\n        $floatSetting = new Setting();\n        $floatSetting\n            ->setName('floatSetting')\n            ->setValue(0.0);\n\n        $booleanSetting = new Setting();\n        $booleanSetting\n            ->setName('boolSetting')\n            ->setValue(false);\n\n        $stringSetting = new Setting();\n        $stringSetting\n            ->setName('stringSetting')\n            ->setValue('default');\n\n        $collectionSetting = new Setting();\n        $collectionSetting\n            ->setName('arraySetting')\n            ->setValue([]);\n\n\n        $testModule = new ModuleConfiguration();\n        $testModule\n            ->setId($this->testModuleId)\n            ->setModuleSource('testPath')\n            ->addModuleSetting($integerSetting)\n            ->addModuleSetting($floatSetting)\n            ->addModuleSetting($booleanSetting)\n            ->addModuleSetting($stringSetting)\n            ->addModuleSetting($collectionSetting);\n\n\n        /** @var ShopConfigurationDaoInterface $dao */\n        $dao = $this->get(ShopConfigurationDaoInterface::class);\n        $shopConfiguration = $dao->get(1);\n        $shopConfiguration->addModuleConfiguration($testModule);\n\n        $dao->save($shopConfiguration, 1);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Facade/ModulesDataProviderTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace Integration\\Internal\\Framework\\Module\\Facade;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ModulesDataProviderInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ModuleActivationServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContext;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class ModulesDataProviderTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private string $activeModuleId = 'activeModuleId';\n    private string $activeModulePath = 'some-path-active';\n    private string $activeModuleSource = 'some-source-active';\n    private string $inactiveModuleId = 'inActiveModuleId';\n    private string $inactiveModulePath = 'some-path-inactive';\n    private string $inactiveModuleSource = 'some-source-inactive';\n\n    private BasicContext $context;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->context = new BasicContext();\n        $this->prepareTestShopConfiguration();\n    }\n\n    public function tearDown(): void\n    {\n        $this->cleanUpTestData();\n\n        parent::tearDown();\n    }\n\n    public function testGetModuleIds(): void\n    {\n        self::assertSame(\n            [$this->activeModuleId, $this->inactiveModuleId],\n            $this->get(ModulesDataProviderInterface::class)->getModuleIds()\n        );\n    }\n\n    public function testGetModulePathsWillReturnSourcePath(): void\n    {\n        self::assertEquals(\n            [\n                Path::join($this->context->getShopRootPath(), $this->activeModuleSource),\n                Path::join($this->context->getShopRootPath(), $this->inactiveModuleSource)\n            ],\n            $this->get(ModulesDataProviderInterface::class)->getModulePaths()\n        );\n    }\n\n    private function prepareTestShopConfiguration(): void\n    {\n        $activeModule = new ModuleConfiguration();\n        $activeModule\n            ->setId($this->activeModuleId)\n            ->setModuleSource($this->activeModuleSource);\n\n        $inactiveModule = new ModuleConfiguration();\n        $inactiveModule\n            ->setId($this->inactiveModuleId)\n            ->setModuleSource($this->inactiveModuleSource);\n\n        /** @var ShopConfigurationDaoInterface $dao */\n        $dao = $this->get(ShopConfigurationDaoInterface::class);\n        $shopConfiguration = $dao->get(1);\n        $shopConfiguration\n            ->addModuleConfiguration($activeModule)\n            ->addModuleConfiguration($inactiveModule);\n\n        $dao->save($shopConfiguration, $this->context->getDefaultShopId());\n\n        $this->get(ModuleActivationServiceInterface::class)\n            ->activate($this->activeModuleId, $this->context->getDefaultShopId());\n    }\n\n    private function cleanUpTestData(): void\n    {\n        $this->get(ModuleActivationServiceInterface::class)\n            ->deactivate($this->activeModuleId, $this->context->getDefaultShopId());\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Install/Service/BootstrapModuleInstallerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace Integration\\Internal\\Framework\\Module\\Install\\Service;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class BootstrapModuleInstallerTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private string $moduleId = 'myTestModule';\n\n    public function testUninstall(): void\n    {\n        $package = new OxidEshopPackage(__DIR__ . '/Fixtures/' . $this->moduleId);\n\n        $this->installModule($package);\n        $this->activateTestModule($package);\n\n        $moduleInstaller = $this->get(ModuleInstallerInterface::class);\n        $moduleInstaller->uninstall($package);\n\n        $this->assertFalse(\n            $moduleInstaller->isInstalled($package)\n        );\n    }\n\n    private function installModule(OxidEshopPackage $package): void\n    {\n        $installService = $this->get(ModuleInstallerInterface::class);\n        $package = new OxidEshopPackage(__DIR__ . '/Fixtures/' . $this->moduleId);\n        $installService->install($package);\n    }\n\n    private function activateTestModule(OxidEshopPackage $package): void\n    {\n        $this->get(ModuleInstallerInterface::class)\n            ->install($package);\n        $this\n            ->get(ModuleActivationBridgeInterface::class)\n            ->activate($this->moduleId, Registry::getConfig()->getShopId());\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Install/Service/Fixtures/myTestModule/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.1';\n\n$aModule = array(\n    'id'          => 'myTestModule',\n    'title'       => 'myTestModule',\n    'description' => 'myTestModule',\n    'thumbnail'   => 'picture.png',\n    'version'     => '1.0',\n    'author'      => 'OXID eSales AG'\n);\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Install/Service/Fixtures/testModuleWithCustomSource/customSourcePath/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.1';\n\n$aModule = array(\n    'id'          => 'withCustomSource',\n    'title'       => 'withCustomSource',\n    'description' => 'withCustomSource',\n    'thumbnail'   => 'picture.png',\n    'version'     => '1.0',\n    'author'      => 'OXID eSales AG'\n);\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Install/Service/ModuleConfigurationInstallerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Install\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\Chain\\ClassExtensionsChainDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\{\n    ModuleConfiguration\\ModuleSettingsDataMapper};\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ClassExtensionsChain;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleConfigurationInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage\\FileStorageFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\n\nfinal class ModuleConfigurationInstallerTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private string $modulePath;\n    /**\n     * @see TestData/TestModule/metadata.php\n     */\n    private string $testModuleId = 'test-module';\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->modulePath = realpath(__DIR__ . '/../../TestData/TestModule/');\n        $this->prepareTestProjectConfiguration();\n    }\n\n    public function testInstall(): void\n    {\n        $configurationInstaller = $this->get(ModuleConfigurationInstallerInterface::class);\n        $configurationInstaller->install($this->modulePath);\n\n        $this->assertProjectConfigurationHasModuleConfigurationForAllShops();\n    }\n\n    #[DoesNotPerformAssertions]\n    public function testInstallWithPreExistingEnvironmentFile(): void\n    {\n        $this->configureModuleInEnvironmentFile();\n        $configurationInstaller = $this->get(ModuleConfigurationInstallerInterface::class);\n        $configurationInstaller->install($this->modulePath);\n    }\n\n    public function testInstallWithTwoShopsWillKeepSeparateModuleConfigurationsPerShop(): void\n    {\n        $shopId1 = 1;\n        $shopId2 = 2;\n        $settingValueShop1 = 'firstShopSetting';\n        $settingValueShop2 = 'secondShopSetting';\n        $testedSettingName = 'string-setting';\n\n        $configurationInstaller = $this->get(ModuleConfigurationInstallerInterface::class);\n\n        $configurationInstaller->install($this->modulePath);\n        $moduleConfigurationDao = $this->get(ModuleConfigurationDaoInterface::class);\n\n        $moduleConfigurationShop1 = $moduleConfigurationDao->get($this->testModuleId, $shopId1);\n        $testedSettingShop1 = $moduleConfigurationShop1->getModuleSetting($testedSettingName);\n        $testedSettingShop1->setValue($settingValueShop1);\n        $moduleConfigurationDao->save($moduleConfigurationShop1, $shopId1);\n\n        $moduleConfigurationShop2 = $moduleConfigurationDao->get($this->testModuleId, $shopId2);\n        $testedSettingShop2 = $moduleConfigurationShop2->getModuleSetting($testedSettingName);\n        $testedSettingShop2->setValue($settingValueShop2);\n        $moduleConfigurationDao->save($moduleConfigurationShop2, $shopId2);\n\n        $configurationInstaller->install($this->modulePath);\n\n        $actualSettingValueShop1 = $moduleConfigurationDao->get($this->testModuleId, $shopId1)\n            ->getModuleSetting($testedSettingName)\n            ->getValue();\n        $actualSettingValueShop2 = $moduleConfigurationDao->get($this->testModuleId, $shopId2)\n            ->getModuleSetting($testedSettingName)\n            ->getValue();\n\n        $this->assertSame($settingValueShop1, $actualSettingValueShop1);\n        $this->assertSame($settingValueShop2, $actualSettingValueShop2);\n    }\n\n    public function testUninstall(): void\n    {\n        $configurationInstaller = $this->get(ModuleConfigurationInstallerInterface::class);\n        $configurationInstaller->install($this->modulePath);\n\n        $configurationInstaller->uninstall($this->modulePath);\n\n        $this->assertModuleConfigurationDeletedForAllShops();\n    }\n\n    public function testUninstallById(): void\n    {\n        $configurationInstaller = $this->get(ModuleConfigurationInstallerInterface::class);\n        $configurationInstaller->install($this->modulePath);\n\n        $configurationInstaller->uninstallById($this->testModuleId);\n\n        $this->assertModuleConfigurationDeletedForAllShops();\n    }\n\n    public function testUninstallIfClassExtensionChainIsEmpty(): void\n    {\n        $configurationInstaller = $this->get(ModuleConfigurationInstallerInterface::class);\n        $configurationInstaller->install($this->modulePath);\n\n        $this->get(ClassExtensionsChainDaoInterface::class)->saveChain(1, new ClassExtensionsChain());\n\n        $configurationInstaller->uninstallById($this->testModuleId);\n\n        $this->assertModuleConfigurationDeletedForAllShops();\n    }\n\n    public function testIsInstalled(): void\n    {\n        $moduleConfigurationInstaller = $this->get(ModuleConfigurationInstallerInterface::class);\n\n        $this->assertFalse(\n            $moduleConfigurationInstaller->isInstalled($this->modulePath)\n        );\n\n        $moduleConfigurationInstaller->install($this->modulePath);\n\n        $this->assertTrue(\n            $moduleConfigurationInstaller->isInstalled($this->modulePath)\n        );\n    }\n\n    public function testRelativeModuleSourcePathIsSetToModuleConfigurations(): void\n    {\n        $moduleConfigurationInstaller = $this->get(ModuleConfigurationInstallerInterface::class);\n        $moduleConfigurationInstaller->install($this->modulePath);\n\n        $shopConfiguration = $this->get(ShopConfigurationDaoInterface::class)->get(1);\n\n        $this->assertStringContainsString(\n            $shopConfiguration->getModuleConfiguration($this->testModuleId)->getModuleSource(),\n            $this->modulePath\n        );\n    }\n\n    private function assertProjectConfigurationHasModuleConfigurationForAllShops(): void\n    {\n        foreach ($this->get(ShopConfigurationDaoInterface::class)->getAll() as $shopConfiguration) {\n            $this->assertContains(\n                $this->testModuleId,\n                $shopConfiguration->getModuleIdsOfModuleConfigurations()\n            );\n        }\n    }\n\n    private function assertModuleConfigurationDeletedForAllShops(): void\n    {\n        foreach ($this->get(ShopConfigurationDaoInterface::class)->getAll() as $shopId => $shopConfiguration) {\n            $this->assertFalse($shopConfiguration->hasModuleConfiguration($this->testModuleId));\n\n            $chain = $this->get(ClassExtensionsChainDaoInterface::class)->getChain($shopId)->getChain();\n            $this->assertNotContains('testModuleClassExtendsShopClass', $chain['shopClass'] ?? []);\n        }\n    }\n\n    private function prepareTestProjectConfiguration(): void\n    {\n        $shopConfigurationWithChain = new ShopConfiguration();\n\n        $chain = new ClassExtensionsChain();\n        $chain->setChain([\n            'shopClass' => ['alreadyInstalledShopClass', 'anotherAlreadyInstalledShopClass'],\n            'someAnotherShopClass' => ['alreadyInstalledShopClass'],\n        ]);\n\n        $shopConfigurationWithChain->setClassExtensionsChain($chain);\n\n        $shopConfigurationWithoutChain = new ShopConfiguration();\n\n        $this->get(ShopConfigurationDaoInterface::class)->save($shopConfigurationWithChain, 1);\n        $this->get(ShopConfigurationDaoInterface::class)->save($shopConfigurationWithoutChain, 2);\n    }\n\n    private function configureModuleInEnvironmentFile(): void\n    {\n        $storage = $this->get(FileStorageFactoryInterface::class)\n            ->create(\n                $this->get(ContextInterface::class)\n                    ->getProjectConfigurationDirectory() . 'environment/1.yaml'\n            );\n\n        $storage->save([\n            'modules' => [\n                $this->testModuleId => [\n                    ModuleSettingsDataMapper::MAPPING_KEY => [\n                        'settingToOverwrite' => [\n                            'value' => 'overwrittenValue',\n                        ]\n                    ]\n                ]\n            ]\n        ]);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Install/Service/ModuleFilesInstallerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Install\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleFilesInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class ModuleFilesInstallerTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private string $modulePackagePath = __DIR__ . '/../../TestData/TestModule';\n    private string $packageName = 'TestModule';\n\n    public function tearDown(): void\n    {\n        $this->cleanupTestData();\n\n        parent::tearDown();\n    }\n\n    public function testModuleNotInstalledByDefault(): void\n    {\n        $installer = $this->getFilesInstaller();\n\n        $this->assertFalse(\n            $installer->isInstalled($this->createPackage())\n        );\n    }\n\n    public function testModuleIsInstalledAfterInstallProcess(): void\n    {\n        $installer = $this->getFilesInstaller();\n        $package = $this->createPackage();\n\n        $installer->install($package);\n\n        $this->assertTrue($installer->isInstalled($package));\n    }\n\n    public function testModuleAssertsAreLinkedAfterInstallProcess(): void\n    {\n        $installer = $this->getFilesInstaller();\n        $package = $this->createPackage();\n\n        $installer->install($package);\n\n        $this->assertFileEquals(\n            $this->modulePackagePath . '/assets/some.css',\n            $this->getTestModuleAssetsPath() . '/some.css'\n        );\n    }\n\n    public function testUninstall(): void\n    {\n        $installer = $this->getFilesInstaller();\n        $package = $this->createPackage();\n        $installer->install($package);\n\n        $installer->uninstall($package);\n\n        $this->assertFalse($installer->isInstalled($package));\n    }\n\n    public function testInstallCreatesRelativeSymlink(): void\n    {\n        $installer = $this->getFilesInstaller();\n        $package = $this->createPackage();\n\n        $installer->install($package);\n\n        $this->assertTrue(Path::isRelative(readlink($this->getTestModuleAssetsPath())));\n    }\n\n    private function getFilesInstaller(): ModuleFilesInstallerInterface\n    {\n        return $this->get(ModuleFilesInstallerInterface::class);\n    }\n\n    private function createPackage(): OxidEshopPackage\n    {\n        return new OxidEshopPackage($this->modulePackagePath);\n    }\n\n    private function getTestModuleAssetsPath(): string\n    {\n        return Path::join(\n            $this->get(ContextInterface::class)->getOutPath(),\n            'modules',\n            'test-module'\n        );\n    }\n\n    private function cleanupTestData(): void\n    {\n        $this->getFilesInstaller()->uninstall($this->createPackage());\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Install/Service/ModuleInstallerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace Integration\\Internal\\Framework\\Module\\Install\\Service;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ModuleInstallerTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private string $moduleId = 'myTestModule';\n\n    public function testUninstallNotActiveModule(): void\n    {\n        $package = $this->getOxidEshopPackage();\n        $this->installModule();\n\n        $moduleInstaller = $this->get(ModuleInstallerInterface::class);\n        $moduleInstaller->uninstall($package);\n\n        $this->assertFalse(\n            $moduleInstaller->isInstalled($package)\n        );\n    }\n\n    public function testUninstallActiveModule(): void\n    {\n        $package = $this->getOxidEshopPackage();\n        $this->installModule();\n        $this->activateTestModule();\n\n        $moduleInstaller = $this->get(ModuleInstallerInterface::class);\n        $moduleInstaller->uninstall($package);\n\n        $this->assertFalse(\n            $moduleInstaller->isInstalled($package)\n        );\n    }\n\n    private function installModule(): void\n    {\n        $installService = $this->get(ModuleInstallerInterface::class);\n        $package = $this->getOxidEshopPackage();\n        $installService->install($package);\n    }\n\n    private function activateTestModule(): void\n    {\n        $this\n            ->get(ModuleActivationBridgeInterface::class)\n            ->activate($this->moduleId, Registry::getConfig()->getShopId());\n    }\n\n    private function getOxidEshopPackage(): OxidEshopPackage\n    {\n        return new OxidEshopPackage(__DIR__ . '/Fixtures/' . $this->moduleId);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/MetaData/Dao/MetaDataProviderTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\MetaData\\Dao;\n\nuse InvalidArgumentException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Converter\\MetaDataConverterInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataNormalizer;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataProvider;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\InvalidMetaDataException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\ModuleIdNotValidException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator\\MetaDataValidatorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\nuse RuntimeException;\n\nfinal class MetaDataProviderTest extends TestCase\n{\n    use ContainerTrait;\n\n    private MetaDataNormalizer $metaDataNormalizerStub;\n\n    private BasicContextInterface $contextStub;\n\n    private MetaDataValidatorInterface $validatorStub;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        $this->metaDataNormalizerStub = $this->createStub(MetaDataNormalizer::class);\n        $this->metaDataNormalizerStub->method('normalizeData')->willReturnArgument(0);\n        $this->contextStub = $this->createStub(BasicContextInterface::class);\n        $this->validatorStub = $this->createStub(MetaDataValidatorInterface::class);\n    }\n\n    public function testGetDataThrowsExceptionOnNonExistingFile(): void\n    {\n        $this->expectException(InvalidArgumentException::class);\n        $metaDataProvider = $this->createMetaDataProvider();\n\n        $this->expectException(InvalidArgumentException::class);\n        $metaDataProvider->getData('non existing file');\n    }\n\n    public function testGetDataThrowsExceptionOnDirectory(): void\n    {\n        $this->expectException(InvalidArgumentException::class);\n        $metaDataProvider = $this->createMetaDataProvider();\n        $this->expectException(InvalidArgumentException::class);\n        $metaDataProvider->getData(__DIR__);\n    }\n\n    #[DataProvider('missingMetaDataVariablesDataProvider')]\n    public function testGetDataThrowsExceptionOnMissingMetaDataVariables(string $metaDataContent): void\n    {\n        $this->expectException(InvalidMetaDataException::class);\n        $metaDataFilePath = $this->getPathToTemporaryFile();\n        if (false === file_put_contents($metaDataFilePath, $metaDataContent)) {\n            throw new RuntimeException('Could not write to ' . $metaDataFilePath);\n        }\n        $metaDataProvider = $this->createMetaDataProvider();\n\n        $this->expectException(InvalidMetaDataException::class);\n        $metaDataProvider->getData($metaDataFilePath);\n    }\n\n    private function getPathToTemporaryFile(): string\n    {\n        $temporaryFileHandle = tmpfile();\n\n        return stream_get_meta_data($temporaryFileHandle)['uri'];\n    }\n\n    public static function missingMetaDataVariablesDataProvider(): array\n    {\n        return [\n            ['<?php '],\n            ['<?php $aModule = [];'],\n            ['<?php $sMetadataVersion = \"2.0\";'],\n        ];\n    }\n\n    public function testGetDataProvidesConfiguredMetadataId(): void\n    {\n        $moduleId = 'test_module';\n        $metaDataContent = '<?php\n            $sMetadataVersion = \"2.0\";\n            $aModule = [\"id\" => \"test_module\"];\n        ';\n\n        $metaDataFilePath = $this->getPathToTemporaryFile();\n        if (false === file_put_contents($metaDataFilePath, $metaDataContent)) {\n            throw new RuntimeException('Could not write to ' . $metaDataFilePath);\n        }\n        $metaDataProvider = $this->createMetaDataProvider();\n        $metaData = $metaDataProvider->getData($metaDataFilePath);\n\n        $this->assertEquals(\n            $moduleId,\n            $metaData[MetaDataProvider::METADATA_MODULE_DATA][MetaDataProvider::METADATA_ID]\n        );\n    }\n\n    public function testGetDataThrowsExceptionIfMetaDataIsNotConfigured(): void\n    {\n        $this->expectException(ModuleIdNotValidException::class);\n        $metaDataFilePath = $this->getPathToTemporaryFile();\n        $metaDataContent = '<?php\n            $sMetadataVersion = \"2.0\";\n            $aModule = [];\n        ';\n        if (false === file_put_contents($metaDataFilePath, $metaDataContent)) {\n            throw new RuntimeException('Could not write to ' . $metaDataFilePath);\n        }\n\n        $metaDataProvider = new MetaDataProvider(\n            $this->metaDataNormalizerStub,\n            $this->contextStub,\n            $this->get(MetaDataValidatorInterface::class),\n            $this->get(MetaDataConverterInterface::class)\n        );\n        $metaDataProvider->getData($metaDataFilePath);\n    }\n\n    public function testGetDataConvertsBackwardsCompatibleClasses(): void\n    {\n        $metaDataFilePath = $this->getPathToTemporaryFile();\n        $metaDataContent = '<?php\n            $sMetadataVersion = \"2.0\";\n            $aModule = [\n                \"id\" => \"MyModuleId\",\n                \"extend\" => [\n                    \"oxarticle\"                 => \\VendorNamespace\\VendorClass1::class,\n                    \"OXORDER\"                   => \"VendorNamespace\\\\VendorClass2\",\n                    \"EShopNamespace\\\\UserClass\" => \\VendorNamespace\\VendorClass3::class,\n                ]\n            ];\n        ';\n        if (false === file_put_contents($metaDataFilePath, $metaDataContent)) {\n            throw new RuntimeException('Could not write to ' . $metaDataFilePath);\n        }\n\n        $basicContext = $this->createStub(BasicContextInterface::class);\n        $basicContext->method('getBackwardsCompatibilityClassMap')->willReturn(\n            [\n                \"oxarticle\" => \"EShopNamespace\\\\ArticleClass\",\n                \"oxorder\"   => \"EShopNamespace\\\\OrderClass\",\n            ]\n        );\n        $metaDataProvider = new MetaDataProvider(\n            $this->metaDataNormalizerStub,\n            $basicContext,\n            $this->validatorStub,\n            $this->get(MetaDataConverterInterface::class)\n        );\n        $metaData = $metaDataProvider->getData($metaDataFilePath);\n\n        $this->assertEquals(\n            [\n                \"EShopNamespace\\\\ArticleClass\" => \"VendorNamespace\\\\VendorClass1\",\n                \"EShopNamespace\\\\OrderClass\"   => \"VendorNamespace\\\\VendorClass2\",\n                \"EShopNamespace\\\\UserClass\"    => \"VendorNamespace\\\\VendorClass3\",\n            ],\n            $metaData['moduleData']['extend']\n        );\n    }\n\n    private function createMetaDataProvider(): MetaDataProvider\n    {\n        return new MetaDataProvider(\n            $this->metaDataNormalizerStub,\n            $this->contextStub,\n            $this->validatorStub,\n            $this->get(MetaDataConverterInterface::class)\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/MetaData/DataMapper/MetaDataMapperTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\MetaData\\DataMapper;\n\nuse InvalidArgumentException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataProvider;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\DataMapper\\MetaDataToModuleConfigurationDataMapperInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class MetaDataMapperTest extends TestCase\n{\n    use ContainerTrait;\n\n    #[DataProvider('missingMetaDataKeysDataProvider')]\n    public function testFromDataWillThrowExceptionOnInvalidParameterFormat(array $invalidData): void\n    {\n        $this->expectException(InvalidArgumentException::class);\n        $this->get(MetaDataToModuleConfigurationDataMapperInterface::class)->fromData($invalidData);\n    }\n\n    public static function missingMetaDataKeysDataProvider(): array\n    {\n        return [\n            'all mandatory keys are missing'    => [[]],\n            'key metaDataVersion is missing'    => [[MetaDataProvider::METADATA_MODULE_DATA => '']],\n            'key moduleData version is missing' => [[MetaDataProvider::METADATA_METADATA_VERSION => '']],\n        ];\n    }\n\n    public function testSettingPositionIsConvertedToInt(): void\n    {\n        $moduleConfiguration = $this->get(MetaDataToModuleConfigurationDataMapperInterface::class)->fromData(\n            [\n                'metaDataVersion' => '2.1',\n                'metaDataFilePath' => 'some-path',\n                'moduleData' => [\n                    'id' => 'some',\n                    'settings' => [\n                        [\n                            'name'  => 'setting',\n                            'type'  => 'bool',\n                            'value' => 'true',\n                            'position' => '2'\n                        ],\n                    ]\n                ]\n            ]\n        );\n\n        $this->assertSame(\n            2,\n            $moduleConfiguration->getModuleSetting('setting')->getPositionInGroup()\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/MetaData/MetaDataMapperTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\MetaData;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\ModuleIdNotValidException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\UnsupportedMetaDataKeyException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\UnsupportedMetaDataValueTypeException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataProviderInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\TestContainerFactory;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class MetaDataMapperTest extends TestCase\n{\n    public function testModuleMetaData20(): void\n    {\n        $metaDataFilePath = $this->getMetaDataFilePath('TestModuleMetaData20');\n        $expectedModuleData = [\n            'id'          => 'TestModuleMetaData20',\n            'title'                   => [\n                'en' => 'Module for testModuleMetaData20'\n            ],\n            'description' => [\n                'de' => 'de description for testModuleMetaData20',\n                'en' => 'en description for testModuleMetaData20',\n            ],\n            'lang'        => 'en',\n            'thumbnail'   => 'picture.png',\n            'version'     => '1.0',\n            'author'      => 'OXID eSales AG',\n            'url'         => 'https://www.oxid-esales.com',\n            'email'       => 'info@oxid-esales.com',\n            'extend'      => [\n                'OxidEsales\\Eshop\\Application\\Model\\Payment' => 'TestModuleMetaData20\\Payment',\n                'OxidEsales\\Eshop\\Application\\Model\\Article' => 'TestModuleMetaData20\\Article',\n            ],\n            'controllers' => [\n                'myvendor_mymodule_MyModuleController'      => 'TestModuleMetaData20\\Controller',\n                'myvendor_mymodule_MyOtherModuleController' => 'TestModuleMetaData20\\OtherController',\n            ],\n            'settings'    => [\n                [\n                    'group' => 'main',\n                    'name' => 'setting_1',\n                    'type' => 'select',\n                    'value' => '0',\n                    'constraints' => ['0', '1', '2', '3'],\n                    'position' => 3],\n                ['group' => 'main', 'name' => 'setting_2', 'type' => 'arr', 'value' => ['value1', 'value2']]\n            ],\n            'events'      => [\n                'onActivate'   => 'TestModuleMetaData20\\Events::onActivate',\n                'onDeactivate' => 'TestModuleMetaData20\\Events::onDeactivate'\n            ],\n        ];\n\n        $container = $this->getCompiledTestContainer();\n\n        $metaDataDataProvider = $container->get(MetaDataProviderInterface::class);\n        $normalizedMetaData = $metaDataDataProvider->getData($metaDataFilePath);\n\n        $metaDataDataMapper = $container->get('oxid_esales.module.metadata.datamapper.metadatamapper');\n        $moduleConfiguration = $metaDataDataMapper->fromData($normalizedMetaData);\n\n        $this->assertSame($expectedModuleData['id'], $moduleConfiguration->getId());\n        $this->assertSame($expectedModuleData['version'], $moduleConfiguration->getVersion());\n        $this->assertSame($expectedModuleData['title'], $moduleConfiguration->getTitle());\n        $this->assertSame($expectedModuleData['description'], $moduleConfiguration->getDescription());\n        $this->assertSame($expectedModuleData['lang'], $moduleConfiguration->getLang());\n        $this->assertSame($expectedModuleData['thumbnail'], $moduleConfiguration->getThumbnail());\n        $this->assertSame($expectedModuleData['author'], $moduleConfiguration->getAuthor());\n        $this->assertSame($expectedModuleData['url'], $moduleConfiguration->getUrl());\n        $this->assertSame($expectedModuleData['email'], $moduleConfiguration->getEmail());\n\n        $classExtensions = [];\n\n        foreach ($moduleConfiguration->getClassExtensions() as $extension) {\n            $classExtensions[$extension->getShopClassName()] = $extension->getModuleExtensionClassName();\n        }\n        $this->assertSame(\n            $expectedModuleData['extend'],\n            $classExtensions\n        );\n\n        $controllers = [];\n\n        foreach ($moduleConfiguration->getControllers() as $controller) {\n            $controllers[$controller->getId()] = $controller->getControllerClassNameSpace();\n        }\n\n        $this->assertSame(\n            $expectedModuleData['controllers'],\n            $controllers\n        );\n\n        $moduleSettings = [];\n\n        foreach ($moduleConfiguration->getModuleSettings() as $index => $setting) {\n            if ($setting->getGroupName()) {\n                $moduleSettings[$index]['group'] = $setting->getGroupName();\n            }\n\n            if ($setting->getName()) {\n                $moduleSettings[$index]['name'] = $setting->getName();\n            }\n\n            if ($setting->getType()) {\n                $moduleSettings[$index]['type'] = $setting->getType();\n            }\n\n            $moduleSettings[$index]['value'] = $setting->getValue();\n\n            if (!empty($setting->getConstraints())) {\n                $moduleSettings[$index]['constraints'] = $setting->getConstraints();\n            }\n\n            if ($setting->getPositionInGroup() > 0) {\n                $moduleSettings[$index]['position'] = $setting->getPositionInGroup();\n            }\n        }\n\n        $this->assertSame(\n            $expectedModuleData['settings'],\n            $moduleSettings\n        );\n        $events = [];\n\n        foreach ($moduleConfiguration->getEvents() as $event) {\n            $events[$event->getAction()] = $event->getMethod();\n        }\n\n        $this->assertSame(\n            $expectedModuleData['events'],\n            $events\n        );\n    }\n\n    public function testModuleMetaData21(): void\n    {\n        $metaDataFilePath = $this->getMetaDataFilePath('TestModuleMetaData21');\n        $expectedModuleData = [\n            'id'                      => 'TestModuleMetaData21',\n            'title'                   => [\n                'en' => 'Module for testModuleMetaData21'\n            ],\n            'description'             => [\n                'de' => 'de description for testModuleMetaData21',\n                'en' => 'en description for testModuleMetaData21',\n            ],\n            'lang'                    => 'en',\n            'thumbnail'               => 'picture.png',\n            'version'                 => '1.0',\n            'author'                  => 'OXID eSales AG',\n            'url'                     => 'https://www.oxid-esales.com',\n            'email'                   => 'info@oxid-esales.com',\n            'extend'                  => [\n                'OxidEsales\\Eshop\\Application\\Model\\Payment' => 'TestModuleMetaData21\\Payment',\n                'OxidEsales\\Eshop\\Application\\Model\\Article' => 'TestModuleMetaData21\\Article'\n            ],\n            'controllers'             => [\n                'myvendor_mymodule_MyModuleController'      => 'TestModuleMetaData21\\Controller',\n                'myvendor_mymodule_MyOtherModuleController' => 'TestModuleMetaData21\\OtherController',\n            ],\n            'settings'                => [\n                [\n                    'group' => 'main',\n                    'name' => 'setting_1',\n                    'type' => 'select',\n                    'value' => '0',\n                    'constraints' => ['0', '1', '2', '3'],\n                    'position' => 3\n                ],\n                ['group' => 'main', 'name' => 'setting_2', 'type' => 'password', 'value' => 'changeMe']\n            ],\n            'events'                  => [\n                'onActivate'   => 'TestModuleMetaData21\\Events::onActivate',\n                'onDeactivate' => 'TestModuleMetaData21\\Events::onDeactivate'\n            ]\n        ];\n\n        $container = $this->getCompiledTestContainer();\n\n        $metaDataDataProvider = $container->get(MetaDataProviderInterface::class);\n        $normalizedMetaData = $metaDataDataProvider->getData($metaDataFilePath);\n\n        $metaDataDataMapper = $container->get('oxid_esales.module.metadata.datamapper.metadatamapper');\n        $moduleConfiguration = $metaDataDataMapper->fromData($normalizedMetaData);\n\n        $this->assertSame($expectedModuleData['id'], $moduleConfiguration->getId());\n        $this->assertSame($expectedModuleData['version'], $moduleConfiguration->getVersion());\n        $this->assertSame($expectedModuleData['title'], $moduleConfiguration->getTitle());\n        $this->assertSame($expectedModuleData['description'], $moduleConfiguration->getDescription());\n        $this->assertSame($expectedModuleData['lang'], $moduleConfiguration->getLang());\n        $this->assertSame($expectedModuleData['thumbnail'], $moduleConfiguration->getThumbnail());\n        $this->assertSame($expectedModuleData['author'], $moduleConfiguration->getAuthor());\n        $this->assertSame($expectedModuleData['url'], $moduleConfiguration->getUrl());\n        $this->assertSame($expectedModuleData['email'], $moduleConfiguration->getEmail());\n\n        $classExtensions = [];\n        foreach ($moduleConfiguration->getClassExtensions() as $extension) {\n            $classExtensions[$extension->getShopClassName()] = $extension->getModuleExtensionClassName();\n        }\n\n        $this->assertSame(\n            $expectedModuleData['extend'],\n            $classExtensions\n        );\n\n        $controllers = [];\n\n        foreach ($moduleConfiguration->getControllers() as $controller) {\n            $controllers[$controller->getId()] = $controller->getControllerClassNameSpace();\n        }\n\n        $this->assertSame(\n            $expectedModuleData['controllers'],\n            $controllers\n        );\n\n        $moduleSettings = [];\n\n        foreach ($moduleConfiguration->getModuleSettings() as $index => $setting) {\n            if ($setting->getGroupName()) {\n                $moduleSettings[$index]['group'] = $setting->getGroupName();\n            }\n\n            if ($setting->getName()) {\n                $moduleSettings[$index]['name'] = $setting->getName();\n            }\n\n            if ($setting->getType()) {\n                $moduleSettings[$index]['type'] = $setting->getType();\n            }\n\n            $moduleSettings[$index]['value'] = $setting->getValue();\n\n            if (!empty($setting->getConstraints())) {\n                $moduleSettings[$index]['constraints'] = $setting->getConstraints();\n            }\n\n            if ($setting->getPositionInGroup() > 0) {\n                $moduleSettings[$index]['position'] = $setting->getPositionInGroup();\n            }\n        }\n\n        $this->assertSame(\n            $expectedModuleData['settings'],\n            $moduleSettings\n        );\n\n        $events = [];\n\n        foreach ($moduleConfiguration->getEvents() as $event) {\n            $events[$event->getAction()] = $event->getMethod();\n        }\n\n        $this->assertSame(\n            $expectedModuleData['events'],\n            $events\n        );\n    }\n\n    /**\n     * Test that on metadata.php, which is only partially filled, safe types are returned by the corresponding methods\n     */\n    public function testModuleWithPartialMetaData(): void\n    {\n        $this->expectException(ModuleIdNotValidException::class);\n        $testModuleDirectory = 'TestModuleWithPartialMetaData';\n\n        $metaDataFilePath = $this->getMetaDataFilePath($testModuleDirectory);\n        $expectedModuleData = [\n            'extend' => [\n                'OxidEsales\\Eshop\\Application\\Model\\Payment' => 'TestModuleWithPartialMetaData\\Payment',\n                'OxidEsales\\Eshop\\Application\\Model\\Article' => 'TestModuleWithPartialMetaData\\Article'\n            ],\n        ];\n\n        $container = $this->getCompiledTestContainer();\n\n        $metaDataDataProvider = $container->get(MetaDataProviderInterface::class);\n        $normalizedMetaData = $metaDataDataProvider->getData($metaDataFilePath);\n\n        $metaDataDataMapper = $container->get('oxid_esales.module.metadata.datamapper.metadatamapper');\n        $moduleConfiguration = $metaDataDataMapper->fromData($normalizedMetaData);\n\n        /**\n         * The module directory name should be set as the module ID is missing in metadata.Same\n         */\n        $this->assertEquals($testModuleDirectory, $moduleConfiguration->getId());\n\n        /** All methods should return type safe default values, if there were no values defined in metadata.php */\n        $this->assertSame([], $moduleConfiguration->getTitle());\n        $this->assertSame([], $moduleConfiguration->getDescription());\n        $this->assertSame('', $moduleConfiguration->getLang());\n        $this->assertSame('', $moduleConfiguration->getThumbnail());\n        $this->assertSame('', $moduleConfiguration->getAuthor());\n        $this->assertSame('', $moduleConfiguration->getUrl());\n        $this->assertSame('', $moduleConfiguration->getEmail());\n\n        /** This is the only value defined in metadata.php */\n\n        $classExtensions = [];\n\n        foreach ($moduleConfiguration->getClassExtensions() as $extension) {\n            $classExtensions[$extension->getShopClassName()] = $extension->getModuleExtensionClassName();\n        }\n\n        $this->assertEquals(\n            $expectedModuleData['extend'],\n            $classExtensions\n        );\n    }\n\n    public function testModuleWithSurplusData(): void\n    {\n        $this->expectException(UnsupportedMetaDataValueTypeException::class);\n        $this->expectException(UnsupportedMetaDataKeyException::class);\n\n        $metaDataFilePath = $this->getMetaDataFilePath('TestModuleWithSurplusData');\n        $expectedModuleData = [\n            'id' => 'TestModuleWithSurplusData',\n        ];\n\n        $container = $this->getCompiledTestContainer();\n\n        $metaDataDataProvider = $container->get(MetaDataProviderInterface::class);\n        $normalizedMetaData = $metaDataDataProvider->getData($metaDataFilePath);\n\n        $metaDataDataMapper = $container->get('oxid_esales.module.metadata.datamapper.metadatamapper');\n        $moduleConfiguration = $metaDataDataMapper->fromData($normalizedMetaData);\n\n        $this->assertEquals($expectedModuleData['id'], $moduleConfiguration->getId());\n    }\n\n\n    private function getMetaDataFilePath(string $testModuleDirectory): string\n    {\n        return Path::join(__DIR__, 'TestData', $testModuleDirectory, 'metadata.php');\n    }\n\n    private function getCompiledTestContainer(): ContainerBuilder\n    {\n        $container = (new TestContainerFactory())->create();\n        $container->compile();\n\n        return $container;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/MetaData/TestData/TestModuleMetaData20/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\Eshop\\Application\\Model\\Payment;\n\n$sMetadataVersion = '2.0';\n\n$aModule = [\n    'id'          => 'TestModuleMetaData20',\n    'title'       => 'Module for testModuleMetaData20',\n    'description' => [\n        'de' => 'de description for testModuleMetaData20',\n        'en' => 'en description for testModuleMetaData20',\n    ],\n    'lang'        => 'en',\n    'thumbnail'   => 'picture.png',\n    'version'     => '1.0',\n    'author'      => 'OXID eSales AG',\n    'url'         => 'https://www.oxid-esales.com',\n    'email'       => 'info@oxid-esales.com',\n    'extend'      => [\n        Payment::class => 'TestModuleMetaData20\\Payment',\n        'oxArticle'                                        => 'TestModuleMetaData20\\Article',\n    ],\n    'controllers' => [\n        'myvendor_mymodule_MyModuleController'      => 'TestModuleMetaData20\\Controller',\n        'myvendor_mymodule_MyOtherModuleController' => 'TestModuleMetaData20\\OtherController',\n    ],\n    'settings'    => [\n        [\n            'group' => 'main',\n            'name' => 'setting_1',\n            'type' => 'select',\n            'value' => '0',\n            'constraints' => '0|1|2|3',\n            'position' => 3\n        ],\n        ['group' => 'main', 'name' => 'setting_2', 'type' => 'arr', 'value' => ['value1', 'value2']]\n    ],\n    'events'      => [\n        'onActivate'   => 'TestModuleMetaData20\\Events::onActivate',\n        'onDeactivate' => 'TestModuleMetaData20\\Events::onDeactivate'\n    ],\n\n];\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/MetaData/TestData/TestModuleMetaData21/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\Eshop\\Application\\Model\\Payment;\n\n$sMetadataVersion = '2.1';\n\n$aModule = [\n    'id'                      => 'TestModuleMetaData21',\n    'title'                   => 'Module for testModuleMetaData21',\n    'description'             => [\n        'de' => 'de description for testModuleMetaData21',\n        'en' => 'en description for testModuleMetaData21',\n    ],\n    'lang'                    => 'en',\n    'thumbnail'               => 'picture.png',\n    'version'                 => '1.0',\n    'author'                  => 'OXID eSales AG',\n    'url'                     => 'https://www.oxid-esales.com',\n    'email'                   => 'info@oxid-esales.com',\n    'extend'                  => [\n        Payment::class => 'TestModuleMetaData21\\Payment',\n        'oxArticle'                                        => 'TestModuleMetaData21\\Article'\n    ],\n    'controllers'             => [\n        'myvendor_mymodule_MyModuleController'      => 'TestModuleMetaData21\\Controller',\n        'myvendor_mymodule_MyOtherModuleController' => 'TestModuleMetaData21\\OtherController',\n    ],\n    'blocks'                  => [\n        [\n            'theme'    => 'theme_id',\n            'template' => 'template_1.html.twig',\n            'block'    => 'block_1',\n            'file'     => '/blocks/template_1.html.twig',\n            'position' => '1'\n        ],\n        [\n            'template' => 'template_2.html.twig',\n            'block'    => 'block_2',\n            'file'     => '/blocks/template_2.html.twig',\n            'position' => '2'\n        ],\n    ],\n    'settings' => [\n        [\n            'group' => 'main',\n            'name' => 'setting_1',\n            'type' => 'select',\n            'value' => '0',\n            'constraints' => '0|1|2|3',\n            'position' => 3\n        ],\n        ['group' => 'main', 'name' => 'setting_2', 'type' => 'password', 'value' => 'changeMe']\n    ],\n    'events'                  => [\n        'onActivate'   => 'TestModuleMetaData21\\Events::onActivate',\n        'onDeactivate' => 'TestModuleMetaData21\\Events::onDeactivate'\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/MetaData/TestData/TestModuleWithPartialMetaData/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\Eshop\\Application\\Model\\Payment;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\MetaData\\TestData\\TestModuleWithPartialMetaData\\Article;\n\n$sMetadataVersion = '2.0';\n\n$aModule = [\n    'extend' => [\n        Payment::class => \\OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\MetaData\\TestData\\TestModuleWithPartialMetaData\\Payment::class,\n        'oxArticle'                                        => Article::class\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/MetaData/TestData/TestModuleWithSurplusData/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.0';\n\n$aModule = [\n    'id'             => 'TestModuleWithSurplusData',\n    'extraStuff'     => [\n        'key1' => 'value1'\n    ],\n    'moreExtraStuff' => new StdClass()\n];\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Setup/ModuleEventsTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Setup;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\Event;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ModuleActivationServiceInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\TestData\\TestModule\\ModuleEvents;\n\nfinal class ModuleEventsTest extends IntegrationTestCase\n{\n    private int $shopId = 1;\n    private string $testModuleId = 'testModuleId';\n\n    public function testActivationEventWasExecuted(): void\n    {\n        $moduleConfiguration = $this->getTestModuleConfiguration();\n        $moduleConfiguration->addEvent(new Event('onActivate', ModuleEvents::class . '::onActivate'));\n\n        $this->persistModuleConfiguration($moduleConfiguration);\n\n        /** @var ModuleActivationServiceInterface $moduleActivationService */\n        $moduleActivationService = $this->get(ModuleActivationServiceInterface::class);\n\n        ob_start();\n        $moduleActivationService->activate($this->testModuleId, $this->shopId);\n        $eventMessage = ob_get_contents();\n        ob_end_clean();\n\n        $this->assertSame('Method onActivate was called', $eventMessage);\n    }\n\n    public function testActivationEventWasExecutedSecondTime(): void\n    {\n        $moduleConfiguration = $this->getTestModuleConfiguration();\n        $moduleConfiguration->addEvent(new Event('onActivate', ModuleEvents::class . '::onActivate'));\n\n        $this->persistModuleConfiguration($moduleConfiguration);\n\n        /** @var ModuleActivationServiceInterface $moduleActivationService */\n        $moduleActivationService = $this->get(ModuleActivationServiceInterface::class);\n\n        ob_start();\n        $moduleActivationService->activate($this->testModuleId, $this->shopId);\n        ob_end_clean();\n\n        $moduleActivationService->deactivate($this->testModuleId, $this->shopId);\n\n        ob_start();\n        $moduleActivationService->activate($this->testModuleId, $this->shopId);\n        $eventMessage = ob_get_contents();\n        ob_end_clean();\n\n        $this->assertSame('Method onActivate was called', $eventMessage);\n    }\n\n\n    public function testDeactivationEventWasExecuted(): void\n    {\n        $moduleConfiguration = $this->getTestModuleConfiguration();\n        $moduleConfiguration->addEvent(new Event('onDeactivate', ModuleEvents::class . '::onDeactivate'));\n\n        $this->persistModuleConfiguration($moduleConfiguration);\n\n        /** @var ModuleActivationServiceInterface $moduleActivationService */\n        $moduleActivationService = $this->get(ModuleActivationServiceInterface::class);\n\n        $moduleActivationService->activate($this->testModuleId, $this->shopId);\n\n        ob_start();\n        $moduleActivationService->deactivate($this->testModuleId, $this->shopId);\n        $eventMessage = ob_get_contents();\n        ob_end_clean();\n\n        $this->assertSame('Method onDeactivate was called', $eventMessage);\n    }\n\n    private function getTestModuleConfiguration(): ModuleConfiguration\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->setId($this->testModuleId);\n        $moduleConfiguration\n            ->setModuleSource('test');\n\n        return $moduleConfiguration;\n    }\n\n    private function persistModuleConfiguration(ModuleConfiguration $moduleConfiguration): void\n    {\n        $shopConfiguration = new ShopConfiguration();\n        $shopConfiguration->addModuleConfiguration($moduleConfiguration);\n\n        $this->get(ShopConfigurationDaoInterface::class)->save(\n            $shopConfiguration,\n            $this->shopId\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Setup/Service/ActiveClassExtensionChainResolverTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Setup\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ClassExtensionsChain;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ActiveClassExtensionChainResolver;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\ClassExtension;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\State\\ModuleStateServiceInterface;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ActiveClassExtensionChainResolverTest extends TestCase\n{\n    public function testActiveExtensionChainGetter(): void\n    {\n        $activeModuleConfiguration1 = $this->getModuleConfiguration('activeModuleName', [\n            'shopClassNamespace'        => 'activeModuleExtensionClass',\n            'anotherShopClassNamespace' => 'activeModuleExtensionClass',\n        ]);\n\n        $activeModuleConfiguration2 = $this->getModuleConfiguration('activeModuleName2', [\n            'shopClassNamespace'        => 'activeModule2ExtensionClass',\n            'anotherShopClassNamespace' => 'activeModule2ExtensionClass',\n        ]);\n\n        $notActiveModuleConfiguration = $this->getModuleConfiguration('notActiveModuleName', [\n            'shopClassNamespace'        => 'notActiveModuleExtensionClass',\n            'anotherShopClassNamespace' => 'notActiveModuleExtensionClass',\n        ]);\n\n        $classExtensionChain = new ClassExtensionsChain();\n        $classExtensionChain->setChain([\n            'shopClassNamespace' => [\n                'activeModule2ExtensionClass',\n                'activeModuleExtensionClass',\n                'notActiveModuleExtensionClass',\n            ],\n            'anotherShopClassNamespace' => [\n                'activeModuleExtensionClass',\n                'notActiveModuleExtensionClass',\n                'activeModule2ExtensionClass',\n            ],\n        ]);\n\n        $shopConfiguration = new ShopConfiguration();\n        $shopConfiguration->setClassExtensionsChain($classExtensionChain);\n\n        $shopConfiguration\n            ->addModuleConfiguration($activeModuleConfiguration1)\n            ->addModuleConfiguration($activeModuleConfiguration2)\n            ->addModuleConfiguration($notActiveModuleConfiguration);\n\n        $shopConfigurationDao = $this->createStub(ShopConfigurationDaoInterface::class);\n        $shopConfigurationDao\n            ->method('get')\n            ->willReturn($shopConfiguration);\n\n        $moduleStateService = $this->createStub(ModuleStateServiceInterface::class);\n        $moduleStateService\n            ->method('isActive')\n            ->willReturnMap([\n                ['activeModuleName', 1, true],\n                ['activeModuleName2', 1, true],\n                ['notActiveModuleName', 1, false],\n            ]);\n\n        $classExtensionChainService = new ActiveClassExtensionChainResolver(\n            $shopConfigurationDao,\n            $moduleStateService\n        );\n\n        $expectedChain = new ClassExtensionsChain();\n        $expectedChain\n            ->setChain(\n                [\n                    'shopClassNamespace' => [\n                        'activeModule2ExtensionClass',\n                        'activeModuleExtensionClass',\n                    ],\n                    'anotherShopClassNamespace' => [\n                        'activeModuleExtensionClass',\n                        'activeModule2ExtensionClass',\n                    ],\n                ]\n            );\n\n        $this->assertEquals(\n            $expectedChain,\n            $classExtensionChainService->getActiveExtensionChain(1)\n        );\n    }\n\n    private function getModuleConfiguration(string $moduleName, array $extensions): ModuleConfiguration\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n\n        foreach ($extensions as $classNamespace => $moduleNamespace) {\n            $classExtensions = new ClassExtension($classNamespace, $moduleNamespace);\n\n            $moduleConfiguration\n                ->setId($moduleName)\n                ->addClassExtension($classExtensions);\n        }\n\n        return $moduleConfiguration;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Setup/Service/Fixtures/Module/services.yaml",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Setup/Service/Fixtures/TestDependentModule/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.1';\n\n$aModule = array(\n    'id'           => 'test-dependent-module',\n    'thumbnail'    => 'picture.png',\n    'version'      => '1.0',\n    'author'       => 'OXID eSales AG',\n);\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Setup/Service/Fixtures/TestMissingDependencyModule/TestModuleService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Setup\\Service\\Fixtures\\TestMissingDependencyModule;\n\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Setup\\Service\\Fixtures\\TestAnotherDependentModule\\TestAnotherModuleService;\n\nclass TestModuleService extends TestAnotherModuleService\n{\n    public function doSomething(): bool\n    {\n        return false;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Setup/Service/Fixtures/TestMissingDependencyModule/dependencies.yaml",
    "content": "modules:\n  - test-missing-module\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Setup/Service/Fixtures/TestMissingDependencyModule/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.1';\n\n$aModule = array(\n    'id'           => 'test-missing-dependency-module',\n    'thumbnail'    => 'picture.png',\n    'version'      => '1.0',\n    'author'       => 'OXID eSales AG',\n);\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Setup/Service/Fixtures/TestMissingDependencyModule/services.yaml",
    "content": "services:\n\n  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Setup\\Service\\Fixtures\\TestMissingDependencyModule\\TestModuleService:\n    class: OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Setup\\Service\\Fixtures\\TestMissingDependencyModule\\TestModuleService\n    public: true\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Setup/Service/Fixtures/TestModuleWithDependency/dependencies.yaml",
    "content": "modules:\n  - test-dependent-module\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Setup/Service/Fixtures/TestModuleWithDependency/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.1';\n\n$aModule = array(\n    'id'           => 'test-module-with-dependency',\n    'thumbnail'    => 'picture.png',\n    'version'      => '1.0',\n    'author'       => 'OXID eSales AG',\n);\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Setup/Service/ModuleActivationServiceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Setup\\Service;\n\nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\ContainerFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ClassExtensionsChain;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\ClassExtension;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\Controller;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModulePathResolver;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModulePathResolverInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setting\\Setting;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ModuleActivationServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator\\ModuleConfigurationValidatorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\State\\ModuleStateServiceInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\TestData\\TestModule\\SomeModuleService;\nuse OxidEsales\\EshopCommunity\\Tests\\TestContainerFactory;\nuse Prophecy\\Argument;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\nuse Psr\\Container\\ContainerInterface;\nuse Symfony\\Component\\DependencyInjection\\Exception\\ServiceNotFoundException;\n\nfinal class ModuleActivationServiceTest extends IntegrationTestCase\n{\n    use ProphecyTrait;\n\n    private ContainerInterface $container;\n    private int $shopId = 1;\n    private string $testModuleId = 'testModuleId';\n    private ?TestContainerFactory $testContainerFactory = null;\n    private string $testModulePath = __DIR__ . '/../../TestData/TestModule';\n\n    public function setup(): void\n    {\n        parent::setUp();\n        $this->container = $this->setupAndConfigureContainer();\n        $this->persistModuleConfiguration($this->getTestModuleConfiguration());\n    }\n\n    public function tearDown(): void\n    {\n        ContainerFacade::get(ModuleInstallerInterface::class)->uninstall(\n            new OxidEshopPackage($this->testModulePath)\n        );\n        parent::tearDown();\n    }\n\n    public function testActivation(): void\n    {\n        $moduleStateService = $this->container->get(ModuleStateServiceInterface::class);\n        $moduleActivationService = $this->container->get(ModuleActivationServiceInterface::class);\n\n        $moduleActivationService->activate($this->testModuleId, $this->shopId);\n\n        $this->assertTrue($moduleStateService->isActive($this->testModuleId, $this->shopId));\n\n        $moduleActivationService->deactivate($this->testModuleId, $this->shopId);\n\n        $this->assertFalse($moduleStateService->isActive($this->testModuleId, $this->shopId));\n    }\n\n    public function testSetActivatedInModuleConfiguration(): void\n    {\n        $moduleConfigurationDao = $this->container->get(ModuleConfigurationDaoInterface::class);\n        $moduleActivationService = $this->container->get(ModuleActivationServiceInterface::class);\n\n        $moduleActivationService->activate($this->testModuleId, $this->shopId);\n        $moduleConfiguration = $moduleConfigurationDao->get($this->testModuleId, $this->shopId);\n\n        $this->assertTrue($moduleConfiguration->isActivated());\n\n        $moduleActivationService->deactivate($this->testModuleId, $this->shopId);\n        $moduleConfiguration = $moduleConfigurationDao->get($this->testModuleId, $this->shopId);\n\n        $this->assertFalse($moduleConfiguration->isActivated());\n    }\n\n    public function testActivationOfModuleServices(): void\n    {\n        $moduleActivationService = $this->container->get(ModuleActivationServiceInterface::class);\n        $moduleActivationService->activate($this->testModuleId, $this->shopId);\n\n        $this->assertInstanceOf(\n            SomeModuleService::class,\n            $this->setupAndConfigureContainer()->get(SomeModuleService::class)\n        );\n    }\n\n    public function testDeActivationOfModuleServices(): void\n    {\n        ContainerFacade::get(ModuleInstallerInterface::class)\n            ->install(\n                new OxidEshopPackage($this->testModulePath)\n            );\n\n        ContainerFacade::get(ModuleActivationBridgeInterface::class)\n            ->activate('test-module', $this->shopId);\n\n        TestContainerFactory::resetContainer();\n\n        ContainerFacade::get(SomeModuleService::class);\n\n        ContainerFacade::get(ModuleActivationBridgeInterface::class)\n            ->deactivate('test-module', $this->shopId);\n\n        TestContainerFactory::resetContainer();\n\n        $this->expectException(ServiceNotFoundException::class);\n        ContainerFacade::get(SomeModuleService::class);\n    }\n\n    public function testActivationWillCallValidatorsAggregate(): void\n    {\n        $controllersValidator = $this->prophesize(ModuleConfigurationValidatorInterface::class);\n        $classExtensionsValidator = $this->prophesize(ModuleConfigurationValidatorInterface::class);\n        $eventsValidator = $this->prophesize(ModuleConfigurationValidatorInterface::class);\n        $servicesValidator = $this->prophesize(ModuleConfigurationValidatorInterface::class);\n\n        $container = $this->testContainerFactory->create();\n        $container->set(\n            'oxid_esales.module.setup.validator.controllers_module_setting_validator',\n            $controllersValidator->reveal()\n        );\n        $container->set(\n            'oxid_esales.module.setup.validator.class_extensions_module_setting_validator',\n            $classExtensionsValidator->reveal()\n        );\n        $container->set(\n            'oxid_esales.module.setup.validator.events_module_setting_validator',\n            $eventsValidator->reveal()\n        );\n        $container->set(\n            'oxid_esales.module.setup.validator.services_yaml_validator',\n            $servicesValidator->reveal()\n        );\n        $container->compile();\n\n        $container->get(ModuleActivationServiceInterface::class)\n            ->activate($this->testModuleId, $this->shopId);\n\n        $controllersValidator->validate(Argument::type(ModuleConfiguration::class), $this->shopId)\n            ->shouldHaveBeenCalledOnce();\n        $classExtensionsValidator->validate(Argument::type(ModuleConfiguration::class), $this->shopId)\n            ->shouldHaveBeenCalledOnce();\n        $eventsValidator->validate(Argument::type(ModuleConfiguration::class), $this->shopId)\n            ->shouldHaveBeenCalledOnce();\n        $servicesValidator->validate(Argument::type(ModuleConfiguration::class), $this->shopId)\n            ->shouldHaveBeenCalledOnce();\n    }\n\n    private function getTestModuleConfiguration(): ModuleConfiguration\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->setId($this->testModuleId);\n        $moduleConfiguration->setModuleSource('test');\n\n        $setting = new Setting();\n        $setting\n            ->setName('test')\n            ->setValue([1, 2])\n            ->setType('aarr')\n            ->setGroupName('group')\n            ->setPositionInGroup(7)\n            ->setConstraints([1, 2]);\n\n        $moduleConfiguration->addModuleSetting($setting);\n\n        $moduleConfiguration\n            ->addController(\n                new Controller(\n                    'originalClassNamespace',\n                    'moduleClassNamespace'\n                )\n            )->addController(\n                new Controller(\n                    'otherOriginalClassNamespace',\n                    'moduleClassNamespace'\n                )\n            )\n            ->addClassExtension(\n                new ClassExtension(\n                    'originalClassNamespace',\n                    'moduleClassNamespace'\n                )\n            )\n            ->addClassExtension(\n                new ClassExtension(\n                    'otherOriginalClassNamespace',\n                    'moduleClassNamespace'\n                )\n            );\n\n        $setting = new Setting();\n        $setting\n            ->setName('grid')\n            ->setValue('row')\n            ->setType('str')\n            ->setGroupName('frontend');\n        $moduleConfiguration->addModuleSetting($setting);\n\n        $setting = new Setting();\n        $setting\n            ->setName('array')\n            ->setValue(['1', '2'])\n            ->setType('arr')\n            ->setGroupName('frontend');\n        $moduleConfiguration->addModuleSetting($setting);\n\n        return $moduleConfiguration;\n    }\n\n    private function persistModuleConfiguration(ModuleConfiguration $moduleConfiguration): void\n    {\n        $chain = new ClassExtensionsChain();\n        $chain->setChain([\n            'originalClassNamespace' => ['moduleClassNamespace'],\n        ]);\n\n        $shopConfiguration = new ShopConfiguration();\n        $shopConfiguration->setClassExtensionsChain($chain);\n        $shopConfiguration->addModuleConfiguration($moduleConfiguration);\n\n        $shopConfigurationDao = $this->container->get(ShopConfigurationDaoInterface::class);\n        $shopConfigurationDao->save($shopConfiguration, $this->shopId);\n    }\n\n    /**\n     * We need to replace services in the container with a mock\n     *\n     * @return ContainerBuilder\n     */\n    private function setupAndConfigureContainer()\n    {\n        if (!$this->testContainerFactory instanceof TestContainerFactory) {\n            $this->testContainerFactory = new TestContainerFactory();\n        }\n        $container = $this->testContainerFactory->create();\n\n        $modulePathResolver = $this->prophesize(ModulePathResolverInterface::class);\n        $modulePathResolver->getFullModulePathFromConfiguration($this->testModuleId, $this->shopId)\n            ->willReturn(__DIR__ . '/../../TestData/TestModule');\n        $container->set(ModulePathResolverInterface::class, $modulePathResolver->reveal());\n        $container->autowire(ModulePathResolverInterface::class, ModulePathResolver::class);\n\n        $container->compile();\n\n        return $container;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Setup/Service/ModuleDependencyActivationTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Setup\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Exception\\DependencyValidationException;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\Group;\n\n#[Group('module-dependency')]\nfinal class ModuleDependencyActivationTest extends IntegrationTestCase\n{\n    private int $shopId = 1;\n    private string $testDependentModulePath = __DIR__ . '/Fixtures/TestDependentModule';\n    private string $testDependentModuleId = 'test-dependent-module';\n    private string $testModuleWithDependencyPath = __DIR__ . '/Fixtures/TestModuleWithDependency';\n    private string $testModuleWithDependencyId = 'test-module-with-dependency';\n    private string $testMissingDependencyModuleId = 'test-missing-dependency-module';\n    private string $testMissingDependencyModulePath = __DIR__ . '/Fixtures/TestMissingDependencyModule';\n\n    public function setup(): void\n    {\n        parent::setUp();\n\n        $moduleInstaller = $this->get(ModuleInstallerInterface::class);\n        $moduleInstaller->install(new OxidEshopPackage($this->testDependentModulePath));\n        $moduleInstaller->install(new OxidEshopPackage($this->testModuleWithDependencyPath));\n    }\n\n    public function tearDown(): void\n    {\n        $moduleInstaller = $this->get(ModuleInstallerInterface::class);\n        $moduleInstaller->uninstall(new OxidEshopPackage($this->testModuleWithDependencyPath));\n        $moduleInstaller->uninstall(new OxidEshopPackage($this->testDependentModulePath));\n\n        parent::tearDown();\n    }\n\n    public function testSuccessfulActivation(): void\n    {\n        $moduleActivation = $this->get(ModuleActivationBridgeInterface::class);\n        $moduleActivation->activate($this->testDependentModuleId, $this->shopId);\n        $moduleActivation->activate($this->testModuleWithDependencyId, $this->shopId);\n\n        $this->assertTrue(\n            $this->get(ModuleActivationBridgeInterface::class)->isActive(\n                $this->testModuleWithDependencyId,\n                $this->shopId\n            )\n        );\n    }\n\n    public function testActivationExceptionThrownIfDependentModuleIsInactive(): void\n    {\n        $this->expectException(DependencyValidationException::class);\n        $this->expectExceptionMessage(sprintf('to be activated: \"%s\"', $this->testDependentModuleId));\n\n        $this->get(ModuleActivationBridgeInterface::class)->activate(\n            $this->testModuleWithDependencyId,\n            $this->shopId\n        );\n    }\n\n    public function testSuccessfulDeactivation(): void\n    {\n        $moduleActivation = $this->get(ModuleActivationBridgeInterface::class);\n        $moduleActivation->activate($this->testDependentModuleId, $this->shopId);\n        $moduleActivation->activate($this->testModuleWithDependencyId, $this->shopId);\n\n        $moduleActivation->deactivate($this->testModuleWithDependencyId, $this->shopId);\n        $moduleActivation->deactivate($this->testDependentModuleId, $this->shopId);\n\n        $this->assertFalse($moduleActivation->isActive($this->testModuleWithDependencyId, $this->shopId));\n        $this->assertFalse($moduleActivation->isActive($this->testDependentModuleId, $this->shopId));\n    }\n\n    public function testDeactivationExceptionThrownIfItIsADependencyOfAnotherModule(): void\n    {\n        $this->expectException(DependencyValidationException::class);\n        $this->expectExceptionMessage(sprintf('to be deactivated: \"%s\"', $this->testModuleWithDependencyId));\n\n        $moduleActivation = $this->get(ModuleActivationBridgeInterface::class);\n        $moduleActivation->activate($this->testDependentModuleId, $this->shopId);\n        $moduleActivation->activate($this->testModuleWithDependencyId, $this->shopId);\n\n        $this->get(ModuleActivationBridgeInterface::class)->deactivate(\n            $this->testDependentModuleId,\n            $this->shopId\n        );\n    }\n\n    public function testDependencyValidatorShouldFireEarlier(): void\n    {\n        $this->expectException(DependencyValidationException::class);\n\n        $moduleInstaller = $this->get(ModuleInstallerInterface::class);\n        $moduleInstaller->install(new OxidEshopPackage($this->testMissingDependencyModulePath));\n\n        $moduleActivation = $this->get(ModuleActivationBridgeInterface::class);\n        $moduleActivation->activate($this->testMissingDependencyModuleId, $this->shopId);\n\n        $moduleInstaller->uninstall(new OxidEshopPackage($this->testMissingDependencyModulePath));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Setup/Service/ModuleDependencyResolverTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Setup\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleDependencyDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleDependencies;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ModuleDependencyResolver;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\Group;\n\n#[Group('module-dependency')]\nfinal class ModuleDependencyResolverTest extends IntegrationTestCase\n{\n    public function testIndependentModuleDoesNotHaveUnresolvedModuleDependenciesDuringActivationProcess(): void\n    {\n        $moduleDependencyDao = $this->createStub(ModuleDependencyDaoInterface::class);\n        $moduleDependencyDao\n            ->method('get')\n            ->willReturn(new ModuleDependencies([]));\n        $moduleConfigurationDao = $this->createStub(ModuleConfigurationDaoInterface::class);\n        $resolver = new ModuleDependencyResolver(\n            $moduleDependencyDao,\n            $moduleConfigurationDao\n        );\n        $unresolvedDependencies = $resolver->getUnresolvedActivationDependencies('module-id-1', 1);\n\n        $this->assertFalse($unresolvedDependencies->hasModuleDependencies());\n    }\n\n    public function testIndependentModuleNotTriggerGetModuleDependenciesMethod(): void\n    {\n        $moduleDependencyDao = $this->createMock(ModuleDependencyDaoInterface::class);\n        $moduleDependencyDao\n            ->expects($this->never())\n            ->method('get');\n        $moduleConfigurationDao = $this->createStub(ModuleConfigurationDaoInterface::class);\n        $moduleConfigurationDao\n            ->method('getAll')\n            ->willReturn([\n                $this->getActiveModuleConfig('module-id-1'),\n            ]);\n        $moduleDependencyResolver = new ModuleDependencyResolver(\n            $moduleDependencyDao,\n            $moduleConfigurationDao\n        );\n\n        $moduleDependencyResolver->getUnresolvedDeactivationDependencies('module-id-1', 1);\n    }\n\n    public function testDependentModuleHasUnresolvedModuleDependenciesDuringDeactivationProcess(): void\n    {\n        $moduleDependencyDao = $this->createStub(ModuleDependencyDaoInterface::class);\n        $moduleDependencyDao\n            ->method('get')\n            ->willReturnCallback(function ($moduleId): ModuleDependencies {\n                return $this->getModuleDependenciesCallback($moduleId);\n            });\n        $moduleConfigurationDao = $this->createStub(ModuleConfigurationDaoInterface::class);\n        $moduleConfigurationDao\n            ->method('getAll')\n            ->willReturn([\n                $this->getActiveModuleConfig('module-id-1'),\n                $this->getActiveModuleConfig('module-id-2'),\n                $this->getActiveModuleConfig('module-id-3'),\n                $this->getActiveModuleConfig('module-id-4'),\n            ]);\n        $resolver = new ModuleDependencyResolver(\n            $moduleDependencyDao,\n            $moduleConfigurationDao\n        );\n\n        $unresolvedDependencies = $resolver->getUnresolvedDeactivationDependencies('module-id-2', 1);\n\n        $this->assertTrue($unresolvedDependencies->hasModuleDependencies());\n        $this->assertEquals(['module-id-1', 'module-id-4'], $unresolvedDependencies->getModuleIds());\n    }\n\n    public function getModuleDependenciesCallback($moduleId): ModuleDependencies\n    {\n        if ($moduleId === 'module-id-1') {\n            return new ModuleDependencies(['modules' => ['module-id-2', 'module-id-3']]);\n        }\n        if ($moduleId === 'module-id-4') {\n            return new ModuleDependencies(['modules' => ['module-id-2']]);\n        }\n\n        return new ModuleDependencies();\n    }\n\n    private function getActiveModuleConfig(string $moduleId): ModuleConfiguration\n    {\n        return (new ModuleConfiguration())\n            ->setId($moduleId)\n            ->setActivated(true);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Setup/Service/ModuleServicesImporterTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Setup\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\DataObject\\DIConfigWrapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Exception\\NoServiceYamlException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Service\\ModuleServicesImporterInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\Group;\nuse Symfony\\Component\\Yaml\\Yaml;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class ModuleServicesImporterTest extends IntegrationTestCase\n{\n    private int $shopId = 1;\n\n    #[Group('exclude-from-compilation')]\n    public function testImport(): void\n    {\n        $moduleDirectory = Path::join(__DIR__, 'Fixtures', 'Module');\n\n        $importer = $this->get(ModuleServicesImporterInterface::class);\n        $importer->addImport($moduleDirectory, 1);\n\n        $expectedImport = Path::makeRelative(\n            Path::join($moduleDirectory, 'services.yaml'),\n            Path::getDirectory($this->getActiveModuleServicesFilePath())\n        );\n\n        $this->assertContains(\n            $expectedImport,\n            $this->getDIConfigWrapper()->getImportFileNames()\n        );\n\n        $importer->removeImport($moduleDirectory, 1);\n\n        $this->assertNotContains(\n            $expectedImport,\n            $this->getDIConfigWrapper()->getImportFileNames()\n        );\n    }\n\n    public function testImportWithNoServicesFileInDirectory(): void\n    {\n        $moduleDirectory = Path::join(__DIR__, 'Fixtures');\n\n        $importer = $this->get(ModuleServicesImporterInterface::class);\n\n        $this->expectException(NoServiceYamlException::class);\n        $importer->addImport($moduleDirectory, 1);\n    }\n\n    private function getDIConfigWrapper(): DIConfigWrapper\n    {\n        return new DIConfigWrapper(\n            Yaml::parse(file_get_contents($this->getActiveModuleServicesFilePath()), Yaml::PARSE_CUSTOM_TAGS)\n        );\n    }\n\n    private function getActiveModuleServicesFilePath(): string\n    {\n        return $this->get(ContextInterface::class)->getActiveModuleServicesFilePath($this->shopId);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Setup/Validator/Fixtures/ModuleWithCorrectServiceYaml/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  some.key:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator\\ServicesYamlValidator"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Setup/Validator/Fixtures/ModuleWithWrongServiceYaml/services.yaml",
    "content": "services:\n  _defaults:\n    autowire: true\n\n  some.key:\n    class: OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator\\NonExisting"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Setup/Validator/ServicesYamlValidatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Setup\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Dao\\ProjectYamlDao;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModulePathResolverInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Exception\\InvalidModuleServicesException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator\\ModuleConfigurationValidatorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator\\ServicesYamlValidator;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\ContextStub;\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class ServicesYamlValidatorTest extends IntegrationTestCase\n{\n    private ModuleConfigurationValidatorInterface $validator;\n    private ModuleConfiguration $moduleConfiguration;\n    private ModulePathResolverInterface|MockObject $modulePathResolver;\n    private string $testModuleId = 'testModuleId';\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $context = new ContextStub();\n        $this->modulePathResolver = $this->createStub(ModulePathResolverInterface::class);\n        $this->moduleConfiguration = new ModuleConfiguration();\n        $this->validator = new ServicesYamlValidator(\n            $context,\n            new ProjectYamlDao(\n                $context,\n                new Filesystem()\n            ),\n            $this->modulePathResolver\n        );\n    }\n\n    #[DoesNotPerformAssertions]\n    public function testValidateNoServicesYaml(): void\n    {\n        $this->moduleConfiguration->setModuleSource('.');\n        $this->moduleConfiguration->setId($this->testModuleId);\n        $this->modulePathResolver->method('getFullModulePathFromConfiguration')\n            ->willReturn(Path::join(__DIR__, 'Fixtures', 'ModuleWithNoServices'));\n\n        $this->validator->validate($this->moduleConfiguration, 1);\n    }\n\n    #[DoesNotPerformAssertions]\n    public function testWithCorrectServiceYaml(): void\n    {\n        $this->moduleConfiguration->setModuleSource('Working');\n        $this->moduleConfiguration->setId(\"testId\");\n        $this->modulePathResolver->method('getFullModulePathFromConfiguration')\n            ->willReturn(Path::join(__DIR__, 'Fixtures', 'ModuleWithCorrectServiceYaml'));\n\n        $this->validator->validate($this->moduleConfiguration, 1);\n    }\n\n    public function testWithWrongServiceYaml(): void\n    {\n        $this->moduleConfiguration->setModuleSource('NotWorking');\n        $this->moduleConfiguration->setId($this->testModuleId);\n        $this->modulePathResolver\n            ->method('getFullModulePathFromConfiguration')\n            ->willReturn(Path::join(__DIR__, 'Fixtures', 'ModuleWithWrongServiceYaml'));\n\n        $this->expectException(InvalidModuleServicesException::class);\n        $this->validator->validate($this->moduleConfiguration, 1);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/State/ModuleStateServiceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\State;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\State\\ModuleStateServiceInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ModuleStateServiceTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    public function testIsActive(): void\n    {\n        $configuration = new ModuleConfiguration();\n        $configuration\n            ->setModuleSource('test')\n            ->setActivated(true)\n            ->setId('testModule');\n\n        $this->get(ModuleConfigurationDaoInterface::class)->save($configuration, 1);\n\n        $moduleStateService = $this->get(ModuleStateServiceInterface::class);\n\n        $this->assertTrue($moduleStateService->isActive('testModule', 1));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/TestData/TestModule/CustomSourceDirectory/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.1';\n\n$aModule = array(\n    'id'           => 'testModule',\n    'thumbnail'    => 'picture.png',\n    'version'      => '1.0',\n    'author'       => 'OXID eSales AG',\n    'extend'       => [\n        'shopClass' => 'testModuleClassExtendsShopClass',\n    ]\n);\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/TestData/TestModule/ModuleEvents.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\TestData\\TestModule;\n\nclass ModuleEvents\n{\n    public static function onActivate(): void\n    {\n        echo 'Method onActivate was called';\n    }\n\n    public static function onDeactivate(): void\n    {\n        echo 'Method onDeactivate was called';\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/TestData/TestModule/SomeModuleService.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\TestData\\TestModule;\n\nclass SomeModuleService\n{\n    public function doSomething()\n    {\n        return null;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/TestData/TestModule/TestEventSubscriber.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\TestData\\TestModule;\n\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Module\\TestData\\TestModule\\TestEvent;\nuse Symfony\\Component\\EventDispatcher\\EventSubscriberInterface;\n\nclass TestEventSubscriber implements EventSubscriberInterface\n{\n    public function handleEvent(TestEvent $event): TestEvent\n    {\n        $event->handle();\n        return $event;\n    }\n\n    public static function getSubscribedEvents()\n    {\n        return [TestEvent::class => 'handleEvent'];\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/TestData/TestModule/assets/some.css",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/TestData/TestModule/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.1';\n\n$aModule = array(\n    'id'           => 'test-module',\n    'thumbnail'    => 'picture.png',\n    'version'      => '1.0',\n    'author'       => 'OXID eSales AG',\n    'extend'       => [\n        'shopClass' => 'testModuleClassExtendsShopClass',\n    ],\n    'settings' => [\n        [\n            'group' => 'main',\n            'name' => 'test-setting',\n            'type' => 'arr',\n            'value' => ['Preis', 'Hersteller'],\n        ],\n        [\n            'name' => 'string-setting',\n            'type' => 'str',\n            'value' => 'default',\n        ]\n    ],\n);\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/TestData/TestModule/readme.txt",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/TestData/TestModule/services.yaml",
    "content": "services:\n\n  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\TestData\\TestModule\\TestEventSubscriber:\n    class: OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\TestData\\TestModule\\TestEventSubscriber\n    tags:\n      - { name: kernel.event_subscriber }\n\n  OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\TestData\\TestModule\\SomeModuleService:\n    class: OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\TestData\\TestModule\\SomeModuleService\n    public: true\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Translation/Locator/AdminAreaModuleTranslationFileLocatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Translation\\Locator;\n\nuse Prophecy\\Prophecy\\ObjectProphecy;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ModulesDataProviderInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Locator\\AdminAreaModuleTranslationFileLocator;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\nuse Symfony\\Component\\Filesystem\\Filesystem;\n\nfinal class AdminAreaModuleTranslationFileLocatorTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n    use ProphecyTrait;\n\n    /** @var ModulesDataProviderInterface */\n    private ObjectProphecy $activeModulesDataProvider;\n\n    private Filesystem $filesystem;\n\n    private string $adminThemeName;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->activeModulesDataProvider = $this->prophesize(ModulesDataProviderInterface::class);\n        $this->filesystem = new Filesystem();\n        $this->adminThemeName = 'admin';\n    }\n\n    public function testLocateWithEmptyPaths(): void\n    {\n        $this->activeModulesDataProvider->getModulePaths()->willReturn([]);\n\n        $modulesLangFileLocator = new AdminAreaModuleTranslationFileLocator(\n            $this->activeModulesDataProvider->reveal(),\n            $this->filesystem,\n            $this->adminThemeName\n        );\n\n        $paths = $modulesLangFileLocator->locate('de');\n\n        self::assertSame([], $paths);\n    }\n\n    public function testLocateForAdminLang(): void\n    {\n        $modulePath = __DIR__ . '/Fixtures/module-name-2';\n        $this->activeModulesDataProvider->getModulePaths()->willReturn([$modulePath]);\n\n        $modulesLangFileLocator = new AdminAreaModuleTranslationFileLocator(\n            $this->activeModulesDataProvider->reveal(),\n            $this->filesystem,\n            $this->adminThemeName\n        );\n\n        $paths = $modulesLangFileLocator->locate('de');\n\n        self::assertSame(\n            [\n                \"$modulePath/views/admin/de/de1_lang.php\",\n                \"$modulePath/views/admin/de/de2_lang.php\",\n                \"$modulePath/views/admin/de/module_options.php\"\n            ],\n            $paths\n        );\n    }\n\n    public function testLocateWithApplicationFolder(): void\n    {\n        $modulePath = __DIR__ . '/Fixtures/module-name-4';\n        $this->activeModulesDataProvider->getModulePaths()->willReturn([$modulePath]);\n\n        $modulesLangFileLocator = new AdminAreaModuleTranslationFileLocator(\n            $this->activeModulesDataProvider->reveal(),\n            $this->filesystem,\n            $this->adminThemeName\n        );\n\n        $paths = $modulesLangFileLocator->locate('de');\n\n        self::assertSame(\n            [\n                \"$modulePath/Application/views/admin/de/de1_lang.php\",\n                \"$modulePath/Application/views/admin/de/module_options.php\"\n            ],\n            $paths\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Translation/Locator/Fixtures/module-name-1/translations/de/cust_lang.php",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Translation/Locator/Fixtures/module-name-1/translations/de/de1_lang.php",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Translation/Locator/Fixtures/module-name-1/translations/de/de2_lang.php",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Translation/Locator/Fixtures/module-name-2/views/admin/de/de1_lang.php",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Translation/Locator/Fixtures/module-name-2/views/admin/de/de2_lang.php",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Translation/Locator/Fixtures/module-name-2/views/admin/de/module_options.php",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Translation/Locator/Fixtures/module-name-3/Application/translations/de/de1_lang.php",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Translation/Locator/Fixtures/module-name-3/Application/translations/de/de2_lang.php",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Translation/Locator/Fixtures/module-name-4/Application/views/admin/de/de1_lang.php",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Translation/Locator/Fixtures/module-name-4/Application/views/admin/de/module_options.php",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Framework/Module/Translation/Locator/FrontendModuleTranslationFileLocatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\Translation\\Locator;\n\nuse Prophecy\\Prophecy\\ObjectProphecy;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ActiveModulesDataProviderInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Translation\\Locator\\FrontendModuleTranslationFileLocator;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\nuse Symfony\\Component\\Filesystem\\Filesystem;\n\nfinal class FrontendModuleTranslationFileLocatorTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n    use ProphecyTrait;\n\n    /** @var ActiveModulesDataProviderInterface */\n    private ObjectProphecy $activeModulesDataProvider;\n\n    private Filesystem $filesystem;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->activeModulesDataProvider = $this->prophesize(ActiveModulesDataProviderInterface::class);\n        $this->filesystem = new Filesystem();\n    }\n\n    public function testLocateWithEmptyPaths(): void\n    {\n        $this->activeModulesDataProvider->getModulePaths()->willReturn([]);\n\n        $modulesLangFileLocator = new FrontendModuleTranslationFileLocator(\n            $this->activeModulesDataProvider->reveal(),\n            $this->filesystem\n        );\n\n        $paths = $modulesLangFileLocator->locate('de');\n\n        self::assertSame([], $paths);\n    }\n\n    public function testLocateWithExistingFile(): void\n    {\n        $modulePath = __DIR__ . '/Fixtures/module-name-1';\n        $this->activeModulesDataProvider->getModulePaths()->willReturn([$modulePath]);\n\n        $modulesLangFileLocator = new FrontendModuleTranslationFileLocator(\n            $this->activeModulesDataProvider->reveal(),\n            $this->filesystem\n        );\n\n        $paths = $modulesLangFileLocator->locate('de');\n\n        self::assertSame(\n            [\n                \"$modulePath/translations/de/de1_lang.php\",\n                \"$modulePath/translations/de/de2_lang.php\"\n            ],\n            $paths\n        );\n    }\n\n    public function testLocateWithApplicationFolder(): void\n    {\n        $modulePath = __DIR__ . '/Fixtures/module-name-3';\n        $this->activeModulesDataProvider->getModulePaths()->willReturn([$modulePath]);\n\n        $modulesLangFileLocator = new FrontendModuleTranslationFileLocator(\n            $this->activeModulesDataProvider->reveal(),\n            $this->filesystem\n        );\n\n        $paths = $modulesLangFileLocator->locate('de');\n\n        self::assertSame(\n            [\n                \"$modulePath/Application/translations/de/de1_lang.php\",\n                \"$modulePath/Application/translations/de/de2_lang.php\"\n            ],\n            $paths\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Storage/YamlFileStorageTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Storage;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Storage\\YamlFileStorage;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Config\\FileLocator;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Lock\\LockFactory;\nuse Symfony\\Component\\Yaml\\Exception\\ParseException;\n\nfinal class YamlFileStorageTest extends TestCase\n{\n    use ContainerTrait;\n\n    /**\n     * @var resource\n     */\n    private $tempFileHandle;\n\n    public function testSaving(): void\n    {\n        $testData = [\n            'one' => [\n                'two',\n            ],\n            'uno' => [\n                'due',\n            ],\n        ];\n\n        $yamlFileStorage = new YamlFileStorage(\n            new FileLocator(),\n            $this->getFilePath(),\n            $this->getLockFactoryFromContainer(),\n            $this->getFileSystemServiceFromContainer()\n        );\n\n        $yamlFileStorage->save($testData);\n\n        $this->assertSame(\n            $testData,\n            $yamlFileStorage->get()\n        );\n    }\n\n    public function testCreatesNewFileIfDoesNotExist(): void\n    {\n        $filePath = $this->getFilePath();\n        unlink($filePath);\n\n        $yamlFileStorage = new YamlFileStorage(\n            new FileLocator(),\n            $filePath,\n            $this->getLockFactoryFromContainer(),\n            $this->getFileSystemServiceFromContainer()\n        );\n\n        $yamlFileStorage->save(['testData']);\n\n        $this->assertSame(\n            ['testData'],\n            $yamlFileStorage->get()\n        );\n    }\n\n    public function testCreatesNewDirectoryAndFileIfDoNotExist(): void\n    {\n        $filePath = $this->getFilePath();\n        unlink($filePath);\n\n        $filePath = $this->getFilePath() . '/fileInNonExistentDirectory.yml';\n\n        $yamlFileStorage = new YamlFileStorage(\n            new FileLocator(),\n            $filePath,\n            $this->getLockFactoryFromContainer(),\n            $this->getFileSystemServiceFromContainer()\n        );\n\n        $yamlFileStorage->save(['testData']);\n\n        $this->assertSame(\n            ['testData'],\n            $yamlFileStorage->get()\n        );\n    }\n\n    public function testStorageWithCorruptedFile(): void\n    {\n        $this->expectException(ParseException::class);\n        $filePath = $this->getFilePath();\n        $yamlContent = \"\\t\";\n\n        file_put_contents($filePath, $yamlContent);\n\n        $yamlFileStorage = new YamlFileStorage(\n            new FileLocator(),\n            $filePath,\n            $this->getLockFactoryFromContainer(),\n            $this->getFileSystemServiceFromContainer()\n        );\n\n        $yamlFileStorage->get();\n    }\n\n    public function testStorageWithEmptyFile(): void\n    {\n        $filePath = $this->getFilePath();\n\n        file_put_contents($filePath, '');\n\n        $yamlFileStorage = new YamlFileStorage(\n            new FileLocator(),\n            $filePath,\n            $this->getLockFactoryFromContainer(),\n            $this->getFileSystemServiceFromContainer()\n        );\n\n        $this->assertSame(\n            [],\n            $yamlFileStorage->get()\n        );\n    }\n\n    public function testEmptyYamlArrayThrowsNoError(): void\n    {\n        $yaml = '[]';\n\n        file_put_contents($this->getFilePath(), $yaml);\n\n        $yamlFileStorage = new YamlFileStorage(\n            new FileLocator(),\n            $this->getFilePath(),\n            $this->getLockFactoryFromContainer(),\n            $this->getFileSystemServiceFromContainer()\n        );\n        $parsedYaml = $yamlFileStorage->get();\n\n        $this->assertEquals([], $parsedYaml);\n    }\n\n    private function getFilePath(): string\n    {\n        if ($this->tempFileHandle === null) {\n            $this->tempFileHandle = tmpfile();\n        }\n\n        return stream_get_meta_data($this->tempFileHandle)['uri'];\n    }\n\n    private function getLockFactoryFromContainer(): LockFactory\n    {\n        return $this->get('oxid_esales.common.storage.flock_store_lock_factory');\n    }\n\n    private function getFileSystemServiceFromContainer(): Filesystem\n    {\n        return $this->get('oxid_esales.symfony.file_system');\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Templating/Cache/MultiShopTemplateCacheServiceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Templating\\Cache;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Cache\\ShopTemplateCacheServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\TestContainerFactory;\nuse OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\ContextStub;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class MultiShopTemplateCacheServiceTest extends TestCase\n{\n    use ContainerTrait;\n\n    private string $testFixturesDirectory = __DIR__ . '/cache_fixtures';\n    private int $shopId1 = 123;\n    private int $shopId2 = 456;\n    private array $allShopIds = [];\n    private array $cacheFixturesForShopId = [];\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->allShopIds = [\n            $this->shopId1,\n            $this->shopId2,\n        ];\n        $this->stubContext();\n        $this->generateCacheFixtures();\n    }\n\n    public function tearDown(): void\n    {\n        $this->cleanupCache();\n\n        parent::tearDown();\n    }\n\n    public function testInvalidateCacheWillKeepOtherShopsCacheFile(): void\n    {\n        $this->get(ShopTemplateCacheServiceInterface::class)->invalidateCache($this->shopId1);\n\n        $this->assertFileDoesNotExist($this->cacheFixturesForShopId[$this->shopId1]);\n        $this->assertFileExists($this->cacheFixturesForShopId[$this->shopId2]);\n    }\n\n    public function testInvalidateAllShopsCacheWillRemoveAllCacheFiles(): void\n    {\n        $this->get(ShopTemplateCacheServiceInterface::class)->invalidateAllShopsCache();\n\n        $this->assertFileDoesNotExist($this->cacheFixturesForShopId[$this->shopId1]);\n        $this->assertFileDoesNotExist($this->cacheFixturesForShopId[$this->shopId2]);\n    }\n\n    private function stubContext(): void\n    {\n        $context = new ContextStub();\n        $context->setAllShopIds($this->allShopIds);\n\n        $this->container = (new TestContainerFactory())->create();\n        $this->container->set(ContextInterface::class, $context);\n        $this->container->setParameter('oxid_esales.build_directory', $this->testFixturesDirectory);\n        $this->container->autowire(ContextInterface::class, ContextInterface::class);\n        $this->container->compile();\n    }\n\n    private function generateCacheFixtures(): void\n    {\n        $filesystem = $this->get('oxid_esales.symfony.file_system');\n\n        foreach ($this->allShopIds as $shopId) {\n            $templateCacheDirectory = $this->get(ShopTemplateCacheServiceInterface::class)->getCacheDirectory($shopId);\n            $filesystem->mkdir($templateCacheDirectory);\n            $this->cacheFixturesForShopId[$shopId] = Path::join($templateCacheDirectory, 'some-cache-file');\n            $filesystem->touch($this->cacheFixturesForShopId[$shopId]);\n        }\n\n        $this->assertFileExists($this->cacheFixturesForShopId[$this->shopId1]);\n        $this->assertFileExists($this->cacheFixturesForShopId[$this->shopId2]);\n    }\n\n    private function cleanupCache(): void\n    {\n        $this->get('oxid_esales.symfony.file_system')->remove($this->testFixturesDirectory);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Theme/Command/Fixtures/shop/source/Application/views/testTheme/theme.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n$aTheme = [\n    'id'          => 'testTheme',\n    'title'       => 'Test Theme',\n];\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Theme/Command/ThemeActivateCommandTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Theme\\Command;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Command\\ThemeActivateCommand;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Symfony\\Component\\Console\\Tester\\CommandTester;\n\nfinal class ThemeActivateCommandTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private string $fixtureDirectory = __DIR__ . '/Fixtures';\n\n    private string $initialThemeId = 'some-theme-id';\n    private string $newThemeId = 'testTheme';\n    private array $originalConfig;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        $this->setShopFixtures();\n    }\n\n    public function testThemeActivationOnSuccess(): void\n    {\n        $this->createCommandTester()\n            ->execute(\n                ['theme-id' => $this->newThemeId]\n            );\n\n        $this->assertSame($this->newThemeId, $this->getActiveTheme());\n    }\n\n    public function testThemeAlreadyActivated(): void\n    {\n        $arguments = ['theme-id' => $this->newThemeId];\n        $commandTester = $this->createCommandTester();\n\n        $commandTester->execute($arguments);\n        $commandTester->execute($arguments);\n\n        $this->assertStringContainsString(\n            \\sprintf('Theme - \"%s\" is already active.', $this->newThemeId),\n            $commandTester->getDisplay()\n        );\n    }\n\n    public function testNonExistingThemeActivation(): void\n    {\n        $nonExistingThemeId = 'some-theme-id';\n        $commandTester = $this->createCommandTester();\n\n        $commandTester->execute(['theme-id' => $nonExistingThemeId]);\n\n        $this->assertStringContainsString(\n            sprintf('Theme - \"%s\" not found.', $nonExistingThemeId),\n            $commandTester->getDisplay()\n        );\n        $this->assertSame($this->initialThemeId, $this->getActiveTheme());\n    }\n\n    private function getActiveTheme(): string\n    {\n        return Registry::getConfig()->getConfigParam('sTheme');\n    }\n\n    private function setShopFixtures(): void\n    {\n        Registry::getConfig()->reinitialize();\n        Registry::getConfig()->setConfigParam('sTheme', $this->initialThemeId);\n\n        $this->setParameter('oxid_esales.shop_source_directory', \"$this->fixtureDirectory/shop/source/\");\n    }\n\n    private function createCommandTester(): CommandTester\n    {\n        return new CommandTester($this->get(ThemeActivateCommand::class));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Framework/Theme/Config/Dao/ThemeSettingDaoTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Theme\\Config\\Dao;\n\nuse Doctrine\\DBAL\\Query\\QueryBuilder;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Config\\Dao\\ThemeSettingDao;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Config\\Dao\\ThemeSettingDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Config\\DataObject\\ThemeSetting;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Utility\\ShopSettingEncoderInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\ConnectionFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\DatabaseTrait;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ThemeSettingDaoTest extends TestCase\n{\n    use ContainerTrait;\n    use DatabaseTrait;\n\n    private const THEME_ID = 'apex';\n\n    protected function setUp(): void\n    {\n        parent::setUp();\n\n        $this->beginTransaction($this->get(ConnectionFactoryInterface::class)->create());\n    }\n\n    protected function tearDown(): void\n    {\n        $this->rollBackTransaction($this->get(ConnectionFactoryInterface::class)->create());\n\n        parent::tearDown();\n    }\n\n    #[DataProvider('settingValueDataProvider')]\n    public function testSave(string $name, string $type, string|int|float|bool|array $value): void\n    {\n        $settingDao = $this->getSettingDao();\n\n        $themeSetting = $this->createSetting($name, $type, $value);\n\n        $settingDao->save($themeSetting);\n\n        $this->assertEquals(\n            $themeSetting,\n            $settingDao->get($name, 1, self::THEME_ID)\n        );\n    }\n\n    public function testGetNonExistentSetting(): void\n    {\n        $settingDao = $this->getSettingDao();\n\n        $this->expectException(EntryDoesNotExistDaoException::class);\n        $settingDao->get('nonExisting', 1, self::THEME_ID);\n    }\n\n    public function testDelete(): void\n    {\n        $settingDao = $this->getSettingDao();\n        $themeSetting = $this->createSetting('testDelete', 'str', 'value');\n\n        $settingDao->save($themeSetting);\n\n        $settingDao->delete($themeSetting);\n\n        $this->expectException(EntryDoesNotExistDaoException::class);\n        $settingDao->get('testDelete', 1, self::THEME_ID);\n    }\n\n    public function testUpdate(): void\n    {\n        $settingDao = $this->getSettingDao();\n        $themeSetting = $this->createSetting('testUpdate', 'str', 'first');\n\n        $settingDao->save($themeSetting);\n\n        $themeSetting->setValue('second');\n\n        $settingDao->save($themeSetting);\n\n        $this->assertEquals(\n            $themeSetting,\n            $settingDao->get('testUpdate', 1, self::THEME_ID)\n        );\n    }\n\n    public function testUpdateDoesNotCreateDuplicationsInDatabase(): void\n    {\n        $this->assertSame(0, $this->getRowCount());\n\n        $settingDao = $this->getSettingDao();\n        $themeSetting = $this->createSetting('testDuplications', 'str', 'first');\n\n        $settingDao->save($themeSetting);\n\n        $this->assertSame(1, $this->getRowCount());\n\n        $themeSetting->setValue('second');\n\n        $settingDao->save($themeSetting);\n\n        $this->assertSame(1, $this->getRowCount());\n    }\n\n    public function testGetDoesNotReturnCachedReference(): void\n    {\n        $settingDao = $this->getSettingDao();\n        $themeSetting = $this->createSetting('cloning_test', 'str', 'initial');\n\n        $settingDao->save($themeSetting);\n\n        $first = $settingDao->get('cloning_test', 1, self::THEME_ID);\n        $first->setValue('changed');\n\n        $second = $settingDao->get('cloning_test', 1, self::THEME_ID);\n\n        $this->assertSame('initial', $second->getValue());\n    }\n\n    public function testGetUsesDatabaseOnlyOnceForSameSetting(): void\n    {\n        $queryBuilder = $this->createMock(QueryBuilder::class);\n\n        $queryBuilder\n            ->expects($this->once())\n            ->method('fetchAssociative')\n            ->willReturn([\n                'type' => 'str',\n                'value' => 'test',\n                'name' => 'test',\n            ]);\n\n        $queryBuilderFactory = $this->createMock(QueryBuilderFactoryInterface::class);\n        $queryBuilderFactory\n            ->expects($this->once())\n            ->method('create')\n            ->willReturn($queryBuilder);\n\n        $encoder = $this->createStub(ShopSettingEncoderInterface::class);\n\n        $eventDispatcher = $this->createStub(EventDispatcherInterface::class);\n\n        $settingDao = new ThemeSettingDao(\n            $queryBuilderFactory,\n            $encoder,\n            $eventDispatcher\n        );\n\n        $settingDao->get('test', 1, self::THEME_ID);\n        $settingDao->get('test', 1, self::THEME_ID);\n    }\n\n    public static function settingValueDataProvider(): array\n    {\n        return [\n            ['string', 'str', 'value'],\n            ['int', 'int', 1],\n            ['float', 'num', 1.23],\n            ['bool', 'bool', true],\n            ['array', 'arr', ['key' => 'value']],\n        ];\n    }\n\n    private function createSetting(string $name, string $type, string|int|float|bool|array $value): ThemeSetting\n    {\n        $setting = new ThemeSetting();\n        $setting\n            ->setShopId(1)\n            ->setThemeId(self::THEME_ID)\n            ->setName($name)\n            ->setType($type)\n            ->setValue($value);\n\n        return $setting;\n    }\n\n    private function getSettingDao(): ThemeSettingDaoInterface\n    {\n        return $this->get(ThemeSettingDaoInterface::class);\n    }\n\n    private function getRowCount(): int\n    {\n        return $this\n            ->get(QueryBuilderFactoryInterface::class)\n            ->create()\n            ->select('*')\n            ->from('oxconfig')\n            ->where('oxshopid = \"1\"')\n            ->andWhere('oxvarname = \"testDuplications\"')\n            ->andWhere('oxmodule = \"theme:' . self::THEME_ID . '\"')\n            ->executeQuery()\n            ->rowCount();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Module/Migration/Fixtures/myTestModuleWithMigrations/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.1';\n\n$aModule = array(\n    'id'          => 'myTestModuleWithMigrations',\n    'title'       => 'myTestModuleWithMigrations',\n    'description' => 'myTestModuleWithMigrations',\n    'thumbnail'   => 'picture.png',\n    'version'     => '1.0',\n    'author'      => 'OXID eSales AG'\n);\n"
  },
  {
    "path": "tests/Integration/Internal/Module/Migration/Fixtures/myTestModuleWithMigrations/migration/data/Version20170530154603.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Migrations;\n\nuse Doctrine\\Migrations\\AbstractMigration;\nuse Doctrine\\DBAL\\Schema\\Schema;\n\n/**\n * Test migration to create data which could be used to check if Migrations actually works.\n */\nclass Version20170530154603 extends AbstractMigration\n{\n    public function up(Schema $schema): void\n    {\n        $this->addSql('CREATE TABLE `test_doctrine_migration_wrapper` (`id` char(255) NOT NULL);');\n        $this->addSql(\"INSERT INTO `test_doctrine_migration_wrapper` (`id`) VALUES ('shop_migration');\");\n    }\n\n    public function down(Schema $schema): void\n    {\n    }\n\n    public function isTransactional(): bool\n    {\n        return false;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Module/Migration/Fixtures/myTestModuleWithMigrations/migration/migrations.yml",
    "content": "table_storage:\n  table_name: test_module_with_migrations\nmigrations_paths:\n  'OxidEsales\\EshopCommunity\\Migrations': data\n"
  },
  {
    "path": "tests/Integration/Internal/Module/Migration/Fixtures/myTestModuleWithoutMigrations/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.1';\n\n$aModule = array(\n    'id'          => 'myTestModuleWithoutMigrations',\n    'title'       => 'myTestModuleWithoutMigrations',\n    'description' => 'myTestModuleWithoutMigrations',\n    'thumbnail'   => 'picture.png',\n    'version'     => '1.0',\n    'author'      => 'OXID eSales AG'\n);\n"
  },
  {
    "path": "tests/Integration/Internal/Module/Migration/Fixtures/myTestModuleWithoutMigrations/migration/migrations.yml",
    "content": "table_storage:\n  table_name: test_module_without_migrations\nmigrations_paths:\n  'OxidEsales\\EshopCommunity\\Migrations': data\n"
  },
  {
    "path": "tests/Integration/Internal/Module/Migration/ModuleMigrationsTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Module\\Migration;\n\nuse OxidEsales\\DoctrineMigrationWrapper\\Migrations;\nuse OxidEsales\\DoctrineMigrationWrapper\\MigrationsBuilder;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Console\\Output\\ConsoleOutput;\nuse Symfony\\Component\\Console\\Output\\ConsoleOutputInterface;\n\n/**\n * Class ModuleMigrationsTest\n */\nfinal class ModuleMigrationsTest extends TestCase\n{\n    use ContainerTrait;\n\n    private string $moduleIdWithMigrations = 'myTestModuleWithMigrations';\n    private string $moduleIdWithoutMigrations = 'myTestModuleWithoutMigrations';\n\n    public function testMigrationsExecutionWithSpecificModule(): void\n    {\n        $this->installModule($this->moduleIdWithMigrations);\n\n        $migrations = $this->getMigrations();\n        $migrations->execute(Migrations::MIGRATE_COMMAND, $this->moduleIdWithMigrations);\n\n        $this->assertIfMigrationExistsInDatabase();\n\n        $this->removeTestModule($this->moduleIdWithMigrations);\n    }\n\n    #[DoesNotPerformAssertions]\n    public function testNoErrorWhenModuleHasNoMigrations(): void\n    {\n        $this->installModule($this->moduleIdWithoutMigrations);\n\n        $migrations = $this->getMigrations();\n        $migrations->execute(Migrations::MIGRATE_COMMAND, 'myTestModule');\n\n        $this->removeTestModule($this->moduleIdWithoutMigrations);\n    }\n\n    public function testAllMigrationsExecuteHasModuleMigrationInside(): void\n    {\n        $this->installModule($this->moduleIdWithMigrations);\n\n        $migrations = $this->getMigrations();\n        $migrations->execute(Migrations::MIGRATE_COMMAND);\n\n        $this->assertIfMigrationExistsInDatabase();\n\n        $this->removeTestModule($this->moduleIdWithMigrations);\n    }\n\n    private function installModule(string $moduleId): void\n    {\n        $package = new OxidEshopPackage(__DIR__ . '/Fixtures/' . $moduleId);\n        ContainerFacade::get(ModuleInstallerInterface::class)\n            ->install($package);\n    }\n\n    private function removeTestModule(string $moduleId): void\n    {\n        $package = new OxidEshopPackage(__DIR__ . '/Fixtures/' . $moduleId);\n        ContainerFacade::get(ModuleInstallerInterface::class)\n            ->uninstall($package);\n    }\n\n    private function assertIfMigrationExistsInDatabase(): void\n    {\n        $queryBuilder = $this->get(QueryBuilderFactoryInterface::class)->create();\n        $result = $queryBuilder\n            ->select('*')\n            ->from('test_module_with_migrations')\n            ->executeQuery();\n\n        $this->assertEquals(1, $result->rowCount());\n    }\n\n    private function getMigrations(): Migrations\n    {\n        $migrations = (new MigrationsBuilder())->build();\n\n        $output = new ConsoleOutput();\n        $output->setVerbosity(ConsoleOutputInterface::VERBOSITY_QUIET);\n        $migrations->setOutput($output);\n\n        return $migrations;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Module/TestData/TestModule/TestEvent.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Module\\TestData\\TestModule;\n\nuse Symfony\\Contracts\\EventDispatcher\\Event;\n\nclass TestEvent extends Event\n{\n    private bool $handled = false;\n\n    public function handle(): void\n    {\n        $this->handled = true;\n    }\n\n    public function isHandled(): bool\n    {\n        return $this->handled;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Setup/Database/SetupDbConnectionFactoryTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Setup\\Database;\n\nuse Doctrine\\DBAL\\Exception\\ConnectionException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\DataObject\\DatabaseConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\SetupDbConnectionFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class SetupDbConnectionFactoryTest extends TestCase\n{\n    use ContainerTrait;\n\n    #[DoesNotPerformAssertions]\n    public function testGetServerConnectionWithCurrentConfig(): void\n    {\n        $dbConfig = new DatabaseConfiguration(getenv('OXID_DB_URL'));\n\n        $this->get(SetupDbConnectionFactoryInterface::class)->getServerConnection($dbConfig);\n    }\n\n    #[DoesNotPerformAssertions]\n    public function testGetDatabaseConnectionWithCurrentConfig(): void\n    {\n        $dbConfig = new DatabaseConfiguration(getenv('OXID_DB_URL'));\n\n        $this->get(SetupDbConnectionFactoryInterface::class)->getDatabaseConnection($dbConfig);\n    }\n\n    public function testGetServerConnectionWithInvalidConfig(): void\n    {\n        $nonExistingHost = uniqid('host-', true);\n        $dbConfig = new DatabaseConfiguration(\"mysql://user:pass@$nonExistingHost:3306/db-name\");\n\n        $this->expectException(ConnectionException::class);\n\n        $this->get(SetupDbConnectionFactoryInterface::class)->getServerConnection($dbConfig);\n    }\n\n    public function testGetDatabaseConnectionWithInvalidConfig(): void\n    {\n        $nonExistingHost = uniqid('host-', true);\n        $dbConfig = new DatabaseConfiguration(\"mysql://user:pass@$nonExistingHost:3306/db-name\");\n\n        $this->expectException(ConnectionException::class);\n\n        $this->get(SetupDbConnectionFactoryInterface::class)->getDatabaseConnection($dbConfig);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Setup/Database/SetupDbConnectionValidatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Setup\\Database;\n\nuse Doctrine\\DBAL\\Exception\\ConnectionException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\DataObject\\DatabaseConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\SetupDbConnectionValidatorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\UnsupportedDatabaseConfigurationException;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\nuse Prophecy\\Argument;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\n\nfinal class SetupDbConnectionValidatorTest extends TestCase\n{\n    use ContainerTrait;\n    use ProphecyTrait;\n\n    public function testValidateWithSocketConnection(): void\n    {\n        $dbConfig = $this->prophesize(DatabaseConfiguration::class);\n        $dbConfig->isSocketConnection()->willReturn(true);\n        $dbConfig->getDatabaseUrl()->willReturn(Argument::type('string'));\n\n        $this->expectException(UnsupportedDatabaseConfigurationException::class);\n\n        $this->get(SetupDbConnectionValidatorInterface::class)->validate($dbConfig->reveal());\n    }\n\n    public function testValidateWithNoUsername(): void\n    {\n        $dbConfig = $this->prophesize(DatabaseConfiguration::class);\n        $dbConfig->isSocketConnection()->willReturn(false);\n        $dbConfig->getUser()->willReturn('');\n        $dbConfig->getDatabaseUrl()->willReturn(Argument::type('string'));\n\n        $this->expectException(UnsupportedDatabaseConfigurationException::class);\n\n        $this->get(SetupDbConnectionValidatorInterface::class)->validate($dbConfig->reveal());\n    }\n\n    public function testValidateWithNoPass(): void\n    {\n        $dbConfig = $this->prophesize(DatabaseConfiguration::class);\n        $dbConfig->isSocketConnection()->willReturn(false);\n        $dbConfig->getUser()->willReturn('user');\n        $dbConfig->getPass()->willReturn('');\n        $dbConfig->getDatabaseUrl()->willReturn(Argument::type('string'));\n\n        $this->expectException(UnsupportedDatabaseConfigurationException::class);\n\n        $this->get(SetupDbConnectionValidatorInterface::class)->validate($dbConfig->reveal());\n    }\n\n    public function testValidateWithNoDbName(): void\n    {\n        $dbConfig = $this->prophesize(DatabaseConfiguration::class);\n        $dbConfig->isSocketConnection()->willReturn(false);\n        $dbConfig->getUser()->willReturn('user');\n        $dbConfig->getPass()->willReturn('pass');\n        $dbConfig->getName()->willReturn('');\n        $dbConfig->getDatabaseUrl()->willReturn(Argument::type('string'));\n\n        $this->expectException(UnsupportedDatabaseConfigurationException::class);\n\n        $this->get(SetupDbConnectionValidatorInterface::class)->validate($dbConfig->reveal());\n    }\n\n    public function testValidateWithNoServerConnection(): void\n    {\n        $dbConfig = $this->prophesize(DatabaseConfiguration::class);\n        $dbConfig->isSocketConnection()->willReturn(false);\n        $dbConfig->getUser()->willReturn('user');\n        $dbConfig->getPass()->willReturn('pass');\n        $dbConfig->getName()->willReturn('db-name');\n        $dbConfig->getConnectionParameters()->willReturn([\n            'user' => 'user',\n            'password' => 'pass',\n            'host' => uniqid('db-server-', true),\n            'driver' => 'pdo_mysql',\n            'port' => 3306,\n        ]);\n\n        $this->expectException(ConnectionException::class);\n\n        $this->get(SetupDbConnectionValidatorInterface::class)->validate($dbConfig->reveal());\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Setup/Database/SetupDbValidatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace Integration\\Internal\\Setup\\Database;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\DataObject\\DatabaseConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\ConnectionFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\DatabaseNotEmptyException;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\SetupDbValidatorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class SetupDbValidatorTest extends TestCase\n{\n    use ContainerTrait;\n\n    private DatabaseConfiguration $dbConfig;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        $this->dbConfig = new DatabaseConfiguration(\n            $this\n                ->get(BasicContextInterface::class)\n                ->getDatabaseUrl()\n        );\n    }\n\n    public function testValidateWithExistingNonEmptyDatabase(): void\n    {\n        $this->expectException(DatabaseNotEmptyException::class);\n\n        $this\n            ->get(SetupDbValidatorInterface::class)\n            ->validate(\n                $this->dbConfig\n            );\n    }\n\n    #[DoesNotPerformAssertions]\n    public function testValidateWithEmptyDatabase(): void\n    {\n        $newDatabaseName = uniqid(\n            'som-db-',\n            true\n        );\n        $this\n            ->get(ConnectionFactoryInterface::class)\n            ->create()\n            ->executeQuery(\"CREATE DATABASE `$newDatabaseName`;\");\n\n        $this\n            ->get(SetupDbValidatorInterface::class)\n            ->validate(\n                $this->withDatabaseName($newDatabaseName)\n            );\n\n        $this\n            ->get(ConnectionFactoryInterface::class)\n            ->create()\n            ->executeQuery(\"DROP DATABASE `$newDatabaseName`;\");\n    }\n\n    #[DoesNotPerformAssertions]\n    public function testValidateWithNonExistentDatabase(): void\n    {\n        $this\n            ->get(SetupDbValidatorInterface::class)\n            ->validate(\n                $this->withDatabaseName(\n                    uniqid(\n                        'som-db-',\n                        true\n                    )\n                )\n            );\n    }\n\n    private function withDatabaseName(string $dbName): DatabaseConfiguration\n    {\n        return new DatabaseConfiguration(\n            str_replace(\n                $this->dbConfig->getName(),\n                $dbName,\n                $this->dbConfig->getDatabaseUrl()\n            )\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Setup/Database/ShopDbManagerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Setup\\Database;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\DataObject\\DatabaseConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\SetupDbConnectionFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\ShopDbManagerInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\DatabaseTrait;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ShopDbManagerTest extends TestCase\n{\n    use ContainerTrait;\n    use DatabaseTrait;\n\n    public function tearDown(): void\n    {\n        $this->setupShopDatabase();\n    }\n\n    public function testCreateOnNonExistingDatabase(): void\n    {\n        $dbManager = $this->get(ShopDbManagerInterface::class);\n        $dbConfig = $this->getDatabaseConfig();\n        $this->dropDatabaseIfExists($dbConfig);\n\n        $dbManager->create($dbConfig);\n\n        $this->assertDatabaseIsCreated($dbConfig);\n    }\n\n    public function testCreateOnEmptyDatabase(): void\n    {\n        $dbManager = $this->get(ShopDbManagerInterface::class);\n        $databaseConfig = $this->getDatabaseConfig();\n        $this->recreateEmptyDatabase($databaseConfig);\n\n        $dbManager->create($databaseConfig);\n\n        $this->assertDatabaseIsCreated($databaseConfig);\n    }\n\n    private function getDatabaseConfig(): DatabaseConfiguration\n    {\n        return new DatabaseConfiguration(getenv('OXID_DB_URL'));\n    }\n\n    private function dropDatabaseIfExists(DatabaseConfiguration $config): void\n    {\n        $this->getDbConnection()->executeStatement(\"DROP DATABASE IF EXISTS `{$config->getName()}`;\");\n        $this->getDbConnection()->close();\n    }\n\n    private function recreateEmptyDatabase(DatabaseConfiguration $config): void\n    {\n        $this->getDbConnection()->executeStatement(\"DROP DATABASE IF EXISTS `{$config->getName()}`;\");\n        $this->getDbConnection()->executeStatement(\n            sprintf('CREATE DATABASE `%s` CHARACTER SET utf8 COLLATE utf8_general_ci;', $config->getName())\n        );\n        $this->getDbConnection()->close();\n    }\n\n    private function assertDatabaseIsCreated(DatabaseConfiguration $config): void\n    {\n        $connection = $this->get(SetupDbConnectionFactoryInterface::class)->getDatabaseConnection($config);\n\n        $migrationsCount = $connection->fetchOne('SELECT COUNT(*) FROM `oxmigrations_ce`');\n        $viewRows = $connection->fetchOne('SELECT COUNT(*) FROM `oxv_oxshops_de`');\n\n        $this->assertGreaterThan(1, $migrationsCount, 'Expected some migrations to be applied.');\n        $this->assertGreaterThan(0, $viewRows, 'Expected shop views to contain rows.');\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Setup/Directory/DirectoryValidatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Setup\\Directory;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Directory\\DirectoryValidator;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Directory\\NonExistenceDirectoryException;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Directory\\NotAbsolutePathException;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContext;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class DirectoryValidatorTest extends TestCase\n{\n    private string $shopSourcePath;\n    private BasicContextInterface $basicContext;\n\n    protected function setUp(): void\n    {\n        $this->shopSourcePath = __DIR__ . '/Fixtures/dir-structure/test-folder';\n        $this->basicContext = new BasicContext();\n\n        parent::setUp();\n    }\n\n    #[DoesNotPerformAssertions]\n    public function testDirectoriesExistentAndPermission(): void\n    {\n        $directoryValidator = $this->getDirectoryValidator();\n        $directoryValidator->validateDirectory($this->shopSourcePath . '/tmp');\n    }\n\n    public function testCheckPathIsAbsolute(): void\n    {\n        $shopCompilePath  = 'source/tmp';\n\n        $directoryValidator = $this->getDirectoryValidator();\n\n        $this->expectException(NotAbsolutePathException::class);\n        $directoryValidator->checkPathIsAbsolute($shopCompilePath);\n    }\n\n    public function testNonExistentDirectories(): void\n    {\n        $directoryValidator = $this->getDirectoryValidator();\n\n        $this->expectException(NonExistenceDirectoryException::class);\n        $directoryValidator->validateDirectory('/notExists/tmp');\n    }\n\n    private function getDirectoryValidator(): DirectoryValidator\n    {\n        return new DirectoryValidator($this->basicContext);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Setup/Directory/Fixtures/dir-structure/test-folder/log/.gitkeep",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Setup/Directory/Fixtures/dir-structure/test-folder/out/media/.gitkeep",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Setup/Directory/Fixtures/dir-structure/test-folder/out/pictures/generated/.gitkeep",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Setup/Directory/Fixtures/dir-structure/test-folder/out/pictures/master/.gitkeep",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Setup/Directory/Fixtures/dir-structure/test-folder/out/pictures/media/.gitkeep",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Setup/Directory/Fixtures/dir-structure/test-folder/out/pictures/promo/.gitkeep",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Setup/Directory/Fixtures/dir-structure/test-folder/tmp/.gitkeep",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Setup/Directory/Fixtures/dir-structure/var/.gitkeep",
    "content": ""
  },
  {
    "path": "tests/Integration/Internal/Setup/Language/LanguageInstallerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Setup\\Language;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Dao\\ShopConfigurationSettingDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\DataObject\\ShopConfigurationSetting;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\DataObject\\ShopSettingType;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\ConnectionFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Language\\DefaultLanguage;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Language\\LanguageInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\DatabaseTrait;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class LanguageInstallerTest extends TestCase\n{\n    use ContainerTrait;\n    use DatabaseTrait;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->beginTransaction($this->get(ConnectionFactoryInterface::class)->create());\n    }\n\n    public function tearDown(): void\n    {\n        $this->rollBackTransaction($this->get(ConnectionFactoryInterface::class)->create());\n\n        parent::tearDown();\n    }\n\n    public function testInstallSetsDefaultLanguage(): void\n    {\n        $english = new DefaultLanguage('en');\n        $installer = $this->get(LanguageInstallerInterface::class);\n        $installer->install($english);\n\n        $configDao = $this->get(ShopConfigurationSettingDaoInterface::class);\n\n        $this->assertSame(\n            $english->getCode(),\n            $configDao->get('sDefaultLang', 1)->getValue()\n        );\n    }\n\n    public function testInstallUpdatesActiveLanguage(): void\n    {\n        $configDao = $this->get(ShopConfigurationSettingDaoInterface::class);\n\n        $testSetting = new ShopConfigurationSetting();\n        $testSetting\n            ->setName('aLanguageParams')\n            ->setShopId(1)\n            ->setType(ShopSettingType::ARRAY)\n            ->setValue([\n                'en' => ['active' => '0'],\n                'de' => ['active' => '1'],\n            ]);\n\n        $configDao->save($testSetting);\n\n        $english = new DefaultLanguage('en');\n        $installer = $this->get(LanguageInstallerInterface::class);\n        $installer->install($english);\n\n        $this->assertEquals(\n            [\n                'en' => ['active' => '1'],\n                'de' => ['active' => '0'],\n            ],\n            $configDao->get('aLanguageParams', 1)->getValue()\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Setup/SetupParametersFactoryTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Setup;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Language\\DefaultLanguage;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Parameters\\SetupParametersFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\n\nfinal class SetupParametersFactoryTest extends TestCase\n{\n    use ContainerTrait;\n    use ProphecyTrait;\n\n    public function testCreate(): void\n    {\n        $setupLanguage = 'de';\n\n        $parameters = $this->get(SetupParametersFactoryInterface::class)->create(new DefaultLanguage($setupLanguage));\n\n        $context = $this->get(ContextInterface::class);\n        $this->assertEquals($context->getCacheDirectory(), $parameters->getCacheDir());\n        $this->assertEquals($context->getCacheDirectory(), $parameters->getCacheDir());\n        $this->assertEquals($context->getDatabaseUrl(), $parameters->getDbConfig()->getDatabaseUrl());\n        $this->assertEquals($context->getShopBaseUrl(), $parameters->getShopBaseUrl()->getUrl());\n        $this->assertEquals($setupLanguage, $parameters->getLanguage()->getCode());\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Setup/ShopConfiguration/ShopConfigurationUpdaterTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Setup\\ShopConfiguration;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Dao\\ShopConfigurationSettingDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\ShopConfiguration\\ShopConfigurationUpdaterInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ShopConfigurationUpdaterTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    public function testSaveShopSetupTime(): void\n    {\n        $oldTime = $this\n            ->get(ShopConfigurationSettingDaoInterface::class)\n            ->get('sTagList', 1)\n            ->getValue();\n\n        $this->get(ShopConfigurationUpdaterInterface::class)->saveShopSetupTime();\n\n        $newTime = $this\n            ->get(ShopConfigurationSettingDaoInterface::class)\n            ->get('sTagList', 1)\n            ->getValue();\n\n        $this->assertGreaterThan($oldTime, $newTime);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Setup/ShopSetupCommandTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Setup;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\DatabaseNotEmptyException;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\ShopSetupCommand;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Console\\Tester\\CommandTester;\n\nfinal class ShopSetupCommandTest extends TestCase\n{\n    use ContainerTrait;\n\n    public function testShopSetupCommandFailsWhenDatabaseIsNotEmpty(): void\n    {\n        $this->expectException(DatabaseNotEmptyException::class);\n\n        (new CommandTester(\n            $this->get(ShopSetupCommand::class)\n        ))->execute([]);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Transition/Adapter/Configuration/Dao/ShopConfigurationSettingDaoTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Transition\\Adapter\\Configuration\\Dao;\n\nuse Doctrine\\DBAL\\Query\\QueryBuilder;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Dao\\ShopConfigurationSettingDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Dao\\ShopConfigurationSettingDao;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\DataObject\\ShopConfigurationSetting;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Utility\\ShopSettingEncoderInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\EntryDoesNotExistDaoException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\ConnectionFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\DatabaseTrait;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ShopConfigurationSettingDaoTest extends TestCase\n{\n    use ContainerTrait;\n    use DatabaseTrait;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->beginTransaction($this->get(ConnectionFactoryInterface::class)->create());\n    }\n\n    public function tearDown(): void\n    {\n        $this->rollBackTransaction($this->get(ConnectionFactoryInterface::class)->create());\n\n        parent::tearDown();\n    }\n\n    #[DataProvider('settingValueDataProvider')]\n    public function testSave(string $name, string $type, string|int|float|bool|array $value): void\n    {\n        $settingDao = $this->getConfigurationSettingDao();\n\n        $shopConfigurationSetting = new ShopConfigurationSetting();\n        $shopConfigurationSetting\n            ->setShopId(1)\n            ->setName($name)\n            ->setType($type)\n            ->setValue($value);\n\n        $settingDao->save($shopConfigurationSetting);\n\n        $this->assertEquals(\n            $shopConfigurationSetting,\n            $settingDao->get($name, 1)\n        );\n    }\n\n    public function testGetNonExistentSetting(): void\n    {\n        $this->expectException(EntryDoesNotExistDaoException::class);\n        $settingDao = $this->getConfigurationSettingDao();\n\n        $this->expectException(EntryDoesNotExistDaoException::class);\n        $settingDao->get('onExistentSetting', 1);\n    }\n\n    public function testDelete(): void\n    {\n        $this->expectException(EntryDoesNotExistDaoException::class);\n        $settingDao = $this->getConfigurationSettingDao();\n\n        $shopConfigurationSetting = new ShopConfigurationSetting();\n        $shopConfigurationSetting\n            ->setShopId(1)\n            ->setName('testDelete')\n            ->setType('someType')\n            ->setValue('value');\n\n        $settingDao->save($shopConfigurationSetting);\n\n        $settingDao->delete($shopConfigurationSetting);\n        $settingDao->get('testDelete', 1);\n    }\n\n    public function testUpdate(): void\n    {\n        $settingDao = $this->getConfigurationSettingDao();\n\n        $shopConfigurationSetting = new ShopConfigurationSetting();\n        $shopConfigurationSetting\n            ->setShopId(1)\n            ->setName('testUpdate')\n            ->setType('someType')\n            ->setValue('firstSaving');\n\n        $settingDao->save($shopConfigurationSetting);\n\n        $shopConfigurationSetting->setValue('secondSaving');\n\n        $settingDao->save($shopConfigurationSetting);\n\n        $this->assertEquals(\n            $shopConfigurationSetting,\n            $settingDao->get('testUpdate', 1)\n        );\n    }\n\n    public function testUpdateDoesNotCreateDuplicationsInDatabase(): void\n    {\n        $this->assertSame(\n            0,\n            $this->getRowCount()\n        );\n\n        $settingDao = $this->getConfigurationSettingDao();\n\n        $shopConfigurationSetting = new ShopConfigurationSetting();\n        $shopConfigurationSetting\n            ->setShopId(1)\n            ->setName('testDuplications')\n            ->setType('someType')\n            ->setValue('firstSaving');\n\n        $settingDao->save($shopConfigurationSetting);\n\n        $this->assertSame(\n            1,\n            $this->getRowCount()\n        );\n\n        $shopConfigurationSetting->setValue('secondSaving');\n\n        $settingDao->save($shopConfigurationSetting);\n\n        $this->assertSame(\n            1,\n            $this->getRowCount()\n        );\n    }\n\n    public function testGetDoesNotReturnCachedReference(): void\n    {\n        $settingDao = $this->getConfigurationSettingDao();\n\n        $shopConfigurationSetting = new ShopConfigurationSetting();\n        $shopConfigurationSetting\n            ->setShopId(1)\n            ->setName('cloning_test')\n            ->setType('str')\n            ->setValue('initial');\n\n        $settingDao->save($shopConfigurationSetting);\n\n        $first = $settingDao->get('cloning_test', 1);\n        $first->setValue('changed');\n\n        $second = $settingDao->get('cloning_test', 1);\n\n        $this->assertSame('initial', $second->getValue());\n    }\n\n    public function testGetUsesDatabaseOnlyOnceForSameSetting(): void\n    {\n        $queryBuilder = $this->createMock(QueryBuilder::class);\n\n        $queryBuilder\n            ->expects($this->once())\n            ->method('fetchAssociative')\n            ->willReturn([\n                'type' => 'str',\n                'value' => 'test',\n                'name' => 'test',\n            ]);\n\n        $queryBuilderFactory = $this->createMock(QueryBuilderFactoryInterface::class);\n        $queryBuilderFactory\n            ->expects($this->once())\n            ->method('create')\n            ->willReturn($queryBuilder);\n\n        $encoder = $this->createStub(ShopSettingEncoderInterface::class);\n\n        $eventDispatcher = $this->createStub(EventDispatcherInterface::class);\n\n        $settingDao = new ShopConfigurationSettingDao(\n            $queryBuilderFactory,\n            $encoder,\n            $eventDispatcher\n        );\n\n        $settingDao->get('test', 1);\n        $settingDao->get('test', 1);\n    }\n\n    public static function settingValueDataProvider(): array\n    {\n        return [\n            [\n                'string',\n                'str',\n                'testString',\n            ],\n            [\n                'int',\n                'int',\n                1,\n            ],\n            [\n                'float',\n                'num',\n                1.333,\n            ],\n            [\n                'bool',\n                'bool',\n                true,\n            ],\n            [\n                'array',\n                'arr',\n                [\n                    'element'   => 'value',\n                    'element2'  => 'value',\n                ],\n            ],\n        ];\n    }\n\n    private function getConfigurationSettingDao(): ShopConfigurationSettingDaoInterface\n    {\n        return $this->get(ShopConfigurationSettingDaoInterface::class);\n    }\n\n    private function getRowCount(): int\n    {\n        return $this\n            ->get(QueryBuilderFactoryInterface::class)\n            ->create()\n            ->select('*')\n            ->from('oxconfig')\n            ->where('oxshopid = \"1\"')\n            ->andWhere('oxvarname = \"testDuplications\"')\n            ->andWhere('oxmodule = \"\"')\n            ->executeQuery()\n            ->rowCount();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Transition/Adapter/TemplateLogic/FormatCurrencyLogicTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\FormatCurrencyLogic;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class FormatCurrencyLogicTest extends IntegrationTestCase\n{\n    private FormatCurrencyLogic $numberFormatLogic;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        $this->numberFormatLogic = new FormatCurrencyLogic();\n    }\n\n    #[DataProvider('numberFormatProvider')]\n    public function testNumberFormat(string $format, int|float $value, string $expected): void\n    {\n        $this->assertEquals($expected, $this->numberFormatLogic->numberFormat($format, $value));\n    }\n\n    public static function numberFormatProvider(): array\n    {\n        return [\n            [\"EUR@ 1.00@ ,@ .@ EUR@ 2\", 25000, '25.000,00'],\n            [\"EUR@ 1.00@ ,@ .@ EUR@ 2\", 25000.1584, '25.000,16'],\n            [\"EUR@ 1.00@ ,@ .@ EUR@ 3\", 25000.1584, '25.000,158'],\n            [\"EUR@ 1.00@ ,@ .@ EUR@ 0\", 25000000.5584, '25.000.001'],\n            [\"EUR@ 1.00@ .@ ,@ EUR@ 2\", 25000000.5584, '25,000,000.56'],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Transition/Adapter/TemplateLogic/FormatDateLogicTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\FormatDateLogic;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class FormatDateLogicTest extends TestCase\n{\n    private FormatDateLogic $formDateLogic;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        $this->formDateLogic = new FormatDateLogic();\n    }\n\n    public function testFormdateWithEmptyValue(): void\n    {\n        $input = '';\n        $expected = \"0000-00-00 00:00:00\";\n\n        $this->assertEquals($expected, $this->formDateLogic->formdate($input, 'datetime', true));\n    }\n\n    public function testFormdateWithNullValue(): void\n    {\n        $input = null;\n        $expected = \"0000-00-00 00:00:00\";\n\n        $this->assertEquals($expected, $this->formDateLogic->formdate($input, 'datetime', true));\n    }\n\n    public function testFormdateWithDatetime(): void\n    {\n        $input = '01.08.2007 11.56.25';\n        $expected = \"2007-08-01 11:56:25\";\n\n        $this->assertEquals($expected, $this->formDateLogic->formdate($input, 'datetime', true));\n    }\n\n    public function testFormdateWithTimestamp(): void\n    {\n        $input = '20070801115625';\n        $expected = \"2007-08-01 11:56:25\";\n\n        $this->assertEquals($expected, $this->formDateLogic->formdate($input, 'timestamp', true));\n    }\n\n    public function testFormdateWithDate(): void\n    {\n        $input = '2007-08-01 11:56:25';\n        $expected = \"2007-08-01\";\n\n        $this->assertEquals($expected, $this->formDateLogic->formdate($input, 'date', true));\n    }\n\n    public function testFormdateUsingObject(): void\n    {\n        $expected = \"2007-08-01 11:56:25\";\n\n        $field = new Field();\n        $field->fldmax_length = \"0\";\n        $field->fldtype = 'datetime';\n        $field->setValue('01.08.2007 11.56.25');\n\n        $this->assertEquals($expected, $this->formDateLogic->formdate($field, 'datetime'));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Transition/Adapter/TemplateLogic/FormatPriceLogicTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse OxidEsales\\Eshop\\Core\\Price;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\FormatPriceLogic;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class FormatPriceLogicTest extends TestCase\n{\n    private FormatPriceLogic $formatPriceLogic;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        $this->formatPriceLogic = new FormatPriceLogic();\n    }\n\n    public function testFormatPriceWithInt(): void\n    {\n        $price = $this->formatPriceLogic->formatPrice(['price' => 1]);\n\n        $this->assertEquals(\n            '1,00 €',\n            $price\n        );\n    }\n\n    public function testFormatPriceWithNull()\n    {\n        $price = $this->formatPriceLogic->formatPrice(['price' => null]);\n\n        $this->assertEquals(\n            '',\n            $price\n        );\n    }\n\n    public function testFormatPriceWithIncorrectString(): void\n    {\n        $price = $this->formatPriceLogic->formatPrice(['price' => 'incorrect']);\n\n        $this->assertEquals(\n            '0,00 €',\n            $price\n        );\n    }\n\n    public function testFormatPriceWithIncorrectPriceObject(): void\n    {\n        $priceObject = new Price();\n        $priceObject->setPrice(false);\n\n        $price = $this->formatPriceLogic->formatPrice(['price' => $priceObject]);\n\n        $this->assertEquals(\n            '0,00 €',\n            $price\n        );\n    }\n\n    public function testFormatPriceWithCorrectPriceObject(): void\n    {\n        $priceObject = new Price();\n        $priceObject->setPrice(120);\n\n        $calculatedOxPrice = $this->formatPriceLogic->formatPrice(['price' => $priceObject]);\n\n        $this->assertEquals(\n            '120,00 €',\n            $calculatedOxPrice\n        );\n    }\n\n    public function testGetFormattedPriceWithEmptyCurrencyAndInteger(): void\n    {\n        $formattedPrice = $this->formatPriceLogic->formatPrice(\n            [\n                'currency' => '',\n                'price' => 10_000\n            ]\n        );\n\n        $this->assertEquals(\n            '10.000,00',\n            $formattedPrice\n        );\n    }\n\n    public function testGetFormattedPriceWithEmptyCurrencyAndNegativeInteger(): void\n    {\n        $formattedPrice = $this->formatPriceLogic->formatPrice(\n            [\n                'currency' => '',\n                'price' => -100\n            ]\n        );\n\n        $this->assertEquals(\n            '',\n            $formattedPrice\n        );\n    }\n\n    public function testGetFormattedPriceWithCustomDecimalSeparator(): void\n    {\n        $formattedPrice = $this->formatPriceLogic->formatPrice(\n            [\n                'currency' => (object)['dec' => '-'],\n                'price' => 10_000\n            ]\n        );\n\n        $this->assertEquals(\n            '10.000-00',\n            $formattedPrice\n        );\n    }\n\n    public function testGetFormattedPriceWithCustomThousandSeparator(): void\n    {\n        $formattedPrice = $this->formatPriceLogic->formatPrice(\n            [\n                'currency' => (object)['thousand' => '-'],\n                'price' => 10_000\n            ]\n        );\n\n        $this->assertEquals(\n            '10-000,00',\n            $formattedPrice\n        );\n    }\n\n    public function testGetFormattedPriceWithCustomSign(): void\n    {\n        $formattedPrice = $this->formatPriceLogic->formatPrice(\n            [\n                'currency' => (object)['sign' => '$'],\n                'price' => 10_000\n            ]\n        );\n\n        $this->assertEquals(\n            '10.000,00 $',\n            $formattedPrice\n        );\n    }\n\n    public function testGetFormattedPriceWithCustomDecimalPlaces(): void\n    {\n        $formattedPrice = $this->formatPriceLogic->formatPrice(\n            [\n                'currency' => (object)['decimal' => 4],\n                'price' => 10_000\n            ]\n        );\n\n        $this->assertEquals(\n            '10.000,0000',\n            $formattedPrice\n        );\n    }\n\n    public function testGetFormattedPriceWithSignOnFront(): void\n    {\n        $formattedPrice = $this->formatPriceLogic->formatPrice(\n            [\n                'currency' => (object)[\n                    'sign' => '$',\n                    'side' => 'Front'\n                ],\n                'price' => 10_000\n            ]\n        );\n\n        $this->assertEquals(\n            '$10.000,00',\n            $formattedPrice\n        );\n    }\n\n    public function testGetFormattedPriceWithSignOnIncorrectSide(): void\n    {\n        $formattedPrice = $this->formatPriceLogic->formatPrice(\n            [\n                'currency' => (object)[\n                    'sign' => '$',\n                    'side' => 'incorrect'\n                ],\n                'price' => 10_000\n            ]\n        );\n\n        $this->assertEquals(\n            '10.000,00 $',\n            $formattedPrice\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Transition/Adapter/TemplateLogic/ScriptLogicTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse OxidEsales\\Eshop\\Core\\Config;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\ScriptLogic;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nuse function sprintf;\n\nfinal class ScriptLogicTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private Config $config;\n    private ScriptLogic $scriptLogic;\n\n    public function setup(): void\n    {\n        parent::setUp();\n        $this->setParameter('oxid_esales.debug_mode', true);\n\n        $this->config = Registry::getConfig();\n        $this->scriptLogic = new ScriptLogic();\n    }\n\n    public function testIncludeFileNotExists(): void\n    {\n        $this->setParameter('oxid_esales.debug_mode', false);\n\n        $this->scriptLogic->include('somescript.js');\n\n        $this->assertNull($this->config->getGlobalParameter('includes'));\n    }\n\n    public function testIncludeFileExists(): void\n    {\n        $this->scriptLogic->include('http://someurl/src/js/libs/jquery.min.js');\n\n        $this->assertArrayHasKey(3, $this->config->getGlobalParameter('includes'));\n        $this->assertContains(\n            'http://someurl/src/js/libs/jquery.min.js',\n            $this->config->getGlobalParameter('includes')[3]\n        );\n    }\n\n    public function testAddNotDynamic(): void\n    {\n        $this->scriptLogic->add('oxidadd');\n\n        $this->assertContains('oxidadd', $this->config->getGlobalParameter('scripts'));\n    }\n\n    public function testAddDynamic(): void\n    {\n        $this->scriptLogic->add('oxidadddynamic', true);\n\n        $this->assertContains('oxidadddynamic', $this->config->getGlobalParameter('scripts_dynamic'));\n    }\n\n    #[DataProvider('addWidgetProvider')]\n    public function testRenderAddWidget(string $script, string $output): void\n    {\n        $expected = sprintf(\n            <<<EOF\n<script>\n    window.addEventListener('load', function() {\n        WidgetsHandler.registerFunction('%s', 'somewidget');\n        }, false )\n</script>\nEOF,\n            $output\n        );\n        $this->scriptLogic->add($script);\n\n        $actual = $this->scriptLogic->render('somewidget', true);\n\n        $this->assertEquals(\n            $this->stripFormatting($expected),\n            $this->stripFormatting($actual),\n        );\n    }\n\n    public static function addWidgetProvider(): array\n    {\n        return [\n            ['oxidadd', 'oxidadd'],\n            ['\"oxidadd\"', '\"oxidadd\"'],\n            [\"'oxidadd'\", \"\\\\'oxidadd\\\\'\"],\n            [\"oxid\\r\\nadd\", 'oxid\\nadd'],\n            [\"oxid\\nadd\", 'oxid\\nadd'],\n        ];\n    }\n\n    public function testRenderIncludeWidget(): void\n    {\n        $this->scriptLogic->include('http://someurl/src/js/libs/jquery.min.js');\n\n        $expected = <<<HTML\n<script>\n    window.addEventListener('load', function() {\n        WidgetsHandler.registerFile('http://someurl/src/js/libs/jquery.min.js', 'somewidget');\n    }, false)\n</script>\nHTML;\n\n        $actual = $this->scriptLogic->render('somewidget', true);\n\n        $this->assertEquals($expected, $actual);\n    }\n\n    private function stripFormatting(string $html): string\n    {\n        return str_replace([\"\\n\", ' '], '', $html);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Transition/Adapter/TemplateLogic/TranslateFunctionLogicTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse OxidEsales\\Eshop\\Core\\Language;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\TranslateFunctionLogic;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nfinal class TranslateFunctionLogicTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private TranslateFunctionLogic $translateFunction;\n\n    public function setUp(): void\n    {\n        $this->get(Language::class)->setTplLanguage(0);\n        parent::setUp();\n    }\n\n    public static function dataProvider(): array\n    {\n        return [\n            [[], 'ERROR: Translation for IDENT MISSING not found!'],\n            [['ident' => 'foobar'], 'ERROR: Translation for foobar not found!'],\n            [['ident' => 'FIRST_NAME', 'suffix' => ''], 'Vorname'],\n            [['ident' => 'FIRST_NAME', 'suffix' => '_foo'], 'ERROR: Translation for FIRST_NAME_foo not found!'],\n            [['ident' => 'FIRST_NAME', 'suffix' => 'LAST_NAME'], 'VornameNachname'],\n            [['ident' => 'foo', 'noerror' => true], 'foo'],\n            [['ident' => 'foo', 'noerror' => 'bar'], 'foo'],\n            [['ident' => 'VAT_PLUS_PERCENT_AMOUNT', 'args' => 0], 'zzgl. 0% MwSt., Betrag'],\n            [['ident' => 'VAT_PLUS_PERCENT_AMOUNT', 'args' => ''], 'zzgl. % MwSt., Betrag'],\n            [['ident' => 'VAT_PLUS_PERCENT_AMOUNT'], 'zzgl. %s%% MwSt., Betrag'],\n            [['ident' => 'VAT_PLUS_PERCENT_AMOUNT', 'args' => false], 'zzgl. %s%% MwSt., Betrag']\n        ];\n    }\n\n    #[DataProvider('dataProvider')]\n    public function testGetTranslation(array $params, string $expectedTranslation): void\n    {\n        $translation = $this->get(TranslateFunctionLogic::class)->getTranslation($params);\n\n        $this->assertEquals($expectedTranslation, $translation);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Transition/Adapter/TemplateLogic/TranslateLogicTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse OxidEsales\\Eshop\\Core\\Language;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\TranslateFilterLogic;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Translator\\LegacyTemplateTranslator;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\n\nfinal class TranslateLogicTest extends IntegrationTestCase\n{\n    use ProphecyTrait;\n\n    public static function provider(): array\n    {\n        return [\n            ['FIRST_NAME', 0, 'Vorname'],\n            ['FIRST_NAME', 1, 'First name'],\n            ['VAT', 1, 'VAT']\n        ];\n    }\n\n    /**\n     * Tests simple assignments, where only translation is fetched\n     */\n    #[DataProvider('provider')]\n    public function testSimpleAssignments(string $ident, int $languageId, string $result): void\n    {\n        $multiLangFilterLogic = new TranslateFilterLogic($this->getContextMock(), $this->getTranslator($languageId));\n\n        $this->assertEquals($result, $multiLangFilterLogic->multiLang($ident));\n    }\n\n    public static function withArgumentsProvider(): array\n    {\n        return [\n            ['MANUFACTURER_S', 0, 'Opel', '| Hersteller: Opel'],\n            ['MANUFACTURER_S', 1, 'Opel', 'Manufacturer: Opel'],\n            ['INVITE_TO_SHOP', 0, ['Admin', 'OXID Shop'], 'Eine Einladung von Admin OXID Shop zu besuchen.'],\n            ['INVITE_TO_SHOP', 1, ['Admin', 'OXID Shop'], 'An invitation from Admin to visit OXID Shop']\n        ];\n    }\n\n    #[DataProvider('withArgumentsProvider')]\n    public function testAssignmentsWithArguments(\n        string $ident,\n        int $languageId,\n        string|array $arguments,\n        string $result\n    ): void {\n        $multiLangFilterLogic = new TranslateFilterLogic($this->getContextMock(), $this->getTranslator($languageId));\n\n        $this->assertEquals($result, $multiLangFilterLogic->multiLang($ident, $arguments));\n    }\n\n    public static function missingTranslationProviderFrontend(): array\n    {\n        return [\n            [\n                true,\n                'MY_MISING_TRANSLATION',\n                'MY_MISING_TRANSLATION',\n            ],\n            [\n                false,\n                'MY_MISING_TRANSLATION',\n                'ERROR: Translation for MY_MISING_TRANSLATION not found!',\n            ],\n        ];\n    }\n\n    #[DataProvider('missingTranslationProviderFrontend')]\n    public function testTranslateFrontendIsMissingTranslation(\n        bool $isProductiveMode,\n        string $ident,\n        string $translation\n    ): void {\n        $context = $this->prophesize(ContextInterface::class);\n        $context->isShopInProductiveMode()->willReturn($isProductiveMode);\n        $multiLangFilterLogic = new TranslateFilterLogic($context->reveal(), $this->getTranslator(1));\n\n        $this->assertEquals($translation, $multiLangFilterLogic->multiLang($ident));\n    }\n\n    private function getContextMock(): ContextInterface\n    {\n        return $this->createStub(ContextInterface::class);\n    }\n\n    private function getTranslator(int $languageId): LegacyTemplateTranslator\n    {\n        $language = new Language();\n        $language->setTplLanguage($languageId);\n        $language->setAdminMode(false);\n        return new LegacyTemplateTranslator($language);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Transition/Adapter/TemplateLogic/TranslateSalutationLogicTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse OxidEsales\\Eshop\\Core\\Language;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\TranslateSalutationLogic;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Translator\\LegacyTemplateTranslator;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nfinal class TranslateSalutationLogicTest extends IntegrationTestCase\n{\n    public static function translateSalutationProvider(): array\n    {\n        return [\n            ['MR', 0, 'Herr'],\n            ['MRS', 0, 'Frau'],\n            ['MR', 1, 'Mr'],\n            ['MRS', 1, 'Mrs']\n        ];\n    }\n\n\n    #[DataProvider('translateSalutationProvider')]\n    public function testTranslateSalutation(string $ident, int $languageId, string $expected): void\n    {\n        $translateSalutationLogic = new TranslateSalutationLogic($this->getTranslator($languageId));\n        $this->assertEquals($expected, $translateSalutationLogic->translateSalutation($ident));\n    }\n\n    private function getTranslator(int $languageId): LegacyTemplateTranslator\n    {\n        $language = new Language();\n        $language->setTplLanguage($languageId);\n        $language->setAdminMode(false);\n        return new LegacyTemplateTranslator($language);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Transition/Adapter/TemplateLogic/TruncateLogicTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\TruncateLogic;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class TruncateLogicTest extends TestCase\n{\n    private TruncateLogic $truncateLogic;\n\n    public function setup(): void\n    {\n        parent::setUp();\n\n        $this->truncateLogic = new TruncateLogic();\n    }\n\n    #[DataProvider('truncateProvider')]\n    public function testTruncate(string $string, string $expected, array $parameters = []): void\n    {\n        $length = $parameters['length'] ?? 80;\n        $suffix = $parameters['suffix'] ?? '...';\n        $breakWords = $parameters['breakWords'] ?? false;\n\n        $this->assertEquals($expected, $this->truncateLogic->truncate($string, $length, $suffix, $breakWords));\n    }\n\n    public static function truncateProvider(): array\n    {\n        return [\n            [\n                'Duis iaculis pellentesque felis, et pulvinar elit lacinia at. Suspendisse dapibus pulvinar sem vitae.',\n                'Duis iaculis pellentesque felis, et pulvinar elit lacinia at. Suspendisse...'\n            ],\n            [\n                'Duis iaculis &#039;pellentesque&#039; felis, et &quot;pulvinar&quot; elit lacinia at. ' .\n                'Suspendisse dapibus pulvinar sem vitae.',\n                'Duis iaculis &#039;pellentesque&#039; felis, et &quot;pulvinar&quot; elit lacinia at. Suspendisse...'\n            ],\n            [\n                '&#039;Duis&#039; &#039;iaculis&#039; &#039;pellentesque&#039; felis, et &quot;pulvinar&quot; ' .\n                'elit lacinia at. Suspendisse dapibus pulvinar sem vitae.',\n                '&#039;Duis&#039; &#039;iaculis&#039; &#039;pellentesque&#039; felis, et &quot;pulvinar&quot; ' .\n                'elit lacinia at....'\n            ],\n        ];\n    }\n\n    #[DataProvider('truncateProviderWithLength')]\n    public function testTruncateWithLength(string $string, string $expected, array $parameters = []): void\n    {\n        $length = $parameters['length'] ?? 80;\n        $suffix = $parameters['suffix'] ?? '...';\n        $breakWords = $parameters['breakWords'] ?? false;\n\n        $this->assertEquals($expected, $this->truncateLogic->truncate($string, $length, $suffix, $breakWords));\n    }\n\n    public static function truncateProviderWithLength(): array\n    {\n        return [\n            [\n                'Duis iaculis pellentesque felis, et pulvinar elit.',\n                'Duis iaculis...',\n                ['length' => 20]\n            ],\n            [\n                'Duis iaculis &#039;pellentesque&#039; felis, et &quot;pulvinar&quot; elit.',\n                'Duis iaculis...',\n                ['length' => 20]\n            ],\n            [\n                '&#039;Duis&#039; &#039;iaculis&#039; &#039;pellentesque&#039; felis, et &quot;pulvinar&quot; elit.',\n                '&#039;Duis&#039; &#039;iaculis&#039;...',\n                ['length' => 20]\n            ],\n        ];\n    }\n\n    #[DataProvider('truncateProviderWithSuffix')]\n    public function testTruncateWithSuffix(string $string, string $expected, array $parameters = []): void\n    {\n        $length = $parameters['length'] ?? 80;\n        $suffix = $parameters['suffix'] ?? '...';\n        $breakWords = $parameters['breakWords'] ?? false;\n\n        $this->assertEquals($expected, $this->truncateLogic->truncate($string, $length, $suffix, $breakWords));\n    }\n\n    public static function truncateProviderWithSuffix(): array\n    {\n        return [\n            [\n                'Duis iaculis pellentesque felis, et pulvinar elit lacinia at. Suspendisse dapibus pulvinar sem vitae.',\n                'Duis iaculis pellentesque felis, et pulvinar elit lacinia at. Suspendisse (...)',\n                ['suffix' => ' (...)']\n            ],\n        ];\n    }\n\n    #[DataProvider('truncateProviderWithBreakWords')]\n    public function testTruncateWithBreakWords(string $string, string $expected, array $parameters = []): void\n    {\n        $length = $parameters['length'] ?? 80;\n        $suffix = $parameters['suffix'] ?? '...';\n        $breakWords = $parameters['breakWords'] ?? false;\n\n        $this->assertEquals($expected, $this->truncateLogic->truncate($string, $length, $suffix, $breakWords));\n    }\n\n    public static function truncateProviderWithBreakWords(): array\n    {\n        return [\n            [\n                'Duis iaculis pellentesque felis, et pulvinar elit lacinia at. Suspendisse dapibus pulvinar sem vitae.',\n                'Duis iaculis pellentesque felis, et pulvinar elit lacinia at. Suspendisse dap...',\n                ['breakWords' => true]\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Transition/Adapter/TemplateLogic/WordwrapLogicTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\WordwrapLogic;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class WordwrapLogicTest extends TestCase\n{\n    private WordwrapLogic $wordWrapLogic;\n\n    protected function setUp(): void\n    {\n        parent::setUp();\n        $this->wordWrapLogic = new WordwrapLogic();\n    }\n\n    public static function nonAsciiProvider(): array\n    {\n        return [\n            [\"HÖ\\nHÖ\", \"HÖ HÖ\", 2],\n            [\"HÖ\\na\\nHÖ\\na\", \"HÖa HÖa\", 2, \"\\n\", true],\n            [\"HÖa\\na\\nHÖa\\na\", \"HÖaa HÖaa\", 3, \"\\n\", true],\n            [\"HÖa\\nHÖa\", \"HÖa HÖa\", 2]\n        ];\n    }\n\n    #[DataProvider('nonAsciiProvider')]\n    public function testWordWrapWithNonAscii(\n        string $expected,\n        string $string,\n        int $length = 80,\n        string $wrapper = \"\\n\",\n        bool $cut = false\n    ): void {\n        self::assertEquals($expected, $this->wordWrapLogic->wordWrap($string, $length, $wrapper, $cut));\n    }\n\n    public static function asciiProvider(): array\n    {\n        return [\n            [\"aaa\\naaa\", 'aaa aaa', 2],\n            [\"aa\\na\\naa\\na\", 'aaa aaa', 2, \"\\n\", true],\n            [\"aaa\\naaa a\", 'aaa aaa a', 5],\n            [\"aaa\\naaa\", 'aaa aaa', 5, \"\\n\", true],\n            [\"  \\naaa\\n  \\naaa\", '   aaa    aaa', 2],\n            [\"  \\naa\\na \\n \\naa\\na\", '   aaa    aaa', 2, \"\\n\", true],\n            [\"  \\naaa  \\n aaa\", '   aaa    aaa', 5],\n            [\"  \\naaa  \\n aaa\", '   aaa    aaa', 5, \"\\n\", true],\n            [\n                \"Pellentesq\\nue nisl\\nnon\\ncondimentu\\nm cursus.\\n \\nconsectetu\\nr a diam\\nsit.\\n\" .\n                \" finibus\\ndiam eu\\nlibero\\nlobortis.\\neu   ex  \\nsit\",\n                \"Pellentesque nisl non condimentum cursus.\\n  consectetur a diam sit.\\n\" .\n                \" finibus diam eu libero lobortis.\\neu   ex   sit\",\n                10,\n                \"\\n\",\n                true\n            ]\n        ];\n    }\n\n    #[DataProvider('asciiProvider')]\n    public function testWordWrapAscii(\n        string $expected,\n        string $string,\n        int $length = 80,\n        string $wrapper = \"\\n\",\n        bool $cut = false\n    ): void {\n        self::assertEquals($expected, $this->wordWrapLogic->wordWrap($string, $length, $wrapper, $cut));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Transition/Utility/BasicContextTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace Integration\\Internal\\Transition\\Utility;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContext;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\EnvTrait;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class BasicContextTest extends TestCase\n{\n    use ContainerTrait;\n    use EnvTrait;\n\n    public function testCacheFileDependsOnCurrentEnvironment(): void\n    {\n        $environment = 'abc';\n        $this->loadEnvFixture(__DIR__, [\"OXID_ENV=$environment\"]);\n\n        $cachePath = $this->get(BasicContextInterface::class)->getContainerCacheFilePath(1);\n\n        $this->assertStringContainsString($environment, $cachePath);\n    }\n\n    public function testCacheFilesChangesWithEnvironment(): void\n    {\n        $this->loadEnvFixture(__DIR__, ['OXID_ENV=abc']);\n\n        $cachePath1 = (new BasicContext())->getContainerCacheFilePath(1);\n\n        $this->loadEnvFixture(__DIR__, ['OXID_ENV=xyz']);\n\n        $cachePath2 = (new BasicContext())->getContainerCacheFilePath(1);\n\n        $this->assertNotEquals($cachePath1, $cachePath2);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Internal/Transition/Utility/ContextTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Transition\\Utility;\n\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\ContainerBuilder;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContext;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ContextTest extends TestCase\n{\n    public function testGetLogLevelShouldReturnAStringValue(): void\n    {\n        $this->assertNotEmpty($this->getContext()->getLogLevel());\n    }\n\n    public function testGetLogLevelWithEnv(): void\n    {\n        $envLogLevel = uniqid('some-log-level-', true);\n        putenv(\"OXID_LOG_LEVEL=$envLogLevel\");\n\n        $logLevel = $this->getContext()->getLogLevel();\n\n        $this->assertEquals($envLogLevel, $logLevel);\n    }\n\n    public function testGetLogLevelWithEmptyEnvWillReturnDefault(): void\n    {\n        $defaultLogLevel = 'error';\n        putenv('OXID_LOG_LEVEL=');\n\n        $logLevel = $this->getContext()->getLogLevel();\n\n        $this->assertEquals($defaultLogLevel, $logLevel);\n    }\n\n    public function testGetLogFilePathWithConfigSetWillReturnStringStartingWithValue(): void\n    {\n        $configValue = ContainerFacade::getParameter('oxid_esales.shop_source_directory');\n\n        $logFilePath = $this->getContext()->getLogFilePath();\n\n        $this->assertStringStartsWith($configValue, $logFilePath);\n    }\n\n    private function getContext(): ContextInterface\n    {\n        return (new ContainerBuilder(new BasicContext(), 1))\n            ->getContainer()\n            ->get(ContextInterface::class);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Component/Widget/CategoryTreeTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Application\\Component\\Widget;\n\nuse OxidEsales\\Eshop\\Application\\Component\\Widget\\CategoryTree;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\n/**\n * @todo move to templating engine component tests\n */\nfinal class CategoryTreeTest extends IntegrationTestCase\n{\n    private CategoryTree $categoryTree;\n\n    private string $fallbackWidgetTemplate = 'widget/sidebar/categorytree';\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        $this->categoryTree = oxNew(CategoryTree::class);\n    }\n\n    public function testRenderWithDefaultTemplate(): void\n    {\n        $renderedTemplate = $this->categoryTree->render();\n\n        $this->assertEquals($this->fallbackWidgetTemplate, $renderedTemplate);\n    }\n\n    public function testRenderWithNonExistingTemplate(): void\n    {\n        $nonExistingWidgetType = uniqid('widget-', true);\n        $this->categoryTree->setViewParameters([\n            'sWidgetType' => $nonExistingWidgetType,\n        ]);\n\n        $renderedTemplate = $this->categoryTree->render();\n\n        $this->assertEquals($this->fallbackWidgetTemplate, $renderedTemplate);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Controller/AccountReviewControllerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Application\\Controller;\n\nuse Exception;\nuse OxidEsales\\Eshop\\Application\\Controller\\AccountReviewController;\nuse OxidEsales\\Eshop\\Application\\Model\\Rating;\nuse OxidEsales\\Eshop\\Application\\Model\\Review;\nuse OxidEsales\\Eshop\\Application\\Model\\User;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Utils;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class AccountReviewControllerTest extends IntegrationTestCase\n{\n    public const TESTUSER_ID = 'AccountReviewControllerTest';\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->createUser(self::TESTUSER_ID);\n    }\n\n    public function tearDown(): void\n    {\n        $this->getUser(self::TESTUSER_ID)->delete();\n\n        parent::tearDown();\n    }\n\n    public function testDeleteReviewAndRating(): void\n    {\n        $this->createTestDataForDeleteReviewAndRating();\n        $this->setUserToSession();\n        $this->setSessionChallenge();\n\n        $this->doDeleteReviewAndRatingRequest();\n\n        $this->assertFalse($this->reviewToDeleteExists());\n        $this->assertFalse($this->ratingToDeleteExists());\n    }\n\n    public function testDeleteReviewAndRatingDoNotDeleteWithInvalidSessionChallenge(): void\n    {\n        $this->createTestDataForDeleteReviewAndRating();\n        $this->setUserToSession();\n\n        $this->setInvalidSessionChallenge();\n\n        $this->doDeleteReviewAndRatingRequest();\n\n        $this->assertTrue($this->reviewToDeleteExists());\n        $this->assertTrue($this->ratingToDeleteExists());\n    }\n\n    public function testReviewAndRatingListPaginationItemsPerPage(): void\n    {\n        $accountReviewController = oxNew(AccountReviewController::class);\n        $itemsPerPage = $accountReviewController->getItemsPerPage();\n\n        $this->assertEquals(10, $itemsPerPage);\n    }\n\n    public function testReviewAndRatingListIsAnEmptyArrayOnNoRatingsAndReviews(): void\n    {\n        $this->setUserToSession();\n        $accountReviewController = oxNew(AccountReviewController::class);\n\n        $this->assertSame([], $accountReviewController->getReviewList());\n    }\n\n    public function testReviewAndRatingListPagination(): void\n    {\n        $this->setUserToSession();\n        $this->createTestDataForReviewAndRatingList();\n\n        $accountReviewController = oxNew(AccountReviewController::class);\n        $displayedReviews = count($accountReviewController->getReviewList());\n\n        $this->assertSame($accountReviewController->getItemsPerPage(), $displayedReviews);\n    }\n\n    public function testInitDoesNotRedirect(): void\n    {\n        $this->setUserToSession();\n        Registry::getConfig()->setConfigParam('blAllowUsersToManageTheirReviews', true);\n        $this->createTestDataForReviewAndRatingList();\n\n        $utilsStub = $this->getMockBuilder(Utils::class)->getMock();\n        $utilsStub->expects($this->never())\n            ->method('redirect');\n        Registry::set(Utils::class, $utilsStub);\n\n        $accountReviewController = oxNew(AccountReviewController::class);\n        $accountReviewController->init();\n    }\n\n    public function testInitRedirectsIfFeatureIsDisabled(): void\n    {\n        $this->setUserToSession();\n        Registry::getConfig()->setConfigParam('blAllowUsersToManageTheirReviews', false);\n        $this->createTestDataForReviewAndRatingList();\n\n        $utilsStub = $this->getMockBuilder(Utils::class)->getMock();\n        $utilsStub->expects($this->once())\n            ->method('redirect');\n        Registry::set(Utils::class, $utilsStub);\n\n        $accountReviewController = oxNew(AccountReviewController::class);\n        $accountReviewController->init();\n    }\n\n    public function testReviewAndRatingListCount(): void\n    {\n        $this->setUserToSession();\n        $this->createTestDataForReviewAndRatingList();\n\n        $accountReviewController = oxNew(AccountReviewController::class);\n\n        $this->assertSame(20, $accountReviewController->getReviewAndRatingItemsCount());\n    }\n\n    private function getUser(string $userId)\n    {\n        $user = oxNew(\\OxidEsales\\EshopCommunity\\Application\\Model\\User::class);\n        if (!$user->load($userId)) {\n            throw new Exception('User ' . $userId . ' could not be loaded');\n        }\n\n        return $user;\n    }\n\n    private function createUser(string $userId)\n    {\n        $user = oxNew(User::class);\n        $user->setId($userId);\n        $user->oxuser__oxactive = new Field(1, Field::T_RAW);\n        $user->save();\n\n        return $user;\n    }\n\n    private function createTestDataForReviewAndRatingList(): void\n    {\n        for ($i = 0; $i < 10; $i++) {\n            $review = oxNew(Review::class);\n            $review->oxreviews__oxuserid = new Field(self::TESTUSER_ID, Field::T_RAW);\n            $review->oxreviews__oxtype = new Field('oxarticle', Field::T_RAW);\n            $review->oxreviews__oxobjectid = new Field('testArticle', Field::T_RAW);\n            $review->oxreviews__oxrating = new Field(2, Field::T_RAW);\n            $review->save();\n        }\n\n        for ($i = 0; $i < 10; $i++) {\n            $rating = oxNew(Rating::class);\n            $rating->oxratings__oxshopid = new Field(1, Field::T_RAW);\n            $rating->oxratings__oxuserid = new Field(self::TESTUSER_ID, Field::T_RAW);\n            $rating->oxratings__oxtype = new Field('oxrecommlist', Field::T_RAW);\n            $rating->oxratings__oxobjectid = new Field('testArticle', Field::T_RAW);\n            $rating->oxratings__oxrating = new Field(4, Field::T_RAW);\n            $rating->save();\n        }\n    }\n\n    private function createTestDataForDeleteReviewAndRating(): void\n    {\n        $review = oxNew(Review::class);\n        $review->setId('testReviewToDelete');\n        $review->oxreviews__oxuserid = new Field(self::TESTUSER_ID, Field::T_RAW);\n        $review->oxreviews__oxtype = new Field('oxarticle', Field::T_RAW);\n        $review->oxreviews__oxobjectid = new Field('testArticle', Field::T_RAW);\n        $review->oxreviews__oxrating = new Field(2, Field::T_RAW);\n        $review->save();\n\n        $rating = oxNew(Rating::class);\n        $rating->setId('testRatingToDelete');\n        $rating->oxratings__oxshopid = new Field(1, Field::T_RAW);\n        $rating->oxratings__oxuserid = new Field(self::TESTUSER_ID, Field::T_RAW);\n        $rating->oxratings__oxtype = new Field('oxrecommlist', Field::T_RAW);\n        $rating->oxratings__oxobjectid = new Field('testArticle', Field::T_RAW);\n        $rating->oxratings__oxrating = new Field(4, Field::T_RAW);\n        $rating->save();\n    }\n\n    private function setUserToSession(): void\n    {\n        $user = $this->getUser(self::TESTUSER_ID);\n        Registry::getSession()->setUser($user);\n    }\n\n    private function setSessionChallenge(): void\n    {\n        Registry::getSession()->setVariable('sess_stoken', 'token');\n        $this->setRequestParameter('stoken', 'token');\n    }\n\n    private function setInvalidSessionChallenge(): void\n    {\n        Registry::getSession()->setVariable('sess_stoken', 'token');\n        $this->setRequestParameter('stoken', 'invalid_token');\n    }\n\n    private function doDeleteReviewAndRatingRequest(): void\n    {\n        $this->setRequestParameter('reviewId', 'testReviewToDelete');\n        $this->setRequestParameter('ratingId', 'testRatingToDelete');\n\n        $accountReviewController = oxNew(AccountReviewController::class);\n        $accountReviewController->deleteReviewAndRating();\n    }\n\n    private function reviewToDeleteExists()\n    {\n        $review = oxNew(Review::class);\n\n        return $review->load('testReviewToDelete');\n    }\n\n    private function ratingToDeleteExists()\n    {\n        $rating = oxNew(Rating::class);\n\n        return $rating->load('testRatingToDelete');\n    }\n\n    private function setRequestParameter(string $key, string $value): void\n    {\n        $_POST[$key] = $value;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Controller/Admin/NavigationTree/Fixtures/module1/menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<OX>\n    <OXMENU id=\"NAVIGATION_ESHOPADMIN\" testParam=\"module1-some-param\">\n        <MAINMENU id=\"mxmainmenu\">\n            <SUBMENU id=\"TEST-MODULE-1-SOME-SUBMENU-NODE\"/>\n            <!--\n            Following element (with ID: \"mxcoresett\" and attribute/value: cl=\"shop\") is expected to exist in shop's XML\n            @see EDITION_PATH/Application/views/admin/menu.xml\n            -->\n            <SUBMENU id=\"mxcoresett\" cl=\"MODULE1_SOME_OVERWRITTEN_VALUE\"/>\n        </MAINMENU>\n    </OXMENU>\n</OX>\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Controller/Admin/NavigationTree/Fixtures/module1/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.1';\n$aModule = [\n    'id' => 'module1',\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Controller/Admin/NavigationTree/Fixtures/module2/menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<OX>\n    <OXMENU id=\"NAVIGATION_ESHOPADMIN\">\n        <MAINMENU id=\"mxmainmenu\">\n            <SUBMENU id=\"TEST-MODULE-2-SOME-SUBMENU-NODE\" />\n        </MAINMENU>\n    </OXMENU>\n</OX>\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Controller/Admin/NavigationTree/Fixtures/module2/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.1';\n$aModule = [\n    'id' => 'module2',\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Controller/Admin/NavigationTree/Fixtures/testModule/ModuleSetup.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Application\\Controller\\Admin\\Fixtures\\testModule;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\ContainerFactory;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Controller\\Admin\\Fixtures\\testModule\\RendererInterface;\n\nclass ModuleSetup\n{\n    /**\n     * Activation function for the module\n     */\n    public static function onActivate(): void\n    {\n        ContainerFactory::getInstance()->getContainer()->get(RendererInterface::class);\n    }\n\n    /**\n     * Deactivation function for the module\n     */\n    public static function onDeactivate(): void\n    {\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Controller/Admin/NavigationTree/Fixtures/testModule/Renderer.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Application\\Controller\\Admin\\Fixtures\\testModule;\n\nclass Renderer implements RendererInterface\n{\n    public function formFilesOutput(): string\n    {\n        return 'Output';\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Controller/Admin/NavigationTree/Fixtures/testModule/RendererInterface.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Application\\Controller\\Admin\\Fixtures\\testModule;\n\ninterface RendererInterface\n{\n    public function formFilesOutput(): string;\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Controller/Admin/NavigationTree/Fixtures/testModule/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.1';\n$aModule = [\n    'id' => 'testModuleId',\n    'title' => 'myTestModule',\n    'description' => 'myTestModule',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n    'settings' => [\n        [\n            'group' => 'someGroup',\n            'name' => 'stringSetting',\n            'type' => 'str',\n            'value' => 'row',\n        ],\n        [\n            'group' => 'someGroup',\n            'name' => 'testInt',\n            'type' => 'num',\n            'value' => 0,\n        ],\n        [\n            'group' => 'someGroup',\n            'name' => 'testFloat',\n            'type' => 'num',\n            'value' => 0,\n        ],\n    ],\n    'events' => [\n        'onActivate' => '\\OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Controller\\Admin\\Fixtures\\testModule\\ModuleSetup::onActivate',\n        'onDeactivate' => '\\OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Controller\\Admin\\Fixtures\\testModule\\ModuleSetup::onDeactivate',\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Controller/Admin/NavigationTree/Fixtures/testModule/services.yaml",
    "content": "services:\n    _defaults:\n        autowire: true\n\n    OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Controller\\Admin\\Fixtures\\testModule\\RendererInterface:\n        class: OxidEsales\\EshopCommunity\\Tests\\Integration\\Application\\Controller\\Admin\\Fixtures\\testModule\\Renderer\n        public: true"
  },
  {
    "path": "tests/Integration/Legacy/Application/Controller/Admin/NavigationTree/NavigationTreeDemoShopTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Application\\Controller\\Admin\\NavigationTree;\n\nuse DOMDocument;\nuse DOMElement;\nuse DOMXPath;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Bridge\\AdminThemeBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass NavigationTreeDemoShopTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    public static function providerDisabledLinksRemoval(): array\n    {\n        return [\n            [false, '1', 4, 'Disabled links should not be removed in normal shop'],\n            [true, '1', 0, 'Disabled links should be removed in demo shop'],\n            [false, '0', 0, 'No links should be removed in normal shop'],\n            [true, '0', 0, 'No links should be removed in demo shop'],\n        ];\n    }\n\n    #[DataProvider('providerDisabledLinksRemoval')]\n    public function testDisabledLinksRemoval(bool $isDemoShop, string $disabled, int $expected, string $msg): void\n    {\n        $this->setParameter('oxid_esales.demo_shop_mode', $isDemoShop);\n\n        $navTree = oxNew('oxNavigationTree');\n        $domMenuXml = $this->getDomMenuXml();\n\n        $xPath = new DomXPath($domMenuXml);\n        foreach ($xPath->query(\"//*[@disableForDemoShop]\") as $node) {\n            $node->setAttribute('disableForDemoShop', $disabled);\n        }\n\n        $navTree->checkDemoShopDenials($domMenuXml);\n\n        $this->assertEquals($expected, $this->getDeniedLinksCount($domMenuXml), $msg);\n    }\n\n    private function getDomMenuXml(): DOMDocument\n    {\n        $menuDom = new DomDocument();\n        $menuDom->preserveWhiteSpace = false;\n\n        if (!$menuDom->load($this->getMenuFilePath())) {\n            $this->fail(\"Admin menu.xml not found.\");\n        }\n\n        $dom = new DOMDocument();\n        $dom->appendChild(new DOMElement('OX'));\n        $xPath = new DOMXPath($dom);\n\n        oxNew('oxNavigationTree')->mergeNodes(\n            $dom->documentElement,\n            $menuDom->documentElement,\n            $xPath,\n            $dom,\n            '/OX'\n        );\n\n        return $dom;\n    }\n\n    private function getDeniedLinksCount(DOMDocument $dom): int\n    {\n        $xPath = new DomXPath($dom);\n        $nodeList = $xPath->query(\"//*[@disableForDemoShop]\");\n        $count = 0;\n\n        foreach ($nodeList as $oNode) {\n            if ($oNode->getAttribute('disableForDemoShop')) {\n                $count++;\n            }\n        }\n\n        return $count;\n    }\n\n    private function getMenuFilePath(): string\n    {\n        $context = ContainerFacade::get(ContextInterface::class);\n        $adminViewsDirectory = Path::join(\n            $context->getSourcePath(),\n            'Application',\n            'views',\n            $this->get(AdminThemeBridgeInterface::class)->getActiveTheme()\n        );\n\n        $edition = strtolower($context->getEdition()->value);\n        $menuFilePath = Path::join($adminViewsDirectory, \"menu_$edition.xml\");\n\n        if (file_exists($menuFilePath)) {\n            return $menuFilePath;\n        }\n\n        $menuFilePath = Path::join($adminViewsDirectory, 'menu.xml');\n        if (!file_exists($menuFilePath)) {\n            $this->fail('Admin menu.xml not found');\n        }\n\n        return $menuFilePath;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Controller/Admin/NavigationTree/NavigationTreeTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Application\\Controller\\Admin\\NavigationTree;\n\nuse DOMDocument;\nuse DOMXPath;\nuse oxfield;\nuse OxidEsales\\EshopCommunity\\Application\\Controller\\Admin\\NavigationTree;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\ContainerFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContext;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Psr\\Container\\ContainerInterface;\nuse Symfony\\Contracts\\Cache\\TagAwareCacheInterface;\nuse Symfony\\Contracts\\Cache\\ItemInterface;\n\nfinal class NavigationTreeTest extends IntegrationTestCase\n{\n    private const FIXTURE_MODULE_NAMES = ['module1', 'module2'];\n\n    private const EXISTING_XML_ELEMENT_ID = 'mxcoresett';\n\n    private const EXISTING_XML_ELEMENTS_ATTRIBUTE_NAME = 'cl';\n\n    private const EXISTING_XML_ELEMENTS_ATTRIBUTE_VALUE_ORIGINAL = 'shop';\n\n    private const EXISTING_XML_ELEMENTS_ATTRIBUTE_VALUE_CHANGED = 'MODULE1_SOME_OVERWRITTEN_VALUE';\n\n    private const NEW_XML_ELEMENT_ID_MODULE_1 = 'TEST-MODULE-1-SOME-SUBMENU-NODE';\n\n    private const NEW_XML_ELEMENT_ID_MODULE_2 = 'TEST-MODULE-2-SOME-SUBMENU-NODE';\n\n    private BasicContext $context;\n\n    private ?ContainerInterface $container = null;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        Registry::getConfig()->setAdminMode(true);\n        $this->context = new BasicContext();\n    }\n\n    public function tearDown(): void\n    {\n        $this->cleanUpTestData();\n        ContainerFactory::resetContainer();\n\n        parent::tearDown();\n    }\n\n    public function testGetDomXmlWithNoActiveModulesWillReturnOriginalXml(): void\n    {\n        $this->assertShopsXmlIsInInitialState();\n    }\n\n    public function testGetDomXmlWith1ActiveModuleWillAdd1NewNode(): void\n    {\n        $this->assertShopsXmlIsInInitialState();\n        $this->installModuleFixture('module1');\n        $this->activateModule('module1');\n\n        $xml = $this->getNavigationTree()\n            ->getDomXml()\n            ->saveXML();\n\n        $this->assertStringContainsString(self::NEW_XML_ELEMENT_ID_MODULE_1, $xml);\n    }\n\n    public function testGetDomXmlWith2ActiveModulesWillAdd2NewNodes(): void\n    {\n        $this->assertShopsXmlIsInInitialState();\n        $this->installModuleFixture('module1');\n        $this->installModuleFixture('module2');\n        $this->activateModule('module1');\n        $this->activateModule('module2');\n\n        $xml = $this->getNavigationTree()\n            ->getDomXml()\n            ->saveXML();\n\n        $this->assertStringContainsString(self::NEW_XML_ELEMENT_ID_MODULE_1, $xml);\n        $this->assertStringContainsString(self::NEW_XML_ELEMENT_ID_MODULE_2, $xml);\n    }\n\n    public function testGetDomXmlWithDeactivationWillAddNewNodeForActiveModule(): void\n    {\n        $this->assertShopsXmlIsInInitialState();\n        $this->installModuleFixture('module1');\n        $this->installModuleFixture('module2');\n        $this->activateModule('module1');\n        $this->activateModule('module2');\n        $this->deactivateModule('module1');\n\n        $xml = $this->getNavigationTree()\n            ->getDomXml()\n            ->saveXML();\n\n        $this->assertStringNotContainsString(self::NEW_XML_ELEMENT_ID_MODULE_1, $xml);\n        $this->assertStringContainsString(self::NEW_XML_ELEMENT_ID_MODULE_2, $xml);\n    }\n\n    public function testGetDomXmlWillApplyCorrectLoadOrderAndOverwriteShopsValue(): void\n    {\n        $this->assertShopsXmlIsInInitialState();\n        $this->installModuleFixture('module1');\n        $this->activateModule('module1');\n\n        $attributeValue = $this->getTestedAttributeValue($this->getNavigationTree()->getDomXml());\n\n        $this->assertSame(self::EXISTING_XML_ELEMENTS_ATTRIBUTE_VALUE_CHANGED, $attributeValue);\n    }\n\n    public function testGetDomXml(): void\n    {\n        $utils = Registry::getUtils();\n        $language = Registry::getLang();\n        $cache = ContainerFacade::get(TagAwareCacheInterface::class);\n        $navigationTree = $this->getNavigationTree();\n\n        $templateLanguageCode = $language->getLanguageArray()[$language->getTplLanguage()]->abbr;\n        $cacheName = 'shop_menu_cache_' . $templateLanguageCode;\n\n        $this->assertEmpty($cache->get($cacheName, function (ItemInterface $item): ?string {\n            return $item->get();\n        }));\n        $cache->delete($cacheName);\n\n        $navigationTree->getDomXml();\n\n        $this->assertNotEmpty(\n            $cache->get(\n                $cacheName,\n                fn(ItemInterface $item): array => $item->get()\n            )\n        );\n\n        $cache->delete($cacheName);\n        $testXml = '<OX><item>Test</item></OX>';\n        $cache->get($cacheName, function () use ($testXml): array {\n            return [\n                'creation_time' => time(),\n                'menu_dom' => $testXml\n            ];\n        });\n\n        $navigationTree = $this->getNavigationTree();\n        $cachedMenuContent = $navigationTree->getDomXml();\n\n        $this->assertXmlStringEqualsXmlString($testXml, $cachedMenuContent->saveXML());\n\n        $cache->delete($cacheName);\n    }\n\n    protected function get(string $class)\n    {\n        if (!$this->container) {\n            $this->container = ContainerFactory::getInstance()->getContainer();\n        }\n        return $this->container->get($class);\n    }\n\n    private function getNavigationTree(): NavigationTree\n    {\n        $navigationTree = (new NavigationTree());\n        /** Add user stub */\n        $user = oxNew('oxuser');\n        $user->oxuser__oxrights = new oxfield('testRights');\n        $navigationTree->setUser($user);\n        return $navigationTree;\n    }\n\n    private function installModuleFixture(string $moduleName): void\n    {\n        $this->get(ModuleInstallerInterface::class)\n            ->install($this->getTestPackage($moduleName));\n    }\n\n    private function uninstallModuleFixture(string $moduleName): void\n    {\n        $this->get(ModuleInstallerInterface::class)\n            ->uninstall($this->getTestPackage($moduleName));\n    }\n\n    private function activateModule(string $moduleId): void\n    {\n        $this->get(ModuleActivationBridgeInterface::class)\n            ->activate($moduleId, $this->context->getDefaultShopId());\n    }\n\n    private function deactivateModule(string $moduleId): void\n    {\n        $this->get(ModuleActivationBridgeInterface::class)\n            ->deactivate($moduleId, $this->context->getDefaultShopId());\n    }\n\n    private function getTestPackage(string $moduleName): OxidEshopPackage\n    {\n        $packageFixturePath = __DIR__ . \"/Fixtures/{$moduleName}/\";\n        return new OxidEshopPackage($packageFixturePath);\n    }\n\n    private function cleanUpTestData(): void\n    {\n        foreach (self::FIXTURE_MODULE_NAMES as $moduleName) {\n            $this->uninstallModuleFixture($moduleName);\n        }\n    }\n\n    private function assertShopsXmlIsInInitialState(): void\n    {\n        $navigationTree = $this->getNavigationTree();\n        $dom = $navigationTree->getDomXml();\n        $attributeValue = $this->getTestedAttributeValue($dom);\n        $xml = $dom->saveXML();\n\n        $this->assertSame(self::EXISTING_XML_ELEMENTS_ATTRIBUTE_VALUE_ORIGINAL, $attributeValue);\n        $this->assertStringNotContainsString(self::NEW_XML_ELEMENT_ID_MODULE_1, $xml);\n        $this->assertStringNotContainsString(self::NEW_XML_ELEMENT_ID_MODULE_2, $xml);\n    }\n\n    private function getTestedAttributeValue(DOMDocument $dom): string\n    {\n        $xPath = new DOMXPath($dom);\n        $existingElementXPath = sprintf('//*[@id=\"%s\"]', self::EXISTING_XML_ELEMENT_ID);\n        return $xPath->query($existingElementXPath)\n            ->item(0)\n            ->getAttribute(self::EXISTING_XML_ELEMENTS_ATTRIBUTE_NAME);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Controller/Admin/PaymentRDFaTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Controller\\Admin\\PaymentRdfa;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class PaymentRDFaTest extends IntegrationTestCase\n{\n    private string $paymentId;\n    private string $descriptionInDefaultLanguage = 'description-in-default-language';\n\n    public function testRenderWithDefaultLanguage(): void\n    {\n        $this->createPayment();\n        $_POST['oxid'] = $this->paymentId;\n        $paymentRdfa = oxNew(PaymentRdfa::class);\n\n        $paymentRdfa->render();\n\n        $paymentDescription = $paymentRdfa->getViewData()['edit']\n            ->getFieldData('OXDESC');\n        $this->assertSame($this->descriptionInDefaultLanguage, $paymentDescription);\n    }\n\n    private function createPayment(): void\n    {\n        $this->paymentId = Registry::getUtilsObject()->generateUId();\n        $this->get(QueryBuilderFactoryInterface::class)\n            ->create()\n            ->insert('oxpayments')\n            ->values([\n                'OXID' => \"\\\"{$this->paymentId}\\\"\",\n                'OXACTIVE' => true,\n                'OXDESC' => \"\\\"{$this->descriptionInDefaultLanguage}\\\"\",\n            ])\n            ->executeStatement();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Controller/Admin/ShopConfigurationTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Application\\Controller\\Admin;\n\nuse OxidEsales\\Eshop\\Application\\Controller\\Admin\\ShopConfiguration;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\ContainerFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ModuleConfigurationDaoBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ShopConfigurationDaoBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setting\\Setting;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ShopConfigurationTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private string $testModuleId = 'testModuleId';\n\n    public function testSaveConfVars(): void\n    {\n        $this->prepareTestModuleConfiguration();\n\n        $_POST['confstrs'] = [\n            'stringSetting' => 'newValue',\n        ];\n\n        $shopConfigurationController = $this\n            ->getStubBuilder(ShopConfiguration::class)\n            ->onlyMethods(['getModuleForConfigVars'])\n            ->disableOriginalConstructor()\n            ->getStub();\n        $shopConfigurationController->method('getModuleForConfigVars')\n            ->willReturn('module:testModuleId');\n        $shopConfigurationController->saveConfVars();\n\n        $container = ContainerFactory::getInstance()->getContainer();\n        $moduleConfiguration = $container->get(ModuleConfigurationDaoBridgeInterface::class)->get($this->testModuleId);\n\n        $this->assertSame('newValue', $moduleConfiguration->getModuleSetting('stringSetting') ->getValue());\n    }\n\n    public function testSaveWhenSettingIsMissingInMetadata(): void\n    {\n        $this->prepareTestModuleConfiguration();\n\n        $_POST['confstrs'] = [\n            'nonExisting' => 'newValue',\n        ];\n\n        $shopConfigurationController = $this\n            ->getStubBuilder(ShopConfiguration::class)\n            ->onlyMethods(['getModuleForConfigVars'])\n            ->disableOriginalConstructor()\n            ->getStub();\n        $shopConfigurationController->method('getModuleForConfigVars')\n            ->willReturn('module:testModuleId');\n        $shopConfigurationController->saveConfVars();\n\n        $this->assertSame('newValue', Registry::getConfig()->getConfigParam('nonExisting'));\n    }\n\n    private function prepareTestModuleConfiguration(): void\n    {\n        $setting = new Setting();\n        $setting\n            ->setName('stringSetting')\n            ->setValue('row')\n            ->setType('str');\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->setId($this->testModuleId);\n        $moduleConfiguration->setModuleSource('testModule');\n        $moduleConfiguration->addModuleSetting($setting);\n\n        $container = ContainerFactory::getInstance()->getContainer();\n        $shopConfigurationDao = $container->get(ShopConfigurationDaoBridgeInterface::class);\n\n        $shopConfiguration = $shopConfigurationDao->get();\n        $shopConfiguration->addModuleConfiguration($moduleConfiguration);\n\n        $shopConfigurationDao->save($shopConfiguration);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Controller/FrontendController/FrontendComponentTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Application\\Controller\\FrontendController;\n\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse oxubase;\n\nfinal class FrontendComponentTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    public function testGetComponentNames(): void\n    {\n        $componentName = get_class($this->getComponentClass());\n\n        $this->setParameter('oxid_esales.cacheable_user_components', [$componentName => 1]);\n\n        $componentNames = [\n            'oxcmp_user' => true,\n            'oxcmp_lang' => false,\n            'oxcmp_cur' => true,\n            'oxcmp_shop' => true,\n            'oxcmp_categories' => false,\n            'oxcmp_utils' => true,\n            'oxcmp_basket' => true,\n            $componentName => true,\n        ];\n\n        $view = oxNew('oxUBase');\n        $this->assertEquals($componentNames, $view->getComponentNames());\n    }\n\n    private function getComponentClass(): oxubase\n    {\n        return new class extends oxUbase {\n        };\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Controller/FrontendController/FrontendSearchEngineTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Application\\Controller\\FrontendController;\n\nuse Exception;\nuse OxidEsales\\Eshop\\Application\\Controller\\FrontendController;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Utils;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nfinal class FrontendSearchEngineTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->truncateDatabase();\n    }\n\n    public function tearDown(): void\n    {\n        $this->unsetRequest();\n\n        parent::tearDown();\n    }\n\n    public static function providerSeoLoggingScenarios(): array\n    {\n        return [\n            [0, false, false, \"Url should not be processed\"],\n            [1, false, false, \"Url should not be processed\"],\n            [0, true, true, \"Url should be processed\"],\n            [1, true, true, \"Url should be processed\"],\n        ];\n    }\n\n    #[DataProvider('providerSeoLoggingScenarios')]\n    public function testRequestProcessingScenarios($productive, bool $seoLogging, bool $expected, string $message): void\n    {\n        $this->setRequest();\n        $this->setUtilsSpy();\n        Registry::getConfig()->setConfigParam('blProductive', $productive);\n\n        $context = ContainerFacade::get(ContextInterface::class);\n        $this->createContainer();\n        $this->container->setParameter('oxid_esales.log_not_seo_urls', $seoLogging);\n        $this->container->setParameter('oxid_esales.build_directory', $context->getCacheDirectory());\n        $this->replaceContainerInstance();\n\n        $frontend = $this->getFrontendSeoLoggingSpy();\n\n        try {\n            $frontend->processRequest();\n        } catch (Exception $oEx) {\n            $this->fail('Error executing \"testRequestProcessingScenarios\" test: ' . $oEx->getMessage());\n        }\n\n        $ident = $this->getIdent($context->getDefaultShopId());\n\n        $this->assertEquals(\n            $expected,\n            (bool)DatabaseProvider::getDb()->getOne(\"select 1 from oxseologs where oxident='$ident'\"),\n            $message\n        );\n    }\n\n    public function testNoIndexRequestCanNotRedirect(): void\n    {\n        $this->setRequest();\n        $this->setUtilsSpy();\n\n        $frontend = $this->getFrontendNoIndexSpy();\n\n        try {\n            $frontend->processRequest();\n        } catch (Exception $oEx) {\n            $this->fail('Error executing \"testNoIndexRequestCanNotRedirect\" test: ' . $oEx->getMessage());\n        }\n\n        $ident = $this->getIdent(ContainerFacade::get(ContextInterface::class)->getDefaultShopId());\n        $this->assertfalse((bool)DatabaseProvider::getDb()->getOne(\"select 1 from oxseologs where oxident='$ident'\"));\n    }\n\n    private function setRequest(): void\n    {\n        $_SERVER[\"REQUEST_METHOD\"] = 'GET';\n        $_SERVER['REQUEST_URI'] = 'index.php?param1=value1&param2=value2';\n    }\n\n    private function getIdent(int $shopId): string\n    {\n        $uri = strtolower(str_replace('&', '&amp;', $_SERVER['REQUEST_URI']));\n\n        return md5($uri . $shopId . Registry::getLang()->getBaseLanguage());\n    }\n\n    private function truncateDatabase(): void\n    {\n        DatabaseProvider::getDb()->execute('DELETE FROM `oxseologs`');\n    }\n\n    private function unsetRequest(): void\n    {\n        unset($_SERVER[\"REQUEST_METHOD\"]);\n        unset($_SERVER['REQUEST_URI']);\n    }\n\n    private function setUtilsSpy(): void\n    {\n        $utils = new class extends Utils {\n            public function redirect($sUrl, $blAddRedirectParam = true, $iHeaderCode = 302)\n            {\n                $aArgs = func_get_args();\n                throw new Exception($aArgs[0]);\n            }\n        };\n\n        Registry::set(Utils::class, $utils);\n    }\n\n    private function getFrontendSeoLoggingSpy(): FrontendController\n    {\n        return new class extends FrontendController {\n            public function redirect($sUrl, $blAddRedirectParam = true, $iHeaderCode = 302)\n            {\n                $aArgs = func_get_args();\n                throw new Exception($aArgs[0]);\n            }\n\n            public function canRedirect(): bool\n            {\n                return false;\n            }\n\n            public function isAdmin(): bool\n            {\n                return false;\n            }\n        };\n    }\n\n    private function getFrontendNoIndexSpy(): FrontendController\n    {\n        return new class extends FrontendController {\n            public function redirect($sUrl, $blAddRedirectParam = true, $iHeaderCode = 302)\n            {\n                $aArgs = func_get_args();\n                throw new Exception($aArgs[0]);\n            }\n\n            public function forceNoIndex()\n            {\n                throw new Exception('forceIndex method should not be called');\n            }\n\n            public function noIndex(): int\n            {\n                return VIEW_INDEXSTATE_NOINDEXFOLLOW;\n            }\n\n            public function canRedirect(): bool\n            {\n                return false;\n            }\n\n            public function isAdmin(): bool\n            {\n                return false;\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Controller/SearchControllerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Application\\Controller;\n\nuse OxidEsales\\EshopCommunity\\Application\\Controller\\SearchController;\nuse OxidEsales\\EshopCommunity\\Application\\Model\\Article;\nuse OxidEsales\\EshopCommunity\\Core\\Field;\nuse OxidEsales\\EshopCommunity\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search\\ProductSearchCriteria;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search\\ProductSearchResult;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search\\ProductSearchException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search\\ProductSearchServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search\\Event\\AfterProductSearchEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search\\Event\\BeforeProductSearchEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Search\\EqualsFilter;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Search\\Pagination;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Search\\SearchTerm;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Search\\SortDirection;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse Psr\\Log\\LoggerInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\RunTestsInSeparateProcesses;\n\n#[RunTestsInSeparateProcesses]\nfinal class SearchControllerTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private string $productTitle1 = '1000';\n\n    private string $productid1 = 'seacharticle1000';\n\n    private string $productTitle2 = '1001';\n\n    private string $productid2 = 'seacharticle1001';\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $product1 = oxNew(Article::class);\n        $product1->setId($this->productid1);\n        $product1->oxarticles__oxtitle = new Field($this->productTitle1);\n        $product1->oxarticles__oxsearchkeys = new Field($this->productTitle1);\n        $product1->save();\n\n        $product2 = oxNew(Article::class);\n        $product2->setId($this->productid2);\n        $product2->oxarticles__oxtitle = new Field($this->productTitle2);\n        $product2->oxarticles__oxsearchkeys = new Field($this->productTitle2);\n        $product2->save();\n    }\n\n    public function testEmptySearchSetsEmptySearchFlag(): void\n    {\n        $controller = oxNew(SearchController::class);\n        $controller->init();\n\n        $this->assertTrue($controller->isEmptySearch());\n        $this->assertNull($controller->getArticleList());\n    }\n\n    public function testProductSearchCriteriaContainsFiltersAndSorting(): void\n    {\n        $capturedCriteria = null;\n        $mock = $this->createMock(ProductSearchServiceInterface::class);\n        $mock->expects($this->once())\n            ->method('search')\n            ->willReturnCallback(\n                function (ProductSearchCriteria $criteria) use (&$capturedCriteria): ProductSearchResult {\n                    $capturedCriteria = $criteria;\n                    return new ProductSearchResult([], 0);\n                }\n            );\n\n        $this->enableCustomProductSearch($mock);\n\n        Registry::getConfig()->setConfigParam('aSortCols', ['oxtitle']);\n        $this->setRequestParameter('searchparam', 'test');\n        $this->setRequestParameter('searchcnid', 'cat1');\n        $this->setRequestParameter('searchvendor', 'vendor1');\n        $this->setRequestParameter('searchmanufacturer', 'man1');\n        $this->setRequestParameter('listorderby', 'oxtitle');\n        $this->setRequestParameter('listorder', 'asc');\n\n        $controller = oxNew(SearchController::class);\n        $controller->init();\n\n        $filters = $capturedCriteria->getFilters();\n        $sorting = $capturedCriteria->getSorting();\n\n        $this->assertCount(3, $filters);\n        $this->assertSame('oxcatnid', $filters[0]->getField());\n        $this->assertSame('cat1', $filters[0]->getValue());\n        $this->assertSame('oxvendorid', $filters[1]->getField());\n        $this->assertSame('vendor1', $filters[1]->getValue());\n        $this->assertSame('oxmanufacturerid', $filters[2]->getField());\n        $this->assertSame('man1', $filters[2]->getValue());\n\n        $this->assertCount(1, $sorting);\n        $this->assertSame('oxtitle', $sorting[0]->getField());\n        $this->assertSame(SortDirection::Asc, $sorting[0]->getDirection());\n    }\n\n    public function testSearchServiceResultsAreUsedForArticleList(): void\n    {\n        $service = $this->createStub(ProductSearchServiceInterface::class);\n        $service->method('search')\n            ->willReturn(new ProductSearchResult([Id::fromString($this->productid1)], 1));\n\n        $this->enableCustomProductSearch($service);\n\n        $this->setRequestParameter('searchparam', 'anything');\n\n        $controller = oxNew(SearchController::class);\n        $controller->init();\n\n        $articleList = $controller->getArticleList();\n        $this->assertSame(1, $articleList->count());\n        $this->assertSame($this->productid1, $articleList->current()->getId());\n        $this->assertSame(1, $controller->getArticleCount());\n    }\n\n    public function testBeforeAndAfterEventsAreDispatchedAndModificationsAreApplied(): void\n    {\n        $capturedCriteria = null;\n        $mock = $this->createMock(ProductSearchServiceInterface::class);\n        $mock->expects($this->once())\n            ->method('search')\n            ->willReturnCallback(\n                function (ProductSearchCriteria $criteria) use (&$capturedCriteria): ProductSearchResult {\n                    $capturedCriteria = $criteria;\n                    return new ProductSearchResult([Id::fromString($this->productid1)], 1);\n                }\n            );\n\n        $this->enableCustomProductSearch($mock);\n\n        $dispatcher = $this->container->get(EventDispatcherInterface::class);\n        $dispatcher->addListener(\n            BeforeProductSearchEvent::class,\n            function (BeforeProductSearchEvent $event): void {\n                $event->setSearchCriteria(new ProductSearchCriteria(\n                    new Pagination(5, 0),\n                    new SearchTerm('replaced'),\n                    [new EqualsFilter('oxcatnid', 'replaced-cat')]\n                ));\n            }\n        );\n        $dispatcher->addListener(\n            AfterProductSearchEvent::class,\n            function (AfterProductSearchEvent $event): void {\n                $event->setSearchResult(new ProductSearchResult([Id::fromString($this->productid2)], 1));\n            }\n        );\n\n        $this->setRequestParameter('searchparam', 'original');\n\n        $controller = oxNew(SearchController::class);\n        $controller->init();\n\n        $this->assertSame('replaced', $capturedCriteria->getTerm()->getValue());\n        $this->assertSame('replaced-cat', $capturedCriteria->getFilters()[0]->getValue());\n\n        $articleList = $controller->getArticleList();\n        $this->assertSame(1, $articleList->count());\n        $this->assertSame($this->productid2, $articleList->current()->getId());\n    }\n\n    public function testManufacturerFilterExcludedWhenManufacturerTreeDisabled(): void\n    {\n        $capturedCriteria = null;\n        $mock = $this->createMock(ProductSearchServiceInterface::class);\n        $mock->expects($this->once())\n            ->method('search')\n            ->willReturnCallback(\n                function (ProductSearchCriteria $criteria) use (&$capturedCriteria): ProductSearchResult {\n                    $capturedCriteria = $criteria;\n                    return new ProductSearchResult([], 0);\n                }\n            );\n\n        $this->enableCustomProductSearch($mock);\n\n        Registry::getConfig()->setConfigParam('bl_perfLoadManufacturerTree', false);\n        $this->setRequestParameter('searchparam', 'test');\n        $this->setRequestParameter('searchmanufacturer', 'man1');\n\n        $controller = oxNew(SearchController::class);\n        $controller->init();\n\n        $this->assertCount(0, $capturedCriteria->getFilters());\n    }\n\n    public function testWhenProductSearchEnabledButServiceNotRegisteredFallsBackToDefaultSearch(): void\n    {\n        $logger = $this->createMock(LoggerInterface::class);\n        $logger->expects($this->once())\n            ->method('warning')\n            ->with('Product search service is not registered, falling back to default search.');\n\n        $this->setParameter('oxid_esales.product_search_enabled', true);\n        $this->container->set(LoggerInterface::class, $logger);\n\n        $this->setRequestParameter('searchparam', $this->productTitle1);\n\n        $controller = oxNew(SearchController::class);\n        $controller->init();\n\n        $articleList = $controller->getArticleList();\n        $this->assertSame(1, $articleList->count());\n        $this->assertSame($this->productid1, $articleList->current()->getId());\n    }\n\n    public function testSearchAnd(): void\n    {\n        Registry::getConfig()->setConfigParam('blSearchUseAND', true);\n\n        $this->setRequestParameter('searchparam', $this->productTitle1 . ' ' . $this->productTitle2);\n\n        $searchController = oxNew(SearchController::class);\n        $searchController->init();\n\n        $this->assertEquals(0, ($searchController->getArticleList())->count());\n\n        $this->setRequestParameter('searchparam', $this->productTitle1);\n        $searchController->init();\n\n        $articleList = $searchController->getArticleList();\n\n        $this->assertEquals(1, ($searchController->getArticleList())->count());\n        $this->assertEquals($this->productid1, $articleList->current()->getId());\n    }\n\n    public function testSearchOr(): void\n    {\n        Registry::getConfig()->setConfigParam('blSearchUseAND', false);\n\n        $this->setRequestParameter('searchparam', $this->productTitle1 . ' ' . $this->productTitle2);\n\n        $searchController = oxNew(SearchController::class);\n        $searchController->init();\n\n        $articleList = $searchController->getArticleList();\n        $this->assertEquals(2, $articleList->count());\n\n        $articleArray = $articleList->getArray();\n\n        $this->assertTrue(array_key_exists($this->productid1, $articleArray));\n        $this->assertTrue(array_key_exists($this->productid2, $articleArray));\n    }\n\n    public function testWhenSearchServiceThrowsFallsBackToDefaultSearch(): void\n    {\n        $mock = $this->createMock(ProductSearchServiceInterface::class);\n        $mock->expects($this->once())\n            ->method('search')\n            ->willThrowException(new ProductSearchException('search failed'));\n\n        $logger = $this->createMock(LoggerInterface::class);\n        $logger->expects($this->once())\n            ->method('error')\n            ->with('Unable to use search service, falling back to default search.');\n\n        $this->setParameter('oxid_esales.product_search_enabled', true);\n        $this->container->set(ProductSearchServiceInterface::class, $mock);\n        $this->container->set(LoggerInterface::class, $logger);\n\n        $this->setRequestParameter('searchparam', $this->productTitle1);\n\n        $controller = oxNew(SearchController::class);\n        $controller->init();\n\n        $articleList = $controller->getArticleList();\n        $this->assertSame(1, $articleList->count());\n        $this->assertSame($this->productid1, $articleList->current()->getId());\n    }\n\n    private function enableCustomProductSearch(ProductSearchServiceInterface $service): void\n    {\n        $this->setParameter('oxid_esales.product_search_enabled', true);\n        $this->container->set(ProductSearchServiceInterface::class, $service);\n    }\n\n    private function setRequestParameter(string $key, string $value): void\n    {\n        $_POST[$key] = $value;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Model/ManufacturerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Manufacturer;\nuse OxidEsales\\Eshop\\Application\\Model\\SeoEncoderManufacturer;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ManufacturerTest extends IntegrationTestCase\n{\n    public function testDelete(): void\n    {\n        $seoEncoderManufacturerMock = $this->createPartialMock(SeoEncoderManufacturer::class, ['onDeleteManufacturer']);\n        Registry::set(SeoEncoderManufacturer::class, $seoEncoderManufacturerMock);\n\n        $id = '_testId';\n        $manufacturer = oxNew(Manufacturer::class);\n        $manufacturer->setId($id);\n        $manufacturer->assign([\n            'oxactive' => 1,\n            'oxtitle' => 'bla',\n        ]);\n        $manufacturer->save();\n\n        $seoEncoderManufacturerMock->expects($this->once())\n            ->method('onDeleteManufacturer')\n            ->with($this->callback(fn ($manufacturer): bool => $manufacturer->getId() === $id));\n        unset($manufacturer);\n\n        $newManufacturer = oxNew(Manufacturer::class);\n        $newManufacturer->delete($id);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Model/RatingTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Application\\Model\\Rating;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class RatingTest extends IntegrationTestCase\n{\n    public function testUpdateProductRatingOnRatingDelete(): void\n    {\n        $this->createTestProduct();\n        $this->createTestRatings();\n\n        $rating = oxNew(Rating::class);\n        $rating->load('id3');\n        $rating->delete();\n\n        $product = oxNew(Article::class);\n        $product->load('testId');\n\n        $this->assertEquals(2, $product->oxarticles__oxratingcnt->value);\n\n        $this->assertEquals(1.5, $product->oxarticles__oxrating->value);\n    }\n\n    public function testUpdateProductRatingOnRatingDeleteWhenAllRatingsForProductAreDeleted(): void\n    {\n        $this->createTestProduct();\n        $this->createTestRatings();\n\n        $rating = oxNew(Rating::class);\n\n        $rating->load('id1');\n        $rating->delete();\n\n        $rating->load('id2');\n        $rating->delete();\n\n        $rating->load('id3');\n        $rating->delete();\n\n        $product = oxNew(Article::class);\n        $product->load('testId');\n\n        $this->assertEquals(0, $product->oxarticles__oxratingcnt->value);\n\n        $this->assertEquals(0, $product->oxarticles__oxrating->value);\n    }\n\n    private function createTestProduct(): void\n    {\n        $product = oxNew(Article::class);\n        $product->setId('testId');\n        $product->oxarticles__oxrating = new Field(2);\n        $product->oxarticles__oxratingcnt = new Field(3);\n        $product->save();\n    }\n\n    private function createTestRatings(): void\n    {\n        $rating = oxNew(Rating::class);\n        $rating->setId('id1');\n        $rating->oxratings__oxobjectid = new Field('testId');\n        $rating->oxratings__oxtype = new Field('oxarticle');\n        $rating->oxratings__oxrating = new Field(1);\n        $rating->save();\n\n        $rating = oxNew(Rating::class);\n        $rating->setId('id2');\n        $rating->oxratings__oxobjectid = new Field('testId');\n        $rating->oxratings__oxtype = new Field('oxarticle');\n        $rating->oxratings__oxrating = new Field(2);\n        $rating->save();\n\n        $rating = oxNew(Rating::class);\n        $rating->setId('id3');\n        $rating->oxratings__oxobjectid = new Field('testId');\n        $rating->oxratings__oxtype = new Field('oxarticle');\n        $rating->oxratings__oxrating = new Field(3);\n        $rating->save();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Model/ReviewTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Review;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\EshopCommunity\\Tests\\DatabaseTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ReviewTest extends IntegrationTestCase\n{\n    use DatabaseTrait;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        $this->beginTransaction();\n    }\n\n    public function tearDown(): void\n    {\n        $this->rollBackTransaction();\n        parent::tearDown();\n    }\n\n    public function testLoadListEscapesHtmlAndAddsLineBreakHtmlTags(): void\n    {\n        $reviewType = 'oxrecommlist';\n        $objectId = uniqid('id-', true);\n        $text = \"<script>alert();\n\nnew\\nlineCharacter\ncarriage\\rreturnCharacter\";\n\n        for ($i = 0; $i < 2; $i++) {\n            $review = oxNew(Review::class);\n            $review->oxreviews__oxobjectid = new Field($objectId);\n            $review->oxreviews__oxtype = new Field($reviewType);\n            $review->oxreviews__oxlang = new Field(0);\n            $review->oxreviews__oxtext = new Field($text);\n            $review->save();\n        }\n\n        $list = (oxNew(Review::class))->loadList($reviewType, $objectId, true, 0);\n\n        foreach ($list as $review) {\n            $this->assertEquals($text, $review->getFieldData('oxtext'));\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Model/SeoEncoderCategoryTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Category;\nuse OxidEsales\\Eshop\\Application\\Model\\SeoEncoderCategory;\nuse OxidEsales\\Eshop\\Core\\SeoEncoder;\nuse OxidEsales\\EshopCommunity\\Core\\UtilsObject;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\ConnectionFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\DatabaseTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class SeoEncoderCategoryTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n    use DatabaseTrait;\n\n    private int $subcategoryPerCategory = 2;\n\n    private int $productPerCategory = 3;\n\n    private array $categoryLanguages = [\n        'de' => 0,\n        'en' => 1,\n    ];\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->replaceContainerInstance();\n        $this->resetDatabaseProvider();\n    }\n\n    public function testOnDeleteCategoryWillSetDependantRecordsToExpired(): void\n    {\n        $seoEncoderCategory = oxNew(SeoEncoderCategory::class);\n        $category = $this->createTestCategoryWithSeoLinks();\n        $expectedRowCount =\n            ($this->productPerCategory + $this->subcategoryPerCategory) * count($this->categoryLanguages);\n\n        $seoEncoderCategory->onDeleteCategory($category);\n\n        $expiredRowCount = $this->get(ConnectionFactoryInterface::class)\n            ->create()\n            ->fetchOne(\n                'SELECT COUNT(*) FROM `oxseo` WHERE `OXSEOURL` like \"%www-some-shop/category-%\" AND `OXEXPIRED` = \"1\";'\n            );\n        $this->assertSame($expectedRowCount, $expiredRowCount);\n    }\n\n    private function createTestCategoryWithSeoLinks(): Category\n    {\n        $baseUrl = uniqid('www.some-shop/', true);\n        $seoEncoder = oxNew(SeoEncoder::class);\n\n        $mainCategory = oxNew(Category::class);\n        $mainCategory->setId(UtilsObject::getInstance()->generateUId());\n        $mainCategory->save();\n\n        foreach ($this->categoryLanguages as $languageCode) {\n            $mainCategoryUrl = uniqid('www.some-shop/category-', true);\n            /** Add entry for main category */\n            $seoEncoder->addSeoEntry(\n                $mainCategory->getId(),\n                1,\n                $languageCode,\n                $baseUrl,\n                $mainCategoryUrl,\n                'oxcategory'\n            );\n            /** Add entries for main sub-categories */\n            for ($i = 0; $i < $this->subcategoryPerCategory; $i++) {\n                $subCategoryId = UtilsObject::getInstance()->generateUId();\n                $this->createSubCategory($mainCategory, $subCategoryId);\n                $seoEncoder->addSeoEntry(\n                    $subCategoryId,\n                    1,\n                    $languageCode,\n                    $mainCategoryUrl,\n                    $mainCategoryUrl . uniqid('/subcategory-', true),\n                    'oxcategory'\n                );\n            }\n            /** Add entries for main category products */\n            for ($i = 1; $i <= $this->productPerCategory; $i++) {\n                $seoEncoder->addSeoEntry(\n                    UtilsObject::getInstance()->generateUId(),\n                    1,\n                    $languageCode,\n                    $mainCategoryUrl,\n                    $mainCategoryUrl . uniqid('/product-', true),\n                    'oxarticle'\n                );\n            }\n        }\n\n        return $mainCategory;\n    }\n\n    private function createSubCategory(Category $mainCategory, string $subCategoryId): void\n    {\n        $subCategory = oxNew(Category::class);\n        $subCategory->setId($subCategoryId);\n        $subCategory->setParentCategory($mainCategory);\n        $subCategory->save();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Application/Model/UserTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Application\\Model;\n\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Application\\Model\\User;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class UserTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    public function testLoginLogoutAdminDemoShop(): void\n    {\n        $this->createAdminUser();\n\n        $this->setParameter('oxid_esales.demo_shop_mode', true);\n        Registry::getConfig()->setAdminMode(true);\n\n        $user = oxNew(User::class);\n        $user->login('admin', 'admin');\n\n        $this->assertNotNull($this->getSessionParam('auth'));\n        $this->assertNull($this->getSessionParam('usr'));\n\n        $user = $user->getUser();\n        $this->assertNotNull($user);\n        $this->assertNotNull($user->getId());\n\n        $user->logout();\n        $this->assertNull($this->getSessionParam('usr'));\n        $this->assertNull($this->getSessionParam('auth'));\n        $this->assertFalse($user->getUser());\n    }\n\n    private function getSessionParam(string $parameterName)\n    {\n        return Registry::getSession()->getVariable($parameterName);\n    }\n\n    private function createAdminUser(): void\n    {\n        DatabaseProvider::getDb()\n            ->execute(\"INSERT INTO `oxuser` (`OXID`, `OXACTIVE`, `OXRIGHTS`, `OXSHOPID`, `OXUSERNAME`, `OXPASSWORD`, `OXPASSSALT`, `OXCUSTNR`, `OXUSTID`, `OXCOMPANY`, `OXFNAME`, `OXLNAME`, `OXSTREET`, `OXSTREETNR`, `OXADDINFO`, `OXCITY`, `OXCOUNTRYID`, `OXSTATEID`, `OXZIP`, `OXFON`, `OXFAX`, `OXSAL`, `OXBONI`, `OXCREATE`, `OXREGISTER`, `OXPRIVFON`, `OXMOBFON`, `OXBIRTHDATE`, `OXURL`, `OXUPDATEKEY`, `OXUPDATEEXP`, `OXPOINTS`) VALUES\n        ('oxdefaultadmin', 1, 'malladmin', 1, 'admin', 'e3a8a383819630e42d9ef90be2347ea70364b5efbb11dfc59adbf98487e196fffe4ef4b76174a7be3f2338581e507baa61c852b7d52f4378e21bd2de8c1efa5e', '61646D696E61646D696E61646D696E', 1, '', 'Your Company Name', 'John', 'Doe', 'Maple Street', '2425', '', 'Any City', 'a7c40f631fc920687.20179984', '', '9041', '217-8918712', '217-8918713', 'MR', 1000, '2003-01-01 00:00:00', '2003-01-01 00:00:00', '', '', '0000-00-00', '', '', 0, 0)\");\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Checkout/BasketWithStockTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Checkout;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Application\\Model\\Basket;\nuse OxidEsales\\Eshop\\Core\\Exception\\OutOfStockException;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class BasketWithStockTest extends IntegrationTestCase\n{\n    private const PRODUCT_ID = 'abc';\n\n    private const PRODUCT_STOCK_SIZE = 8.0;\n\n    private const STOCK_FLAG_NON_ORDERABLE = 3;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->createProduct();\n        Registry::getConfig()->setConfigParam('blAllowNegativeStock', false);\n        Registry::getConfig()->setConfigParam('blUseStock', true);\n    }\n\n    public function testAddToBasketWithinStockWillAddExpectedAmount(): void\n    {\n        $basket = oxNew(Basket::class);\n        $expectedCount = self::PRODUCT_STOCK_SIZE - 1;\n\n        $basket->addToBasket(self::PRODUCT_ID, $expectedCount);\n        $basket->calculateBasket(true);\n        $basket->onUpdate();\n        $count = $basket->getItemsCount();\n\n        $this->assertSame($expectedCount, $count);\n    }\n\n    public function testAddToBasketWithStockExceededWillLimitBasketItemAmount(): void\n    {\n        $basket = oxNew(Basket::class);\n\n        try {\n            $basket->addToBasket(self::PRODUCT_ID, 10);\n        } catch (OutOfStockException) {\n            /** stock size was exceeded */\n        }\n        $basket->calculateBasket(true);\n        $basket->onUpdate();\n        $count = $basket->getItemsCount();\n\n        $this->assertSame(self::PRODUCT_STOCK_SIZE, $count);\n    }\n\n    private function createProduct(): void\n    {\n        $product = oxNew(Article::class);\n        $product->setId(self::PRODUCT_ID);\n        $product->oxarticles__oxstock = new Field(self::PRODUCT_STOCK_SIZE);\n        $product->oxarticles__oxstockflag = new Field(self::STOCK_FLAG_NON_ORDERABLE);\n        $product->save();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Database/Adapter/DatabaseInterfaceImplementation.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Core\\Database\\Adapter;\n\nuse Doctrine\\DBAL\\TransactionIsolationLevel;\nuse InvalidArgumentException;\nuse oxDb;\nuse OxidEsales\\Eshop\\Core\\Exception\\DatabaseErrorException;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nabstract class DatabaseInterfaceImplementation extends DatabaseInterfaceImplementationBase\n{\n    public function testSelectWithParameters(): void\n    {\n        $this->loadFixtureToTestTable();\n\n        $resultSet = $this->database->select(\n            'SELECT OXID FROM ' . self::TABLE_NAME . ' WHERE OXID = ?', [self::FIXTURE_OXID_2]\n        );\n\n        $result = $resultSet->fetchAll();\n\n        $this->assertEquals([['OXID' => self::FIXTURE_OXID_2]], $result);\n    }\n\n    public function testSelectWithNonReadStatementThrowsException(): void\n    {\n        $this->expectException(InvalidArgumentException::class);\n\n        $this->database->select('INSERT INTO ' . self::TABLE_NAME . ' VALUES (\\'a\\',\\'b\\')');\n    }\n\n    public function testSelectPreparedWithInvalidParameterDoesNotThrowException(): void\n    {\n        $this->loadFixtureToTestTable();\n\n        $resultSet = $this->database->select(\n            'SELECT OXID FROM ' . self::TABLE_NAME . ' WHERE OXID = ?', [[ 'key' => 'value']]\n        );\n\n        $result = $resultSet->fetchAll();\n\n        $this->assertEquals([], $result);\n    }\n\n    public function testSelectLimitWithParameters(): void\n    {\n        $this->loadFixtureToTestTable();\n\n        $resultSet = $this->database->select(\n            'SELECT OXID FROM ' . self::TABLE_NAME . ' WHERE OXID <> ?', [self::FIXTURE_OXID_2]\n        );\n\n        $result = $resultSet->fetchAll();\n\n        $this->assertEquals([['OXID' => self::FIXTURE_OXID_1], ['OXID' => self::FIXTURE_OXID_3]], $result);\n    }\n\n    public static function dataProviderTestSelectLimitForDifferentLimitAndOffsetValues(): array\n    {\n        return [[\n            'If parameter rowCount is integer 0, no rows are returned at all',\n            0,\n            // row count\n            0,\n            // offset\n            [],\n        ], [\n            'If parameter rowCount is string \"2\" and offset is string \"0\", the first 2 rows will be returned',\n            '2',\n            // row count as a string\n            '0',\n            // offset as string\n            [\n                ['OXID' => self::FIXTURE_OXID_1], ['OXID' => self::FIXTURE_OXID_2],  // expected result\n            ],\n        ], [\n            'If parameter rowCount has the value 2 and offset has the value 0, the first 2 rows will be returned',\n            2,\n            // row count\n            0,\n            // offset\n            [\n                ['OXID' => self::FIXTURE_OXID_1], ['OXID' => self::FIXTURE_OXID_2],  // expected result\n            ],\n        ], [\n            'If parameter rowCount has the value 2 and offset has the value 1, the last 2 rows will be returned',\n            2,\n            // row count\n            1,\n            // offset\n            [\n                ['OXID' => self::FIXTURE_OXID_2], ['OXID' => self::FIXTURE_OXID_3], // expected result\n            ],\n        ]];\n    }\n\n    #[DataProvider('dataProviderTestSelectLimitForDifferentLimitAndOffsetValues')]\n    public function testSelectLimitReturnsExpectedResultForDifferentOffsetAndLimit(\n        string $assertionMessage,\n        int|string $rowCount,\n        int|string $offset,\n        array $expectedResult\n    ): void {\n        $this->loadFixtureToTestTable();\n        $sql = 'SELECT OXID FROM ' . self::TABLE_NAME . ' WHERE OXID IN (' .\n               '\"' . self::FIXTURE_OXID_1 . '\",' .\n               '\"' . self::FIXTURE_OXID_2 . '\",' .\n               '\"' . self::FIXTURE_OXID_3 . '\"' .\n               ')';\n\n        $resultSet = $this->database->selectLimit($sql, $rowCount, $offset);\n        $actualResult = $resultSet->fetchAll();\n\n        $this->assertSame($expectedResult, $actualResult, $assertionMessage);\n    }\n\n    public function testSelectLimitReturnsExpectedResultForMissingOffsetParameter(): void\n    {\n        $rowCount = 2;\n        $expectedResult = [['OXID' => self::FIXTURE_OXID_1], ['OXID' => self::FIXTURE_OXID_2]];\n        $assertionMessage = 'If parameter offet is not set, selectLimit will return the number of records \n        given in the parameter $rowcount starting from the first record in the result set';\n\n        $this->loadFixtureToTestTable();\n        $sql = 'SELECT OXID FROM ' . self::TABLE_NAME . ' WHERE OXID IN (' .\n               '\"' . self::FIXTURE_OXID_1 . '\",' .\n               '\"' . self::FIXTURE_OXID_2 . '\",' .\n               '\"' . self::FIXTURE_OXID_3 . '\"' .\n               ')';\n\n        $resultSet = $this->database->selectLimit($sql, $rowCount);\n        $actualResult = $resultSet->fetchAll();\n\n        $this->assertSame($expectedResult, $actualResult, $assertionMessage);\n    }\n\n    public function testSelectWithEmptyResultSelect(): void\n    {\n        $result = $this->database->select('SELECT OXID FROM ' . self::TABLE_NAME);\n\n        $expectedRows = [];\n        $allRows = $result->fetchAll();\n        $this->assertSame($expectedRows, $allRows);\n    }\n\n    public function testExecuteWithEmptyResultAndSelectNotOnFirstChar(): void\n    {\n        $result = $this->database->select('   SELECT OXID FROM ' . self::TABLE_NAME);\n\n        $expectedRows = [];\n        $allRows = $result->fetchAll();\n        $this->assertSame($expectedRows, $allRows);\n    }\n\n    public function testExecuteWithNonEmptySelect(): void\n    {\n        $this->loadFixtureToTestTable();\n\n        $result = $this->database->select('SELECT OXID FROM ' . self::TABLE_NAME . ' ORDER BY OXID');\n\n        $this->assertFalse($result->EOF);\n        $this->assertSame(['OXID' => self::FIXTURE_OXID_1], $result->fields);\n\n        $expectedRows = [\n            ['OXID' => self::FIXTURE_OXID_1],\n            ['OXID' => self::FIXTURE_OXID_2],\n            ['OXID' => self::FIXTURE_OXID_3]\n        ];\n        $allRows = $result->fetchAll();\n\n        $this->assertSame($expectedRows, $allRows);\n    }\n\n    public function testExecuteThrowsExceptionForInvalidNonSelectQueryString(): void\n    {\n        $this->expectException(DatabaseErrorException::class);\n\n        $this->database->execute('SOME INVALID QUERY');\n    }\n\n    public function testSelectThrowsExceptionForInvalidSelectQueryString(): void\n    {\n        $this->expectException(DatabaseErrorException::class);\n\n        oxDb::getMaster()->select('SELECT SOME INVALID QUERY');\n    }\n\n    public function testSetTransactionIsolationLevel(): void\n    {\n        $connection = $this->database->getPublicConnection();\n\n        $transactionIsolationLevelPre = $connection->getTransactionIsolation();\n\n        $expectedLevel = TransactionIsolationLevel::READ_COMMITTED;\n        $this->database->setTransactionIsolationLevel('READ COMMITTED');\n        $transactionIsolationLevel = $connection->getTransactionIsolation();\n\n        $this->assertSame($expectedLevel, $transactionIsolationLevel);\n\n        $connection->setTransactionIsolation($transactionIsolationLevelPre);\n    }\n\n    public function testGetColWithoutParametersEmptyResult(): void\n    {\n        $result = $this->database->getCol('SELECT OXID FROM ' . self::TABLE_NAME);\n\n        $this->assertIsArray($result);\n        $this->assertCount(\n            0,\n            $result\n        );\n    }\n\n    public function testGetColWithoutParameters(): void\n    {\n        $this->loadFixtureToTestTable();\n\n        $result = $this->database->getCol('SELECT OXUSERID FROM ' . self::TABLE_NAME);\n\n        $this->assertIsArray($result);\n        $this->assertCount(\n            3,\n            $result\n        );\n        $this->assertSame([self::FIXTURE_OXUSERID_1, self::FIXTURE_OXUSERID_2, self::FIXTURE_OXUSERID_3], $result);\n    }\n\n    public function testGetColWithParameters(): void\n    {\n        $this->loadFixtureToTestTable();\n\n        $result = $this->database->getCol(\n            'SELECT OXUSERID FROM ' . self::TABLE_NAME . ' WHERE OXUSERID LIKE ? ',\n            ['%2']\n        );\n\n        $this->assertIsArray($result);\n        $this->assertCount(\n            1,\n            $result\n        );\n        $this->assertSame([self::FIXTURE_OXUSERID_2], $result);\n    }\n\n    public function testGetColhWithNonReadStatementThrowsException(): void\n    {\n        $this->expectException(InvalidArgumentException::class);\n\n        $this->database->getCol('INSERT INTO ' . self::TABLE_NAME . \" VALUES ('a', 'b')\");\n    }\n\n    public function testRollbackTransactionRevertsChanges(): void\n    {\n        $exampleOxId = 'XYZ';\n\n        $this->truncateTestTable();\n        $this->database->startTransaction();\n        $this->database->execute('INSERT INTO ' . self::TABLE_NAME . \" (OXID) VALUES ('$exampleOxId');\");\n\n        // assure, that the changes are made in this transaction\n        $this->assertTestTableHasOnly($exampleOxId);\n\n        $this->database->rollbackTransaction();\n\n        // assure, that the changes are reverted\n        $this->assureTestTableIsEmpty();\n    }\n\n    public function testCommitTransactionCommitsChanges(): void\n    {\n        $exampleOxId = 'XYZ';\n\n        $this->truncateTestTable();\n        $this->database->startTransaction();\n        $this->database->execute('INSERT INTO ' . self::TABLE_NAME . \" (OXID) VALUES ('$exampleOxId');\");\n\n        // assure, that the changes are made in this transaction\n        $this->assertTestTableHasOnly($exampleOxId);\n        $this->database->commitTransaction();\n\n        // assure, that the changes persist the transaction\n        $this->assertTestTableHasOnly($exampleOxId);\n    }\n\n    public function testGetAllWithEmptyParameter(): void\n    {\n        $message = 'The expected result is returned when passing an empty array as parameter to Doctrine::getAll()';\n        $expectedResult = [['OXID' => self::FIXTURE_OXID_1]];\n\n        $this->truncateTestTable();\n        $this->database->execute(\n            'INSERT INTO ' . self::TABLE_NAME . \" (OXID) VALUES ('\" . self::FIXTURE_OXID_1 . \"')\"\n        );\n\n        $actualResult = $this->database->getAll(\n            'SELECT OXID FROM ' . self::TABLE_NAME . \" WHERE OXID = '\" . self::FIXTURE_OXID_1 . \"'\"\n        );\n\n        $this->assertEquals($actualResult, $expectedResult, $message);\n    }\n\n    public function testGetAllWithOneParameter(): void\n    {\n        $message = 'The expected result is returned when passing an array with one parameter to Doctrine::getAll()';\n        $expectedResult = [['OXID' => self::FIXTURE_OXID_1]];\n\n        $this->truncateTestTable();\n        $this->database->execute(\n            'INSERT INTO ' . self::TABLE_NAME . \" (OXID) VALUES ('\" . self::FIXTURE_OXID_1 . \"')\"\n        );\n\n        $actualResult = $this->database->getAll(\n            'SELECT OXID FROM ' . self::TABLE_NAME . ' WHERE OXID = ?',\n            [self::FIXTURE_OXID_1]\n        );\n\n        $this->assertEquals($actualResult, $expectedResult, $message);\n    }\n\n    public function testGetAllWithMoreThanOneParameters(): void\n    {\n        $message = 'The expected result is returned when passing an array with more than one parameter'\n            . ' to Doctrine::getAll()';\n        $expectedResult = [\n            [\n                'OXID' => self::FIXTURE_OXID_1\n            ],\n            [\n                'OXID' => self::FIXTURE_OXID_2\n            ]\n        ];\n\n        $this->truncateTestTable();\n        $this->database->execute(\n            'INSERT INTO ' . self::TABLE_NAME . \" (OXID) VALUES ('\" . self::FIXTURE_OXID_1 . \"')\"\n        );\n        $this->database->execute(\n            'INSERT INTO ' . self::TABLE_NAME . \" (OXID) VALUES ('\" . self::FIXTURE_OXID_2 . \"')\"\n        );\n\n        $actualResult = $this->database->getAll(\n            'SELECT OXID FROM ' . self::TABLE_NAME . ' WHERE OXID IN (?, ?)',\n            [self::FIXTURE_OXID_1, self::FIXTURE_OXID_2]\n        );\n\n        $this->assertEquals($actualResult, $expectedResult, $message);\n    }\n\n    public function testGetAllThrowsDatabaseExceptionOnInvalidQueryString(): void\n    {\n        $this->expectException(DatabaseErrorException::class);\n\n        $this->database->getAll('SOME INVALID QUERY');\n    }\n\n    public function testInsertIdOnNonAutoIncrement(): void\n    {\n        $this->database->execute(\n            'INSERT INTO ' . self::TABLE_NAME . ' (OXUSERID) VALUES (\"' . self::FIXTURE_OXUSERID_1 . '\")'\n        );\n\n        $this->expectException(DatabaseErrorException::class);\n\n        $this->database->getLastInsertId();\n    }\n\n    public function testInsertIdWithoutInsertion(): void\n    {\n        $this->database->select('SELECT * FROM ' . self::TABLE_NAME);\n\n        $this->expectException(DatabaseErrorException::class);\n\n        $this->database->getLastInsertId();\n    }\n\n    public function testInsertIdWithInsertion(): void\n    {\n        $this->database->execute(\n            'CREATE TABLE oxdoctrinetest_autoincrement '\n            . '(oxid INT NOT NULL AUTO_INCREMENT, oxname CHAR, PRIMARY KEY (oxid));'\n        );\n\n        $this->database->execute('INSERT INTO oxdoctrinetest_autoincrement(oxname) VALUES (\"OXID eSales\")');\n        $firstInsertedId = $this->database->getLastInsertId();\n\n        $this->database->execute('INSERT INTO oxdoctrinetest_autoincrement(oxname) VALUES (\"OXID eSales\")');\n        $lastInsertedId = $this->database->getLastInsertId();\n\n        $this->database->execute('DROP TABLE oxdoctrinetest_autoincrement;');\n\n        $this->assertEquals(1, $firstInsertedId);\n        $this->assertEquals(2, $lastInsertedId);\n    }\n\n    public function testGetOneWithEmptyTable(): void\n    {\n        $result = $this->database->getOne('SELECT * FROM ' . self::TABLE_NAME);\n\n        $this->assertFalse($result);\n    }\n\n    public function testGetOneWithWrongSqlStatement(): void\n    {\n        $result = $this->database->getOne(\n            'INSERT INTO ' . self::TABLE_NAME . \" (oxid) VALUES ('\" . self::FIXTURE_OXID_1 . \"')\"\n        );\n\n        $this->assertFalse($result);\n    }\n\n    public function testGetOneWithNonEmptyTable(): void\n    {\n        $this->loadFixtureToTestTable();\n\n        $result = $this->database->getOne('SELECT * FROM ' . self::TABLE_NAME);\n\n        $this->assertEquals(self::FIXTURE_OXID_1, $result);\n    }\n\n    public function testGetOneWithShowStatement(): void\n    {\n        $result = $this->database->getOne('SHOW COLUMNS FROM ' . self::TABLE_NAME);\n\n        $this->assertEquals('oxid', $result);\n    }\n\n    public function testGetOneWithNonEmptyTableAndGivenColumnName(): void\n    {\n        $this->loadFixtureToTestTable();\n\n        $result = $this->database->getOne('SELECT OXUSERID FROM ' . self::TABLE_NAME);\n\n        $this->assertEquals(self::FIXTURE_OXUSERID_1, $result);\n    }\n\n    public function testGetOneWithEmptyParameters(): void\n    {\n        $this->loadFixtureToTestTable();\n\n        $result = $this->database->getOne('SELECT OXUSERID FROM ' . self::TABLE_NAME);\n\n        $this->assertEquals(self::FIXTURE_OXUSERID_1, $result);\n    }\n\n    public function testGetOneWithNonEmptyParameters(): void\n    {\n        $this->loadFixtureToTestTable();\n\n        $result = $this->database->getOne(\n            'SELECT OXUSERID FROM ' . self::TABLE_NAME . ' WHERE oxid = ?',\n            [self::FIXTURE_OXID_3]\n        );\n\n        $this->assertEquals(self::FIXTURE_OXUSERID_3, $result);\n    }\n\n    public function testGetRowIncorrectSqlStatement(): void\n    {\n        $this->truncateTestTable();\n\n        $this->expectException(InvalidArgumentException::class);\n\n        $this->database->getRow(\n            'INSERT INTO ' . self::TABLE_NAME . \" (oxid) VALUES ('\" . self::FIXTURE_OXID_1 . \"')\"\n        );\n    }\n\n    public function testGetRowNonEmptyTableWithParameters(): void\n    {\n        $this->loadFixtureToTestTable();\n\n        $result = $this->database->getRow(\n            'SELECT * FROM ' . self::TABLE_NAME . ' WHERE oxid = ?',\n            [self::FIXTURE_OXID_2]\n        );\n\n        $this->assertIsArray($result);\n        $this->assertEquals(['oxid' => self::FIXTURE_OXID_2, 'oxuserid' => self::FIXTURE_OXUSERID_2], $result);\n    }\n\n    /**\n     * Test, that the method 'getRow' gives back the correct result, when called with parameters and consecutive calls.\n     */\n    public function testGetRowNonEmptyTableWithParametersAndConsecutiveCalls(): void\n    {\n        $this->loadFixtureToTestTable();\n\n        $this->database->getRow('SELECT * FROM ' . self::TABLE_NAME);\n        $result = $this->database->getRow('SELECT * FROM ' . self::TABLE_NAME);\n\n        $this->assertIsArray($result);\n        $this->assertEquals(['oxid' => self::FIXTURE_OXID_1, 'oxuserid' => self::FIXTURE_OXUSERID_1], $result);\n    }\n\n    public function testMetaColumnsMethod(): void\n    {\n        $metaColumnsTestTable = self::TABLE_NAME . '_testmetacolumns';\n        $this->createTableForTestMetaColumns($metaColumnsTestTable);\n        $columnInformation = $this->database->metaColumns($metaColumnsTestTable);\n\n        $expectedColumns = $this->getExpectedColumnsByTestMetaColumns();\n\n        foreach ($expectedColumns as $key => $sub) {\n            foreach ($sub as $attributeName => $attributeValue) {\n                $this->assertObjectHasAttributeWithValue($columnInformation[$key], $attributeName, $attributeValue);\n            }\n        }\n    }\n\n    #[DataProvider('dataProviderTestQuoteWithValidValues')]\n    public function testQuoteWithValidValues(\n        mixed $value,\n        mixed $expectedQuotedValue,\n        mixed $expectedResult,\n        string $message\n    ): void {\n        $this->loadFixtureToTestTable();\n\n        $actualQuotedValue = $this->database->quote($value);\n\n        $this->assertSame($expectedQuotedValue, $actualQuotedValue, $message);\n\n        $query = 'SELECT OXID FROM ' . self::TABLE_NAME . \" WHERE OXID = $actualQuotedValue\";\n        $resultSet = $this->database->select($query);\n        $actualResult = $resultSet->fetchAll();\n\n        $this->assertSame($expectedResult, $actualResult, $message);\n    }\n\n    public static function dataProviderTestQuoteWithValidValues(): array\n    {\n        return [\n            [\n                self::FIXTURE_OXID_1,\n                \"'\" . self::FIXTURE_OXID_1 . \"'\",\n                [['OXID' => self::FIXTURE_OXID_1]],\n                'The string \"' . self::FIXTURE_OXID_1 . '\" 1  will be converted into the string \"\\'' .\n                self::FIXTURE_OXID_1 . '\\'\" and the query result will be [' . self::FIXTURE_OXID_1 . ']',\n            ],\n            [\n                1,\n                \"'1'\",\n                [],\n                'The integer 1  will be converted into the string \"1\" and the query result will be empty'\n            ],\n            [\n                1.5,\n                \"'1.5'\",\n                [],\n                'The float 1.5 will be converted into the string \"1.5\" and the query result will be empty',\n            ],\n            [\n                false,\n                \"''\",\n                [],\n                'The boolean false will be converted into the empty string and the query result will be empty',\n            ],\n            [\n                true,\n                \"'1'\",\n                [],\n                'The boolean true will be converted into the string \"1\" and the query result will be empty',\n            ],\n            [\n                null,\n                \"''\",\n                [],\n                'The null value will be converted into the empty string and the query result will be empty',\n            ],\n        ];\n    }\n\n    /*\n     * There is another special table needed for testMetaColumns.\n     *\n     * @param string $metaColumnsTestTable The name of the table to create\n     */\n    protected function createTableForTestMetaColumns(string $metaColumnsTestTable): void\n    {\n        $dbh = self::getDatabaseHandler();\n        $dbh->exec('CREATE TABLE IF NOT EXISTS ' . $metaColumnsTestTable . \" (\n            OXINT INT(11) NOT NULL AUTO_INCREMENT COMMENT 'a column with type INT',\n            OXUSERID CHAR(32) CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'  COMMENT 'a column with type CHAR',\n            OXTIME TIME COMMENT 'a column of type TIME',\n            OXBIT BIT(6) NOT NULL  COMMENT 'a column with type BIT',\n            OXDEC DEC(6,2) UNSIGNED NOT NULL DEFAULT 1.3 COMMENT 'a column with type DECIMAL',\n            OXTEXT TEXT  CHARACTER SET 'utf8' COLLATE 'utf8_general_ci' NOT NULL COMMENT 'a column with type TEXT',\n            OXID CHAR(32)  CHARACTER SET 'utf8' COLLATE 'utf8_general_ci' NOT NULL COMMENT 'a column with type CHAR',\n            OXBLOB BLOB  COMMENT 'a column with type BLOB',\n            OXFLOAT FLOAT(5,2) UNSIGNED NOT NULL DEFAULT 1.3 COMMENT 'a column with type FLOAT',\n            PRIMARY KEY (OXINT)\n        ) ENGINE innoDb;\");\n    }\n\n    /*\n     * Specify which results the method 'metaColumns' expects for each column of the testing table.\n     */\n    protected function getExpectedColumnsByTestMetaColumns(): array\n    {\n        return [\n            'OXINT' => [\n                'name' => 'OXINT',\n                'type' => 'int',\n                'not_null' => true,\n                'primary_key' => true,\n                'auto_increment' => true,\n                'binary' => false,\n                'unsigned' => false,\n                'has_default' => false,\n                'comment' => 'a column with type INT',\n            ],\n            'OXUSERID' => [\n                'name' => 'OXUSERID',\n                'max_length' => '32',\n                'type' => 'char',\n                'not_null' => false,\n                'primary_key' => false,\n                'auto_increment' => false,\n                'binary' => false,\n                'unsigned' => false,\n                'comment' => 'a column with type CHAR',\n            ],\n            'OXTIME' => [\n                'name' => 'OXTIME',\n                'type' => 'time',\n                'not_null' => false,\n                'primary_key' => false,\n                'auto_increment' => false,\n                'binary' => false,\n                'unsigned' => false,\n                'comment' => 'a column of type TIME',\n            ],\n            'OXBIT' => [\n                'name' => 'OXBIT',\n                'max_length' => '6',\n                'type' => 'bit',\n                'not_null' => true,\n                'primary_key' => false,\n                'auto_increment' => false,\n                'binary' => false,\n                'unsigned' => false,\n                'comment' => 'a column with type BIT',\n            ],\n            'OXDEC' => [\n                'name' => 'OXDEC',\n                'max_length' => '6',\n                'type' => 'decimal',\n                'not_null' => true,\n                'primary_key' => false,\n                'auto_increment' => false,\n                'binary' => false,\n                'unsigned' => true,\n                'has_default' => true,\n                'default_value' => '1.30',\n                'scale' => '2',\n                'comment' => 'a column with type DECIMAL',\n            ],\n            'OXTEXT' => [\n                'name' => 'OXTEXT',\n                'type' => 'text',\n                'not_null' => true,\n                'primary_key' => false,\n                'auto_increment' => false,\n                'binary' => false,\n                'unsigned' => false,\n                'has_default' => false,\n                'comment' => 'a column with type TEXT',\n            ],\n            'OXID' => [\n                'name' => 'OXID',\n                'max_length' => '32',\n                'type' => 'char',\n                'not_null' => true,\n                'primary_key' => false,\n                'auto_increment' => false,\n                'binary' => false,\n                'unsigned' => false,\n                'has_default' => false,\n                'comment' => 'a column with type CHAR',\n            ],\n            'OXBLOB' => [\n                'name' => 'OXBLOB',\n                'type' => 'blob',\n                'not_null' => false,\n                'primary_key' => false,\n                'auto_increment' => false,\n                'binary' => true,\n                'unsigned' => false,\n                'comment' => 'a column with type BLOB',\n            ],\n            'OXFLOAT' => [\n                'name' => 'OXFLOAT',\n                'max_length' => '5',\n                'scale' => '2',\n                'type' => 'float',\n                'not_null' => true,\n                'primary_key' => false,\n                'auto_increment' => false,\n                'binary' => false,\n                'unsigned' => true,\n                'has_default' => true,\n                'default_value' => '1.30',\n            ]\n        ];\n    }\n\n    /**\n     * Assure, that the table oxdoctrinetest has only the given oxId.\n     *\n     * @param string $oxId The oxId we want to be the only one in the oxdoctrinetest table.\n     */\n    protected function assertTestTableHasOnly(string $oxId): void\n    {\n        $oxIds = $this->fetchAllTestTableRows();\n\n        $this->assertNotEmpty($oxIds);\n        $this->assertCount(\n            1,\n            $oxIds\n        );\n        $this->assertArrayHasKey('0', $oxIds);\n        $this->assertSame($oxId, $oxIds[0]['oxid']);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Database/Adapter/DatabaseInterfaceImplementationBase.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Core\\Database\\Adapter;\n\nuse oxDb;\nuse OxidEsales\\EshopCommunity\\Core\\Database\\Adapter\\DatabaseInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\DataObject\\DatabaseConfiguration;\nuse PDO;\nuse PHPUnit\\Framework\\TestCase;\n\n/**\n * Abstract base class for database integration tests.\n * Extend this class to have a common setup for low level database tests.\n */\nabstract class DatabaseInterfaceImplementationBase extends TestCase\n{\n    /**\n     * @var string The name of the table, we use to test the database.\n     */\n    public const TABLE_NAME = 'oxdoctrinetest';\n\n    /**\n     * @var string The first fixture oxId.\n     */\n    public const FIXTURE_OXID_1 = 'OXID_1';\n\n    /**\n     * @var string The second fixture oxId.\n     */\n    public const FIXTURE_OXID_2 = 'OXID_2';\n\n    /**\n     * @var string The third fixture oxId.\n     */\n    public const FIXTURE_OXID_3 = 'OXID_3';\n\n    /**\n     * @var string The first fixture oxUserId.\n     */\n    public const FIXTURE_OXUSERID_1 = 'OXUSERID_1';\n\n    /**\n     * @var string The first fixture oxUserId.\n     */\n    public const FIXTURE_OXUSERID_2 = 'OXUSERID_2';\n\n    /**\n     * @var string The first fixture oxUserId.\n     */\n    public const FIXTURE_OXUSERID_3 = 'OXUSERID_3';\n\n    public const EXPECTED_MYSQL_SYNTAX_ERROR_CODE = 1064;\n\n    public const EXPECTED_MYSQL_SYNTAX_ERROR_MESSAGE = 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \\'INVALID SQL QUERY\\' at line 1';\n\n    /**\n     * @var array Holds the errors caught by the user-defined error handler\n     */\n    protected $errors;\n\n    /**\n     * @var DatabaseInterface The database to test.\n     */\n    protected $database;\n\n    public static function setUpBeforeClass(): void\n    {\n        parent::setUpBeforeClass();\n\n        self::getDatabaseHandler()\n            ->exec(\n                'CREATE TABLE IF NOT EXISTS ' . self::TABLE_NAME . ' (oxid CHAR(32), oxuserid CHAR(32)) ENGINE innoDb;'\n            );\n    }\n\n    public static function tearDownAfterClass(): void\n    {\n        self::getDatabaseHandler()->exec('DROP TABLE IF EXISTS ' . self::TABLE_NAME . ';');\n\n        parent::tearDownAfterClass();\n    }\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        /** Set a user-defined error handler in order to handle errors triggered with trigger_error */\n        $this->errors = [];\n        set_error_handler($this->errorHandler(...));\n\n        $this->initializeDatabase();\n        $this->truncateTestTable();\n        $this->assureTestTableIsEmpty();\n    }\n\n    public function tearDown(): void\n    {\n        $this->truncateTestTable();\n        $this->database->closeConnection();\n        gc_collect_cycles();\n\n        restore_error_handler();\n        parent::tearDown();\n    }\n\n    /**\n     * Provides an error handler\n     *\n     * @param integer $errorLevel Error number as defined in http://php.net/manual/en/errorfunc.constants.php\n     * @param string $errorMessage Error message\n     * @param string $errorFile Error file\n     * @param integer $errorLine Error line\n     * @param array $errorContext Error context\n     */\n    public function errorHandler($errorLevel, $errorMessage, $errorFile = '', $errorLine = 0, $errorContext = []): void\n    {\n        $this->errors[] = compact('errorLevel', 'errorMessage', 'errorFile', 'errorLine', 'errorContext');\n    }\n\n    /**\n     * Get a PDO instance representing a connection to the database.\n     * Use this static method to access the database without using the shop adapters.\n     */\n    protected static function getDatabaseHandler(): PDO\n    {\n        $databaseConfig = new DatabaseConfiguration(getenv('OXID_DB_URL'));\n        return new PDO(\n            sprintf(\n                'mysql:host=%s;port=%s;dbname=%s',\n                $databaseConfig->getHost(),\n                $databaseConfig->getPort(),\n                $databaseConfig->getName()\n            ),\n            $databaseConfig->getUser(),\n            $databaseConfig->getPass()\n        );\n    }\n\n    /**\n     * Create the database, we want to test.\n     */\n    protected function initializeDatabase()\n    {\n        $this->database = oxDb::getMaster();\n    }\n\n    protected function loadFixtureToTestTable(?DatabaseInterface $database = null): void\n    {\n        if ($database === null) {\n            $database = $this->database;\n        }\n        $this->truncateTestTable();\n\n        $queryValuesParts = [];\n\n        foreach (\n            [\n                self::FIXTURE_OXID_1 => self::FIXTURE_OXUSERID_1,\n                self::FIXTURE_OXID_2 => self::FIXTURE_OXUSERID_2,\n                self::FIXTURE_OXID_3 => self::FIXTURE_OXUSERID_3,\n            ] as $oxId => $oxUserId\n        ) {\n            $queryValuesParts[] = \"('{$oxId}','{$oxUserId}')\";\n        }\n        $database->execute(\n            'INSERT INTO ' . self::TABLE_NAME . '(OXID, OXUSERID) VALUES '\n            . implode(',', $queryValuesParts) . ';'\n        );\n    }\n\n    protected function truncateTestTable()\n    {\n        return $this->database->execute('TRUNCATE ' . self::TABLE_NAME . ';');\n    }\n\n    /**\n     * Assert, that the given object has the wished attribute with the given value.\n     *\n     * @param object $object The object we want to check for the given attribute.\n     * @param string $attributeName The name of the attribute we want to exist.\n     * @param mixed $attributeValue The wished value of the attribute.\n     */\n    protected function assertObjectHasAttributeWithValue($object, string $attributeName, mixed $attributeValue)\n    {\n        $this->assertTrue(isset($object->{$attributeName}), 'Missing field \"' . $attributeName . '\".');\n        $this->assertSame($attributeValue, $object->{$attributeName});\n    }\n\n    protected function assureTestTableIsEmpty()\n    {\n        $this->assertEmpty($this->fetchAllTestTableRows(), \"Table '\" . self::TABLE_NAME . \"' is empty\");\n    }\n\n    protected function fetchAllTestTableRows()\n    {\n        return $this->database->select('SELECT * FROM ' . self::TABLE_NAME)->fetchAll();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Database/Adapter/Doctrine/DatabaseTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Core\\Database\\Adapter\\Doctrine;\n\nuse Exception;\nuse InvalidArgumentException;\nuse OxidEsales\\EshopCommunity\\Core\\Database\\Adapter\\DatabaseInterface;\nuse OxidEsales\\EshopCommunity\\Core\\Database\\Adapter\\Doctrine\\ResultSet;\nuse OxidEsales\\EshopCommunity\\Core\\Exception\\DatabaseErrorException;\nuse OxidEsales\\EshopCommunity\\Core\\Exception\\DatabaseException;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Core\\Database\\Adapter\\DatabaseInterfaceImplementation;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse stdClass;\nuse TypeError;\n\nfinal class DatabaseTest extends DatabaseInterfaceImplementation\n{\n    public const DATABASE_EXCEPTION_CLASS = DatabaseErrorException::class;\n\n    public const RESULT_SET_CLASS = ResultSet::class;\n\n    protected $database;\n\n    /**\n     * Test, that the method 'selectLimit' returns the expected rows from the database for different\n     * values of limit and offset.\n     *\n     * This test assumes that there are at least 3 entries in the table.\n     *\n     * @param string $assertionMessage A message explaining the assertion\n     * @param int|string $rowCount Maximum number of rows to return\n     * @param string|int $offset Offset of the first row to return\n     * @param array $expectedResult The expected result of the method call.\n     */\n    #[DataProvider('dataProviderTestSelectLimitForInvalidOffsetAndLimit')]\n    public function testSelectLimitForInvalidOffsetAndLimit(\n        string $assertionMessage,\n        int|string $rowCount,\n        string|int $offset,\n        array $expectedResult\n    ): void {\n        $this->loadFixtureToTestTable();\n        $sql = 'SELECT OXID FROM ' . self::TABLE_NAME . ' WHERE OXID IN (' .\n            '\"' . self::FIXTURE_OXID_1 . '\",' .\n            '\"' . self::FIXTURE_OXID_2 . '\",' .\n            '\"' . self::FIXTURE_OXID_3 . '\"' .\n            ')';\n        if ($offset < 0) {\n            $this->expectException(InvalidArgumentException::class);\n        }\n        $resultSet = $this->database->selectLimit($sql, $rowCount, $offset);\n        $this->assertError(\n            'Parameters rowCount and offset have to be numeric in DatabaseInterface::selectLimit(). ' .\n            'Please fix your code as this error may trigger an exception in future versions of OXID eShop.'\n        );\n        $actualResult = $resultSet->fetchAll();\n\n        $this->assertSame($expectedResult, $actualResult, $assertionMessage);\n    }\n\n    /**\n     * Data provider for testing selectLimit() with invalid parameters\n     */\n    public static function dataProviderTestSelectLimitForInvalidOffsetAndLimit(): array\n    {\n        return [[\n            'If parameter rowCount is integer 2 and offset is string \" UNION SELECT oxusername FROM oxuser\" , a warning will be triggered and the first 2 rows will be returned',\n            2,\n            // row count\n            ' UNION SELECT oxusername FROM oxuser',\n            // offset\n            [\n                ['OXID' => self::FIXTURE_OXID_1], ['OXID' => self::FIXTURE_OXID_2],  // expected result\n            ],\n        ], [\n            'If parameter rowCount is integer 2 and offset is string \"1  UNION SELECT oxusername FROM oxuser -- \" , a warning will be triggered and last 2 rows will be returned',\n            2,\n            // row count\n            '1  UNION SELECT oxusername FROM oxuser',\n            // offset\n            [\n                ['OXID' => self::FIXTURE_OXID_2], ['OXID' => self::FIXTURE_OXID_3],  // expected result\n            ],\n        ], [\n            'If parameter rowCount is string \" UNION SELECT oxusername FROM oxuser  --\" and offset is 0, a warning will be triggered and the first 2 rows will be returned',\n            ' UNION SELECT oxusername FROM oxuser  --',\n            // row count\n            0,\n            // offset\n            [],\n        ], [\n            'If parameter rowCount is string \"1 UNION SELECT oxusername FROM oxuser  --\" and offset is 0, a warning will be triggered and the first 2 rows will be returned',\n            '1  UNION SELECT oxusername FROM oxuser --',\n            // row count\n            0,\n            // offset\n            [\n                ['OXID' => self::FIXTURE_OXID_1],  // expected result\n            ],\n        ]];\n    }\n\n    /**\n     * Verify that method 'selectLimit' does not allow a negative offset value.\n     */\n    public function testSelectLimitForOffsetBelowZero(): void\n    {\n        $this->loadFixtureToTestTable();\n        $sql = 'SELECT OXID FROM ' . self::TABLE_NAME . ' WHERE OXID IN (' .\n            '\"' . self::FIXTURE_OXID_1 . '\",' .\n            '\"' . self::FIXTURE_OXID_2 . '\",' .\n            '\"' . self::FIXTURE_OXID_3 . '\"' .\n            ')';\n\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('Argument $offset must not be smaller than zero.');\n\n        $this->database->selectLimit($sql, 1, -1);\n    }\n\n    public function testSetTransactionIsolationLevelThrowsExpectedExceptionOnInvalidParameter(): void\n    {\n        $this->expectException(InvalidArgumentException::class);\n\n        $this->database->setTransactionIsolationLevel('INVALID TRANSACTION ISOLATION LEVEL');\n    }\n\n    public function testExceptionGetCodeAndExceptionGetMessageReturnSameResultsAsErrorNoAndErrorMsg(): void\n    {\n        $expectedCode = self::EXPECTED_MYSQL_SYNTAX_ERROR_CODE;\n\n        try {\n            $this->database->execute('INVALID SQL QUERY');\n            $actualCode = 0;\n        } catch (Exception $exception) {\n            $actualCode = $exception->getCode();\n        }\n\n        $this->assertSame($expectedCode, $actualCode);\n    }\n\n    public function testQuoteIdentifierWithValidValues(): void\n    {\n        $this->loadFixtureToTestTable();\n        $quotedIdentifier = $this->database->quoteIdentifier('OXID');\n\n        $expectedResult = [['OXID' => self::FIXTURE_OXID_1]];\n        $resultSet = $this->database\n            ->select(\n                'SELECT OXID FROM ' . self::TABLE_NAME . \" WHERE OXID = '\" . self::FIXTURE_OXID_1 . \"' ORDER BY \" . $quotedIdentifier\n            );\n        $actualResult = $resultSet->fetchAll();\n\n        $this->assertSame($expectedResult, $actualResult);\n    }\n\n    #[DataProvider('dataProviderTestQuoteIdentifierWithInvalidValues')]\n    public function testQuoteIdentifierWithInvalidValues(string $identifier): void\n    {\n        $this->expectException(DatabaseException::class);\n\n        $quotedIdentifier = $this->database->quoteIdentifier($identifier);\n\n        $this->database->select('SELECT * FROM ' . self::TABLE_NAME . ' ORDER BY ' . $quotedIdentifier);\n    }\n\n    public static function dataProviderTestQuoteIdentifierWithInvalidValues(): array\n    {\n        return [\n            [\n                // An arbitrary string will be converted in a column name\n                'SELECT * from oxuser',\n            ],\n            [\n                // An arbitrary string, which contains a backtick, will be converted in a column name\n                'columnName ` columnName',\n            ],\n        ];\n    }\n\n    #[DataProvider('dataProviderTestQuoteWithInvalidValues')]\n    public function testQuoteWithInvalidValues(\n        mixed $value,\n        mixed $expectedQuotedValue,\n        string $expectedException,\n        string $message\n    ): void {\n        $this->loadFixtureToTestTable();\n\n        $this->expectException(TypeError::class);\n        $actualQuotedValue = $this->database->quote($value);\n        $this->assertSame($expectedQuotedValue, $actualQuotedValue, $message);\n\n        $this->expectException($expectedException);\n\n        $query = 'SELECT OXID FROM ' . self::TABLE_NAME . \" WHERE OXID = $actualQuotedValue\";\n        $resultSet = $this->database->select($query);\n        $resultSet->fetchAll();\n    }\n\n    public function testExceptionForDuplicatedEntry(): void\n    {\n        $tableName = self::TABLE_NAME;\n        $id = self::FIXTURE_OXID_1;\n        $this->database->execute('ALTER TABLE `oxdoctrinetest`ADD UNIQUE `oxid` (`oxid`);');\n        $this->database->execute(\"INSERT INTO $tableName (OXID) VALUES ('$id');\");\n\n        try {\n            $this->database->execute(\"INSERT INTO $tableName (OXID) VALUES ('$id');\");\n        } catch (DatabaseErrorException $e) {\n            $this->assertEquals(DatabaseInterface::DUPLICATE_KEY_ERROR_CODE, $e->getCode());\n            return;\n        }\n\n        $this->fail('Database exception must be thrown due to duplicated entry.');\n    }\n\n    public static function dataProviderTestQuoteWithInvalidValues(): array\n    {\n        return [\n            [[\n                'key' => 'value',\n            ],\n                false,\n                self::DATABASE_EXCEPTION_CLASS,\n                'An array will be converted into boolean \"false\" and an exception is thrown, when the statement is executed '\n            ],\n            [\n                new stdClass(),\n                false,\n                self::DATABASE_EXCEPTION_CLASS,\n                'An object will be converted into boolean \"false\" and an exception is thrown, when the statement is executed',\n            ],\n        ];\n    }\n\n    /**\n     * Test, that affected rows is set to the expected values by consecutive calls to execute()\n     */\n    public function testExecuteSetsAffectedRows(): void\n    {\n        $this->loadFixtureToTestTable();\n\n        /** One row will be updated by the query */\n        $expectedAffectedRows = 1;\n        $actualAffectedRows = $this->database->execute(\n            'UPDATE ' . self::TABLE_NAME . ' SET oxuserid = \"somevalue\" WHERE OXID = ?',\n            [self::FIXTURE_OXID_1]\n        );\n\n        $this->assertEquals($expectedAffectedRows, $actualAffectedRows, '1 row was updated by the query');\n\n        /** Two rows will be updated by the query */\n        $expectedAffectedRows = 2;\n        $actualAffectedRows = $this->database->execute(\n            'UPDATE ' . self::TABLE_NAME . ' SET oxuserid = \"someothervalue\" WHERE OXID IN (?, ?)',\n            [self::FIXTURE_OXID_1, self::FIXTURE_OXID_2]\n        );\n\n        $this->assertEquals($expectedAffectedRows, $actualAffectedRows, '2 rows was updated by the query');\n    }\n\n    /**\n     * Test, that the method 'execute' works for insert and delete.\n     */\n    public function testExecuteWithInsertAndDelete(): void\n    {\n        $this->truncateTestTable();\n\n        $exampleOxId = self::FIXTURE_OXID_1;\n\n        $affectedRows = $this->database->execute(\n            'INSERT INTO ' . self::TABLE_NAME . \" (OXID) VALUES ('$exampleOxId');\"\n        );\n\n        $this->assertSame(1, $affectedRows);\n        $this->assertTestTableHasOnly($exampleOxId);\n\n        $affectedRows = $this->database->execute('DELETE FROM ' . self::TABLE_NAME . \" WHERE OXID = '$exampleOxId';\");\n\n        $this->assertSame(1, $affectedRows);\n        $this->assertEmpty($this->fetchAllTestTableRows());\n    }\n\n    /**\n     * Test, that the method 'getRow' gives an empty array with empty table and default fetch mode.\n     */\n    public function testGetRowEmptyTableDefaultFetchMode(): void\n    {\n        $result = $this->database->getRow('SELECT * FROM ' . self::TABLE_NAME);\n\n        $this->assertIsArray($result);\n        $this->assertEmpty($result);\n    }\n\n    /**\n     * Test, that the method 'getAll' leads to unique rows with the SQL clause 'ORDER BY rand()'.\n     */\n    public function testGetAllWithOrderByRand(): void\n    {\n        $resultSet = $this->database->select('SELECT oxid FROM oxarticles ORDER BY RAND()');\n        $rows = $resultSet->fetchAll();\n        $oxIds = [];\n\n        foreach ($rows as $row) {\n            $oxIds[] = $row['oxid'];\n        }\n\n        $this->assertArrayIsUnique($oxIds);\n    }\n\n    /**\n     * Test, that the method 'moveNext' leads to unique rows with the SQL clause 'ORDER BY rand()'.\n     */\n    public function testMoveNextWithOrderByRand(): void\n    {\n        $oxIds = $this->database->getCol('SELECT oxid FROM oxarticles ORDER BY RAND()');\n\n        $this->assertArrayIsUnique($oxIds);\n    }\n\n    /*\n     * After applying the driverOptions to the Doctrine DriverManager, in our case the sql_mode should be\n     * set on the database connection.\n     */\n    public function testAddDriverOptionsSetsSqlMode(): void\n    {\n        $query = 'SELECT @@SESSION.sql_mode';\n\n        $expectedSqlMode = '';\n        $actualSqlMode = $this->database->getOne($query);\n        $this->assertSame(\n            $expectedSqlMode,\n            $actualSqlMode,\n            'The sql_mode variable on the database is not the expected one.'\n        );\n    }\n\n    protected function getResultSetClassName(): string\n    {\n        return self::RESULT_SET_CLASS;\n    }\n\n    /**\n     * Assert a given error level and a given error message\n     *\n     * @param string $errorMessage Error message\n     */\n    private function assertError(string $errorMessage): void\n    {\n        foreach ($this->errors as $error) {\n            if (\n                $error['errorMessage'] === $errorMessage\n                && $error['errorLevel'] === E_USER_DEPRECATED\n            ) {\n                return;\n            }\n        }\n\n        $this->fail('No error with level ' . E_USER_DEPRECATED . \" and message '\" . $errorMessage . \"' was triggered\");\n    }\n\n    private function assertArrayIsUnique(array $expectUnique): void\n    {\n        $unique = array_unique($expectUnique);\n        $this->assertEquals($unique, $expectUnique, 'There should not be any doubled entries in the given array!');\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Database/Adapter/Doctrine/ResultSetTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Core\\Database\\Adapter\\Doctrine;\n\nuse oxDb;\nuse OxidEsales\\EshopCommunity\\Core\\Database\\Adapter\\DatabaseInterface;\nuse OxidEsales\\EshopCommunity\\Core\\Database\\Adapter\\ResultSetInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Core\\Database\\Adapter\\DatabaseInterfaceImplementationBase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nfinal class ResultSetTest extends DatabaseInterfaceImplementationBase\n{\n    public function testFieldCount(): void\n    {\n        $this->assertEquals(1, $this->getResultSet()->fieldCount());\n        $this->assertEquals(2, $this->database->select('SELECT * FROM ' . self::TABLE_NAME)->fieldCount());\n    }\n\n    public function testGetIteratorEmptyResultSet(): void\n    {\n        $nonExistingId = uniqid('some-id-', true);\n        $count = $this->countQueryIterations(\"SELECT * FROM oxconfig where oxid = '{$nonExistingId}'\");\n\n        $this->assertEquals(0, $count);\n    }\n\n    public function testGetIteratorNonEmptyResultSet(): void\n    {\n        $count = $this->countQueryIterations('SELECT * FROM oxconfig');\n\n        $this->assertGreaterThan(0, $count);\n    }\n\n    public function testFields(): void\n    {\n        $expected = [\n            'OXID' => self::FIXTURE_OXID_1,\n            'OXUSERID' => self::FIXTURE_OXUSERID_1,\n        ];\n        $this->loadFixtureToTestTable();\n\n        $resultSet = $this->database->select('SELECT OXID,OXUSERID FROM ' . self::TABLE_NAME . ' ORDER BY OXID');\n\n        $this->truncateTestTable();\n        $this->assertSame($expected, $resultSet->getFields());\n    }\n\n    public function testFetchRowWithEmptyResultSet(): void\n    {\n        $resultSet = $this->getResultSet();\n        $this->assertEquals(0, $resultSet->count());\n\n        $methodResult = $resultSet->fetchRow();\n\n        $this->assertTrue($resultSet->EOF);\n        $this->assertFalse($resultSet->getFields());\n        $this->assertFalse($methodResult);\n    }\n\n    public function testFetchRowWithNonEmptyResultSet(): void\n    {\n        $this->loadFixtureToTestTable();\n        $resultSet = $this->getResultSet();\n        $this->assertSame(3, $resultSet->count());\n\n        $this->assertFalse($resultSet->EOF);\n        $this->assertSame(['OXID' => self::FIXTURE_OXID_1], $resultSet->fields);\n\n        $methodResult = $resultSet->fetchRow();\n\n        $this->assertFalse($resultSet->EOF);\n        $this->assertSame(['OXID' => self::FIXTURE_OXID_2], $resultSet->fields);\n        $this->assertSame(['OXID' => self::FIXTURE_OXID_2], $methodResult);\n\n        $methodResult = $resultSet->fetchRow();\n\n        $this->assertFalse($resultSet->EOF);\n        $this->assertSame(['OXID' => self::FIXTURE_OXID_3], $resultSet->fields);\n        $this->assertSame(['OXID' => self::FIXTURE_OXID_3], $methodResult);\n    }\n\n    public function testFetchRowWithNonEmptyResultSetReachingEnd(): void\n    {\n        $this->loadFixtureToTestTable();\n        $resultSet = $this->getResultSet();\n\n        $resultSet->fetchRow();\n        $resultSet->fetchRow();\n        $methodResult = $resultSet->fetchRow();\n\n        $this->assertTrue($resultSet->EOF);\n        $this->assertFalse($resultSet->fields);\n        $this->assertFalse($methodResult);\n\n        $methodResult = $resultSet->fetchRow();\n\n        $this->assertTrue($resultSet->EOF);\n        $this->assertFalse($resultSet->fields);\n        $this->assertFalse($methodResult);\n    }\n\n    public function testFetchRowWithNonEmptyResult(): void\n    {\n        $this->loadFixtureToTestTable();\n\n        $resultSet = $this->getResultSet();\n        $this->initializeDatabase();\n\n        $this->assertFalse($resultSet->EOF);\n        $this->assertSame([\n            'OXID' => self::FIXTURE_OXID_1,\n        ], $resultSet->fields);\n\n        $methodResult = $resultSet->fetchRow();\n\n        $this->assertFalse($resultSet->EOF);\n        $this->assertSame([\n            'OXID' => self::FIXTURE_OXID_2,\n        ], $resultSet->fields);\n        $this->assertSame([\n            'OXID' => self::FIXTURE_OXID_2,\n        ], $methodResult);\n\n        $methodResult = $resultSet->fetchRow();\n\n        $this->assertFalse($resultSet->EOF);\n        $this->assertSame([\n            'OXID' => self::FIXTURE_OXID_3,\n        ], $resultSet->fields);\n        $this->assertSame([\n            'OXID' => self::FIXTURE_OXID_3,\n        ], $methodResult);\n    }\n\n    public function testFetchAllWithEmptyResultSet(): void\n    {\n        $resultSet = $this->getResultSet();\n\n        $rows = $resultSet->fetchAll();\n\n        $this->assertIsArray($rows);\n        $this->assertEmpty($rows);\n    }\n\n    public function testFetchAllWithNonEmptyResultSet(): void\n    {\n        $this->loadFixtureToTestTable();\n        $resultSet = $this->getResultSet();\n\n        $this->assertSame(['OXID' => self::FIXTURE_OXID_1], $resultSet->fields);\n        $rows = $resultSet->fetchAll();\n\n        $this->assertIsArray($rows);\n        $this->assertNotEmpty($rows);\n        $this->assertSame(3, count($rows));\n        $this->assertSame(self::FIXTURE_OXID_1, $rows[0]['OXID']);\n        $this->assertSame(self::FIXTURE_OXID_2, $rows[1]['OXID']);\n        $this->assertSame(self::FIXTURE_OXID_3, $rows[2]['OXID']);\n    }\n\n    public function testEofWithEmptyResultSet(): void\n    {\n        $this->assertTrue($this->getResultSet()->EOF);\n    }\n\n    public function testEofWithNonEmptyResultSet(): void\n    {\n        $this->loadFixtureToTestTable();\n\n        $this->assertFalse($this->getResultSet()->EOF);\n    }\n\n    public function testCloseEmptyResultSet(): void\n    {\n        $resultSet = $this->getResultSet();\n\n        $resultSet->close();\n\n        $this->assertTrue($resultSet->EOF);\n        $this->assertSame([], $resultSet->fields);\n    }\n\n    public function testCloseEmptyResultSetWithFetchingAfterClosing(): void\n    {\n        $resultSet = $this->getResultSet();\n\n        $resultSet->close();\n\n        $firstRow = $resultSet->fetchRow();\n\n        $this->assertFalse($firstRow);\n        $this->assertTrue($resultSet->EOF);\n        $this->assertFalse($resultSet->fields);\n    }\n\n    public function testCloseNonEmptyResultSet(): void\n    {\n        $this->loadFixtureToTestTable();\n        $resultSet = $this->getResultSet();\n\n        $firstRow = $resultSet->getFields();\n\n        $resultSet->close();\n\n        $this->assertSame(['OXID' => self::FIXTURE_OXID_1], $firstRow);\n        $this->assertFalse($resultSet->EOF);\n        $this->assertSame([], $resultSet->fields);\n    }\n\n    public function testGetRowIteration(): void\n    {\n        $this->loadFixtureToTestTable();\n        $resultSet = $this->getResultSet();\n\n        $expectedResults = [\n            ['OXID' => self::FIXTURE_OXID_1],\n            ['OXID' => self::FIXTURE_OXID_2],\n            ['OXID' => self::FIXTURE_OXID_3]\n        ];\n\n        $this->assertSame($expectedResults[0], $resultSet->getFields());\n        $counter = 1;\n        while ($row = $resultSet->fetchRow()) {\n            $this->assertSame($expectedResults[$counter], $row);\n            $counter++;\n        }\n        $resultSet->close();\n    }\n\n    public function testResultSetFields(): void\n    {\n        $this->loadFixtureToTestTable();\n\n        $resultSet = $this->database->select(\n            'SELECT * FROM ' . self::TABLE_NAME . ' WHERE OXID in (?, ?)', [self::FIXTURE_OXID_2, self::FIXTURE_OXID_3]\n        );\n        $this->assertSame([\n            'oxid' => 'OXID_2',\n            'oxuserid' => 'OXUSERID_2',\n        ], $resultSet->fields);\n    }\n\n    private function countQueryIterations(string $query): int\n    {\n        $resultSet = $this->database->select($query);\n\n        $count = 0;\n        foreach ($resultSet->getIterator() as $ignored) {\n            $count++;\n        }\n\n        return $count;\n    }\n\n    private function getResultSet(): ResultSetInterface\n    {\n        return $this->database->select('SELECT OXID FROM ' . self::TABLE_NAME);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Model/BaseModelTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Core\\Model;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class BaseModelTest extends TestCase\n{\n    public function testFunctionIsPropertyLoadedReturnsFalseWhenPropertyIsNotLoadedAndIsField(): void\n    {\n        $model = $this->getModelWithLazyLoading();\n        $fieldName = $this->getTestFieldNameOfModelWithLazyLoading();\n\n        $this->assertFalse($model->isPropertyLoaded($fieldName));\n    }\n\n    public function testFunctionIsPropertyLoadedReturnsTrueWhenPropertyIsLoadedAndIsField(): void\n    {\n        $model = $this->getModelWithLazyLoading();\n        $fieldName = $this->getTestFieldNameOfModelWithLazyLoading();\n\n        $model->{$fieldName};\n\n        $this->assertTrue($model->isPropertyLoaded($fieldName));\n    }\n\n    public function testLazyLoadingMagicIssetReturnsTrueWhenPropertyIsNotLoadedAndIsField(): void\n    {\n        $model = $this->getModelWithLazyLoading();\n        $fieldName = $this->getTestFieldNameOfModelWithLazyLoading();\n\n        $this->assertTrue(isset($model->{$fieldName}));\n    }\n\n    public function testLazyLoadingMagicIssetLoadsPropertyWhenPropertyIsNotLoadedAndIsField(): void\n    {\n        $model = $this->getModelWithLazyLoading();\n        $fieldName = $this->getTestFieldNameOfModelWithLazyLoading();\n\n        $this->assertTrue(isset($model->{$fieldName}));\n    }\n\n    public function testLazyLoadingMagicIssetReturnsTrueWhenPropertyIsLoadedAndIsField(): void\n    {\n        $model = $this->getModelWithLazyLoading();\n        $fieldName = $this->getTestFieldNameOfModelWithLazyLoading();\n\n        $model->{$fieldName};\n\n        $this->assertTrue(isset($model->{$fieldName}));\n    }\n\n    public function testLazyLoadingMagicIssetOnValueOfFieldReturnsTrueWhenFieldIsNotLoaded(): void\n    {\n        $model = $this->getModelWithLazyLoading();\n        $fieldName = $this->getTestFieldNameOfModelWithLazyLoading();\n\n        $this->assertTrue(isset($model->{$fieldName}->value));\n    }\n\n    public function testLazyLoadingMagicIssetOnValueOfFieldReturnsTrueWhenFieldIsLoaded(): void\n    {\n        $model = $this->getModelWithLazyLoading();\n        $fieldName = $this->getTestFieldNameOfModelWithLazyLoading();\n\n        $model->{$fieldName};\n\n        $this->assertTrue(isset($model->{$fieldName}->value));\n    }\n\n    public function testLazyLoadingMagicIssetOnValueOfPropertyReturnsFalseWhenPropertyIsNotFieldAndNotLoaded(): void\n    {\n        $model = $this->getModelWithLazyLoading();\n\n        $this->assertFalse(isset($model->someProperty->value));\n    }\n\n    private function getModelWithLazyLoading()\n    {\n        $model = oxNew(Article::class);\n        $model->init('oxarticles');\n\n        $model->setId('2000');\n        $model->save();\n\n        return $model;\n    }\n\n    private function getTestFieldNameOfModelWithLazyLoading(): string\n    {\n        return 'oxarticles__oxartnum';\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Module/Fixtures/InvalidNamespaceModule/Controller/NonExistentClass.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nclass _NonExistentClass\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Module/Fixtures/InvalidNamespaceModule/Model/.gitkeep",
    "content": ""
  },
  {
    "path": "tests/Integration/Legacy/Core/Module/Fixtures/InvalidNamespaceModule/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\Eshop\\Application\\Controller\\ContentController;\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\EshopCommunity\\Tests\\Acceptance\\Admin\\testData\\modules\\oxid\\InvalidNamespaceModule1\\Controller\\NonExistentClass;\nuse OxidEsales\\EshopCommunity\\Tests\\Acceptance\\Admin\\testData\\modules\\oxid\\InvalidNamespaceModule1\\Model\\NonExistentFile;\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'InvalidNamespaceModule',\n    'title' => 'Invalid Namespaced Module',\n    'description' => 'Test module validation for modules, which use namespaces',\n    'thumbnail' => 'module.png',\n    'version' => '1.0',\n    'author' => 'OXID',\n    'extend' => [\n        /**\n         * In this test case the file with the proper name is present, but it contains the wrong class.\n         * This means the class cannot be loaded properly\n         */\n        ContentController::class => NonExistentClass::class,\n        /**\n         * In this test case the class file does not exist at all and thus the class cannot be loaded\n         */\n        Article::class => NonExistentFile::class,\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Module/Fixtures/chainTestModuleClasses/FirstUser.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Core\\Module\\Fixtures\\chainTestModuleClasses;\n\nclass FirstUser extends FirstUser_parent\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Module/Fixtures/chainTestModuleClasses/FourthUser.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Core\\Module\\Fixtures\\chainTestModuleClasses;\n\nclass FourthUser extends FourthUser_parent\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Module/Fixtures/chainTestModuleClasses/SecondUser.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Core\\Module\\Fixtures\\chainTestModuleClasses;\n\nclass SecondUser extends SecondUser_parent\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Module/Fixtures/chainTestModuleClasses/ThirdUser.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Core\\Module\\Fixtures\\chainTestModuleClasses;\n\nclass ThirdUser extends ThirdUser_parent\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Module/Fixtures/with_class_extensions/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\n\n$sMetadataVersion = '2.1';\n$aModule = [\n    'id' => 'with_class_extensions',\n    'title' => 'Smarty plugin directoies',\n    'description' => 'Test defining smarty plugin directoies',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n    'extend' => [\n        Article::class => 'with_class_extensions/ModuleArticle',\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Module/Fixtures/with_class_extensions2/Controllers/ContentController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Core\\Module\\Fixtures\\with_class_extenstions2\\Controllers;\n\nuse OxidEsales\\EshopCommunity\\Tests\\Acceptance\\Admin\\testData\\modules\\oxid\\namespace1\\Models\\Content;\n\nclass ContentController extends ContentController_parent\n{\n    /**\n     * @return mixed\n     */\n    public function render()\n    {\n        $sTpl = parent::render();\n\n        /** @var Content $content */\n        $content = oxNew(Content::class);\n        $this->_oContent->oxcontents__oxtitle->setValue(\n            $this->_oContent->oxcontents__oxtitle . $content->testContent()\n        );\n\n        return $sTpl;\n    }\n\n    public function showContent(): void\n    {\n        $content = oxNew(Content::class);\n\n        echo $content->testContent();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Module/Fixtures/with_class_extensions2/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\Eshop\\Application\\Controller\\ContentController;\n\n$sMetadataVersion = '2.1';\n$aModule = [\n    'id' => 'with_class_extensions2',\n    'title' => 'with_class_extensions2 module',\n    'description' => 'test module',\n    'thumbnail' => 'module.png',\n    'version' => '1.0',\n    'author' => 'OXID',\n    'extend' => [\n        ContentController::class => \\OxidEsales\\EshopCommunity\\Tests\\Integration\\Core\\Module\\Fixtures\\with_class_extenstions2\\Controllers\\ContentController::class,\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Module/Fixtures/with_class_extensions_cleaner/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\n\n$sMetadataVersion = '2.1';\n$aModule = [\n    'id' => 'with_class_extensions_cleaner',\n    'title' => 'Smarty plugin directoies',\n    'description' => 'Test defining smarty plugin directoies',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n    'extend' => [\n        Article::class => 'with_class_extensions_cleaner/ModuleArticle',\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Module/Fixtures/with_everything/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.1';\n\n$aModule = [\n    'id' => 'with_everything',\n    'title' => 'some-test-title',\n    'description' => 'some-test-description',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n    'extend' => [\n        'oxarticle' => 'with_everything/myarticle',\n        'oxuser' => 'with_everything/myuser',\n        'oxorder' => 'with_everything/myorder1',\n    ],\n    'blocks' => [\n        [\n            'template' => 'page/checkout/basket.tpl',\n            'block' => 'basket_btn_next_top',\n            'file' => '/views/blocks/page/checkout/myexpresscheckout.tpl',\n        ],\n        [\n            'theme' => 'shop_theme_id',\n            'template' => 'page/checkout/payment.tpl',\n            'block' => 'select_payment',\n            'file' => '/views/blocks/page/checkout/mypaymentselector.tpl',\n        ],\n    ],\n    'templates' => [\n        'order_special.tpl' => 'with_everything/views/admin/tpl/order_special.tpl',\n        'user_connections.tpl' => 'with_everything/views/tpl/user_connections.tpl',\n        'shop_theme_id' => [\n            '01.tpl' => '01.theme.ext.tpl',\n            '02.tpl' => '02.theme.ext.tpl',\n        ],\n    ],\n    'settings' => [\n        [\n            'group' => 'my_checkconfirm',\n            'name' => 'blCheckConfirm',\n            'type' => 'bool',\n            'value' => true,\n        ],\n        [\n            'group' => 'my_displayname',\n            'name' => 'sDisplayName',\n            'type' => 'str',\n            'value' => 'Some name',\n        ],\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Module/Fixtures/with_extending_blocks/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'with_extending_blocks',\n    'title' => 'Test extending blocks classes',\n    'description' => 'Module testing extending blocks classes',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n    'blocks' => [\n        [\n            'template' => 'page/checkout/basket.tpl',\n            'block' => 'basket_btn_next_top',\n            'file' => '/views/blocks/page/checkout/myexpresscheckout.tpl',\n        ],\n        [\n            'template' => 'page/checkout/basket.tpl',\n            'block' => 'basket_btn_next_bottom',\n            'file' => '/views/blocks/page/checkout/myexpresscheckout.tpl',\n        ],\n        [\n            'template' => 'page/checkout/payment.tpl',\n            'block' => 'select_payment',\n            'file' => '/views/blocks/page/checkout/mypaymentselector.tpl',\n        ],\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Module/Fixtures/with_metadata_v21/Smarty/PluginDirectory1WithMetadataVersion21/.gitkeep",
    "content": ""
  },
  {
    "path": "tests/Integration/Legacy/Core/Module/Fixtures/with_metadata_v21/Smarty/PluginDirectory2WithMetadataVersion21/.gitkeep",
    "content": ""
  },
  {
    "path": "tests/Integration/Legacy/Core/Module/Fixtures/with_metadata_v21/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.1';\n$aModule = [\n    'id' => 'with_metadata_v21',\n    'title' => 'Smarty plugin directoies',\n    'description' => 'Test defining smarty plugin directoies',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n    'templates' => [\n        'test_template.tpl' => 'test_template.tpl',\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Module/Fixtures/with_metadata_v21/test_template.tpl",
    "content": ""
  },
  {
    "path": "tests/Integration/Legacy/Core/Module/Fixtures/with_multiple_extensions/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Application\\Model\\Basket;\nuse OxidEsales\\Eshop\\Application\\Model\\Order;\n\n$sMetadataVersion = '2.1';\n$aModule = [\n    'id' => 'with_multiple_extensions',\n    'title' => 'with multiple extensions',\n    'description' => 'Test multiple extensions',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n    'extend' => [\n        Article::class => 'with_multiple_extensions/articleExtension1&with_multiple_extensions/articleExtension2&with_multiple_extensions/articleExtension3',\n        Order::class => 'with_multiple_extensions/oxOrder',\n        Basket::class => 'with_multiple_extensions/basketExtension',\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Module/ModuleListTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Core\\Module;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Application\\Model\\Basket;\nuse OxidEsales\\Eshop\\Application\\Model\\Order;\nuse OxidEsales\\Eshop\\Core\\Module\\ModuleList;\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\ContainerFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Psr\\Container\\ContainerInterface;\n\nfinal class ModuleListTest extends IntegrationTestCase\n{\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->getContainer()\n            ->get('oxid_esales.module.install.service.launched_shop_project_configuration_generator')\n            ->generate();\n    }\n\n    public function testGetDisabledModuleClasses(): void\n    {\n        $notActiveModuleId = 'with_class_extensions';\n        $this->installModule($notActiveModuleId);\n\n        $this->assertSame(\n            ['with_class_extensions/ModuleArticle'],\n            oxNew(ModuleList::class)->getDisabledModuleClasses()\n        );\n    }\n\n    public function testGetModuleExtensionsWithMultipleExtensions(): void\n    {\n        $extensions = [\n            Article::class => [\n                'with_multiple_extensions/articleExtension1',\n                'with_multiple_extensions/articleExtension2',\n                'with_multiple_extensions/articleExtension3',\n            ],\n            Order::class => ['with_multiple_extensions/oxOrder'],\n            Basket::class => ['with_multiple_extensions/basketExtension'],\n        ];\n\n        $this->installModule('with_multiple_extensions');\n        $this->activateModule('with_multiple_extensions');\n\n        $this->assertSame($extensions, oxNew(ModuleList::class)->getModuleExtensions('with_multiple_extensions'));\n    }\n\n    public function testGetModuleExtensionsWithNoExtensions(): void\n    {\n        $this->installModule('with_metadata_v21');\n        $this->assertSame([], oxNew(ModuleList::class)->getModuleExtensions('with_metadata_v21'));\n    }\n\n    private function installModule(string $id): void\n    {\n        $package = new OxidEshopPackage(__DIR__ . '/Fixtures/' . $id);\n\n        $this->getContainer()\n            ->get(ModuleInstallerInterface::class)\n            ->install($package);\n    }\n\n    private function activateModule(string $id): void\n    {\n        $this->getContainer()\n            ->get(ModuleActivationBridgeInterface::class)->activate($id, 1);\n    }\n\n    private function getContainer(): ContainerInterface\n    {\n        return ContainerFactory::getInstance()->getContainer();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/Routing/ControllerIdToClassResolvingTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Core\\Routing;\n\nuse OxidEsales\\Eshop\\Application\\Controller\\StartController;\nuse OxidEsales\\EshopCommunity\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ControllerIdToClassResolvingTest extends IntegrationTestCase\n{\n    /**\n     * Test controller classid to class mapping.\n     */\n    public function testIdToClassMapping(): void\n    {\n        $classId = 'start';\n        $resolvedClass = Registry::getControllerClassNameResolver()->getClassNameById($classId);\n        $this->assertEquals(StartController::class, $resolvedClass);\n    }\n\n    /**\n     * Test controller class to classId mapping.\n     */\n    public function testClassToIdMapping(): void\n    {\n        $class = StartController::class;\n        $this->assertEquals('start', Registry::getControllerClassNameResolver()->getIdByClassName($class));\n    }\n\n    /**\n     * Test controller class to classId mapping.\n     */\n    public function testClassToIdMappingNotExistingClass(): void\n    {\n        $class = 'classDoesNotExist';\n        $this->assertEquals(null, Registry::getControllerClassNameResolver()->getClassNameById($class));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/UtilsFileLocalImagesHandlingTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Core;\n\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\UtilsFile;\nuse OxidEsales\\EshopCommunity\\Application\\Model\\Article;\nuse OxidEsales\\EshopCommunity\\Core\\Di\\ContainerFacade;\nuse OxidEsales\\EshopCommunity\\Core\\Field;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\ImageHandlerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\MasterImageHandler as LocalImageHandler;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class UtilsFileLocalImagesHandlingTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private string $testFile = '';\n\n    private string $testFileDestination = '';\n\n    private string $testFileDuplicateDestination = '';\n\n    private $someTmpDir = '';\n\n    public function setUp(): void\n    {\n        $this->checkImageHandlerIsTestable();\n        parent::setUp();\n        $this->prepareTestDirectories();\n    }\n\n    public function tearDown(): void\n    {\n        $this->clearTestDirectories();\n        parent::tearDown();\n    }\n\n    public function testProcessFilesWillCopyFile(): void\n    {\n        $tempPath = Path::join($this->someTmpDir, 'tmp.jpg');\n        $product = oxNew(Article::class);\n        $files = [\n            'myfile' => [\n                'tmp_name' => [\n                    0 => $tempPath,\n                ],\n                'name' => [\n                    0 => $this->testFile,\n                ],\n            ],\n        ];\n        (new Filesystem())->touch($tempPath);\n\n        (new UtilsFile())->processFiles($product, $files, true);\n\n        $this->assertFileExists($this->testFileDestination);\n    }\n\n    public function testProcessFilesWithSuperGlobals(): void\n    {\n        $tempPath = Path::join($this->someTmpDir, 'tmp.jpg');\n        $_FILES['myfile'] = [\n            'name' => [\n                0 => $this->testFile,\n            ],\n            'tmp_name' => [\n                0 => $tempPath,\n            ],\n            'error' => [\n                0 => 0,\n            ],\n        ];\n        (new Filesystem())->touch($tempPath);\n\n        (new UtilsFile())->processFiles(null, null, true);\n\n        $this->assertFileExists($this->testFileDestination);\n    }\n\n    public function testProcessFilesWithCreateDuplicateName(): void\n    {\n        $tempPath = Path::join($this->someTmpDir, 'tmp.jpg');\n        $product = oxNew(Article::class);\n        $files = [\n            'myfile' => [\n                'tmp_name' => [\n                    0 => $tempPath,\n                ],\n                'name' => [\n                    0 => $this->testFile,\n                ],\n            ],\n        ];\n        (new Filesystem())->touch($tempPath);\n\n        (new UtilsFile())->processFiles($product, $files, true);\n        (new UtilsFile())->processFiles($product, $files, true);\n\n        $this->assertFileExists($this->testFileDestination);\n        $this->assertFileExists($this->testFileDuplicateDestination);\n    }\n\n    public function testProcessFilesWillOverwrite(): void\n    {\n        $tempPath1 = Path::join($this->someTmpDir, 'tmp.jpg');\n        $tempPath2 = Path::join($this->someTmpDir, 'tmp2.jpg');\n        $product = oxNew(Article::class);\n        $files = [\n            'myfile' => [\n                'tmp_name' => [\n                    0 => $tempPath1,\n                ],\n                'name' => [\n                    0 => $this->testFile,\n                ],\n            ],\n        ];\n        $files2 = [\n            'myfile' => [\n                'tmp_name' => [\n                    0 => $tempPath2,\n                ],\n                'name' => [\n                    0 => $this->testFile,\n                ],\n            ],\n        ];\n        $utilsFile = new UtilsFile();\n        (new Filesystem())->dumpFile($tempPath1, 'abc');\n        (new Filesystem())->dumpFile($tempPath2, 'xyz');\n\n        $utilsFile->processFiles($product, $files, true);\n        $utilsFile->processFiles($product, $files2, true, false);\n\n        $this->assertFileDoesNotExist($this->testFileDuplicateDestination);\n        $this->assertSame('xyz', file_get_contents($this->testFileDestination));\n        $this->assertSame(1, $utilsFile->getNewFilesCounter());\n    }\n\n    public function testGetNewFilesCounter(): void\n    {\n        $tempPath1 = Path::join($this->someTmpDir, 'tmp.jpg');\n        $tempPath2 = Path::join($this->someTmpDir, 'tmp2.jpg');\n        $product = oxNew(Article::class);\n        $files = [\n            'myfile' => [\n                'tmp_name' => [\n                    0 => $tempPath1,\n                    1 => $tempPath2,\n                ],\n                'name' => [\n                    0 => $this->testFile,\n                    1 => $this->testFile,\n                ],\n            ],\n        ];\n        (new Filesystem())->touch($tempPath1);\n        (new Filesystem())->touch($tempPath2);\n        $utilsFile = new UtilsFile();\n\n        $utilsFile->processFiles($product, $files, true);\n\n        $this->assertSame(2, $utilsFile->getNewFilesCounter());\n    }\n\n    public function testProcessFilesSetsObjectValue(): void\n    {\n        $tempPath = Path::join($this->someTmpDir, 'tmp.jpg');\n        $product = oxNew(Article::class);\n        $product->oxarticles__oxfile = new Field();\n        $files = [\n            'myfile' => [\n                'tmp_name' => [\n                    'something@oxarticles__oxfile' => $tempPath,\n                ],\n                'name' => [\n                    'something@oxarticles__oxfile' => $this->testFile,\n                ],\n            ],\n        ];\n        (new Filesystem())->touch($tempPath);\n\n        (new UtilsFile())->processFiles($product, $files, true);\n\n        $this->assertSame($this->testFile, $product->oxarticles__oxfile->getRawValue());\n    }\n\n    private function prepareTestDirectories(): void\n    {\n        $this->someTmpDir = ContainerFacade::getParameter('oxid_esales.build_directory');\n        $pictureDir = Registry::getConfig()->getPictureDir(false);\n        $uniqueFilename = uniqid('some_image_', true);\n        $this->testFile = sprintf('%s.jpg', $uniqueFilename);\n        $testFileDuplicate = sprintf('%s(1).jpg', $uniqueFilename);\n        $this->testFileDestination = Path::join($pictureDir, '0', $this->testFile);\n        $this->testFileDuplicateDestination = Path::join($pictureDir, '0', $testFileDuplicate);\n    }\n\n    private function clearTestDirectories(): void\n    {\n        (new Filesystem())->remove($this->testFileDestination);\n        (new Filesystem())->remove($this->testFileDuplicateDestination);\n    }\n\n    private function checkImageHandlerIsTestable(): void\n    {\n        if ($this->get(ImageHandlerInterface::class)::class !== LocalImageHandler::class) {\n            $this->markTestSkipped('This test runs only when local filesystem is used for image storage.');\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Core/WidgetControlTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Core;\n\nuse OxidEsales\\Eshop\\Application\\Controller\\SearchController;\nuse OxidEsales\\Eshop\\Core\\Exception\\ObjectException;\nuse OxidEsales\\Eshop\\Core\\Routing\\ControllerClassNameResolver;\nuse OxidEsales\\Eshop\\Core\\WidgetControl;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class WidgetControlTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->setParameter('oxid_esales.debug_mode', true);\n        $this->replaceContainerInstance();\n        $_SERVER['REQUEST_METHOD'] = 'POST';\n    }\n\n    public function testIfDoesNotAllowToInitiateNonWidgetClass(): void\n    {\n        $nonWidgetClass = (new ControllerClassNameResolver())\n            ->getIdByClassName(SearchController::class);\n\n        $this->expectException(ObjectException::class);\n\n        oxNew(WidgetControl::class)\n            ->start($nonWidgetClass);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Encryptor/EncryptationTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Encryptor;\n\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nfinal class EncryptationTest extends IntegrationTestCase\n{\n    public static function providerEncodingAndDecodingGivesSameResultWithCorrectKey(): array\n    {\n        return [['testString', ''],\n            ['testString', 1],\n            ['testString', 'shortKey'],\n            ['testString', 'longKeyLongKey_LongKeyLongKey'],\n            ['', 'testKey'],\n        ];\n    }\n\n    #[DataProvider('providerEncodingAndDecodingGivesSameResultWithCorrectKey')]\n    public function testEncodingAndDecodingGivesSameResultWithCorrectKey(string $sString, string|int $sKey): void\n    {\n        $oEncryptor = oxNew('oxEncryptor');\n        $oDecryptor = oxNew('oxDecryptor');\n\n        $sEncrypted = $oEncryptor->encrypt($sString, $sKey);\n        $this->assertSame($sString, $oDecryptor->decrypt($sEncrypted, $sKey));\n    }\n\n    public function testEncodingAndDecodingGivesDifferentResultWithIncorrectKey(): void\n    {\n        $oEncryptor = oxNew('oxEncryptor');\n        $oDecryptor = oxNew('oxDecryptor');\n\n        $sEncrypted = $oEncryptor->encrypt('testString', 'correctKey');\n        $this->assertNotSame('testString', $oDecryptor->decrypt($sEncrypted, 'incorrectKey'));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/ModuleInheritanceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules;\n\nuse OxidEsales\\Eshop\\Application\\Controller\\ContentController as EshopContentController;\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\ContainerFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\ShopCacheCleanerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Exception\\InvalidClassExtensionNamespaceException;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\ShopAdapter;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\ShopAdapterInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\module_chain_extension_3_1\\vendor_1_module_3_1_myclass;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\module_native_extension\\ContentController as ModuleContentController;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\module_native_extension\\NativeExtendingArticle;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\module_native_extension\\NativeExtendingContentController;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor1\\ModuleChainExtension36\\MyClass36;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor1\\ModuleInheritance28a\\MyClass;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor1\\namespaced_from_ns\\MyClass as namespaced_from_ns;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor1\\own_namespace_extending_unified_namespace\\MyClass as own_namespace_extending_unified_namespace;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor2\\ModuleChainExtension44\\MyClass44;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor2\\ModuleInheritance24\\MyClass as ModuleInheritance24MyClass;\nuse OxidEsales\\EshopCommunity\\Tests\\TestContainerFactory;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Psr\\Container\\ContainerInterface;\nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder;\nuse function Symfony\\Component\\String\\u;\n\n/**\n * Test, that the inheritance of modules and the shop works as expected.\n *\n * Below, there are listed all possible combinations which are possible. You have to read the tables as follows:\n * E.g. Test Case 1.1 is: A \"plain module class\" \"extends via PHP\" a \"Plain shop class\"\n *\n *\n * 1. Simple extending shop classes in modules\n * +-------------------------------+--------------------+-------------------------+---------------------------------+\n * |        extends via PHP        | plain module class | namespaced module class | unified namespaced module class |\n * +-------------------------------+--------------------+-------------------------+---------------------------------+\n * | Plain shop class              |                1.1 |                     1.6 | not planned                     |\n * | Namespaced shop class         |                1.2 |                     1.7 | not planned                     |\n * | unified namespaced shop class |                1.5 |                    1.10 | not planned                     |\n * +-------------------------------+--------------------+-------------------------+---------------------------------+\n *\n *\n *\n * 2. Simple extending module classes from other modules\n * +--------------------------------------------------------------+--------------------+-------------------------+\n * |                       extends via PHP                        | plain module class | namespaced module class |\n * +--------------------------------------------------------------+--------------------+-------------------------+\n * | plain module class which extends another class               |                2.1 |                     2.3 |\n * | namespaced module class which extends another class          |                2.2 |                     2.4 |\n * | plain module class which chain extends a shop class          |                2.5 |                     2.7 |\n * | namespaced module class which does not extend another class  |                2.6 |                     2.8 |\n * +--------------------------------------------------------------+--------------------+-------------------------+\n *\n * Together with \"2. Simple extending module classes from other modules\" we implemented some other test cases.\n * These test cases should be already covered by the test cases in table 1 and 3.\n * If you remove these unnecessary test cases, there should be only 4 test cases left:\n * +--------------------------+--------------------+-------------------------+\n * |     extends via PHP      | plain module class | namespaced module class |\n * +--------------------------+--------------------+-------------------------+\n * | plain module class       |                    |                         |\n * | namespaced module class  |                    |                         |\n * +--------------------------+--------------------+-------------------------+\n *\n *\n *\n *  3. Chain extending shop classes in modules\n * +-------------------------------+--------------------+-------------------------+\n * |       extends via chain       | plain module class | namespaced module class |\n * +-------------------------------+--------------------+-------------------------+\n * | Plain shop class              | 3.1                | 3.4                     |\n * | Namespaced shop class         | 3.2                | 3.5                     |\n * | unified namespaced shop class | 3.3                | 3.6                     |\n * +-------------------------------+--------------------+-------------------------+\n *\n *\n *\n * 4. Chain extending module classes from other modules\n * +-------------------------+--------------------+-------------------------+\n * |    extends via chain    | plain module class | namespaced module class |\n * +-------------------------+--------------------+-------------------------+\n * | plain module class      |                4.1 |                     4.3 |\n * | namespaced module class |                4.2 |                     4.4 |\n * +-------------------------+--------------------+-------------------------+\n */\nfinal class ModuleInheritanceTest extends IntegrationTestCase\n{\n    private ContainerInterface $container;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->container = ContainerFactory::getInstance()->getContainer();\n    }\n\n    public function tearDown(): void\n    {\n        $this->container->get(ShopCacheCleanerInterface::class)->clear(1);\n\n        parent::tearDown();\n    }\n\n    /**\n     * This test covers the PHP inheritance between one module class and one shop class.\n     *\n     * The module class extends the PHP class directly like '<module class> extends <shop class>'.\n     * In this case the parent class of the module class must be the shop class as instantiated with oxNew.\n     *\n     * @param array $moduleToActivate The module we want to activate.\n     * @param string $moduleClassName The module class we want to instantiate.\n     * @param array $shopClassNames The shop class from which the module class should inherit.\n     */\n    #[DataProvider('dataProviderTestModuleInheritanceTestPhpInheritance')]\n    public function testModuleInheritanceByPhpInheritance(\n        array $moduleToActivate,\n        string $moduleClassName,\n        array $shopClassNames\n    ): void {\n        $this->installModules($moduleToActivate);\n        $this->activateModules($moduleToActivate);\n\n        $this->assertClassInheritance($moduleClassName, $shopClassNames);\n    }\n\n    /**\n     * It is forbidden to directly extend shop classes from edition namespaces.\n     * Shop checks this during module activation and prevents by throwing an error.\n     *\n     * This test covers the PHP inheritance between one module class and one shop class.\n     *\n     * The module class extends the PHP class directly like '<module class> extends <shop class>'.\n     * In this case the parent class of the module class must be the shop class as instantiated with oxNew.\n     *\n     * @param array $moduleToActivate The module we want to activate.\n     */\n    #[DataProvider('dataProviderTestModuleInheritanceTestPhpInheritanceForbidden')]\n    public function testModuleInheritanceTestPhpInheritanceForbidden(\n        array $moduleToActivate\n    ): void {\n        $this->installModules($moduleToActivate);\n\n        $this->expectException(InvalidClassExtensionNamespaceException::class);\n\n        $this->activateModules($moduleToActivate);\n    }\n\n    /**\n     * This test covers PHP inheritance between module classes.\n     *\n     * The tested module class extends the other module class directly\n     * like '<module anotherclass> extends <module class>'\n     * or '<moduleA class> extends <moduleB class>'\n     * In this case the parent class of the module class must be the parent module class as instantiated with oxNew\n     *\n     * @param array $modulesToActivate The modules we want to activate.\n     * @param string $moduleClassName The module class we want to instantiate.\n     * @param array $shopClassNames The shop class from which the module class should inherit.\n     */\n    #[DataProvider('dataProviderTestMultiModuleInheritanceTestPhpInheritance')]\n    public function testMultiModuleInheritanceTestPhpInheritance(\n        array $modulesToActivate,\n        string $moduleClassName,\n        array $shopClassNames\n    ): void {\n        $container = (new TestContainerFactory())->create();\n        $container = $this->disableShopEditionClassExtensionProtection($container);\n        $container->compile();\n\n        $container->get('oxid_esales.module.install.service.launched_shop_project_configuration_generator')\n            ->generate();\n\n        $this->container = $container;\n\n        $this->installModules($modulesToActivate);\n        $this->activateModules($modulesToActivate);\n\n        $this->assertClassInheritance($moduleClassName, $shopClassNames);\n    }\n\n    /**\n     * This test covers loading chain of a classes which are extended by another class with native php \"extends\".\n     */\n    #[DataProvider('dataProviderTestNativeExtensionOfChainExtendingClass')]\n    public function testNativeExtensionOfChainExtendingClass(\n        array $moduleToActivate,\n        string $extensionClass,\n        string $classToExtend\n    ): void {\n        $this->installModules($moduleToActivate);\n        $this->activateModules($moduleToActivate);\n\n        $this->assertClassInheritance($extensionClass, [$classToExtend]);\n    }\n\n    public static function dataProviderTestNativeExtensionOfChainExtendingClass(): array\n    {\n        $modules = ['module_chain_extension_3_1', 'module_native_extension'];\n\n        return [\n            [\n                'moduleToActivate' => $modules,\n                'extensionClass' => ModuleContentController::class,\n                'classToExtend' => EshopContentController::class,\n            ],\n            [\n                'moduleToActivate' => $modules,\n                'extensionClass' => NativeExtendingContentController::class,\n                'classToExtend' => ModuleContentController::class,\n            ],\n            [\n                'moduleToActivate' => $modules,\n                'extensionClass' => TestDataInheritance\\modules\\module_native_extension\\Article::class,\n                'classToExtend' => Article::class,\n            ],\n            [\n                'moduleToActivate' => $modules,\n                'extensionClass' => NativeExtendingArticle::class,\n                'classToExtend' => TestDataInheritance\\modules\\module_native_extension\\Article::class,\n            ],\n            [\n                'moduleToActivate' => $modules,\n                'extensionClass' => NativeExtendingArticle::class,\n                'classToExtend' => vendor_1_module_3_1_myclass::class,\n            ],\n        ];\n    }\n\n    public static function dataProviderTestModuleInheritanceTestPhpInheritance(): array\n    {\n        return [\n            'case_1_6' => [\n                'moduleToActivate' => ['Vendor1/ModuleInheritance16'],\n                'moduleClassName' => TestDataInheritance\\modules\\Vendor1\\ModuleInheritance16\\MyClass::class,\n                'shopClassNames' => [\\OxidEsales\\EshopCommunity\\Application\\Model\\Article::class, 'oxArticle'],\n            ],\n            'case_1_7' => [\n                'moduleToActivate' => ['Vendor1/namespaced_from_ns'],\n                'moduleClassName' => namespaced_from_ns::class,\n                'shopClassNames' => [\\OxidEsales\\EshopCommunity\\Application\\Model\\Article::class],\n            ],\n            'case_1_10' => [\n                'moduleToActivate' => ['Vendor1/own_namespace_extending_unified_namespace'],\n                'moduleClassName' => own_namespace_extending_unified_namespace::class,\n                'shopClassNames' => [Article::class],\n            ],\n            'case_3_1' => [\n                'moduleToActivate' => ['module_chain_extension_3_1'],\n                'moduleClassName' => vendor_1_module_3_1_myclass::class,\n                'shopClassNames' => ['oxArticle'],\n            ],\n            'case_3_6' => [\n                'moduleToActivate' => ['Vendor1/ModuleChainExtension36'],\n                'moduleClassName' => MyClass36::class,\n                'shopClassNames' => [Article::class],\n            ],\n        ];\n    }\n\n    /**\n     * Please have a look at the comment of this class for the different test cases.\n     *\n     * @return array The different test cases we execute.\n     */\n    public static function dataProviderTestModuleInheritanceTestPhpInheritanceForbidden(): array\n    {\n        return [\n            'case_3_2' => [\n                //Test case 3.2 plain module chain extends namespaced OXID eShop Community class\n                'moduleToActivate' => ['module_chain_extension_3_2'],],\n            'case_3_5' => [\n                // Test case 3.5 namespaced module class chain extends namespaced OXID eShop Community class\n                'moduleToActivate' => ['Vendor1/ModuleChainExtension35'],\n            ],\n        ];\n    }\n\n    /**\n     * Please have a look at the comment of this class for the different test cases.\n     *\n     * @return array The different test cases we execute.\n     */\n    public static function dataProviderTestMultiModuleInheritanceTestPhpInheritance(): array\n    {\n        return [\n            'case_2_4' => [\n                // Test case 2.4 namespaced module class extends an other modules extended namespaced module class\n                'modulesToActivate' => ['Vendor1/namespaced_from_ns', 'Vendor2/ModuleInheritance24'],\n                'moduleClassName' => ModuleInheritance24MyClass::class,\n                'shopClassNames' => [\n                    namespaced_from_ns::class,\n                    \\OxidEsales\\EshopCommunity\\Application\\Model\\Article::class,\n                ],\n            ],\n            'case_2_8' => [\n                // Test case 2.8 namespaced module_2 extends namespaced module_1\n                'modulesToActivate' => ['Vendor1/ModuleInheritance28a', 'Vendor2/ModuleInheritance28b'],\n                'moduleClassName' => TestDataInheritance\\modules\\Vendor2\\ModuleInheritance28b\\MyClass::class,\n                'shopClassNames' => [\n                    MyClass::class,\n                ],\n            ],\n            'case_4_4' => [\n                // Test case 4.4 namespaced module class chain extends other namespaced module class\n                'modulesToActivate' => ['Vendor1/ModuleChainExtension44', 'Vendor2/ModuleChainExtension44'],\n                'moduleClassName' => MyClass44::class,\n                'shopClassNames' => [\n                    TestDataInheritance\\modules\\Vendor1\\ModuleChainExtension44\\MyClass44::class,\n                ],\n            ],\n        ];\n    }\n\n    private function installModules(array $modulesToActivate): void\n    {\n        $installService = $this->container->get(ModuleInstallerInterface::class);\n\n        foreach ($modulesToActivate as $modulePath) {\n            $package = new OxidEshopPackage(__DIR__ . '/TestDataInheritance/modules/' . $modulePath);\n            $installService->install($package);\n        }\n    }\n\n    private function activateModules(array $modulesToActivate): void\n    {\n        $activationService = $this->container->get(ModuleActivationBridgeInterface::class);\n\n        foreach ($modulesToActivate as $moduleId) {\n            $moduleId = u($moduleId)\n                ->replace('/', '_');\n            $activationService->activate((string)$moduleId, 1);\n        }\n    }\n\n    private function disableShopEditionClassExtensionProtection(ContainerBuilder $containerBuilder): ContainerBuilder\n    {\n        $shopAdapter = $this\n            ->createStub(ShopAdapter::class);\n\n        $shopAdapter->method('isShopEditionNamespace')\n            ->willReturn(false);\n\n        $containerBuilder->set(ShopAdapterInterface::class, $shopAdapter);\n        $containerBuilder->autowire(ShopAdapterInterface::class, ShopAdapter::class);\n\n        return $containerBuilder;\n    }\n\n    private function assertClassInheritance(string $moduleClassName, $shopClassNames): void\n    {\n        $model = oxNew($moduleClassName);\n\n        foreach ($shopClassNames as $shopClassName) {\n            $this->assertTrue(\n                is_subclass_of($model, $shopClassName),\n                'Expected, that object of type \"' . $model::class . '\" is subclass of \"' . $shopClassName . '\"!'\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/ModuleTranslationsTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules;\n\nuse OxidEsales\\Eshop\\Core\\Language;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\ContainerFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ModuleTranslationsTest extends IntegrationTestCase\n{\n    public function testTranslation(): void\n    {\n        $this->get(ModuleInstallerInterface::class)\n            ->install(\n                new OxidEshopPackage(__DIR__ . '/TestData/modules/translation_Application')\n            );\n        $this->get(ModuleActivationBridgeInterface::class)\n            ->activate('translation_Application', 1);\n\n        Registry::set(Language::class, null);\n\n        $translatedGerman = Registry::getLang()->translateString('BIRTHDATE', 0);\n        $translatedEnglish = Registry::getLang()->translateString('BIRTHDATE', 1);\n\n        $this->assertEquals('MODUL: Geburtsdatum', $translatedGerman);\n        $this->assertEquals('MODULE: Date of birth', $translatedEnglish);\n    }\n\n    public function get(string $serviceId)\n    {\n        return ContainerFactory::getInstance()->getContainer()->get($serviceId);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestData/modules/extending_1_class/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\EshopCommunity\\Application\\Controller\\FrontendController;\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'extending_1_class',\n    'title' => 'Test extending 1 shop class',\n    'description' => 'Module testing extending 1 shop class',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n    'extend' => [\n        'oxorder' => 'oeTest/extending_1_class/myorder',\n    ],\n    'controllers' => [\n        FrontendController::class => 'oeTest/controller_1_class/myFrontendController',\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestData/modules/extending_1_class/myorder.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nclass myOrder\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestData/modules/extending_1_class_3_extensions/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'extending_1_class_3_extensions',\n    'title' => 'Test extending 1 shop class with 3 extensions',\n    'description' => 'Module testing extending 1 shop class with 3 extensions',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n    'extend' => [\n        'oxorder' => 'oeTest/extending_1_class_3_extensions/myorder1',\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestData/modules/translation_Application/Application/translations/de/_lang.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sLangName = 'Deutsch';\n\n$aLang = [\n    'BIRTHDATE' => 'MODUL: Geburtsdatum',\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestData/modules/translation_Application/Application/translations/en/_lang.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sLangName = 'English';\n\n$aLang = [\n    'BIRTHDATE' => 'MODULE: Date of birth',\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestData/modules/translation_Application/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'translation_Application',\n    'title' => 'Translations in Application folder',\n    'description' => 'In this module the translations lay in the Application folder.',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n    'extend' => [\n        // This one is needed, cause if the module is not extending anything, we don't search for the translations!\n        'oxarticle' => 'translation_Application/myarticle',\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestData/modules/translation_Application/myarticle.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nclass myarticle\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestData/modules/with_everything/Event/MyEvents.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestData\\modules\\with_everything\\Event;\n\nuse oxRegistry;\n\nclass MyEvents\n{\n    public static function onActivate(): void\n    {\n        $oConfig = oxRegistry::getConfig();\n        $oConfig->setConfigParam('sTestActivateEvent', 'Activate');\n    }\n\n    public static function onDeactivate(): void\n    {\n        $oConfig = oxRegistry::getConfig();\n        $oConfig->setConfigParam('sTestDeactivateEvent', 'Deactivate');\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestData/modules/with_everything/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Application\\Model\\Order;\nuse OxidEsales\\Eshop\\Application\\Model\\User;\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'with_everything',\n    'title' => 'Test extending 1 shop class',\n    'description' => 'Module testing extending 1 shop class',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n    'extend' => [\n        Article::class => 'with_everything/myarticle',\n        User::class => 'with_everything/myuser',\n        Order::class => 'with_everything/myorder1',\n    ],\n    'blocks' => [[\n        'template' => 'page/checkout/basket.tpl',\n        'block' => 'basket_btn_next_top',\n        'file' => '/views/blocks/page/checkout/myexpresscheckout.tpl',\n    ], [\n        'template' => 'page/checkout/payment.tpl',\n        'block' => 'select_payment',\n        'file' => '/views/blocks/page/checkout/mypaymentselector.tpl',\n    ]],\n    'events' => [\n        'onActivate' => 'OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestData\\modules\\with_everything\\Event\\MyEvents::onActivate',\n        'onDeactivate' => 'OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestData\\modules\\with_everything\\Event\\MyEvents::onDeactivate',\n    ],\n    'templates' => [\n        'order_special.tpl' => 'with_everything/views/admin/tpl/order_special.tpl',\n        'user_connections.tpl' => 'with_everything/views/tpl/user_connections.tpl',\n    ],\n    'settings' => [[\n        'group' => 'my_checkconfirm',\n        'name' => 'blCheckConfirm',\n        'type' => 'bool',\n        'value' => 'true',\n    ], [\n        'group' => 'my_displayname',\n        'name' => 'sDisplayName',\n        'type' => 'str',\n        'value' => 'Some name',\n    ]],\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/Vendor1/ModuleChainExtension35/MyClass35.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor1\\ModuleChainExtension35;\n\nclass MyClass35 extends MyClass35_parent\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/Vendor1/ModuleChainExtension35/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\EshopCommunity\\Application\\Model\\Article;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor1\\ModuleChainExtension35\\MyClass35;\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'Vendor1_ModuleChainExtension35',\n    'title' => 'Test OXID eShop class module chain extension 3.5',\n    'description' => 'The module class and the chain extended OXID eShop class life in their namespaces.',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n    'extend' => [\n        Article::class => MyClass35::class,\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/Vendor1/ModuleChainExtension36/MyClass36.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor1\\ModuleChainExtension36;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\n\nclass MyClass36 extends Article\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/Vendor1/ModuleChainExtension36/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor1\\ModuleChainExtension36\\MyClass36;\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'Vendor1_ModuleChainExtension36',\n    'title' => 'Test OXID eShop class module chain extension 3.6',\n    'description' => 'The module class and the chain extended OXID eShop class life in their namespaces. The OXID eShop class is from the unified namespace.',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n    'extend' => [\n        Article::class => MyClass36::class,\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/Vendor1/ModuleChainExtension44/MyClass44.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor1\\ModuleChainExtension44;\n\nclass MyClass44\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/Vendor1/ModuleChainExtension44/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'Vendor1_ModuleChainExtension44',\n    'title' => 'Test OXID eShop class module chain extension 4.4',\n    'description' => 'The module class and the chain extended OXID eShop class life in their namespaces.',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/Vendor1/ModuleInheritance16/MyClass.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor1\\ModuleInheritance16;\n\nuse oxArticle;\n\nclass MyClass extends oxArticle\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/Vendor1/ModuleInheritance16/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'Vendor1_ModuleInheritance16',\n    // maybe find a better name for that\n    'title' => 'Test PHP class inheritance 1.6',\n    'description' => 'Namespace module class extends old oxclass',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/Vendor1/ModuleInheritance28a/MyClass.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor1\\ModuleInheritance28a;\n\nclass MyClass\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/Vendor1/ModuleInheritance28a/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'Vendor1_ModuleInheritance28a',\n    'title' => 'Test PHP class inheritance 2.8',\n    'description' => 'Namespaced module class extends namespaced module',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/Vendor1/namespaced_from_ns/MyClass.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor1\\namespaced_from_ns;\n\nuse OxidEsales\\EshopCommunity\\Application\\Model\\Article;\n\nclass MyClass extends Article\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/Vendor1/namespaced_from_ns/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'Vendor1_namespaced_from_ns',\n    // maybe find a better name for that\n    'title' => 'Test case 1.7: a namespaced module class inherits from the shop namespace',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/Vendor1/own_namespace_extending_unified_namespace/MyClass.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor1\\own_namespace_extending_unified_namespace;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\n\nclass MyClass extends Article\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/Vendor1/own_namespace_extending_unified_namespace/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'Vendor1_own_namespace_extending_unified_namespace',\n    'title' => 'Test case 1.10: a namespaced module class inherits from the unified shop namespace',\n    'description' => 'Both module class and shop class use the namespaces, but the shop class is from the unified shop namespace.',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/Vendor2/ModuleChainExtension44/MyClass44.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor2\\ModuleChainExtension44;\n\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor1\\ModuleChainExtension44\\MyClass44 as ModuleChainExtension44MyClass44;\n\nclass MyClass44 extends ModuleChainExtension44MyClass44\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/Vendor2/ModuleChainExtension44/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor1\\ModuleChainExtension44\\MyClass44;\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'Vendor2_ModuleChainExtension44',\n    'title' => 'Test OXID eShop class module chain extension 4.4',\n    'description' => 'The module class and the chain extended OXID eShop class life in their namespaces.',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n    'extend' => [\n        MyClass44::class\n        => \\OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor2\\ModuleChainExtension44\\MyClass44::class,\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/Vendor2/ModuleInheritance24/MyClass.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor2\\ModuleInheritance24;\n\nclass MyClass extends \\OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor1\\namespaced_from_ns\\MyClass\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/Vendor2/ModuleInheritance24/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'Vendor2_ModuleInheritance24',\n    // maybe find a better name for that\n    'title' => 'Test PHP class inheritance 2.4',\n    'description' => 'Namespace module class extends other module namespace class which extends from shop namespace class',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/Vendor2/ModuleInheritance28b/MyClass.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor2\\ModuleInheritance28b;\n\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\Vendor1\\ModuleInheritance28a\\MyClass as AnotherMyClass;\n\nclass MyClass extends AnotherMyClass\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/Vendor2/ModuleInheritance28b/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'Vendor2_ModuleInheritance28b',\n    'title' => 'Test PHP class inheritance 2.8',\n    'description' => 'Namespaced module class extends namespaced module',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/module_chain_extension_3_1/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\module_chain_extension_3_1\\vendor_1_module_3_1_myclass;\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'module_chain_extension_3_1',\n    'title' => 'Test OXID eShop class module chain extension 3.1',\n    'description' => 'Both module class and shop class use the old notation without namespaces',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n    'extend' => [\n        'oxarticle' => vendor_1_module_3_1_myclass::class,\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/module_chain_extension_3_1/vendor_1_module_3_1_myclass.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\module_chain_extension_3_1;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\n\nclass vendor_1_module_3_1_myclass extends Article\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/module_chain_extension_3_2/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\EshopCommunity\\Application\\Model\\Article;\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'module_chain_extension_3_2',\n    'title' => 'Test OXID eShop class module chain extension 3.2',\n    'description' => 'The module class has no namespace and chain extends a namespaced OXID eShop class',\n    'thumbnail' => 'picture.png',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n    'extend' => [\n        Article::class => 'module_chain_extension_3_2/vendor_1_module_3_2_myclass',\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/module_chain_extension_3_2/vendor_1_module_3_2_myclass.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nclass vendor_1_module_3_2_myclass extends vendor_1_module_3_2_myclass_parent\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/module_native_extension/Article.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\module_native_extension;\n\nclass Article extends Article_parent\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/module_native_extension/ContentController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\module_native_extension;\n\nclass ContentController extends ContentController_parent\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/module_native_extension/NativeExtendingArticle.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\module_native_extension;\n\nclass NativeExtendingArticle extends Article\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/module_native_extension/NativeExtendingContentController.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\module_native_extension;\n\nclass NativeExtendingContentController extends ContentController\n{\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Modules/TestDataInheritance/modules/module_native_extension/metadata.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\Eshop\\Application\\Controller\\ContentController as EshopContentController;\nuse OxidEsales\\Eshop\\Application\\Model\\Article as EshopArticleModel;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\module_native_extension\\Article as ModuleArticleModel;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\module_native_extension\\ContentController as ModuleContentController;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Modules\\TestDataInheritance\\modules\\module_native_extension\\NativeExtendingContentController;\n\n$sMetadataVersion = '2.0';\n$aModule = [\n    'id' => 'module_native_extension',\n    'title' => 'Test OXID eShop native extend of chain extended class',\n    'version' => '1.0',\n    'author' => 'OXID eSales AG',\n    'extend' => [\n        EshopContentController::class => ModuleContentController::class,\n        EshopArticleModel::class => ModuleArticleModel::class,\n    ],\n    'controllers' => [\n        'nativeinheritedcontentcontroller' => NativeExtendingContentController::class,\n    ],\n];\n"
  },
  {
    "path": "tests/Integration/Legacy/Multilanguage/AdditionalTablesTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Multilanguage;\n\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\DbMetaDataHandler;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\DatabaseTrait;\nuse PHPUnit\\Framework\\Attributes\\Group;\nuse PHPUnit\\Framework\\TestCase;\n\n#[Group('triggers-implicit-transaction-commit')]\nfinal class AdditionalTablesTest extends TestCase\n{\n    use DatabaseTrait;\n    use MultilanguageTrait;\n    use ContainerTrait;\n\n    public function tearDown(): void\n    {\n        $this->setupShopDatabase();\n\n        parent::tearDown();\n    }\n\n    public function testCreateLanguagesAfterAdditionalTable(): void\n    {\n        $this->createMultilanguageTable();\n        $languageId = $this->createLanguages();\n\n        $this->insertTestData($languageId);\n\n        $this->assertEquals('addtest_set1', $this->getTableName());\n        $this->assertEquals('latin1_general_ci', $this->getTableCollation());\n        $this->assertEquals('latin1', $this->getColumnCharset());\n        $this->assertEquals('some additional title', $this->getTitleInLanguage($languageId));\n        $this->assertEquals('some default title', $this->getTitleInLanguage(0));\n    }\n\n    public function testCreateAdditionalTableAfterCreatingLanguages(): void\n    {\n        $languageId = $this->createLanguages();\n        $this->createMultilanguageTable();\n        oxNew(DbMetaDataHandler::class)->updateViews();\n\n        $this->insertTestData($languageId);\n\n        $this->assertEquals('addtest_set1', $this->getTableName());\n        $this->assertEquals('latin1_general_ci', $this->getTableCollation());\n        $this->assertEquals('latin1', $this->getColumnCharset());\n        $this->assertSame('some additional title', $this->getTitleInLanguage($languageId));\n        $this->assertSame('some default title', $this->getTitleInLanguage(0));\n    }\n\n    private function createMultilanguageTable(): void\n    {\n        $sql = 'CREATE TABLE `addtest` (' .\n            \"`OXID` char(32) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL COMMENT 'Item id',\" .\n            \"`TITLE` varchar(128) NOT NULL DEFAULT '' COMMENT 'Title (multilanguage)',\" .\n            \"`TITLE_1` varchar(128) NOT NULL DEFAULT '',\" .\n            \"`TITLE_2` varchar(128) NOT NULL DEFAULT '',\" .\n            \"`TITLE_3` varchar(128) NOT NULL DEFAULT '',\" .\n            \"`TITLE_4` varchar(128) NOT NULL DEFAULT '',\" .\n            \"`TITLE_5` varchar(128) NOT NULL DEFAULT '',\" .\n            \"`TITLE_6` varchar(128) NOT NULL DEFAULT '',\" .\n            \"`TITLE_7` varchar(128) NOT NULL DEFAULT '',\" .\n            'PRIMARY KEY (`OXID`)' .\n            \") ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='for testing'\";\n\n        DatabaseProvider::getDb()->execute($sql);\n\n        $this->setParameter('oxid_esales.multilingual_tables', ['addtest']);\n    }\n\n    private function insertTestData(int $languageId): void\n    {\n        DatabaseProvider::getDb()\n            ->execute(\n                \"INSERT INTO addtest (OXID, TITLE) VALUES ('_test101', 'some default title')\"\n            );\n        DatabaseProvider::getDb()\n            ->execute(\n                \"INSERT INTO addtest_set1 (OXID, TITLE_$languageId) VALUES ('_test101', 'some additional title')\"\n            );\n    }\n\n    private function getTableName(): string\n    {\n        return DatabaseProvider::getDb()\n            ->getOne(\n                \"SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES  WHERE TABLE_NAME = 'addtest_set1'\"\n            );\n    }\n\n    private function getTableCollation(): string\n    {\n        return DatabaseProvider::getDb()\n            ->getOne(\n                \"SELECT TABLE_COLLATION  FROM INFORMATION_SCHEMA.TABLES\n                                    WHERE TABLE_NAME = 'addtest_set1'\"\n            );\n    }\n\n    private function getColumnCharset(): string\n    {\n        return DatabaseProvider::getDb()\n            ->getOne(\n                \"SELECT character_set_name FROM INFORMATION_SCHEMA.`COLUMNS` WHERE table_name = 'addtest_set1'\n                              AND column_name = 'TITLE_8';\"\n            );\n    }\n\n    private function getTitleInLanguage(int $languageId): string\n    {\n        return DatabaseProvider::getDb()->getOne(\n            sprintf(\n                \"SELECT TITLE FROM %s WHERE OXID = '_test101'\",\n                oxNew(TableViewNameGenerator::class)->getViewName('addtest', $languageId)\n            )\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Multilanguage/LanguageMainControllerStub.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Multilanguage;\n\nuse OxidEsales\\Eshop\\Application\\Controller\\Admin\\LanguageMain;\n\nclass LanguageMainControllerStub extends LanguageMain\n{\n    public function setLanguageData($languageData): void\n    {\n        $this->_aLangData = $languageData;\n    }\n\n    public function getLanguages()\n    {\n        return parent::getLanguages();\n    }\n\n    public function getAvailableLangBaseId()\n    {\n        return count($this->_aLangData['params']) - 1;\n    }\n\n    public function checkMultilangFieldsExistsInDb($sOxId)\n    {\n        return parent::checkMultilangFieldsExistsInDb($sOxId);\n    }\n\n    public function addNewMultilangFieldsToDb()\n    {\n        parent::addNewMultilangFieldsToDb();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Multilanguage/MultiLanguageModelTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Multilanguage;\n\nuse OxidEsales\\Eshop\\Core\\DbMetaDataHandler;\nuse OxidEsales\\Eshop\\Core\\Model\\MultiLanguageModel;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\ConnectionFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\Attributes\\Group;\nuse PHPUnit\\Framework\\TestCase;\n\n#[Group('triggers-implicit-transaction-commit')]\nfinal class MultiLanguageModelTest extends TestCase\n{\n    use ContainerTrait;\n\n    private string $testTableName = 'test';\n    private string $testRecordId = 'test_multilang_lowercase_fields';\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        $this->addTableToMultilanguageConfiguration();\n    }\n\n    public function testGetAvailableInLangsReturnsLanguagesWithData(): void\n    {\n        $this->removeTestTable();\n        $this->createTestTableWithUppercaseFields();\n        oxNew(DbMetaDataHandler::class)->updateViews();\n\n        $queryBuilder = $this->get(QueryBuilderFactoryInterface::class)->create();\n        $queryBuilder\n            ->insert($this->testTableName)\n            ->values([\n                'OXID' => ':oxid',\n                'TEST_FIELD' => ':testField',\n                'TEST_FIELD_1' => ':testField1',\n                'TEST_FIELD_2' => ':testField2',\n            ])\n            ->setParameters([\n                'oxid' => $this->testRecordId,\n                'testField' => 'test',\n                'testField1' => 'test_en',\n                'testField2' => '',\n            ])\n            ->executeStatement();\n\n        $multiLanguageModel = oxNew(MultiLanguageModel::class);\n        $multiLanguageModel->init($this->testTableName);\n        $multiLanguageModel->load($this->testRecordId);\n        $availableLanguages = $multiLanguageModel->getAvailableInLangs();\n\n        $this->assertCount(2, $availableLanguages);\n    }\n\n    public function testGetAvailableInLangsWorksWithLowercaseFieldNames(): void\n    {\n        $this->removeTestTable();\n        $this->createTestTableWithLowercaseFields();\n        oxNew(DbMetaDataHandler::class)->updateViews();\n\n        $queryBuilder = $this->get(QueryBuilderFactoryInterface::class)->create();\n        $queryBuilder\n            ->insert($this->testTableName)\n            ->values([\n                'oxid' => ':oxid',\n                'test_field' => ':testField',\n                'test_field_1' => ':testField1',\n            ])\n            ->setParameters([\n                'oxid' => $this->testRecordId,\n                'testField' => 'test',\n                'testField1' => 'test_en',\n            ])\n            ->executeStatement();\n\n        $multiLanguageModel = oxNew(MultiLanguageModel::class);\n        $multiLanguageModel->init($this->testTableName);\n        $multiLanguageModel->load($this->testRecordId);\n        $availableLanguages = $multiLanguageModel->getAvailableInLangs();\n\n        $this->assertCount(2, $availableLanguages);\n    }\n\n    public function testGetAvailableInLangsIgnoresEmptyValues(): void\n    {\n        $this->removeTestTable();\n        $this->createTestTableWithUppercaseFields();\n        oxNew(DbMetaDataHandler::class)->updateViews();\n\n        $queryBuilder = $this->get(QueryBuilderFactoryInterface::class)->create();\n        $queryBuilder\n            ->insert($this->testTableName)\n            ->values([\n                'OXID' => ':oxid',\n                'TEST_FIELD' => ':testField',\n                'TEST_FIELD_1' => ':testField1',\n            ])\n            ->setParameters([\n                'oxid' => $this->testRecordId,\n                'testField' => '',\n                'testField1' => null,\n            ])\n            ->executeStatement();\n\n        $multiLanguageModel = oxNew(MultiLanguageModel::class);\n        $multiLanguageModel->init($this->testTableName);\n        $multiLanguageModel->load($this->testRecordId);\n        $availableLanguages = $multiLanguageModel->getAvailableInLangs();\n\n        $this->assertEmpty($availableLanguages);\n    }\n\n    private function createTestTableWithUppercaseFields(): void\n    {\n        $connection = $this->get(ConnectionFactoryInterface::class)->create();\n\n        $createTableQuery = \"CREATE TABLE IF NOT EXISTS \" . $this->testTableName . \" (\n            OXID char(32) NOT NULL,\n            TEST_FIELD varchar(10),\n            TEST_FIELD_1 varchar(10),\n            TEST_FIELD_2 varchar(10),\n            PRIMARY KEY (OXID)\n        ) ENGINE=InnoDB DEFAULT CHARSET=utf8\";\n\n        $connection->executeStatement($createTableQuery);\n    }\n\n    private function createTestTableWithLowercaseFields(): void\n    {\n        $connection = $this->get(ConnectionFactoryInterface::class)->create();\n\n        $createTableQuery = \"CREATE TABLE IF NOT EXISTS \" . $this->testTableName . \" (\n            oxid char(32) NOT NULL,\n            test_field varchar(10),\n            test_field_1 varchar(10),\n            test_field_2 varchar(10),\n            PRIMARY KEY (oxid)\n        ) ENGINE=InnoDB DEFAULT CHARSET=utf8\";\n\n        $connection->executeStatement($createTableQuery);\n    }\n\n    private function removeTestTable(): void\n    {\n        $connection = $this->get(ConnectionFactoryInterface::class)->create();\n        $connection->executeStatement(\"DROP TABLE IF EXISTS \" . $this->testTableName);\n    }\n\n    private function addTableToMultilanguageConfiguration(): void\n    {\n        $this->setParameter('oxid_esales.multilingual_tables', [$this->testTableName]);\n        $this->replaceContainerInstance();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Multilanguage/MultilanguageTrait.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Multilanguage;\n\nuse OxidEsales\\Eshop\\Application\\Controller\\Admin\\LanguageMain;\nuse OxidEsales\\Eshop\\Core\\Language;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\TableViewNameGenerator;\n\nuse function chr;\n\ntrait MultilanguageTrait\n{\n    private LanguageMain $controller;\n\n    protected function createLanguages(): int\n    {\n        /**\n         * @var $extraLanguagesCount - number of languages to add to start creating additional _set* tables\n         * @see \\OxidEsales\\EshopCommunity\\Core\\DbMetaDataHandler::ensureMultiLanguageFields\n         */\n        $extraLanguagesCount = 9;\n        for ($i = 0; $i < $extraLanguagesCount; $i++) {\n            $languageCode = str_repeat(chr(97 + $i), 2);\n            $languageId = $this->insertLanguage($languageCode);\n        }\n        Registry::set(Language::class, null);\n        Registry::set(TableViewNameGenerator::class, null);\n\n        return $languageId;\n    }\n\n    /**\n     * Use admin controller to mock \"creation of a new language\", the changes in the DB are verified later in tests.\n     * This approach is very slow.\n     */\n    private function insertLanguage(string $languageCode): int\n    {\n        $baseId = $this->getController()->getAvailableLangBaseId();\n        $languages = $this->getController()->getLanguages();\n        $languages['params'][$languageCode] = [\n            'baseId' => $baseId,\n            'active' => 1,\n            'sort' => $baseId * 100,\n        ];\n        $languages['lang'][$languageCode] = $languageCode;\n        $this->getController()->setLanguageData($languages);\n\n        Registry::getConfig()->saveShopConfVar('aarr', 'aLanguageParams', $languages['params']);\n        Registry::getConfig()->saveShopConfVar('aarr', 'aLanguages', $languages['lang']);\n        Registry::getConfig()->saveShopConfVar('arr', 'aLanguageURLs', $languages['urls']);\n        Registry::getConfig()->saveShopConfVar('arr', 'aLanguageSSLURLs', $languages['sslUrls']);\n\n        if (!$this->getController()->checkMultilangFieldsExistsInDb($languageCode)) {\n            $this->getController()->addNewMultilangFieldsToDb();\n        }\n\n        return $baseId;\n    }\n\n    private function getController(): LanguageMain\n    {\n        if (!isset($this->controller)) {\n            $this->controller = new LanguageMainControllerStub();\n            $this->controller->render();\n        }\n        return $this->controller;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Multilanguage/ViewTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Multilanguage;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Tests\\DatabaseTrait;\nuse PHPUnit\\Framework\\Attributes\\Group;\nuse PHPUnit\\Framework\\TestCase;\n\n#[Group('triggers-implicit-transaction-commit')]\nfinal class ViewTest extends TestCase\n{\n    use DatabaseTrait;\n    use MultilanguageTrait;\n\n    private string $productId;\n    private int $originalBaseLanguageId;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->originalBaseLanguageId = Registry::getLang()->getBaseLanguage();\n    }\n\n    public function tearDown(): void\n    {\n        $this->setupShopDatabase();\n\n        parent::tearDown();\n    }\n\n    public function testMultilanguageViewsAddLanguagesAfterAddingProduct(): void\n    {\n        $this->createProduct();\n        $languageId = $this->createLanguages();\n        Registry::getLang()->setBaseLanguage($languageId);\n\n        $product = oxNew(Article::class);\n        $product->setLanguage($languageId);\n        $product->load($this->productId);\n\n        //As we have no data for this language added in table oxarticle_set1, so article title is null.\n        $this->assertNull($product->getFieldData('oxtitle'));\n\n        //Make sure we have the expected value for the base language.\n        //Effect of #6216 was that base language data was wrongly used for language id >= 8 with no way to change this.\n        Registry::getLang()->setBaseLanguage($this->originalBaseLanguageId);\n        $product = oxNew(Article::class);\n        $product->setLanguage($this->originalBaseLanguageId);\n        $product->load($this->productId);\n        $this->assertEquals('TEST_MULTI_LANGUAGE', $product->getFieldData('oxtitle'));\n    }\n\n    public function testMultilanguageViewsAddProductInDifferentDefaultLanguage(): void\n    {\n        $languageId = $this->createLanguages();\n        Registry::getLang()->setBaseLanguage($languageId);\n        $this->createProduct();\n\n        $product = oxNew(Article::class);\n        $product->setLanguage($languageId);\n        $product->load($this->productId);\n\n        //We stored article in switched default language\n        $this->assertEquals('TEST_MULTI_LANGUAGE', $product->getFieldData('oxtitle'));\n\n        //As article was stored in switched base language, related original base language field is empty.\n        Registry::getLang()->setBaseLanguage($this->originalBaseLanguageId);\n        $product = oxNew(Article::class);\n        $product->setLanguage($this->originalBaseLanguageId);\n        $product->load($this->productId);\n        $this->assertEquals('', $product->getFieldData('oxtitle'));\n    }\n\n    private function createProduct(): void\n    {\n        $this->productId = substr_replace((string)Registry::getUtilsObject()->generateUId(), '_', 0, 1);\n\n        $product = oxNew(Article::class);\n        $product->setId($this->productId);\n        $product->oxarticles__oxartnum = new Field('123');\n        $product->oxarticles__oxtitle = new Field('TEST_MULTI_LANGUAGE');\n        $product->save();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/OnlineInfo/CurlSpy.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\OnlineInfo;\n\nuse OxidEsales\\Eshop\\Core\\Curl;\n\nfinal class CurlSpy extends Curl\n{\n    public function __construct(\n        private readonly string $logPath\n    ) {\n    }\n\n    public function getStatusCode(): int\n    {\n        return 200;\n    }\n\n    public function execute(): bool\n    {\n        return true;\n    }\n\n    public function setQuery($query): void\n    {\n        $xmlContent = urldecode(substr($query, strlen('xmlRequest=')));\n\n        file_put_contents($this->logPath, $xmlContent);\n        parent::setQuery($query);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/OnlineInfo/FrontendServersInformationStoringTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\OnlineInfo;\n\nuse OxidEsales\\Eshop\\Core\\Dao\\ApplicationServerDao;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\DataObject\\ApplicationServer;\nuse OxidEsales\\Eshop\\Core\\Service\\ApplicationServerService;\nuse OxidEsales\\Eshop\\Core\\UtilsServer;\nuse OxidEsales\\EshopCommunity\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class FrontendServersInformationStoringTest extends IntegrationTestCase\n{\n    public function setUp(): void\n    {\n        parent::setUp();\n        DatabaseProvider::getDb()->execute(\"DELETE FROM oxconfig WHERE oxvarname like 'aServersData_%'\");\n    }\n\n    /**\n     * Add first new application server in frontend.\n     */\n    public function testUpdateAppServerInformationNewAppServer(): void\n    {\n        $currentTime = \\OxidEsales\\Eshop\\Core\\Registry::get('oxUtilsDate')->getTime();\n\n        $service = $this->getApplicationServerServiceObject($currentTime);\n        $service->updateAppServerInformationInFrontend();\n\n        /** @var ApplicationServer[] $appServers */\n        $appServers = $service->loadAppServerList();\n\n        $this->assertEquals(1, count($appServers));\n        $this->assertEquals('serverNameHash1', $appServers['serverNameHash1']->getId());\n    }\n\n    /**\n     * There is one up to date application server, so no data to update.\n     */\n    public function testUpdateAppServerInformationAppServerExists(): void\n    {\n        $currentTime = \\OxidEsales\\Eshop\\Core\\Registry::get('oxUtilsDate')->getTime();\n\n        $this->storeAppServer1Information(($currentTime - (11 * 3600)));\n\n        $service = $this->getApplicationServerServiceObject($currentTime);\n        $service->updateAppServerInformationInFrontend();\n\n        /** @var ApplicationServer[] $appServers */\n        $appServers = $service->loadAppServerList();\n\n        $this->assertEquals(1, count($appServers));\n        $this->assertEquals('adminUsageTimestampUpdated', $appServers['serverNameHash1']->getLastAdminUsage());\n    }\n\n    /**\n     * There is one not active application server, the information of this server must be updated.\n     */\n    public function testUpdateAppServerInformationUpdateAppServerData(): void\n    {\n        $currentTime = \\OxidEsales\\Eshop\\Core\\Registry::get('oxUtilsDate')->getTime();\n\n        $this->storeAppServer1Information(($currentTime - (25 * 3600)));\n\n        $service = $this->getApplicationServerServiceObject($currentTime);\n        $appServers = $service->loadActiveAppServerList();\n\n        $this->assertEquals(0, count($appServers));\n\n        $service->updateAppServerInformationInFrontend();\n\n        /** @var ApplicationServer[] $appServers */\n        $appServers = $service->loadActiveAppServerList();\n\n        $this->assertEquals(1, count($appServers));\n        $this->assertNotNull($appServers['serverNameHash1']->getLastFrontendUsage());\n    }\n\n    /**\n     * Add second new application server in frontend.\n     */\n    public function testUpdateAppServerInformationAddAppServer(): void\n    {\n        $currentTime = \\OxidEsales\\Eshop\\Core\\Registry::get('oxUtilsDate')->getTime();\n\n        $this->storeAppServer2Information($currentTime);\n\n        $service = $this->getApplicationServerServiceObject($currentTime);\n        $service->updateAppServerInformationInFrontend();\n\n        /** @var ApplicationServer[] $appServers */\n        $appServers = $service->loadAppServerList();\n\n        $this->assertEquals(2, count($appServers));\n        $this->assertEquals('serverNameHash1', $appServers['serverNameHash1']->getId());\n        $this->assertEquals('serverNameHash2', $appServers['serverNameHash2']->getId());\n    }\n\n    /**\n     * Add second new application server in frontend, when one server is not active anymore.\n     */\n    public function testUpdateAppServerInformationIfOneIsNotActiveAppServer(): void\n    {\n        $currentTime = \\OxidEsales\\Eshop\\Core\\Registry::get('oxUtilsDate')->getTime();\n\n        $this->storeAppServer2Information(($currentTime - (25 * 3600)));\n\n        $service = $this->getApplicationServerServiceObject($currentTime);\n        $service->updateAppServerInformationInFrontend();\n\n        /** @var ApplicationServer[] $appServers */\n        $appServers = $service->loadActiveAppServerList();\n\n        $this->assertEquals(1, count($appServers));\n        $this->assertEquals('serverNameHash1', $appServers['serverNameHash1']->getId());\n    }\n\n    /**\n     * Add second new application server in admin, when the other is out dated and must be deleted.\n     */\n    public function testUpdateAppServerInformationIfOneIsOutdatedAppServer(): void\n    {\n        $currentTime = \\OxidEsales\\Eshop\\Core\\Registry::get('oxUtilsDate')->getTime();\n\n        $this->storeAppServer2Information(($currentTime - (75 * 3600)));\n\n        $service = $this->getApplicationServerServiceObject($currentTime);\n        $service->updateAppServerInformationInAdmin();\n\n        /** @var ApplicationServer[] $appServers */\n        $appServers = $service->loadAppServerList();\n\n        $this->assertEquals(1, count($appServers));\n        $this->assertEquals('serverNameHash1', $appServers['serverNameHash1']->getId());\n    }\n\n    private function getApplicationServerServiceObject($currentTime)\n    {\n        $config = Registry::getConfig();\n        $database = DatabaseProvider::getDb();\n        $appServerDao = oxNew(ApplicationServerDao::class, $database, $config);\n        $utilsServer = $this->createStub(UtilsServer::class);\n        $utilsServer->method('getServerNodeId')\n            ->willReturn('serverNameHash1');\n        $utilsServer->method('getServerIp')\n            ->willReturn('127.0.0.1');\n\n        return oxNew(ApplicationServerService::class, $appServerDao, $utilsServer, $currentTime);\n    }\n\n    private function storeAppServer1Information(int|float $timestamp): void\n    {\n        $config = Registry::getConfig();\n        $appServer = [\n            'id' => 'serverNameHash1',\n            'timestamp' => $timestamp,\n            'ip' => '127.0.0.1',\n            'lastFrontendUsage' => '',\n            'lastAdminUsage' => 'adminUsageTimestampUpdated',\n        ];\n        $config->saveSystemConfigParameter('arr', 'aServersData_serverNameHash1', $appServer);\n    }\n\n    private function storeAppServer2Information($timestamp): void\n    {\n        $config = Registry::getConfig();\n        $appServer = [\n            'id' => 'serverNameHash2',\n            'timestamp' => $timestamp,\n            'ip' => '127.0.0.1',\n            'lastFrontendUsage' => '',\n            'lastAdminUsage' => 'adminUsageTimestampUpdated',\n        ];\n        $config->saveSystemConfigParameter('arr', 'aServersData_serverNameHash2', $appServer);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/OnlineInfo/OnlineLicenseCheckRequestFormationTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\OnlineInfo;\n\nuse OxidEsales\\Eshop\\Core\\Dao\\ApplicationServerDao;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\OnlineLicenseCheck;\nuse OxidEsales\\Eshop\\Core\\OnlineLicenseCheckCaller;\nuse OxidEsales\\Eshop\\Core\\OnlineServerEmailBuilder;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\Service\\ApplicationServerExporter;\nuse OxidEsales\\Eshop\\Core\\Service\\ApplicationServerExporterInterface;\nuse OxidEsales\\Eshop\\Core\\Service\\ApplicationServerService;\nuse OxidEsales\\Eshop\\Core\\ShopVersion;\nuse OxidEsales\\Eshop\\Core\\SimpleXml;\nuse OxidEsales\\Eshop\\Core\\UserCounter;\nuse OxidEsales\\Eshop\\Core\\UtilsDate;\nuse OxidEsales\\Eshop\\Core\\UtilsServer;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse SimpleXMLElement;\n\nfinal class OnlineLicenseCheckRequestFormationTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private int $adminUserCount;\n\n    private int $timestamp;\n\n    private string $clusterId;\n\n    private string $documentName = 'olcRequest';\n\n    private string $edition;\n\n    private string $licenseKeyExisting;\n\n    private string $licenseKeyNew;\n\n    private string $pVersion = '1.1';\n\n    private string $productId = 'eShop';\n\n    private string $serverId = 'server_id1';\n\n    private string $serverIp = '127.0.0.1';\n\n    private string $shopUrl;\n\n    private string $shopVersion;\n\n    private string $xmlLog;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->prepareTestData();\n    }\n\n    public function tearDown(): void\n    {\n        $this->cleanUpTestData();\n\n        parent::tearDown();\n    }\n\n    public function testRequestFormationWithExistingSerials(): void\n    {\n        $licenseCaller = new OnlineLicenseCheckCaller(\n            oxNew(CurlSpy::class, $this->xmlLog),\n            oxNew(OnlineServerEmailBuilder::class),\n            oxNew(SimpleXml::class)\n        );\n        $licenseCheck = new OnlineLicenseCheck($licenseCaller);\n        $licenseCheck->setUserCounter(oxNew(UserCounter::class));\n        $licenseCheck->setAppServerExporter($this->getApplicationServerExporter());\n\n        $licenseCheck->validateShopSerials();\n\n        $xml = $this->loadRequestLogXml();\n        $this->assertEquals(8, $xml->count());\n        $this->assertEquals($this->documentName, $xml->getName());\n        $this->assertEquals($this->pVersion, $xml->pVersion);\n        $this->assertEquals($this->clusterId, $xml->clusterId);\n        $this->assertEquals($this->edition, $xml->edition);\n        $this->assertEquals($this->shopVersion, $xml->version);\n        $this->assertEquals($this->shopUrl, $xml->shopUrl);\n        $this->assertEquals($this->productId, $xml->productId);\n        /** keys */\n        $this->assertEquals(1, $xml->keys->children()->count());\n        $this->assertEquals($this->licenseKeyExisting, $xml->keys->key);\n        /** productSpecificInformation */\n        $this->assertEquals(2, $xml->productSpecificInformation->children()->count());\n        /** servers */\n        $this->assertEquals(1, $xml->productSpecificInformation->servers->children()->count());\n        $this->assertEquals($this->serverId, $xml->productSpecificInformation->servers->server->id);\n        $this->assertEquals($this->serverIp, $xml->productSpecificInformation->servers->server->ip);\n        $this->assertEquals(\n            (string) $this->timestamp,\n            $xml->productSpecificInformation->servers->server->lastFrontendUsage\n        );\n        $this->assertEquals(\n            (string) $this->timestamp,\n            $xml->productSpecificInformation->servers->server->lastAdminUsage\n        );\n        /** counters */\n        $this->assertEquals(3, $xml->productSpecificInformation->counters->children()->count());\n        /** admin users */\n        $this->assertEquals(2, $xml->productSpecificInformation->counters->counter[0]->children()->count());\n        $this->assertEquals('admin users', (string) $xml->productSpecificInformation->counters->counter[0]->name);\n        $this->assertEquals($this->adminUserCount, (int) $xml->productSpecificInformation->counters->counter[0]->value);\n        /** active admin users */\n        $this->assertEquals(2, $xml->productSpecificInformation->counters->counter[1]->children()->count());\n        $this->assertEquals(\n            'active admin users',\n            (string) $xml->productSpecificInformation->counters->counter[1]->name\n        );\n        $this->assertEquals($this->adminUserCount, (int) $xml->productSpecificInformation->counters->counter[1]->value);\n        /** subShops */\n        $this->assertEquals(2, $xml->productSpecificInformation->counters->counter[2]->children()->count());\n        $this->assertEquals('subShops', (string) $xml->productSpecificInformation->counters->counter[2]->name);\n        $this->assertEquals(1, (int) $xml->productSpecificInformation->counters->counter[2]->value);\n    }\n\n    public function testRequestFormationWithNewSerial(): void\n    {\n        $licenseCaller = new OnlineLicenseCheckCaller(\n            oxNew(CurlSpy::class, $this->xmlLog),\n            oxNew(OnlineServerEmailBuilder::class),\n            oxNew(SimpleXml::class)\n        );\n        $licenseCheck = new OnlineLicenseCheck($licenseCaller);\n        $licenseCheck->setUserCounter(oxNew(UserCounter::class));\n        $licenseCheck->setAppServerExporter($this->getApplicationServerExporter());\n\n        $licenseCheck->validateNewSerial($this->licenseKeyNew);\n\n        $xml = $this->loadRequestLogXml();\n        $this->assertEquals(8, $xml->count());\n        $this->assertEquals($this->documentName, $xml->getName());\n        $this->assertEquals($this->pVersion, $xml->pVersion);\n        $this->assertEquals($this->clusterId, $xml->clusterId);\n        $this->assertEquals($this->edition, $xml->edition);\n        $this->assertEquals($this->shopVersion, $xml->version);\n        $this->assertEquals($this->shopUrl, $xml->shopUrl);\n        $this->assertEquals($this->productId, $xml->productId);\n        /** keys */\n        $this->assertEquals(2, $xml->keys->children()->count());\n        $this->assertEquals($this->licenseKeyExisting, $xml->keys->key[0]);\n        $this->assertEquals($this->licenseKeyNew, $xml->keys->key[1]);\n        $this->assertEquals('new', $xml->keys->key[1]->attributes()->state);\n        /** productSpecificInformation */\n        $this->assertEquals(2, $xml->productSpecificInformation->children()->count());\n        /** servers */\n        $this->assertEquals(1, $xml->productSpecificInformation->servers->children()->count());\n        $this->assertEquals($this->serverId, $xml->productSpecificInformation->servers->server->id);\n        $this->assertEquals($this->serverIp, $xml->productSpecificInformation->servers->server->ip);\n        $this->assertEquals(\n            (string) $this->timestamp,\n            $xml->productSpecificInformation->servers->server->lastFrontendUsage\n        );\n        $this->assertEquals(\n            (string) $this->timestamp,\n            $xml->productSpecificInformation->servers->server->lastAdminUsage\n        );\n        /** counters */\n        $this->assertEquals(3, $xml->productSpecificInformation->counters->children()->count());\n        /** admin users */\n        $this->assertEquals(2, $xml->productSpecificInformation->counters->counter[0]->children()->count());\n        $this->assertEquals('admin users', (string) $xml->productSpecificInformation->counters->counter[0]->name);\n        $this->assertEquals($this->adminUserCount, (int) $xml->productSpecificInformation->counters->counter[0]->value);\n        /** active admin users */\n        $this->assertEquals(2, $xml->productSpecificInformation->counters->counter[1]->children()->count());\n        $this->assertEquals(\n            'active admin users',\n            (string) $xml->productSpecificInformation->counters->counter[1]->name\n        );\n        $this->assertEquals($this->adminUserCount, (int) $xml->productSpecificInformation->counters->counter[1]->value);\n        /** subShops */\n        $this->assertEquals(2, $xml->productSpecificInformation->counters->counter[2]->children()->count());\n        $this->assertEquals('subShops', (string) $xml->productSpecificInformation->counters->counter[2]->name);\n        $this->assertEquals(1, (int) $xml->productSpecificInformation->counters->counter[2]->value);\n    }\n\n    private function prepareTestData(): void\n    {\n        $this->xmlLog = \\sprintf('%s/%s.xml', __DIR__, uniqid('request_log_', true));\n        $this->licenseKeyExisting = uniqid('license-', true);\n        $this->licenseKeyNew = uniqid('license-', true);\n        $this->clusterId = uniqid('cluster-', true);\n        $this->edition = Registry::getConfig()->getEdition()->value;\n        $this->shopVersion = ShopVersion::getVersion();\n        $this->shopUrl = Registry::getConfig()->getShopUrl();\n        $this->timestamp = Registry::getUtilsDate()->getTime();\n        $this->adminUserCount = $this->getActiveAdminCount();\n\n        Registry::getConfig()->setConfigParam('aSerials', [$this->licenseKeyExisting]);\n        Registry::getConfig()->setConfigParam('sClusterId', [$this->clusterId]);\n\n        $this->setSeversDataConfiguration();\n    }\n\n    private function setSeversDataConfiguration(): void\n    {\n        Registry::getConfig()\n            ->saveSystemConfigParameter(\n                'arr',\n                \"aServersData_{$this->serverId}\",\n                [\n                    'id' => $this->serverId,\n                    'timestamp' => $this->timestamp,\n                    'ip' => $this->serverIp,\n                    'lastFrontendUsage' => $this->timestamp,\n                    'lastAdminUsage' => $this->timestamp,\n                ]\n            );\n    }\n\n    private function loadRequestLogXml(): SimpleXMLElement\n    {\n        return simplexml_load_string(file_get_contents($this->xmlLog));\n    }\n\n    private function getApplicationServerExporter(): ApplicationServerExporterInterface\n    {\n        $appServerDao = oxNew(ApplicationServerDao::class, DatabaseProvider::getDb(), Registry::getConfig());\n        $service = oxNew(\n            ApplicationServerService::class,\n            $appServerDao,\n            oxNew(UtilsServer::class),\n            Registry::get(UtilsDate::class)->getTime()\n        );\n\n        return oxNew(ApplicationServerExporter::class, $service);\n    }\n\n    private function cleanUpTestData(): void\n    {\n        $fileSystem = $this->get('oxid_esales.symfony.file_system');\n        if ($fileSystem->exists($this->xmlLog)) {\n            $fileSystem->remove($this->xmlLog);\n        }\n    }\n\n    private function cleanupServersData(): void\n    {\n        DatabaseProvider::getDb()->execute(\"DELETE FROM oxconfig WHERE oxvarname like 'aServersData_%'\");\n    }\n\n    private function getActiveAdminCount(): int\n    {\n        return (int) DatabaseProvider::getDb()\n            ->getOne(\"SELECT COUNT(1) FROM oxuser WHERE oxrights != 'user' AND oxactive = 1 \");\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/OnlineInfo/OnlineModuleNotifierRequestFormationTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\OnlineInfo;\n\nuse OxidEsales\\Eshop\\Core\\OnlineModuleVersionNotifier;\nuse OxidEsales\\Eshop\\Core\\OnlineModuleVersionNotifierCaller;\nuse OxidEsales\\Eshop\\Core\\OnlineServerEmailBuilder;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\ShopVersion;\nuse OxidEsales\\Eshop\\Core\\SimpleXml;\nuse OxidEsales\\EshopCommunity\\Application\\Controller\\FrontendController;\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\ContainerFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Bridge\\ModuleActivationBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse Psr\\Container\\ContainerInterface;\nuse SimpleXMLElement;\nuse Throwable;\n\nfinal class OnlineModuleNotifierRequestFormationTest extends IntegrationTestCase\n{\n    private ContainerInterface $container;\n    private string $clusterId;\n    private string $documentName = 'omvnRequest';\n    private string $edition;\n    private string $module1Id = 'extending_1_class';\n    private string $module1Title = 'Test extending 1 shop class';\n    private string $module1Description = 'Module testing extending 1 shop class';\n    private string $module1ClassExtensionsShopClass = 'OxidEsales\\Eshop\\Application\\Model\\Order';\n    private string $module1ClassExtensionsModuleClass = 'oeTest/extending_1_class/myorder';\n    private string $module1ControllerId = FrontendController::class;\n    private string $module1ControllerClassNameSpace = 'oeTest/controller_1_class/myFrontendController';\n    private string $module2Id = 'extending_1_class_3_extensions';\n    private string $module2Title = 'Test extending 1 shop class with 3 extensions';\n    private string $module2Description = 'Module testing extending 1 shop class with 3 extensions';\n    private string $module2ClassExtensionsShopClass = 'OxidEsales\\Eshop\\Application\\Model\\Order';\n    private string $module2ClassExtensionsModuleClass = 'oeTest/extending_1_class_3_extensions/myorder1';\n    private string $moduleVersion = '1.0';\n    private string $pVersion = '1.1';\n    private string $productId = 'eShop';\n    private string $shopUrl;\n    private string $shopVersion;\n    private string $xmlLog;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->container = ContainerFactory::getInstance()->getContainer();\n        $this->prepareTestData();\n    }\n\n    public function tearDown(): void\n    {\n        $this->cleanUpTestData();\n\n        parent::tearDown();\n    }\n\n    public function testRequestFormation(): void\n    {\n        $licenseCaller = new OnlineModuleVersionNotifierCaller(\n            oxNew(CurlSpy::class, $this->xmlLog),\n            oxNew(OnlineServerEmailBuilder::class),\n            oxNew(SimpleXml::class)\n        );\n        (new OnlineModuleVersionNotifier($licenseCaller))->versionNotify();\n\n        $xml = $this->loadRequestLogXml();\n        $this->assertEquals(7, $xml->count());\n        $this->assertEquals($this->documentName, $xml->getName());\n        $this->assertEquals($this->pVersion, $xml->pVersion);\n        $this->assertEquals($this->edition, $xml->edition);\n        $this->assertEquals($this->shopVersion, $xml->version);\n        $this->assertEquals($this->shopUrl, $xml->shopUrl);\n        $this->assertEquals($this->productId, $xml->productId);\n        $this->assertEquals(2, $xml->modules->children()->count());\n        /** module 1 */\n        $this->assertEquals(10, $xml->modules->module[0]->children()->count());\n        $this->assertEquals($this->module1Id, $xml->modules->module[0]->id);\n        $this->assertEquals($this->moduleVersion, $xml->modules->module[0]->version);\n        $this->assertEquals($this->module1Title, $xml->modules->module[0]->title);\n        $this->assertEquals($this->module1Description, $xml->modules->module[0]->description);\n        $this->assertEquals(1, $xml->modules->module[0]->classExtensions->children()->count());\n        $this->assertEquals(\n            $this->module1ClassExtensionsShopClass,\n            $xml->modules->module[0]->classExtensions->children()->children()->shopClass\n        );\n        $this->assertEquals(\n            $this->module1ClassExtensionsModuleClass,\n            $xml->modules->module[0]->classExtensions->children()->children()->moduleClass\n        );\n        $this->assertEquals(1, $xml->modules->module[0]->controllers->children()->count());\n        $this->assertEquals(\n            $this->module1ControllerId,\n            $xml->modules->module[0]->controllers->children()->children()->id\n        );\n        $this->assertEquals(\n            $this->module1ControllerClassNameSpace,\n            $xml->modules->module[0]->controllers->children()->children()->controllerClassNameSpace\n        );\n        /** active in shops */\n        $this->assertEquals(1, $xml->modules->module[0]->activeInShops->children()->count());\n        $this->assertEquals($this->shopUrl, $xml->modules->module[0]->activeInShops->activeInShop);\n        /** module 2 */\n        $this->assertEquals(10, $xml->modules->module[1]->children()->count());\n        $this->assertEquals($this->module2Id, $xml->modules->module[1]->id);\n        $this->assertEquals($this->moduleVersion, $xml->modules->module[1]->version);\n        $this->assertEquals($this->module2Title, $xml->modules->module[1]->title);\n        $this->assertEquals($this->module2Description, $xml->modules->module[1]->description);\n        $this->assertCount(1, $xml->modules->module[0]->classExtensions->children());\n        $this->assertEquals(\n            $this->module2ClassExtensionsShopClass,\n            $xml->modules->module[1]->classExtensions->children()->children()->shopClass\n        );\n        $this->assertEquals(\n            $this->module2ClassExtensionsModuleClass,\n            $xml->modules->module[1]->classExtensions->children()->children()->moduleClass\n        );\n        $this->assertEmpty($xml->modules->module[1]->controllers->children());\n        /** active in shops */\n        $this->assertEquals(1, $xml->modules->module[1]->activeInShops->children()->count());\n        $this->assertEquals($this->shopUrl, $xml->modules->module[1]->activeInShops->activeInShop);\n    }\n\n    private function prepareTestData(): void\n    {\n        $this->xmlLog = sprintf('%s/%s.xml', __DIR__, uniqid('request_log_', true));\n        $this->edition = Registry::getConfig()->getEdition()->value;\n        $this->shopVersion = ShopVersion::getVersion();\n        $this->shopUrl = Registry::getConfig()->getShopUrl();\n        $this->clusterId = uniqid('cluster-', true);\n\n        Registry::getConfig()->setConfigParam('sClusterId', [$this->clusterId]);\n\n        $this->container\n            ->get('oxid_esales.module.install.service.launched_shop_project_configuration_generator')\n            ->generate();\n\n        $this->installModule($this->module1Id);\n        $this->activateModule($this->module1Id);\n\n        $this->installModule($this->module2Id);\n        $this->activateModule($this->module2Id);\n    }\n\n    private function installModule(string $moduleId): void\n    {\n        $package = new OxidEshopPackage(__DIR__ . '/../Modules/TestData/modules/' . $moduleId);\n        $this->container->get(ModuleInstallerInterface::class)->install($package);\n    }\n\n    private function uninstallModule(string $moduleId): void\n    {\n        $package = new OxidEshopPackage(__DIR__ . '/../Modules/TestData/modules/' . $moduleId);\n        $this->container->get(ModuleInstallerInterface::class)->uninstall($package);\n    }\n\n    private function activateModule(string $moduleId): void\n    {\n        $this->container->get(ModuleActivationBridgeInterface::class)->activate($moduleId, 1);\n    }\n\n    private function loadRequestLogXml(): SimpleXMLElement\n    {\n        return simplexml_load_string(file_get_contents($this->xmlLog));\n    }\n\n    private function cleanUpTestData(): void\n    {\n        $fileSystem = $this->container->get('oxid_esales.symfony.file_system');\n        if ($fileSystem->exists($this->xmlLog)) {\n            $fileSystem->remove($this->xmlLog);\n        }\n        try {\n            $this->uninstallModule($this->module1Id);\n            $this->uninstallModule($this->module2Id);\n        } catch (Throwable) {\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/BasketConstruct.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Price;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Application\\Model\\Basket;\nuse OxidEsales\\Eshop\\Application\\Model\\Category;\nuse OxidEsales\\Eshop\\Application\\Model\\Delivery;\nuse OxidEsales\\Eshop\\Application\\Model\\Discount;\nuse OxidEsales\\Eshop\\Application\\Model\\Payment;\nuse OxidEsales\\Eshop\\Application\\Model\\User;\nuse OxidEsales\\Eshop\\Application\\Model\\VoucherSerie;\nuse OxidEsales\\Eshop\\Application\\Model\\Wrapping;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Model\\BaseModel;\nuse OxidEsales\\Eshop\\Core\\Registry;\n\nuse function is_array;\n\nclass BasketConstruct\n{\n    public User $user;\n\n    public function calculateBasket(array $testCase): Basket\n    {\n        // gather data from test case\n        $products = $testCase['articles'] ?? [];\n        $discounts = $testCase['discounts'] ?? [];\n        $costs = $testCase['costs'] ?? [];\n        $options = $testCase['options'] ?? [];\n        $userData = $testCase['user'] ?? [];\n        $categories = $testCase['categories'] ?? [];\n        // set custom configs\n        $this->setOptions($options);\n\n        // categories preparation\n        $this->createCategories($categories);\n\n        // products preparation, returns data required for adding to basket\n        $productsForBasket = $this->createProducts($products);\n\n        // create & set discounts\n        $this->createDiscounts($discounts);\n\n        // create & set wrappings\n        $wrapping = $this->createWrappings($costs['wrapping'] ?? []);\n\n        // create & set delivery costs\n        $delivery = $this->createDeliveryCosts($costs['delivery'] ?? []);\n\n        // create & set payment costs\n        $payment = $this->createPayments($costs['payment'] ?? []);\n\n        // create & set vouchers\n        $voucherIDs = $this->createVouchers($costs['voucherserie'] ?? []);\n\n        // basket preparation\n        $basket = oxNew(Basket::class);\n\n        // setup and login user for basket\n        if (empty($userData)) {\n            $userData = $this->getDefaultUserData();\n        }\n        $basket->setBasketUser($this->createUser($userData));\n\n        // group setup\n        $this->createGroup($testCase['group'] ?? []);\n\n        // adding products to basket\n        foreach ($productsForBasket as $productData) {\n            if ($productData['amount'] === null) {\n                continue;\n            }\n            if (($productData['amount']) === 0) {\n                continue;\n            }\n            $orderProduct = $basket->addToBasket($productData['id'], $productData['amount']);\n            // adding wrapping if need\n            if ($wrapping !== []) {\n                $orderProduct->setWrapping($wrapping[$productData['id']] ?? null);\n            }\n        }\n\n        if (isset($wrapping['card']) && $wrapping['card']) {\n            $basket->setCardId($wrapping['card']);\n        }\n\n        // try to add delivery\n        if (!empty($delivery)) {\n            $basket->setShipping($delivery['delivery_set_id'] ?? $delivery[0]);\n        }\n\n        // try to add payment\n        $basket->setPayment(\n            $payment[0] ?? 'oxidinvoice'\n        );\n\n        // try to add vouchers\n        $basket->setSkipVouchersChecking(true);\n        if ($voucherIDs !== []) {\n            foreach ($voucherIDs as $iValue) {\n                $basket->addVoucher($iValue);\n            }\n        }\n\n        // calculate basket\n        $basket->calculateBasket();\n\n        return $basket;\n    }\n\n    public function setOptions(array $options): void\n    {\n        Registry::getConfig()->init();\n        if (!empty($options['config'])) {\n            foreach ($options['config'] as $key => $value) {\n                Registry::getConfig()->setConfigParam($key, $value);\n            }\n        }\n        if (isset($options['activeCurrencyRate']) && $options['activeCurrencyRate']) {\n            // load active currencies, change if set option\n            $currencies = Registry::getConfig()->getConfigParam('aCurrencies');\n            $currencies[0] = 'EUR@ ' . $options['activeCurrencyRate'] . '@ ,@ .@ €@ 2';\n            Registry::getConfig()->setConfigParam('aCurrencies', $currencies);\n        }\n    }\n\n    public function createObj2Obj(array $data, string $object2ObjectTable): void\n    {\n        $newData = is_array($data[0] ?? null) ? $data : [$data];\n        foreach ($newData as $values) {\n            $object = oxNew(BaseModel::class);\n            $object->init($object2ObjectTable);\n            foreach ($values as $key => $value) {\n                $field = $object2ObjectTable . '__' . $key;\n                $object->{$field} = new Field($value, Field::T_RAW);\n            }\n            $object->save();\n        }\n    }\n\n    public function createGroup(array $data): void\n    {\n        foreach ($data as $groupData) {\n            $group = $this->createObj($groupData, 'oxgroups', ' oxgroups');\n            if (!empty($groupData['oxobject2group'])) {\n                foreach ($groupData['oxobject2group'] as $iValue) {\n                    $connection = [\n                        'oxgroupsid' => $group->getId(),\n                        'oxobjectid' => $iValue ?? null,\n                    ];\n                    $this->createObj2Obj($connection, 'oxobject2group');\n                }\n            }\n        }\n    }\n\n    public function createObj(array $data, string $objectClass, string $table): ?BaseModel\n    {\n        if (empty($data)) {\n            return null;\n        }\n        $object = oxNew($objectClass);\n        if (isset($data['oxid']) && $data['oxid']) {\n            $object->setId($data['oxid']);\n        }\n        foreach ($data as $key => $value) {\n            if (!is_array($value)) {\n                $field = $table . '__' . $key;\n                $object->{$field} = new Field($value, Field::T_RAW);\n            }\n        }\n        $object->save();\n\n        return $object;\n    }\n\n    public static function getDefaultCountryId(): string\n    {\n        return (string)DatabaseProvider::getDb()\n            ->select('SELECT OXID FROM oxcountry WHERE OXTITLE = \"Deutschland\"')\n            ->fetchRow();\n    }\n\n    private function createUser(array $userData): User\n    {\n        if (empty($userData['oxcountryid'])) {\n            $userData['oxcountryid'] = self::getDefaultCountryId();\n        }\n\n        return $this->createObj($userData, 'oxuser', 'oxuser');\n    }\n\n    public function createCategories(array $categories): void\n    {\n        foreach ($categories as $categoryData) {\n            $category = $this->createObj($categoryData, Category::class, 'oxcategories');\n            if (!empty($categoryData['oxarticles'])) {\n                foreach ($categoryData['oxarticles'] as $productId) {\n                    $data = [\n                        'oxcatnid' => $category->getId(),\n                        'oxobjectid' => $productId,\n                    ];\n                    $this->createObj2Obj($data, 'oxobject2category');\n                }\n            }\n        }\n    }\n\n    /**\n     * @return array of id's and basket amounts of created products\n     */\n    public function createProducts(array $products): array\n    {\n        $result = [];\n        foreach ($products as $outerKey => $productData) {\n            $product = oxNew(Article::class);\n            $product->setId($productData['oxid']);\n            foreach ($productData as $key => $value) {\n                if (str_contains((string)$key, 'ox')) {\n                    $field = \"oxarticles__$key\";\n                    $product->{$field} = new Field($value);\n                }\n            }\n            $product->save();\n            if (isset($productData['scaleprices']) && $productData['scaleprices']) {\n                $this->createScalePrices([$productData['scaleprices']]);\n            }\n            $result[$outerKey]['id'] = $productData['oxid'];\n            $result[$outerKey]['amount'] = $productData['amount'] ?? null;\n        }\n\n        return $result;\n    }\n\n    private function createScalePrices(array $scalePrices): void\n    {\n        $this->createObj2Obj($scalePrices, 'oxprice2article');\n    }\n\n    public function createDiscounts(array $discounts): void\n    {\n        foreach ($discounts as $discountData) {\n            // add discounts\n            $discount = oxNew(Discount::class);\n            $discount->setId($discountData['oxid']);\n            foreach ($discountData as $key => $value) {\n                if (!is_array($value)) {\n                    $field = 'oxdiscount__' . $key;\n                    $discount->{$field} = new Field((string)($value));\n                } else {\n                    foreach ($value as $id) {\n                        $aData = [\n                            'oxid' => $discount->getId() . '_' . $id,\n                            'oxdiscountid' => $discount->getId(),\n                            'oxobjectid' => $id,\n                            'oxtype' => $key,\n                        ];\n                        $this->createObj2Obj($aData, 'oxobject2discount');\n                    }\n                }\n            }\n            $discount->save();\n        }\n    }\n\n    private function createWrappings(array $wrappings): array\n    {\n        $result = [];\n        foreach ($wrappings as $wrapping) {\n            $card = oxNew(Wrapping::class);\n            foreach ($wrapping as $key => $value) {\n                if (!is_array($value)) {\n                    $field = 'oxwrapping__' . $key;\n                    $card->{$field} = new Field($value, Field::T_RAW);\n                }\n            }\n            $card->save();\n            if (isset($wrapping['oxarticles']) && $wrapping['oxarticles']) {\n                foreach ($wrapping['oxarticles'] as $productId) {\n                    $result[$productId] = $card->getId();\n                }\n            } else {\n                $result['card'] = $card->getId();\n            }\n        }\n\n        return $result;\n    }\n\n    private function createDeliveryCosts(array $deliveryCosts): ?array\n    {\n        if (empty($deliveryCosts)) {\n            return null;\n        }\n        $deliveries = [];\n\n        $deliverySet = $this->createObj(\n            [\n                'oxtitle' => 'Delivery set',\n                'oxactive' => 1,\n            ],\n            'oxdeliveryset',\n            'oxdeliveryset'\n        );\n\n        foreach ($deliveryCosts as $deliveryData) {\n            $delivery = oxNew(Delivery::class);\n            $delivery->save();\n            foreach ($deliveryData as $key => $value) {\n                if (!is_array($value)) {\n                    $field = 'oxdelivery__' . $key;\n                    $delivery->{$field} = new Field(\"{$value}\");\n                } else {\n                    foreach ($value as $sId) {\n                        $data = [\n                            'oxdeliveryid' => $delivery->getId(),\n                            'oxobjectid' => $sId,\n                            'oxtype' => $key,\n                        ];\n                        $this->createObj2Obj($data, 'oxobject2delivery');\n                    }\n                }\n            }\n            $delivery->save();\n            $deliveries[] = $delivery->getId();\n\n            $this->createObj2Obj(\n                [\n                    'oxdelid' => $delivery->getId(),\n                    'oxdelsetid' => $deliverySet->getId(),\n                ],\n                'oxdel2delset'\n            );\n        }\n        $this->createObj2Obj(\n            [\n                'oxpaymentid' => 'oxidinvoice',\n                'oxobjectid' => $deliverySet->getId(),\n                'oxtype' => 'oxdelset'\n            ],\n            'oxobject2payment'\n        );\n        $deliveries['delivery_set_id'] = $deliverySet->getId();\n\n        return $deliveries;\n    }\n\n    private function createPayments(array $payments): array\n    {\n        $result = [];\n        foreach ($payments as $paymentData) {\n            $payment = oxNew(Payment::class);\n            foreach ($paymentData as $key => $value) {\n                if (!is_array($value)) {\n                    $field = 'oxpayments__' . $key;\n                    $payment->{$field} = new Field((string)($value));\n                }\n            }\n            $payment->save();\n            $result[] = $payment->getId();\n        }\n\n        return $result;\n    }\n\n    private function createVouchers(array $voucherSeries): array\n    {\n        $voucherIDs = [];\n        foreach ($voucherSeries as $voucherData) {\n            $voucherSerie = oxNew(VoucherSerie::class);\n            foreach ($voucherData as $key => $value) {\n                $field = 'oxvoucherseries__' . $key;\n                $voucherSerie->{$field} = new Field($value, Field::T_RAW);\n            }\n            $voucherSerie->save();\n            // inserting vouchers\n            for ($i = 1; $i <= $voucherData['voucher_count']; $i++) {\n                $data = [\n                    'oxreserved' => 0,\n                    'oxvouchernr' => md5(uniqid((string)random_int(0, mt_getrandmax()), true)),\n                    'oxvoucherserieid' => $voucherSerie->getId(),\n                ];\n                $voucher = $this->createObj($data, 'oxvoucher', 'oxvouchers');\n                $voucherIDs[] = $voucher->getId();\n            }\n        }\n\n        return $voucherIDs;\n    }\n\n    private function getDefaultUserData(): array\n    {\n        return [\n            'oxrights' => 'malladmin',\n            'oxactive' => '1',\n            'oxusername' => 'admin',\n            'oxpassword' => 'f6fdffe48c908deb0f4c3bd36c032e72',\n            'oxpasssalt' => '61646D696E',\n            'oxcompany' => 'Your Company Name',\n            'oxfname' => 'John',\n            'oxlname' => 'Doe',\n            'oxstreet' => 'Maple Street',\n            'oxstreetnr' => '10',\n            'oxcity' => 'Any City',\n            'oxcountryid' => 'a7c40f631fc920687.20179984',\n            'oxzip' => '9041',\n            'oxfon' => '217-8918712',\n            'oxfax' => '217-8918713',\n            'oxstateid' => null,\n            'oxaddinfo' => null,\n            'oxustid' => null,\n            'oxsal' => 'MR',\n            'oxustidstatus' => '0',\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/BasketTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Price;\n\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Symfony\\Component\\Yaml\\Yaml;\n\nuse function is_array;\n\nfinal class BasketTest extends IntegrationTestCase\n{\n    public static function providerBasketCalculation(): array\n    {\n        $cases = PHP_VERSION_ID >= 80400 ? 'basket_php84' : 'basket_php83';\n        $testCases = [];\n        foreach (glob(__DIR__ . \"/testcases/$cases/*.yaml\") as $filePath) {\n            $testCases[$filePath] = [Yaml::parseFile($filePath)];\n        }\n        return $testCases;\n    }\n\n    #[DataProvider('providerBasketCalculation')]\n    public function testBasketCalculation(array $testCase): void\n    {\n        // gathering data arrays\n        $expected = $testCase['expected'];\n\n        // load calculated basket from provided data\n        $basketConstruct = new BasketConstruct();\n        $basket = $basketConstruct->calculateBasket($testCase);\n\n        // check basket item list\n        $expectedProducts = $expected['articles'];\n        $basketItemList = $basket->getContents();\n\n        $this->assertCount(\n            count($expectedProducts),\n            $basketItemList,\n            \"Expected basket product amount doesn't match actual\"\n        );\n\n        if ($basketItemList) {\n            foreach ($basketItemList as $basketItem) {\n                $productId = $basketItem->getArticle()\n                    ->getID();\n                $this->assertEquals(\n                    $expectedProducts[$productId][0],\n                    $basketItem->getFUnitPrice(),\n                    \"Unit price of product id $productId\"\n                );\n                $this->assertEquals(\n                    $expectedProducts[$productId][1],\n                    $basketItem->getFTotalPrice(),\n                    \"Total price of product id $productId\"\n                );\n            }\n        }\n\n        // Total discounts\n        $expectedDiscounts = $expected['totals']['discounts'] ?? null;\n        $expectedDiscountCount = (is_array($expectedDiscounts)) ? count($expectedDiscounts) : 0;\n        $productDiscounts = $basket->getDiscounts();\n        $productDiscountsCount = (is_array($productDiscounts)) ? count($productDiscounts) : 0;\n        $this->assertEquals(\n            $expectedDiscountCount,\n            $productDiscountsCount,\n            \"Expected basket discount amount doesn't match actual\"\n        );\n        if (! empty($expectedDiscounts)) {\n            foreach ($productDiscounts as $discount) {\n                $this->assertEquals(\n                    $expectedDiscounts[$discount->sOXID],\n                    $discount->fDiscount,\n                    \"Total discount of $discount->sOXID\"\n                );\n            }\n        }\n\n        // Total vats\n        $expectedVats = $expected['totals']['vats'] ?? null;\n        $expectedVatsCount = (is_array($expectedVats)) ? count($expectedVats) : 0;\n        $productVats = $basket->getProductVats();\n        $productVatsCount = (is_array($productVats)) ? count($productVats) : 0;\n        $this->assertEquals(\n            $expectedVatsCount,\n            $productVatsCount,\n            \"Expected basket different vat amount doesn't match actual\"\n        );\n        if (! empty($expectedVats)) {\n            foreach ($productVats as $percent => $sum) {\n                $this->assertEquals($expectedVats[$percent], $sum, \"Total Vat of $percent%\");\n            }\n        }\n\n        // Wrapping costs\n        $expectedWrappings = $expected['totals']['wrapping'] ?? null;\n        if (! empty($expectedWrappings)) {\n            $this->assertEquals(\n                $expectedWrappings['brutto'],\n                $basket->getFWrappingCosts(),\n                'Total wrappings brutto price'\n            );\n            $this->assertEquals(\n                $expectedWrappings['netto'] ?? null,\n                $basket->getWrappCostNet(),\n                'Total wrappings netto price'\n            );\n            $this->assertEquals(\n                $expectedWrappings['vat'] ?? null,\n                $basket->getWrappCostVat(),\n                'Total wrappings vat price'\n            );\n        }\n\n        // Giftcard costs\n        $expectedCards = $expected['totals']['giftcard'] ?? null;\n        if (! empty($expectedCards)) {\n            $this->assertEquals(\n                $expectedCards['brutto'],\n                $basket->getFGiftCardCosts(),\n                'Total giftcard brutto price'\n            );\n            $this->assertEquals(\n                $expectedCards['netto'] ?? null,\n                $basket->getGiftCardCostNet(),\n                'Total giftcard netto price'\n            );\n            $this->assertEquals(\n                $expectedCards['vat'] ?? null,\n                $basket->getGiftCardCostVat(),\n                'Total giftcard vat price'\n            );\n        }\n\n        // Delivery costs\n        $expectedDeliveryCosts = $expected['totals']['delivery'] ?? null;\n        if (! empty($expectedDeliveryCosts)) {\n            $this->assertEquals(\n                $expectedDeliveryCosts['brutto'],\n                number_format(\n                    (float)$basket->getDeliveryCosts(),\n                    2,\n                    ',',\n                    '.'\n                ),\n                'Delivery total brutto price'\n            );\n            $this->assertEquals(\n                $expectedDeliveryCosts['netto'] ?? null,\n                $basket->getDelCostNet(),\n                'Delivery total netto price'\n            );\n            $this->assertEquals(\n                $expectedDeliveryCosts['vat'] ?? null,\n                $basket->getDelCostVat(),\n                'Delivery total vat price'\n            );\n        }\n\n        // Payment costs\n        $expectedPayments = $expected['totals']['payment'] ?? null;\n        if (! empty($expectedPayments)) {\n            $this->assertEquals(\n                $expectedPayments['brutto'] ?? null,\n                number_format(\n                    $basket->getPaymentCosts(),\n                    2,\n                    ',',\n                    '.'\n                ),\n                'Payment total brutto price'\n            );\n            $this->assertEquals(\n                $expectedPayments['netto'] ?? null,\n                $basket->getPayCostNet(),\n                'Payment total netto price'\n            );\n            $this->assertEquals(\n                $expectedPayments['vat'] ?? null,\n                $basket->getPayCostVat(),\n                'Payment total vat price'\n            );\n        }\n\n        // Vouchers\n        $expectedVouchers = $expected['totals']['voucher'] ?? null;\n        if (! empty($expectedVouchers)) {\n            $this->assertEquals(\n                $expectedVouchers['brutto'],\n                number_format(\n                    $basket->getVoucherDiscValue(),\n                    2,\n                    ',',\n                    '.'\n                ),\n                'Voucher total discount brutto'\n            );\n        }\n\n        // Total netto & brutto, grand total\n        $this->assertEquals($expected['totals']['totalNetto'], $basket->getProductsNetPrice(), 'Total Netto');\n        $this->assertEquals($expected['totals']['totalBrutto'], $basket->getFProductsPrice(), 'Total Brutto');\n        $this->assertEquals($expected['totals']['grandTotal'], $basket->getFPrice(), 'Grand Total');\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/DeliveryCostTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Price;\n\nuse OxidEsales\\Eshop\\Application\\Model\\DeliveryList;\nuse OxidEsales\\Eshop\\Application\\Model\\User;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Symfony\\Component\\Yaml\\Yaml;\n\nfinal class DeliveryCostTest extends IntegrationTestCase\n{\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        DatabaseProvider::getDb()\n            ->execute(\n                'UPDATE oxdelivery SET oxactive = 0'\n            );\n    }\n\n    public static function providerDeliveryCostRules(): array\n    {\n        $testCases = [];\n        foreach (glob(__DIR__ . '/testcases/delivery_cost/rules/*.yaml') as $filePath) {\n            $testCases[$filePath] = [Yaml::parseFile($filePath)];\n        }\n        return $testCases;\n    }\n\n    #[DataProvider('providerDeliveryCostRules')]\n    public function testDeliveryCostRules(array $testCase): void\n    {\n        $basket = (new BasketConstruct())->calculateBasket($testCase);\n\n        $this->assertEquals(\n            $testCase['expected']['costs']['totals']['delivery']['brutto'],\n            $basket->getDeliveryCosts(),\n        );\n    }\n\n    public static function providerDeliveryCostRulesWithRuleAssignment(): array\n    {\n        $testCases = [];\n        foreach (glob(__DIR__ . '/testcases/delivery_cost/rules_assigned_to/*.yaml') as $filePath) {\n            $testCases[$filePath] = [Yaml::parseFile($filePath)];\n        }\n        return $testCases;\n    }\n\n    #[DataProvider('providerDeliveryCostRulesWithRuleAssignment')]\n    public function testGetDeliveryCostsWithRuleAssignedWillUseCorrectRuleCost($testCase): void\n    {\n        $basket = (new BasketConstruct())->calculateBasket($testCase);\n\n        $this->assertEquals(\n            $testCase['expected']['costs']['totals']['delivery']['brutto'],\n            $basket->getDeliveryCosts(),\n        );\n    }\n\n    #[DataProvider('providerDeliveryCostRulesWithRuleAssignment')]\n    public function testGetListWithRuleAssigned($testCase): void\n    {\n        (new BasketConstruct())->calculateBasket($testCase);\n\n        $activeDeliveries = oxNew(DeliveryList::class)->getList();\n        $this->assertCount(3, $activeDeliveries);\n    }\n\n    #[DataProvider('providerDeliveryCostRulesWithRuleAssignment')]\n    public function testHasDeliveriesWithRuleAssigned($testCase): void\n    {\n        $basket = (new BasketConstruct())->calculateBasket($testCase);\n\n        $user = oxNew(User::class);\n        $user->load($testCase['user']['oxid']);\n        $deliveryList = oxNew(DeliveryList::class);\n\n        $hasDeliveries = $deliveryList->hasDeliveries(\n            $basket,\n            $user,\n            BasketConstruct::getDefaultCountryId(),\n            $basket->getShippingId(),\n        );\n\n        $this->assertTrue($hasDeliveries);\n    }\n\n    #[DataProvider('providerDeliveryCostRulesWithRuleAssignment')]\n    public function testGetDeliveryListWithRuleAssignedWillSkipWrongRule($testCase): void\n    {\n        $basket = (new BasketConstruct())->calculateBasket($testCase);\n\n        $user = oxNew(User::class);\n        $user->load($testCase['user']['oxid']);\n        $deliveryList = oxNew(DeliveryList::class);\n\n        $suitableDeliveries = $deliveryList->getDeliveryList(\n            $basket,\n            $user,\n            BasketConstruct::getDefaultCountryId(),\n            $basket->getShippingId(),\n        );\n\n        $this->assertCount(1, $suitableDeliveries);\n        $this->assertEquals('ok-rule', reset($suitableDeliveries)->getFieldData('oxtitle'));\n    }\n\n    #[DataProvider('providerDeliveryCostRules')]\n    public function testDeliveryCostNotDoubledWithSameDeliveryObject(array $testCase): void\n    {\n        $basket = (new BasketConstruct())->calculateBasket($testCase);\n\n        $user = oxNew(User::class);\n        $user->load($testCase['user']['oxid']);\n        $deliveryList = oxNew(DeliveryList::class);\n        $deliveries = $deliveryList->getDeliveryList(\n            $basket,\n            $user,\n            BasketConstruct::getDefaultCountryId(),\n            $basket->getShippingId()\n        );\n        $delivery = reset($deliveries);\n\n        $delivery->isForBasket($basket);\n        $firstPrice = $delivery->getDeliveryPrice()->getBruttoPrice();\n\n        $delivery->setDeliveryPrice(null);\n        $delivery->isForBasket($basket);\n        $secondPrice = $delivery->getDeliveryPrice()->getBruttoPrice();\n\n        $this->assertEquals($firstPrice, $secondPrice);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/OrderNumberingTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Price;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Order;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\Attributes\\RunTestsInSeparateProcesses;\nuse Symfony\\Component\\Yaml\\Yaml;\n\n#[RunTestsInSeparateProcesses]\nfinal class OrderNumberingTest extends IntegrationTestCase\n{\n    public static function providerOrderNumbering(): array\n    {\n        $testCases = [];\n        foreach (glob(__DIR__ . '/testcases/order_numbering/*.yaml') as $filePath) {\n            $testCases[$filePath] = [Yaml::parseFile($filePath)];\n        }\n\n        return $testCases;\n    }\n\n    /**\n     * Tests order numbering with separateNumbering parameter.\n     */\n    #[DataProvider('providerOrderNumbering')]\n    public function testOrderNumberingForDifferentShops(array $testCase): void\n    {\n        $options = $testCase['options'];\n\n        $basket = (new BasketConstruct())->calculateBasket($testCase);\n\n        $user = $basket->getBasketUser();\n\n        $order1 = $this->getOrderStub();\n\n        if ($basket->getProductsCount()) {\n            $order1->finalizeOrder($basket, $user);\n        }\n\n        $order2 = $this->getOrderStub();\n        // If separate numbering, then it must be restarted.\n        $order2->setSeparateNumbering($options['separateNumbering']);\n\n        if ($basket->getProductsCount()) {\n            $order2->finalizeOrder($basket, $user);\n        }\n\n        $order1Nr = $order1->oxorder__oxordernr->value;\n        $order2Nr = $order2->oxorder__oxordernr->value;\n        if ($options['separateNumbering']) {\n            $this->assertEquals(1, $order2Nr, 'Second order must start from beginning if separate numbering.');\n        } else {\n            $this->assertEquals(\n                $order1Nr,\n                ($order2Nr - 1),\n                'Second order must had bigger number if no separate numbering.'\n            );\n        }\n    }\n\n    /**\n     * Tests order numbering when middle one is deleted.\n     */\n    #[DataProvider('providerOrderNumbering')]\n    public function testOrderNumberingForDifferentShops2(array $testCase): void\n    {\n        $options = $testCase['options'];\n\n        $basketConstruct = new BasketConstruct();\n        $basket = $basketConstruct->calculateBasket($testCase);\n\n        $user = $basket->getBasketUser();\n\n        $order1 = $this->getOrderStub();\n\n        if ($basket->getProductsCount()) {\n            $order1->finalizeOrder($basket, $user);\n        }\n\n        $order2 = $this->getOrderStub();\n\n        if ($basket->getProductsCount()) {\n            $order2->finalizeOrder($basket, $user);\n        }\n\n        $order2->delete();\n\n        $order3 = $this->getOrderStub();\n        // If separate numbering, then it must be restarted.\n        $order3->setSeparateNumbering($options['separateNumbering']);\n\n        if ($basket->getProductsCount()) {\n            $order3->finalizeOrder($basket, $user);\n        }\n\n        $order1Nr = $order1->oxorder__oxordernr->value;\n        $order3Nr = $order3->oxorder__oxordernr->value;\n        if ($options['separateNumbering']) {\n            $this->assertEquals(1, $order3Nr, 'Second order must start from beginning if separate numbering.');\n        } else {\n            $this->assertEquals(\n                $order1Nr,\n                ($order3Nr - 2),\n                'Second order must had bigger number if no separate numbering.'\n            );\n        }\n    }\n\n    /**\n     * Tests order numbering when middle one is saved without finalizing.\n     */\n    #[DataProvider('providerOrderNumbering')]\n    public function testOrderNumberingForDifferentShops3(array $testCase): void\n    {\n        $options = $testCase['options'];\n\n        $basketConstruct = new BasketConstruct();\n        $basket = $basketConstruct->calculateBasket($testCase);\n\n        $user = $basket->getBasketUser();\n\n        $order1 = $this->getOrderStub();\n\n        if ($basket->getProductsCount()) {\n            $order1->finalizeOrder($basket, $user);\n        }\n\n        $order2 = $this->getOrderStub();\n        $order2->save();\n\n        $order3 = $this->getOrderStub();\n        // If separate numbering, then it must be restarted.\n        $order3->setSeparateNumbering($options['separateNumbering']);\n\n        if ($basket->getProductsCount()) {\n            $order3->finalizeOrder($basket, $user);\n        }\n\n        $order1Nr = $order1->oxorder__oxordernr->value;\n        $order3Nr = $order3->oxorder__oxordernr->value;\n        if ($options['separateNumbering']) {\n            $this->assertEquals(1, $order3Nr, 'Second order must start from beginning if separate numbering.');\n        } else {\n            $this->assertEquals(\n                $order1Nr,\n                ($order3Nr - 1),\n                'Second order must had bigger number if no separate numbering.'\n            );\n        }\n    }\n\n    private function getOrderStub(): Order\n    {\n        $order = $this\n            ->getStubBuilder(Order::class)\n            ->disableOriginalConstructor()\n            ->onlyMethods([\n                'sendOrderByEmail',\n                'validateDeliveryAddress',\n                'validateDelivery',\n                'validatePayment',\n                'getCoreTableName',\n            ])\n            ->getStub();\n\n        $order->method('sendOrderByEmail')->willReturn(0);\n        $order->method('validateDeliveryAddress')->willReturn(null);\n        $order->method('validateDelivery')->willReturn(null);\n        $order->method('validatePayment')->willReturn(null);\n        $order->method('getCoreTableName')->willReturn('oxorder');\n\n        return $order;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/OrderTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Price;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Application\\Model\\DeliveryList;\nuse OxidEsales\\Eshop\\Application\\Model\\Order;\nuse OxidEsales\\Eshop\\Application\\Model\\OrderArticle;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\Attributes\\RunTestsInSeparateProcesses;\nuse Symfony\\Component\\Yaml\\Yaml;\n\n#[RunTestsInSeparateProcesses]\nfinal class OrderTest extends IntegrationTestCase\n{\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->truncateDatabaseData();\n    }\n\n    public static function providerOrderCalculation(): array\n    {\n        $testCases = [];\n        foreach (glob(__DIR__ . '/testcases/order/*.yaml') as $filePath) {\n            $testCases[$filePath] = [Yaml::parseFile($filePath)];\n        }\n\n        return $testCases;\n    }\n\n    #[DataProvider('providerOrderCalculation')]\n    public function testOrderCalculation(array $testCase): void\n    {\n        $expected = $testCase['expected'];\n        $actions = $testCase['actions'] ?? null;\n\n        $basket = (new BasketConstruct())->calculateBasket($testCase);\n        $user = $basket->getBasketUser();\n        $order = $this->getOrderStub();\n\n        if ($basket->getProductsCount()) {\n            $success = $order->finalizeOrder($basket, $user);\n            $this->assertEquals(0, $success);\n        }\n\n        $this->checkTotals($expected, 1, $order);\n        if (!empty($actions)) {\n            foreach ($actions as $function => $parameters) {\n                match ($function) {\n                    'changeConfigs' => $this->changeConfigs($parameters),\n                    'changeArticles' => $this->changeProducts($parameters, $order),\n                    'addArticles' => $this->addProducts($parameters, $order),\n                    'removeArticles' => $this->removeProducts($parameters, $order),\n                };\n            }\n            Registry::set(DeliveryList::class, null);\n            $order->recalculateOrder();\n            $this->checkTotals($expected, 2, $order);\n        }\n    }\n\n    private function truncateDatabaseData(): void\n    {\n        $database = DatabaseProvider::getDb();\n        $database->execute('DELETE FROM oxarticles WHERE 1');\n        $database->execute('DELETE FROM oxcategories WHERE 1');\n        $database->execute('DELETE FROM oxdiscount WHERE 1');\n        $database->execute('DELETE FROM oxobject2discount WHERE 1');\n        $database->execute('DELETE FROM oxwrapping WHERE 1');\n        $database->execute('DELETE FROM oxdelivery WHERE 1');\n        $database->execute('DELETE FROM oxdel2delset WHERE 1');\n        $database->execute('DELETE FROM oxobject2payment WHERE 1');\n        $database->execute('DELETE FROM oxobject2category WHERE 1');\n        $database->execute('DELETE FROM oxvouchers WHERE 1');\n        $database->execute('DELETE FROM oxvoucherseries WHERE 1');\n        $database->execute('DELETE FROM oxuser WHERE 1');\n        $database->execute('DELETE FROM oxdeliveryset WHERE 1');\n        $database->execute('DELETE FROM oxpayments WHERE 1');\n        $database->execute('DELETE FROM oxprice2article WHERE 1');\n    }\n\n    private function checkTotals(array $expected, int $iteration, Order $order): void\n    {\n        $expectedTotals = $expected[$iteration]['totals'];\n        $products = $expected[$iteration]['articles'];\n        $isNettoMode = $order->isNettoMode();\n        $orderProduct = $order->getOrderArticles();\n\n        foreach ($orderProduct as $product) {\n            $productId = $product->getFieldData('oxartid');\n            if ($isNettoMode) {\n                $unitPrice = $product->getNetPriceFormated();\n                $totalPrice = $product->getTotalNetPriceFormated();\n            } else {\n                $unitPrice = $product->getBrutPriceFormated();\n                $totalPrice = $product->getTotalBrutPriceFormated();\n            }\n            if (isset($products[$productId])) {\n                $this->assertEquals(\n                    $products[$productId][0],\n                    $unitPrice,\n                    \"#$iteration Unit price of order art no #{$productId}\"\n                );\n                $this->assertEquals(\n                    $products[$productId][1],\n                    $totalPrice,\n                    \"#$iteration Total price of order art no #{$productId}\"\n                );\n            }\n        }\n\n        $productVats = $order->getProductVats(true);\n\n        $this->assertEquals(\n            $expectedTotals['totalNetto'],\n            $order->getFormattedTotalNetSum(),\n            \"Product Net Price #$iteration\"\n        );\n        $this->assertEquals($expectedTotals['discount'], $order->getFormattedDiscount(), \"Discount #$iteration\");\n\n        if ($productVats) {\n            foreach ($productVats as $vat => $vatPrice) {\n                $this->assertEquals($expectedTotals['vats'][$vat], $vatPrice, \"Vat %{$vat} total cost #$iteration\");\n            }\n        }\n\n        $this->assertEquals(\n            $expectedTotals['totalBrutto'],\n            $order->getFormattedTotalBrutSum(),\n            \"Product Gross Price #$iteration\"\n        );\n\n        if ($expectedTotals['voucher'] ?? null) {\n            $this->assertEquals(\n                $expectedTotals['voucher']['brutto'],\n                $order->getFormattedTotalVouchers(),\n                \"Voucher costs #$iteration\"\n            );\n        }\n\n        if ($expectedTotals['delivery'] ?? null) {\n            $this->assertEquals(\n                $expectedTotals['delivery']['brutto'],\n                $order->getFormattedDeliveryCost(),\n                \"Shipping costs #$iteration\"\n            );\n        }\n\n        if ($expectedTotals['wrapping'] ?? null) {\n            $this->assertEquals(\n                $expectedTotals['wrapping']['brutto'],\n                $order->getFormattedWrapCost(),\n                \"Wrapping costs #$iteration\"\n            );\n        }\n\n        if ($expectedTotals['giftcard'] ?? null) {\n            $this->assertEquals(\n                $expectedTotals['giftcard']['brutto'],\n                $order->getFormattedGiftCardCost(),\n                \"Giftcard costs #$iteration\"\n            );\n        }\n\n        if ($expectedTotals['payment'] ?? null) {\n            $this->assertEquals(\n                $expectedTotals['payment']['brutto'],\n                $order->getFormattedPayCost(),\n                \"Charge Payment Method #$iteration\"\n            );\n        }\n        $this->assertEquals(\n            $expectedTotals['grandTotal'],\n            $order->getFormattedTotalOrderSum(),\n            \"Sum total #$iteration\"\n        );\n    }\n\n    /* --- Expected functions for changing saved order --- */\n\n    private function changeConfigs(array $configOptions): void\n    {\n        if (!empty($configOptions)) {\n            foreach ($configOptions as $sKey => $sValue) {\n                Registry::getConfig()->setConfigParam($sKey, $sValue);\n            }\n        }\n    }\n\n    private function addProducts(array $productsData, Order $order): void\n    {\n        $products = (new BasketConstruct())->createProducts($productsData);\n        foreach ($products as $productData) {\n            $product = oxNew(Article::class);\n            $product->load($productData['id']);\n            $amount = $productData['amount'];\n            $orderProduct = oxNew(OrderArticle::class);\n            $orderProduct->oxorderarticles__oxartid = new Field($product->getId());\n            $orderProduct->oxorderarticles__oxartnum = new Field($product->oxarticles__oxartnum->value);\n            $orderProduct->oxorderarticles__oxamount = new Field($amount);\n            $orderProduct->oxorderarticles__oxselvariant = new Field(\n                Registry::getRequest()->getRequestEscapedParameter('sel')\n            );\n            $order->recalculateOrder([$orderProduct]);\n        }\n    }\n\n    private function removeProducts(array $productIds, $order): void\n    {\n        foreach ($order->getOrderArticles() as $orderProduct) {\n            foreach ($productIds as $productId) {\n                if ((int)$orderProduct->getFieldData('oxartid') === (int)$productId) {\n                    $orderProduct->delete();\n                }\n            }\n        }\n    }\n\n    private function changeProducts(array $productAmounts, $order): void\n    {\n        foreach ($order->getOrderArticles() as $orderProduct) {\n            foreach ($productAmounts as $productAmount) {\n                if ((int)$orderProduct->getFieldData('oxartid') === (int)$productAmount['oxid']) {\n                    $orderProduct->setNewAmount($productAmount['amount']);\n                }\n            }\n        }\n    }\n\n    private function getOrderStub(): Order\n    {\n        $order = $this\n            ->getStubBuilder(Order::class)\n            ->disableOriginalConstructor()\n            ->onlyMethods([\n                'sendOrderByEmail',\n                'validateDeliveryAddress',\n                'validateDelivery',\n                'validatePayment',\n                'getCoreTableName',\n            ])\n            ->getStub();\n\n        $order->method('sendOrderByEmail')->willReturn(0);\n        $order->method('validateDeliveryAddress')->willReturn(null);\n        $order->method('validateDelivery')->willReturn(null);\n        $order->method('validatePayment')->willReturn(null);\n        $order->method('getCoreTableName')->willReturn('oxorder');\n\n        return $order;\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/PriceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Price;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Core\\DatabaseProvider;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Symfony\\Component\\Yaml\\Yaml;\n\nuse function number_format;\nuse function round;\n\nfinal class PriceTest extends IntegrationTestCase\n{\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->truncateDatabaseData();\n    }\n\n    public static function providerPrice(): array\n    {\n        $testCases = [];\n        foreach (glob(__DIR__ . '/testcases/price/*.yaml') as $filePath) {\n            $testCases[$filePath] = [Yaml::parseFile($filePath)];\n        }\n        return $testCases;\n    }\n\n    #[DataProvider('providerPrice')]\n    public function testPrice(array $testCase): void\n    {\n        $basket = new BasketConstruct();\n        // create user if specified\n        $oUser = $basket->createObj($testCase['user'] ?? [], 'oxuser', 'oxuser');\n        // create group and assign\n        $basket->createGroup($testCase['group'] ?? []);\n        // user login\n        if ($oUser) {\n            $oUser->load($oUser->getId());\n            $oUser->login($testCase['user']['oxusername'], '');\n        }\n        // setup options\n        $basket->setOptions($testCase['options']);\n        // create categories\n        $basket->createCategories($testCase['categories'] ?? []);\n        // create articles\n        $products = $basket->createProducts($testCase['articles']);\n        // apply discounts\n        $basket->createDiscounts($testCase['discounts'] ?? []);\n\n        // iteration through expectations\n        foreach ($products as $productData) {\n            $expected = $testCase['expected'][$productData['id']] ?? null;\n            if (empty($expected)) {\n                continue;\n            }\n            $product = oxNew(Article::class);\n            $product->load($productData['id']);\n\n            $this->assertEquals(\n                $expected['base_price'],\n                number_format(round($product->getBasePrice(), 2), 2, ',', '.'),\n                \"Base Price of product #{$productData['id']}\"\n            );\n            $this->assertEquals($expected['price'], $product->getFPrice(), \"Price of product #{$productData['id']}\");\n\n            if (isset($expected['rrp_price'])) {\n                $this->assertEquals(\n                    $expected['rrp_price'],\n                    $product->getFTPrice(),\n                    \"RRP price of product #{$productData['id']}\"\n                );\n            }\n\n            if (isset($expected['unit_price'])) {\n                $this->assertEquals(\n                    $expected['unit_price'],\n                    $product->getFUnitPrice(),\n                    \"Unit Price of product #{$productData['id']}\"\n                );\n            }\n\n            if (isset($expected['is_range_price'])) {\n                $this->assertEquals(\n                    $expected['is_range_price'],\n                    $product->isRangePrice(),\n                    \"Is range price check of product #{$productData['id']}\"\n                );\n            }\n\n            if (isset($expected['min_price'])) {\n                $this->assertEquals(\n                    $expected['min_price'],\n                    $product->getFMinPrice(),\n                    \"Min price of product #{$productData['id']}\"\n                );\n            }\n\n            if (isset($expected['var_min_price'])) {\n                $this->assertEquals(\n                    $expected['var_min_price'],\n                    $product->getFVarMinPrice(),\n                    \"Var min price of product #{$productData['id']}\"\n                );\n            }\n\n            if (isset($expected['show_rrp'])) {\n                $blShowRPP = false;\n                if ($product->getTPrice() && $product->getTPrice()->getPrice() > $product->getPrice()->getPrice()) {\n                    $blShowRPP = true;\n                }\n                $this->assertEquals(\n                    $expected['show_rrp'],\n                    $blShowRPP,\n                    \"RRP price showing of product #{$productData['id']}\"\n                );\n            }\n        }\n    }\n\n    private function truncateDatabaseData(): void\n    {\n        $database = DatabaseProvider::getDb();\n        $database->execute('DELETE FROM oxarticles WHERE 1');\n        $database->execute('DELETE FROM oxdiscount WHERE 1');\n        $database->execute('DELETE FROM oxprice2article WHERE 1');\n        $database->execute('DELETE FROM oxobject2discount WHERE 1');\n        $database->execute('DELETE FROM oxuser WHERE 1');\n        $database->execute('DELETE FROM oxobject2group WHERE 1');\n        $database->execute('DELETE FROM oxgroups WHERE 1');\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/ProductVatTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Price;\n\nuse OxidEsales\\Eshop\\Application\\Component\\UserComponent;\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Application\\Model\\Basket;\nuse OxidEsales\\Eshop\\Application\\Model\\User;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\ShopIdCalculator;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ProductVatTest extends IntegrationTestCase\n{\n    private string $productId1 = '101';\n    private string $productId2 = '102';\n    private string $productId3 = '103';\n    private array $countriesId = [\n        'germany' => 'a7c40f631fc920687.20179984',\n        'switzerland' => 'a7c40f6321c6f6109.43859248',\n    ];\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->createProduct($this->productId1, 20);\n        $this->createProduct($this->productId2, 30);\n        $this->createProduct($this->productId3, 40);\n\n        $this->createActiveUser();\n        $this->updateProductVat($this->productId1, 5);\n        $this->updateProductVat($this->productId2, 10);\n    }\n\n    private function createActiveUser(): void\n    {\n        $sTestUserId = substr_replace(Registry::getUtilsObject()->generateUId(), '_', 0, 1);\n\n        $user = oxNew(User::class);\n        $user->setId($sTestUserId);\n\n        $user->oxuser__oxactive = new Field('1');\n        $user->oxuser__oxrights = new Field('user');\n        $user->oxuser__oxshopid = new Field(ShopIdCalculator::BASE_SHOP_ID);\n        $user->oxuser__oxusername = new Field('testuser@oxideshop.dev');\n        $user->oxuser__oxpassword = new Field(\n            'c630e7f6dd47f9ad60ece4492468149bfed3da3429940181464baae99941d0ffa5562' .\n            'aaecd01eab71c4d886e5467c5fc4dd24a45819e125501f030f61b624d7d'\n        ); //password is asdfasdf\n        $user->oxuser__oxpasssalt = new Field('3ddda7c412dbd57325210968cd31ba86');\n        $user->oxuser__oxcustnr = new Field('667');\n        $user->oxuser__oxfname = new Field('Erna');\n        $user->oxuser__oxlname = new Field('Helvetia');\n        $user->oxuser__oxstreet = new Field('Dorfstrasse');\n        $user->oxuser__oxstreetnr = new Field('117');\n        $user->oxuser__oxcity = new Field('Oberbuchsiten');\n        $user->oxuser__oxcountryid = new Field($this->countriesId[strtolower('germany')]);\n        $user->oxuser__oxzip = new Field('4625');\n        $user->oxuser__oxsal = new Field('MRS');\n        $user->oxuser__oxcreate = new Field('2015-05-20 22:10:51');\n        $user->oxuser__oxregister = new Field('2015-05-20 22:10:51');\n        $user->oxuser__oxboni = new Field('1000');\n\n        $user->save();\n    }\n\n    private function createProduct(string $productId, int $price): void\n    {\n        $product = oxNew(Article::class);\n        $product->setAdminMode(false);\n        $product->setId($productId);\n        $product->oxarticles__oxprice = new Field($price);\n        $product->oxarticles__oxshopid = new Field(1);\n        $product->oxarticles__oxtitle = new Field('test');\n        $product->save();\n    }\n\n    private function updateProductVat(string $productId, int $vat): void\n    {\n        $product = oxNew(Article::class);\n        $product->setId($productId);\n        $product->oxarticles__oxvat = new Field($vat);\n        $product->save();\n    }\n\n    public function testProductVat(): void\n    {\n        $basket = oxNew(Basket::class);\n        $basket->addToBasket($this->productId1, 1);\n        $basket->addToBasket($this->productId2, 1);\n        $basket->addToBasket($this->productId3, 1);\n\n        $basket->calculateBasket(true);\n        $this->assertSame(79.93, $basket->getNettoSum());\n\n        $this->assertSame([\n            5 => '0,95',\n            10 => '2,73',\n            19 => '6,39',\n        ], $basket->getProductVats(true));\n\n        $this->loginUser();\n\n        $this->changeUserAddress();\n\n        $basket->calculateBasket(true);\n        $this->assertSame(79.93, $basket->getNettoSum());\n\n        $this->assertSame([\n            0 => '0,00',\n        ], $basket->getProductVats(true));\n    }\n\n    private function loginUser(): void\n    {\n        $_POST['lgn_usr'] = 'testuser@oxideshop.dev';\n        $_POST['lgn_pwd'] = 'asdfasdf';\n        oxNew(UserComponent::class)->login();\n    }\n\n    private function changeUserAddress(): void\n    {\n        $countryInfo = [\n            'germany' => [\n                'oxuser__oxfname' => 'Erna',\n                'oxuser__oxlname' => 'Hahnentritt',\n                'oxuser__oxstreetnr' => '117',\n                'oxuser__oxstreet' => 'Landstrasse',\n                'oxuser__oxzip' => '22769',\n                'oxuser__oxcity' => 'Hamburg',\n                'oxuser__oxcountryid' => $this->countriesId['germany'],\n            ],\n            'switzerland' => [\n                'oxuser__oxfname' => 'Erna',\n                'oxuser__oxlname' => 'Hahnentritt',\n                'oxuser__oxstreetnr' => '117',\n                'oxuser__oxstreet' => 'Landstrasse',\n                'oxuser__oxzip' => '3741',\n                'oxuser__oxcity' => 'PULKAU',\n                'oxuser__oxcountryid' => $this->countriesId['switzerland'],\n            ],\n        ];\n\n        $_POST['invadr'] = $countryInfo[strtolower('switzerland')];\n        $_POST['stoken'] = Registry::getSession()->getSessionChallengeToken();\n\n        $userComponent = oxNew(UserComponent::class);\n        $this->assertSame('payment', $userComponent->changeUser());\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/VatForShippingCountryTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Price;\n\nuse OxidEsales\\Eshop\\Application\\Component\\UserComponent;\nuse OxidEsales\\Eshop\\Application\\Model\\Address;\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Application\\Model\\Basket;\nuse OxidEsales\\Eshop\\Application\\Model\\User;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\ShopIdCalculator;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class VatForShippingCountryTest extends IntegrationTestCase\n{\n    private const USER_ID = '_testVatUserId';\n    private const ADDRESS_ID = '_testVatAddressId';\n    private const FIRST_PRODUCT_ID = '101';\n    private const SECOND_PRODUCT_ID = '102';\n    private const THIRD_PRODUCT_ID = '103';\n    private array $addressInfo = [];\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->initiateAddressInfo();\n\n        $this->createProduct(self::FIRST_PRODUCT_ID, 20);\n        $this->createProduct(self::SECOND_PRODUCT_ID, 30);\n        $this->createProduct(self::THIRD_PRODUCT_ID, 40);\n\n        $this->createActiveUser();\n        $this->updateProductVat(self::FIRST_PRODUCT_ID, 5);\n        $this->updateProductVat(self::SECOND_PRODUCT_ID, 10);\n    }\n\n    public function testProductVat(): void\n    {\n        $config = Registry::getConfig();\n        $config->setConfigParam('blShippingCountryVat', true);\n\n        $basket = oxNew(Basket::class);\n        $basket->addToBasket(self::FIRST_PRODUCT_ID, 1);\n        $basket->addToBasket(self::SECOND_PRODUCT_ID, 1);\n        $basket->addToBasket(self::THIRD_PRODUCT_ID, 1);\n        $basket->calculateBasket(true);\n\n        $this->loginUser();\n\n        // Billing Germany - shipping Germany\n        $this->setBillingAddress('Germany');\n        $this->setShippingAddress('Germany');\n        $this->assertSame([\n            5 => '0,95',\n            10 => '2,73',\n            19 => '6,39',\n        ], $basket->getProductVats(true));\n\n        // Billing Germany - shipping Switzerland\n        $this->setBillingAddress('Germany');\n        $this->setShippingAddress('Switzerland');\n        $basket->calculateBasket(true);\n        $this->assertSame([\n            0 => '0,00',\n        ], $basket->getProductVats(true));\n\n        // Billing Switzerland - shipping Germany\n        $this->setBillingAddress('Switzerland');\n        $this->setShippingAddress('Germany');\n        $basket->calculateBasket(true);\n        $this->assertSame([\n            5 => '0,95',\n            10 => '2,73',\n            19 => '6,39',\n        ], $basket->getProductVats(true));\n    }\n\n    private function initiateAddressInfo(): void\n    {\n        $germany = [\n            'oxfname' => 'Erna',\n            'oxlname' => 'Hahnentritt',\n            'oxstreetnr' => '117',\n            'oxstreet' => 'Landstrasse',\n            'oxzip' => '22769',\n            'oxcity' => 'Hamburg',\n            'oxcountryid' => 'a7c40f631fc920687.20179984',\n            'oxcompany' => 'myCompany',\n            'oxfon' => '217-8918713',\n            'oxfax' => '217-8918713',\n            'oxsal' => 'MRS',\n        ];\n\n        $switzerland = [\n            'oxfname' => 'Erna',\n            'oxlname' => 'Hahnentritt',\n            'oxstreetnr' => '117',\n            'oxstreet' => 'Landstrasse',\n            'oxzip' => '3741',\n            'oxcity' => 'PULKAU',\n            'oxcountryid' => 'a7c40f6321c6f6109.43859248',\n            'oxcompany' => 'myCompany',\n            'oxfon' => '217-8918713',\n            'oxfax' => '217-8918713',\n            'oxsal' => 'MRS',\n        ];\n\n        $this->addressInfo = [\n            'Germany' => $germany,\n            'Switzerland' => $switzerland,\n        ];\n    }\n\n    private function createProduct(string $productId, int $price): void\n    {\n        $product = oxNew(Article::class);\n        $product->setAdminMode(false);\n        $product->setId($productId);\n        $product->oxarticles__oxprice = new Field($price);\n        $product->oxarticles__oxshopid = new Field(1);\n        $product->oxarticles__oxtitle = new Field('test');\n        $product->save();\n    }\n\n    private function createActiveUser(): void\n    {\n        $addressInfo = $this->addressInfo['Germany'];\n\n        $user = oxNew(User::class);\n        $user->setId(self::USER_ID);\n\n        $user->oxuser__oxactive = new Field('1');\n        $user->oxuser__oxrights = new Field('user');\n        $user->oxuser__oxshopid = new Field(ShopIdCalculator::BASE_SHOP_ID);\n        $user->oxuser__oxusername = new Field('testuser@oxideshop.dev');\n        $user->oxuser__oxpassword = new Field(\n            'c630e7f6dd47f9ad60ece4492468149bfed3da3429940181464baae99941d0ffa5562' .\n            'aaecd01eab71c4d886e5467c5fc4dd24a45819e125501f030f61b624d7d'\n        ); //password is asdfasdf\n        $user->oxuser__oxpasssalt = new Field('3ddda7c412dbd57325210968cd31ba86');\n        $user->oxuser__oxcustnr = new Field('667');\n        $user->oxuser__oxcreate = new Field('2015-05-20 22:10:51');\n        $user->oxuser__oxregister = new Field('2015-05-20 22:10:51');\n        $user->oxuser__oxboni = new Field('1000');\n\n        $user->oxuser__oxfname = new Field($addressInfo['oxfname']);\n        $user->oxuser__oxlname = new Field($addressInfo['oxlname']);\n        $user->oxuser__oxstreet = new Field($addressInfo['oxstreet']);\n        $user->oxuser__oxstreetnr = new Field($addressInfo['oxstreetnr']);\n        $user->oxuser__oxcity = new Field($addressInfo['oxcity']);\n        $user->oxuser__oxcountryid = new Field($addressInfo['oxcountryid']);\n        $user->oxuser__oxzip = new Field($addressInfo['oxzip']);\n        $user->oxuser__oxsal = new Field($addressInfo['oxsal']);\n\n        $user->save();\n    }\n\n    private function updateProductVat(string $productId, int $vat): void\n    {\n        $product = oxNew(Article::class);\n        $product->setId($productId);\n        $product->oxarticles__oxvat = new Field($vat);\n        $product->save();\n    }\n\n    private function loginUser(): void\n    {\n        $_POST['lgn_usr'] = 'testuser@oxideshop.dev';\n        $_POST['lgn_pwd'] = 'asdfasdf';\n        oxNew(UserComponent::class)->login();\n    }\n\n    private function setBillingAddress(string $country): void\n    {\n        $addressInfo = $this->addressInfo[$country];\n\n        $_POST['invadr'] = [\n            'oxuser__oxfname' => $addressInfo['oxfname'],\n            'oxuser__oxlname' => $addressInfo['oxlname'],\n            'oxuser__oxstreetnr' => $addressInfo['oxstreetnr'],\n            'oxuser__oxstreet' => $addressInfo['oxstreet'],\n            'oxuser__oxzip' => $addressInfo['oxzip'],\n            'oxuser__oxcity' => $addressInfo['oxcity'],\n            'oxuser__oxcountryid' => $addressInfo['oxcountryid'],\n        ];\n\n        $_POST['stoken'] = Registry::getSession()->getSessionChallengeToken();\n\n        $this->assertSame('payment', oxNew(UserComponent::class)->changeUser());\n    }\n\n    private function setShippingAddress(string $country): void\n    {\n        $addressId = self::ADDRESS_ID . $country;\n        $addressInfo = $this->addressInfo[$country];\n\n        $address = oxNew(Address::class);\n        $address->setId($addressId);\n\n        $address->oxaddress__oxuserid = new Field(self::USER_ID);\n        $address->oxaddress__oxaddressuserid = new Field(self::USER_ID);\n        $address->oxaddress__oxfname = new Field($addressInfo['oxfname']);\n        $address->oxaddress__oxlname = new Field($addressInfo['oxlname']);\n        $address->oxaddress__oxstreetnr = new Field($addressInfo['oxstreetnr']);\n        $address->oxaddress__oxstreet = new Field($addressInfo['oxstreet']);\n        $address->oxaddress__oxzip = new Field($addressInfo['oxzip']);\n        $address->oxaddress__oxcity = new Field($addressInfo['oxcity']);\n        $address->oxaddress__oxcountryid = new Field($addressInfo['oxcountryid']);\n        $address->oxaddress__oxcompany = new Field($addressInfo['oxcompany']);\n        $address->oxaddress__oxaddinfo = new Field(null);\n        $address->oxaddress__oxstateid = new Field(null);\n        $address->oxaddress__oxfon = new Field($addressInfo['oxfon']);\n        $address->oxaddress__oxfax = new Field($addressInfo['oxfax']);\n        $address->oxaddress__oxsal = new Field($addressInfo['oxsal']);\n\n        $address->save();\n        Registry::getSession()->setVariable('deladrid', $addressId);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/VoucherTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Price;\n\nuse DateTime;\nuse OxidEsales\\Eshop\\Application\\Component\\UserComponent;\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Application\\Model\\Basket;\nuse OxidEsales\\Eshop\\Application\\Model\\Category;\nuse OxidEsales\\Eshop\\Application\\Model\\Object2Category;\nuse OxidEsales\\Eshop\\Application\\Model\\Object2Group;\nuse OxidEsales\\Eshop\\Application\\Model\\User;\nuse OxidEsales\\Eshop\\Application\\Model\\Voucher;\nuse OxidEsales\\Eshop\\Application\\Model\\VoucherSerie;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Model\\BaseModel;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\Eshop\\Core\\ShopIdCalculator;\nuse OxidEsales\\EshopCommunity\\Core\\Exception\\VoucherException;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class VoucherTest extends IntegrationTestCase\n{\n    private const FIRST_VOUCHER_ID = 'testVoucherId1';\n    private const SECOND_VOUCHER_ID = 'testVoucherId2';\n    private const FIRST_VOUCHER_SERIES_ID = 'testVoucherSeries1';\n    private const SECOND_VOUCHER_SERIES_ID = 'testVoucherSeries2';\n    private const FIRST_PRODUCT_ID = '101';\n    private const SECOND_PRODUCT_ID = '102';\n    private const THIRD_PRODUCT_ID = '103';\n    private const FOURTH_PRODUCT_ID = '104';\n    private const GROUP_ID = 'oxidnewcustomer';\n    private const FIRST_TEST_CATEGORY_ID = 'testCategory1';\n    private const SECOND_TEST_CATEGORY_ID = 'testCategory2';\n    private const FIRST_VOUCHER_NUMBER = 'test111';\n    private const SECOND_VOUCHER_NUMBER = 'test222';\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->createCategory(self::FIRST_TEST_CATEGORY_ID, 'Test Title 1');\n        $this->createCategory(self::SECOND_TEST_CATEGORY_ID, 'Test Title 2');\n        $this->addProductToCategory(self::FIRST_PRODUCT_ID, self::FIRST_TEST_CATEGORY_ID);\n        $this->addProductToCategory(self::SECOND_PRODUCT_ID, self::FIRST_TEST_CATEGORY_ID);\n        $this->addProductToCategory(self::THIRD_PRODUCT_ID, self::SECOND_TEST_CATEGORY_ID);\n        $this->addProductToCategory(self::FOURTH_PRODUCT_ID, self::SECOND_TEST_CATEGORY_ID);\n\n        $this->createProduct(self::FIRST_PRODUCT_ID, 14);\n        $this->createProduct(self::SECOND_PRODUCT_ID, 6);\n        $this->createProduct(self::THIRD_PRODUCT_ID, 10);\n        $this->createProduct(self::FOURTH_PRODUCT_ID, 10);\n\n        $this->createVoucherSeries(self::FIRST_VOUCHER_SERIES_ID, 5, true);\n        $this->createVoucherSeries(self::SECOND_VOUCHER_SERIES_ID, 10, false);\n\n        $this->addVoucherToSeries(\n            self::FIRST_VOUCHER_ID,\n            self::FIRST_VOUCHER_SERIES_ID,\n            self::FIRST_VOUCHER_NUMBER\n        );\n        $this->addVoucherToSeries(\n            self::SECOND_VOUCHER_ID,\n            self::SECOND_VOUCHER_SERIES_ID,\n            self::SECOND_VOUCHER_NUMBER\n        );\n    }\n\n    public function testVoucherForSpecificCategory(): void\n    {\n        $this->assignVoucherToCategory(self::FIRST_VOUCHER_SERIES_ID, self::FIRST_TEST_CATEGORY_ID);\n        $this->assignVoucherToCategory(self::SECOND_VOUCHER_SERIES_ID, self::SECOND_TEST_CATEGORY_ID);\n\n        // Voucher and product in basket Are not in same category so voucher does not work\n        $basket = oxNew(Basket::class);\n        $basket->addToBasket(self::FIRST_PRODUCT_ID, 1);\n        $basket->addToBasket(self::SECOND_PRODUCT_ID, 1);\n\n        $session = Registry::getSession();\n        $session->setBasket($basket);\n\n        $basket->calculateBasket(true);\n        $this->assertEquals(16.81, $basket->getNettoSum());\n\n        $basket->addVoucher(self::SECOND_VOUCHER_NUMBER);\n\n        $basket->calculateBasket(true);\n        $this->assertEquals(16.81, $basket->getNettoSum());\n\n        // Apply a voucher that in same category\n\n        $basket->addVoucher(self::FIRST_VOUCHER_NUMBER);\n\n        $basket->calculateBasket(true);\n        $this->assertEquals(12.61, $basket->getNettoSum());\n    }\n\n    public function testVoucherForSpecificProduct(): void\n    {\n        $this->assignVoucherSeriesToProduct(self::FIRST_VOUCHER_SERIES_ID, self::FIRST_PRODUCT_ID);\n        $this->assignVoucherSeriesToProduct(self::FIRST_VOUCHER_SERIES_ID, self::SECOND_PRODUCT_ID);\n        $this->assignVoucherSeriesToProduct(self::SECOND_VOUCHER_SERIES_ID, self::THIRD_PRODUCT_ID);\n\n        // Voucher and product in basket Are not in same category so voucher does not work\n        $basket = oxNew(Basket::class);\n        $basket->addToBasket(self::FIRST_PRODUCT_ID, 1);\n        $basket->addToBasket(self::SECOND_PRODUCT_ID, 1);\n\n        $session = Registry::getSession();\n        $session->setBasket($basket);\n\n        $basket->calculateBasket(true);\n        $this->assertEquals(16.81, $basket->getNettoSum());\n\n        $basket->addVoucher(self::SECOND_VOUCHER_NUMBER);\n\n        $basket->calculateBasket(true);\n        $this->assertEquals(16.81, $basket->getNettoSum());\n\n        // Apply a voucher that in same category as products\n\n        $basket->addVoucher(self::FIRST_VOUCHER_NUMBER);\n        $basket->calculateBasket(true);\n        $this->assertEquals(12.61, $basket->getNettoSum());\n    }\n\n    public function testVoucherForSpecificUserGroup(): void\n    {\n        $this->createUser();\n        $this->assignVoucherSeriesToUserGroup(self::FIRST_VOUCHER_SERIES_ID);\n        $this->assignVoucherSeriesToUserGroup(self::SECOND_VOUCHER_SERIES_ID);\n\n        // If user not login yet voucher should not work\n        $basket = oxNew(Basket::class);\n        $basket->addToBasket(self::FIRST_PRODUCT_ID, 1);\n        $basket->calculateBasket(true);\n        $this->assertEquals(11.76, $basket->getNettoSum());\n        $this->expectException(VoucherException::class);\n        $basket->addVoucher(self::FIRST_VOUCHER_NUMBER);\n        $basket->calculateBasket(true);\n        $this->assertEquals(11.76, $basket->getNettoSum());\n\n        $this->loginUser();\n\n        // After login voucher should work and reduce the basket price\n        $basket->removeVoucher(self::FIRST_VOUCHER_NUMBER);\n        $basket->addVoucher(self::FIRST_VOUCHER_NUMBER);\n        $basket->calculateBasket(true);\n        $this->assertEqualsWithDelta(7.56, $basket->getNettoSum(), 0.001);\n        $this->assertEquals(5.0, $basket->getVoucherDiscount()->getPrice());\n\n        $basket->addToBasket(self::THIRD_PRODUCT_ID, 1);\n        $basket->calculateBasket(true);\n        $this->assertEquals(15.97, $basket->getNettoSum());\n\n        // Test with another voucher in same userGroup\n        $basket->addVoucher(self::SECOND_VOUCHER_NUMBER);\n        $basket->calculateBasket(true);\n        $this->assertEqualsWithDelta(7.56, $basket->getNettoSum(), 0.001);\n        $this->assertEquals(15.0, $basket->getVoucherDiscount()->getPrice());\n\n        // Test with increasing quantity of one of the products\n        $basket->addToBasket(self::THIRD_PRODUCT_ID, 2);\n        $basket->calculateBasket(true);\n        $this->assertEquals(24.37, $basket->getNettoSum());\n        $this->assertEquals(15.0, $basket->getVoucherDiscount()->getPrice());\n    }\n\n    public function testVoucherCanBeApplyOnlyOnce(): void\n    {\n        $basket = oxNew(Basket::class);\n        $basket->addToBasket(self::FIRST_PRODUCT_ID, 1);\n        $basket->addToBasket(self::SECOND_PRODUCT_ID, 1);\n        $basket->calculateBasket(true);\n        $this->assertEquals(16.81, $basket->getNettoSum());\n\n        $basket->addVoucher(self::FIRST_VOUCHER_NUMBER);\n        $basket->calculateBasket(true);\n        $this->assertEquals(12.61, $basket->getNettoSum());\n        $this->assertEquals(5.0, $basket->getVoucherDiscount()->getPrice());\n    }\n\n    private function createVoucherSeries(string $seriesId, int $discount, bool $calculateOnce): void\n    {\n        $startDate = (new DateTime())->modify('-1 day')\n            ->format('Y-m-d H:i:s');\n        $endDate = (new DateTime())->modify('+1 day')\n            ->format('Y-m-d H:i:s');\n\n        $voucherSeries = oxNew(VoucherSerie::class);\n        $voucherSeries->setId($seriesId);\n        $voucherSeries->oxvoucherseries__oxshopid = new Field('1');\n        $voucherSeries->oxvoucherseries__oxdiscount = new Field($discount);\n        $voucherSeries->oxvoucherseries__oxdiscounttype = new Field('absolute');\n        $voucherSeries->oxvoucherseries__oxbegindate = new Field($startDate);\n        $voucherSeries->oxvoucherseries__oxenddate = new Field($endDate);\n        $voucherSeries->oxvoucherseries__oxallowsameseries = new Field('1');\n        $voucherSeries->oxvoucherseries__oxallowotherseries = new Field('1');\n        $voucherSeries->oxvoucherseries__oxallowuseanother = new Field('1');\n        $voucherSeries->oxvoucherseries__oxminimumvalue = new Field('0.00');\n        $voucherSeries->oxvoucherseries__oxcalculateonce = new Field((int) $calculateOnce);\n\n        $voucherSeries->save();\n    }\n\n    private function addVoucherToSeries(string $voucherId, string $seriesId, string $voucherNumber): void\n    {\n        $voucher = oxNew(Voucher::class);\n        $voucher->setId($voucherId);\n        $voucher->oxvouchers__oxvouchernr = new Field($voucherNumber);\n        $voucher->oxvouchers__oxvoucherserieid = new Field($seriesId);\n\n        $voucher->save();\n    }\n\n    private function createUser(): void\n    {\n        $sTestUserId = substr_replace(Registry::getUtilsObject()->generateUId(), '_', 0, 1);\n\n        $user = oxNew(User::class);\n        $user->setId($sTestUserId);\n\n        $user->oxuser__oxrights = new Field('user');\n        $user->oxuser__oxshopid = new Field(ShopIdCalculator::BASE_SHOP_ID);\n        $user->oxuser__oxusername = new Field('testuser@oxideshop.dev');\n        $user->oxuser__oxpassword = new Field(\n            'c630e7f6dd47f9ad60ece4492468149bfed3da3429940181464baae99941d0ffa5562' .\n            'aaecd01eab71c4d886e5467c5fc4dd24a45819e125501f030f61b624d7d'\n        ); //password is asdfasdf\n        $user->oxuser__oxpasssalt = new Field('3ddda7c412dbd57325210968cd31ba86');\n        $user->oxuser__oxcustnr = new Field('667');\n        $user->oxuser__oxfname = new Field('Erna');\n        $user->oxuser__oxlname = new Field('Helvetia');\n        $user->oxuser__oxstreet = new Field('Dorfstrasse');\n        $user->oxuser__oxstreetnr = new Field('117');\n        $user->oxuser__oxcity = new Field('Oberbuchsiten');\n        $user->oxuser__oxcountryid = new Field('a7c40f631fc920687.20179984');\n        $user->oxuser__oxzip = new Field('4625');\n        $user->oxuser__oxsal = new Field('MRS');\n        $user->oxuser__oxactive = new Field('1');\n        $user->oxuser__oxcreate = new Field('2015-05-20 22:10:51');\n        $user->oxuser__oxregister = new Field('2015-05-20 22:10:51');\n        $user->oxuser__oxboni = new Field('1000');\n\n        $user->save();\n\n        $group = oxNew(Object2Group::class);\n        $group->oxobject2group__oxobjectid = new Field($user->getId());\n        $group->oxobject2group__oxgroupsid = new Field(self::GROUP_ID);\n\n        $group->save();\n    }\n\n    private function assignVoucherSeriesToUserGroup(string $seriesId): void\n    {\n        $group = oxNew(Object2Group::class);\n        $group->setId(substr_replace(Registry::getUtilsObject()->generateUId(), '_', 0, 1));\n        $group->oxobject2group__oxshopid = new Field('1');\n        $group->oxobject2group__oxobjectid = new Field($seriesId);\n        $group->oxobject2group__oxgroupsid = new Field(self::GROUP_ID);\n\n        $group->save();\n    }\n\n    private function assignVoucherToCategory(string $voucherId, string $categoryId): void\n    {\n        $object2Discount = oxNew(BaseModel::class);\n        $object2Discount->init('oxobject2discount');\n        $object2Discount->setId(substr_replace(Registry::getUtilsObject()->generateUId(), '_', 0, 1));\n        $object2Discount->oxobject2discount__oxdiscountid = new Field($voucherId);\n        $object2Discount->oxobject2discount__oxobjectid = new Field($categoryId);\n        $object2Discount->oxobject2discount__oxtype = new Field('oxcategories');\n\n        $object2Discount->save();\n    }\n\n    private function loginUser(): void\n    {\n        $_POST['lgn_usr'] = 'testuser@oxideshop.dev';\n        $_POST['lgn_pwd'] = 'asdfasdf';\n        oxNew(UserComponent::class)->login();\n    }\n\n    private function createProduct(string $productId, int $price): void\n    {\n        $product = oxNew(Article::class);\n        $product->setAdminMode(false);\n        $product->setId($productId);\n        $product->oxarticles__oxprice = new Field($price);\n        $product->oxarticles__oxshopid = new Field(Registry::getConfig()->getBaseShopId());\n        $product->oxarticles__oxtitle = new Field('test_' . $productId);\n        $product->oxarticles__oxstock = new Field(100);\n        $product->oxarticles__oxactive = new Field('1');\n\n        $product->save();\n    }\n\n    private function createCategory(string $categoryId, string $title): void\n    {\n        $category = oxNew(Category::class);\n        $category->setId($categoryId);\n        $category->oxcategories__oxparentid = new Field('oxrootid');\n        $category->oxcategories__oxrootid = new Field($categoryId);\n        $category->oxcategories__oxactive = new Field(1);\n        $category->oxcategories__oxhidden = new Field(0);\n        $category->oxcategories__oxleft = new Field('1');\n        $category->oxcategories__oxright = new Field('2');\n        $category->oxcategories__oxshopid = new Field(Registry::getConfig()->getBaseShopId());\n        $category->oxcategories__oxtitle = new Field($title);\n\n        $category->save();\n    }\n\n    private function addProductToCategory(string $productId, string $categoryId): void\n    {\n        $category = oxNew(Object2Category::class);\n        $category->setId(substr_replace(Registry::getUtilsObject()->generateUId(), '_', 0, 1));\n        $category->oxobject2category__oxobjectid = new Field($productId);\n        $category->oxobject2category__oxcatnid = new Field($categoryId);\n        $category->oxobject2category__oxtime = new Field(time());\n\n        $category->save();\n    }\n\n    private function assignVoucherSeriesToProduct(string $seriesId, string $productId): void\n    {\n        $object2Discount = oxNew(BaseModel::class);\n        $object2Discount->init('oxobject2discount');\n        $object2Discount->setId(substr_replace(Registry::getUtilsObject()->generateUId(), '_', 0, 1));\n        $object2Discount->oxobject2discount__oxdiscountid = new Field($seriesId);\n        $object2Discount->oxobject2discount__oxobjectid = new Field($productId);\n        $object2Discount->oxobject2discount__oxtype = new Field('oxarticles');\n\n        $object2Discount->save();\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/PriceB2basket2.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 70.0, oxpricea: 70, oxpriceb: 85, oxpricec: 0, amount: 7, oxvat: 19, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [1003, _testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    articles: { 1003: ['68,00', '476,00'] }\n    totals: { totalBrutto: '476,00', totalNetto: '400,00', vats: { 19: '76,00' }, grandTotal: '476,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case1.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9002, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9001, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9001], oxsort: 10 }\n    - { oxid: shopdiscount5for9002, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9002], oxsort: 20 }\n    - { oxid: basketdiscount5for9001, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9001], oxsort: 30 }\n    - { oxid: basketdiscount5for9002, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9002], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9001, oxprice: 9, oxactive: 1, oxarticles: [9001] }, { oxtype: WRAP, oxname: testWrap9002, oxprice: 6, oxactive: 1, oxarticles: [9002] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxarticles: [9001, 9002] }]\n    voucherserie: [{ oxserienr: abs_4_voucher_serie, oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9001: ['63,92', '2.109,36'], 9002: ['40,08', '641,28'] }\n    totals: { totalBrutto: '2.750,64', totalNetto: '2.305,18', vats: { 19: '437,98' }, discounts: { absolutebasketdiscount: '3,40' }, wrapping: { brutto: '267,24', netto: '224,57', vat: '42,67' }, delivery: { brutto: '4,08', netto: '3,43', vat: '0,65' }, payment: { brutto: '0,68', netto: '0,57', vat: '0,11' }, voucher: { brutto: '4,08' }, grandTotal: '3.015,16' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case10.yaml",
    "content": "articles:\n    - { oxid: 9206, oxprice: 103, oxvat: 19, amount: 1 }\nexpected:\n    articles: { 9206: ['103,00', '103,00'] }\n    totals: { totalBrutto: '103,00', totalNetto: '86,55', vats: { 19: '16,45' }, grandTotal: '103,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case100.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.55, oxvat: 33, amount: 1 }\n    - { oxid: 1112, oxprice: 1101.1, oxvat: 33, amount: 1 }\n    - { oxid: 1113, oxprice: 110, oxvat: 33, amount: 1 }\n    - { oxid: 1114, oxprice: 1.0, oxvat: 33, amount: 1 }\n    - { oxid: 1115, oxprice: 945.95, oxvat: 50, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['1.101,10', '1.101,10'], 1113: ['110,00', '110,00'], 1114: ['1,00', '1,00'], 1115: ['945,95', '945,95'] }\n    totals: { totalBrutto: '2.728,58', totalNetto: '2.158,60', vats: { 33: '360,16', 50: '425,68' }, discounts: { discountforbasket10%: '215,86' }, delivery: { brutto: '1.579,02', netto: '1.187,23', vat: '391,79' }, payment: { brutto: '73,15', netto: '55,00', vat: '18,15' }, grandTotal: '4.380,75' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case101.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.55, oxvat: 33, amount: 1 }\n    - { oxid: 1112, oxprice: 1101.1, oxvat: 33, amount: 1 }\n    - { oxid: 1113, oxprice: 110, oxvat: 33, amount: 1 }\n    - { oxid: 1114, oxprice: 1.0, oxvat: 33, amount: 1 }\n    - { oxid: 1115, oxprice: 945.95, oxvat: 50, amount: 2 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['1.101,10', '1.101,10'], 1113: ['110,00', '110,00'], 1114: ['1,00', '1,00'], 1115: ['945,95', '1.891,90'] }\n    totals: { totalBrutto: '4.005,61', totalNetto: '3.104,55', vats: { 33: '360,16', 50: '851,36' }, discounts: { discountforbasket10%: '310,46' }, delivery: { brutto: '2.561,25', netto: '1.707,50', vat: '853,75' }, payment: { brutto: '82,50', netto: '55,00', vat: '27,50' }, grandTotal: '6.649,36' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case102.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.55, oxvat: 33, amount: 1 }\n    - { oxid: 1112, oxprice: 945.95, oxvat: 50, amount: 2 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['945,95', '1.891,90'] }\n    totals: { totalBrutto: '2.554,72', totalNetto: '1.892,45', vats: { 33: '0,16', 50: '851,36' }, discounts: { discountforbasket10%: '189,25' }, delivery: { brutto: '1.561,28', netto: '1.040,85', vat: '520,43' }, payment: { brutto: '82,50', netto: '55,00', vat: '27,50' }, grandTotal: '4.198,50' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case103.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 945.95, oxvat: 50, amount: 2 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['945,95', '1.891,90'] }\n    totals: { totalBrutto: '2.554,07', totalNetto: '1.891,90', vats: { 50: '851,36' }, discounts: { discountforbasket10%: '189,19' }, delivery: { brutto: '1.560,83', netto: '1.040,55', vat: '520,28' }, payment: { brutto: '82,50', netto: '55,00', vat: '27,50' }, grandTotal: '4.197,40' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case104.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 1, 0: 0, oxvat: 20, amount: 2 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 111: ['1,00', '2,00'] }\n    totals: { totalBrutto: '2,40', totalNetto: '2,00', vats: { 20: '0,40' }, delivery: { brutto: '1,32', netto: '1,10', vat: '0,22' }, payment: { brutto: '0,24', netto: '0,20', vat: '0,04' }, grandTotal: '3,96' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blShowVATForPayCharge: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case105.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 1, 0: 0, oxvat: 20, amount: 2 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 111: ['1,00', '2,00'] }\n    totals: { totalBrutto: '2,16', totalNetto: '2,00', vats: { 20: '0,36' }, discounts: { discountforbasket10%: '0,20' }, delivery: { brutto: '1,32', netto: '1,10', vat: '0,22' }, payment: { brutto: '0,24', netto: '0,20', vat: '0,04' }, grandTotal: '3,72' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blPaymentVatOnTop: true, blDeliveryVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case106.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 1, 0: 0, oxvat: 20, amount: 1 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 111: ['1,20', '1,20'] }\n    totals: { totalBrutto: '1,20', totalNetto: '1,00', vats: { 20: '0,20' }, delivery: { brutto: '0,79', netto: '0,66', vat: '0,13' }, payment: { brutto: '0,14', netto: '0,12', vat: '0,02' }, grandTotal: '2,13' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case107.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 1, 0: 0, oxvat: 20, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 111: ['1,20', '1,20'] }\n    totals: { totalBrutto: '1,20', totalNetto: '0,90', vats: { 20: '0,18' }, discounts: { discountforbasket10%: '0,12' }, delivery: { brutto: '0,79', netto: '0,66', vat: '0,13' }, payment: { brutto: '0,14', netto: '0,12', vat: '0,02' }, grandTotal: '2,01' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case108.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 1, 0: 0, oxvat: 20, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 15 }]\nexpected:\n    articles: { 111: ['1,20', '1,20'] }\n    totals: { totalBrutto: '1,20', totalNetto: '0,90', vats: { 20: '0,18' }, discounts: { discountforbasket10%: '0,12' }, delivery: { brutto: '0,79', netto: '0,66', vat: '0,13' }, payment: { brutto: '0,23', netto: '0,19', vat: '0,04' }, grandTotal: '2,10' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case109.yaml",
    "content": "articles:\n    - { oxid: 333, oxtitle: item1, oxprice: 60, oxvat: 20, amount: 2 }\n    - { oxid: 444, oxtitle: item2, oxprice: 110, oxvat: 10, amount: 1 }\ndiscounts:\n    - { oxid: discount20, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxactive: 1, oxarticles: [333], oxsort: 10 }\n    - { oxid: discount50, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxactive: 1, oxarticles: [444], oxsort: 20 }\n    - { oxid: discount20forBasket, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxactive: 1, oxsort: 30 }\ncosts: {  }\nexpected:\n    articles: { 333: ['48,00', '96,00'], 444: ['55,00', '55,00'] }\n    totals: { totalBrutto: '151,00', discounts: { discount20forBasket: '30,20' }, totalNetto: '104,00', vats: { 20: '12,80', 10: '4,00' }, grandTotal: '120,80' }\noptions:\n    insertMode: brutto\n    viewMode: brutto\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case11.yaml",
    "content": "articles:\n    - { oxid: 9205, oxprice: 25.9, oxvat: 18, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_9205, oxaddsum: 5.31, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9205], oxsort: 10 }\nexpected:\n    articles: { 9205: ['11,53', '11,53'] }\n    totals: { totalBrutto: '11,53', totalNetto: '9,77', vats: { 18: '1,76' }, grandTotal: '11,53' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.56\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case110.yaml",
    "content": "articles:\n    - { oxid: 1, oxprice: 24.72, oxvat: 7, amount: 2 }\n    - { oxid: 2, oxprice: 14.57, oxvat: 7, amount: 1 }\n    - { oxid: 3, oxprice: 1.49, oxvat: 7, amount: 5 }\n    - { oxid: 4, oxprice: 1.65, oxvat: 7, amount: 5 }\n    - { oxid: 5, oxprice: 17.06, oxvat: 7, amount: 1 }\n    - { oxid: 6, oxprice: 1.63, oxvat: 7, amount: 6 }\n    - { oxid: 7, oxprice: 21.57, oxvat: 7, amount: 1 }\n    - { oxid: 8, oxprice: 21.57, oxvat: 7, amount: 1 }\n    - { oxid: 9, oxprice: 24.44, oxvat: 7, amount: 1 }\nexpected:\n    articles: { 1: ['24,72', '49,44'], 2: ['14,57', '14,57'], 3: ['1,49', '7,45'], 4: ['1,65', '8,25'], 5: ['17,06', '17,06'], 6: ['1,63', '9,78'], 7: ['21,57', '21,57'], 8: ['21,57', '21,57'], 9: ['24,44', '24,44'] }\n    totals: { totalBrutto: '186,32', totalNetto: '174,13', vats: { 7: '12,19' }, grandTotal: '186,32' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case111.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 1 }\nexpected:\n    articles: { 111: ['24,95', '24,95'] }\n    totals: { totalBrutto: '24,95', totalNetto: '20,97', vats: { 19: '3,98' }, grandTotal: '24,95' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case112.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['24,95', '2.495,00'] }\n    totals: { totalBrutto: '2.495,00', totalNetto: '2.096,64', vats: { 19: '398,36' }, grandTotal: '2.495,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case113.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['24,95', '2.495,00'] }\n    totals: { totalBrutto: '2.495,00', totalNetto: '2.096,64', vats: { 19: '398,36' }, grandTotal: '2.495,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case114.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['12,48', '1.248,00'] }\n    totals: { totalBrutto: '1.248,00', totalNetto: '1.048,74', vats: { 19: '199,26' }, grandTotal: '1.248,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case115.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 17.55, amount: 250 }\nexpected:\n    articles: { 111: ['12,48', '3.120,00'] }\n    totals: { totalBrutto: '3.120,00', totalNetto: '2.654,19', vats: { '17.55': '465,81' }, grandTotal: '3.120,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case116.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['12,48', '1.248,00'] }\n    totals: { totalBrutto: '1.248,00', totalNetto: '1.048,74', vats: { 19: '199,26' }, grandTotal: '1.248,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case117.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['12,48', '1.248,00'] }\n    totals: { totalBrutto: '1.248,00', totalNetto: '1.048,74', vats: { 19: '199,26' }, grandTotal: '1.248,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case118.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 25, amount: 100 }\nexpected:\n    articles: { 111: ['12,48', '1.248,00'] }\n    totals: { totalBrutto: '1.248,00', totalNetto: '998,40', vats: { 25: '249,60' }, grandTotal: '1.248,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case119.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 25, amount: 250 }\nexpected:\n    articles: { 111: ['12,48', '3.120,00'] }\n    totals: { totalBrutto: '3.120,00', totalNetto: '2.496,00', vats: { 25: '624,00' }, grandTotal: '3.120,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case12.yaml",
    "content": "articles:\n    - { oxid: 9203, oxprice: 29.99, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_9203, oxaddsum: 2.01, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9203], oxsort: 10 }\nexpected:\n    articles: { 9203: ['27,98', '27,98'] }\n    totals: { totalBrutto: '33,30', totalNetto: '27,98', vats: { 19: '5,32' }, grandTotal: '33,30' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case120.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 1 }\nexpected:\n    articles: { 111: ['20,97', '20,97'] }\n    totals: { totalBrutto: '24,95', totalNetto: '20,97', vats: { 19: '3,98' }, grandTotal: '24,95' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: proportional }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case121.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['20,97', '2.097,00'] }\n    totals: { totalBrutto: '2.495,43', totalNetto: '2.097,00', vats: { 19: '398,43' }, grandTotal: '2.495,43' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: proportional }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case122.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['20,97', '2.097,00'] }\n    totals: { totalBrutto: '2.495,43', totalNetto: '2.097,00', vats: { 19: '398,43' }, grandTotal: '2.495,43' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case123.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['10,49', '1.049,00'] }\n    totals: { totalBrutto: '1.248,31', totalNetto: '1.049,00', vats: { 19: '199,31' }, grandTotal: '1.248,31' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case124.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 17.55, amount: 250 }\nexpected:\n    articles: { 111: ['10,62', '2.655,00'] }\n    totals: { totalBrutto: '3.120,95', totalNetto: '2.655,00', vats: { '17.55': '465,95' }, grandTotal: '3.120,95' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case125.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['10,49', '1.049,00'] }\n    totals: { totalBrutto: '1.248,31', totalNetto: '1.049,00', vats: { 19: '199,31' }, grandTotal: '1.248,31' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case126.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 150 }\nexpected:\n    articles: { 111: ['10,49', '1.573,50'] }\n    totals: { totalBrutto: '1.872,47', totalNetto: '1.573,50', vats: { 19: '298,97' }, grandTotal: '1.872,47' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case127.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 150 }\nexpected:\n    articles: { 111: ['10,49', '1.573,50'] }\n    totals: { totalBrutto: '1.872,47', totalNetto: '1.573,50', vats: { 19: '298,97' }, grandTotal: '1.872,47' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case128.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 150 }\nexpected:\n    articles: { 111: ['10,49', '1.573,50'] }\n    totals: { totalBrutto: '1.872,47', totalNetto: '1.573,50', vats: { 19: '298,97' }, grandTotal: '1.872,47' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case129.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 150 }\n    - { oxid: 222, oxprice: 7.99, oxvat: 19, amount: 1 }\nexpected:\n    articles: { 111: ['10,49', '1.573,50'], 222: ['3,36', '3,36'] }\n    totals: { totalBrutto: '1.876,46', totalNetto: '1.576,86', vats: { 19: '299,60' }, grandTotal: '1.876,46' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case13.yaml",
    "content": "articles:\n    - { oxid: 9201, oxprice: 77.9, oxvat: 17, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_9201, oxaddsum: 5.05, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 10 }\nexpected:\n    articles: { 9201: ['72,85', '72,85'] }\n    totals: { totalBrutto: '72,85', totalNetto: '62,26', vats: { 17: '10,59' }, grandTotal: '72,85' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case130.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 150 }\n    - { oxid: 222, oxprice: 7.99, oxvat: 19, amount: 1 }\nexpected:\n    articles: { 111: ['10,49', '1.573,50'], 222: ['3,36', '3,36'] }\n    totals: { totalBrutto: '1.876,46', totalNetto: '1.576,86', vats: { 19: '299,60' }, grandTotal: '1.876,46' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case131.yaml",
    "content": "articles:\n    - { oxid: '4425', oxprice: 879, amount: 1 }\ndiscounts:\n    - { oxid: discount10euro, oxaddsum: 10, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 0, oxactive: 1, oxarticles: [4425], oxsort: 10 }\nexpected:\n    articles: { 4425: ['869,00', '869,00'] }\n    totals: { totalBrutto: '869,00', totalNetto: '730,25', vats: { 19: '138,75' }, grandTotal: '869,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case132.yaml",
    "content": "articles:\n    - { oxid: '3727', oxprice: 5, amount: 1 }\ndiscounts:\n    - { oxid: discount500forShop, oxaddsum: 500, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 3727: ['5,00', '5,00'] }\n    totals: { totalBrutto: '5,00', discounts: { discount500forShop: '5,00' }, totalNetto: '0,00', vats: { 19: '0,00' }, grandTotal: '0,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case133.yaml",
    "content": "articles:\n    - { oxid: '3727', oxprice: 5, amount: 1 }\ndiscounts:\n    - { oxid: discount500forShop, oxaddsum: 500, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 3727: ['0,00', '0,00'] }\n    totals: { totalBrutto: '0,00', totalNetto: '0,00', vats: { 19: '0,00' }, grandTotal: '0,00' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case134.yaml",
    "content": "articles:\n    - { oxid: '3587', oxtitle: newspaper, oxprice: 2.98, amount: 200 }\ndiscounts:\n    - { oxid: discount2forShop, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: ['3587'], oxsort: 10 }\nexpected:\n    articles: { 3587: ['2,92', '584,00'] }\n    totals: { totalBrutto: '584,00', totalNetto: '490,76', vats: { 19: '93,24' }, grandTotal: '584,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case135.yaml",
    "content": "articles:\n    - { oxid: '3587', oxtitle: newspaper, oxprice: 2.98, amount: 200 }\ndiscounts:\n    - { oxid: discount2forShop, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 3587: ['2,92', '584,00'] }\n    totals: { totalBrutto: '584,00', totalNetto: '490,76', vats: { 19: '93,24' }, grandTotal: '584,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case136.yaml",
    "content": "articles:\n    - { oxid: '3587', oxtitle: newspaper, oxprice: 2.98, amount: 200 }\ndiscounts:\n    - { oxid: discount2forBasket, oxaddsum: 2, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: ['3587'], oxsort: 10 }\ncosts: {  }\nexpected:\n    articles: { 3587: ['2,92', '584,00'] }\n    totals: { totalBrutto: '584,00', totalNetto: '490,76', vats: { 19: '93,24' }, grandTotal: '584,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case137.yaml",
    "content": "articles:\n    - { oxid: '3587', oxtitle: newspaper, oxprice: 2.98, amount: 200 }\ndiscounts:\n    - { oxid: discount2forBasket, oxaddsum: 2, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 3587: ['2,98', '596,00'] }\n    totals: { totalBrutto: '596,00', discounts: { discount2forBasket: '11,92' }, totalNetto: '490,82', vats: { 19: '93,26' }, grandTotal: '584,08' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case138.yaml",
    "content": "articles:\n    - { oxid: _tArticle, oxprice: 50, oxweight: 10, oxstock: 100, oxstockflag: 2, oxvat: 10, amount: 2 }\n    - { oxid: 2000, oxprice: 29.9, oxtitle: 'Wall Clock ROBOT', oxstock: 3, oxstockflag: 1, amount: 1 }\n    - { oxid: _t1651, oxprice: 29.9, oxtitle: 'Beer homebrew kit CHEERS!', oxstock: 10000, oxstockflag: 1, amount: 1 }\ndiscounts:\n    - { oxid: testdiscount0, oxactive: 1, oxtitle: 'Test discount 0', oxamount: 1, oxamountto: 99999, oxprice: 1, oxpriceto: 99999, oxaddsumtype: abs, oxaddsum: 5, oxarticles: [2000, _tArticle], oxsort: 10 }\nexpected:\n    articles: { _tArticle: ['45,00', '90,00'], 2000: ['24,90', '24,90'], _t1651: ['29,90', '29,90'] }\n    totals: { totalBrutto: '144,80', totalNetto: '127,87', vats: { 19: '8,75', 10: '8,18' }, discounts: {  }, grandTotal: '144,80' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case139.yaml",
    "content": "articles:\n    - { oxid: testarticle, oxprice: 19, oxweight: 10, oxstock: 100, oxstockflag: 2, amount: 1, scaleprices: {  } }\ndiscounts:\n    - { oxid: testdiscount0, oxactive: 1, oxtitle: 'Test discount 0', oxamount: 1, oxamountto: 99999, oxprice: 1, oxpriceto: 99999, oxaddsumtype: abs, oxaddsum: 5, oxsort: 10 }\n    - { oxid: testdiscount1, oxactive: 1, oxtitle: 'Test discount 1', oxamount: 1, oxamountto: 99999, oxprice: 1, oxpriceto: 99999, oxaddsumtype: abs, oxaddsum: 7, oxsort: 20 }\nexpected:\n    articles: { testarticle: ['19,00', '19,00'] }\n    totals: { totalBrutto: '19,00', totalNetto: '5,88', vats: { 19: '1,12' }, discounts: { testdiscount0: '5,00', testdiscount1: '7,00' }, grandTotal: '7,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case14.yaml",
    "content": "articles:\n    - { oxid: 9201, oxprice: 77.9, oxvat: 17, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_9201, oxaddsum: 5.05, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 10 }\nexpected:\n    articles: { 9201: ['49,54', '49,54'] }\n    totals: { totalBrutto: '49,54', totalNetto: '42,34', vats: { 17: '7,20' }, grandTotal: '49,54' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case140.yaml",
    "content": "articles:\n    - { oxid: testarticle, oxprice: 12.95, amount: 1, scaleprices: { oxaddabs: 11.95, oxamount: 2, oxamountto: 2, oxartid: testarticle } }\ndiscounts:\n    - { oxid: _testDiscount, oxactive: 1, oxtitle: 'new discount', oxprice: 12, oxpriceto: 24.99, oxaddsumtype: abs, oxaddsum: 3, oxsort: 10 }\nexpected:\n    articles: { testarticle: ['12,95', '12,95'] }\n    totals: { totalBrutto: '12,95', totalNetto: '8,36', vats: { 19: '1,59' }, discounts: { _testDiscount: '3,00' }, grandTotal: '9,95' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case141.yaml",
    "content": "articles:\n    - { oxid: testarticle, oxprice: 19, oxweight: 10, oxstock: 100, oxstockflag: 2, oxskipdiscounts: 1, amount: 1, scaleprices: {  } }\ndiscounts:\n    - { oxid: testdiscount0, oxactive: 1, oxtitle: 'Test discount 0', oxamount: 1, oxamountto: 99999, oxprice: 1, oxpriceto: 99999, oxaddsumtype: abs, oxaddsum: 5, oxsort: 10 }\n    - { oxid: testdiscount1, oxactive: 1, oxtitle: 'Test discount 1', oxamount: 1, oxamountto: 99999, oxprice: 1, oxpriceto: 99999, oxaddsumtype: abs, oxaddsum: 7, oxsort: 20 }\nexpected:\n    articles: { testarticle: ['19,00', '19,00'] }\n    totals: { totalBrutto: '19,00', totalNetto: '15,97', vats: { 19: '3,03' }, discounts: {  }, grandTotal: '19,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case142.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87.0, oxvat: 17, amount: 63 }\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 48 }\n    - { oxid: 9206, oxprice: 103.0, oxvat: 19, amount: 99 }\n    - { oxid: 9216, oxprice: 56.45, oxvat: 17, amount: 22 }\nexpected:\n    articles: { 9200: ['87,00', '5.481,00'], 9201: ['72,85', '3.496,80'], 9206: ['103,00', '10.197,00'], 9216: ['56,45', '1.241,90'] }\n    totals: { totalBrutto: '20.416,70', totalNetto: '17.303,70', vats: { 17: '1.484,91', 19: '1.628,09' }, grandTotal: '20.416,70' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case143.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87, oxvat: 17, amount: 20315 }\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 210 }\n    - { oxid: 9202, oxprice: 16.2, oxvat: 17, amount: 56 }\n    - { oxid: 9208, oxprice: 72.11, oxvat: 17, amount: 691 }\n    - { oxid: 9212, oxprice: 16.37, oxvat: 17, amount: 548 }\n    - { oxid: 9216, oxprice: 56.45, oxvat: 17, amount: 36 }\ndiscounts:\n    - { oxid: discount5for9200, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9200], oxsort: 10 }\n    - { oxid: discount2for9201, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 20 }\n    - { oxid: discount3for9208, oxaddsum: 3, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9208], oxsort: 30 }\n    - { oxid: discount1for9212, oxaddsum: 1, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9212], oxsort: 40 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9202, oxprice: 0.63, oxactive: 1, oxarticles: [9202] }, { oxtype: WRAP, oxname: wrapFor9216, oxprice: 0.33, oxactive: 1, oxarticles: [9216] }]\n    delivery: [{ oxactive: 1, oxaddsum: 117, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 9999999 }]\n    payment: [{ oxaddsum: 3, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 2000000, oxchecked: 1 }]\nexpected:\n    articles: { 9200: ['82,65', '1.679.034,75'], 9201: ['71,39', '14.991,90'], 9202: ['16,20', '907,20'], 9208: ['69,95', '48.335,45'], 9212: ['16,21', '8.883,08'], 9216: ['56,45', '2.032,20'] }\n    totals: { totalBrutto: '1.754.184,58', totalNetto: '1.499.303,06', vats: { 17: '254.881,52' }, wrapping: { brutto: '47,16', netto: '40,30', vat: '6,86' }, delivery: { brutto: '117,00', netto: '100,00', vat: '17,00' }, payment: { brutto: '3,00', netto: '2,56', vat: '0,44' }, grandTotal: '1.754.351,74' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case144.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87, oxvat: 17, amount: 589 }\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 1325 }\n    - { oxid: 9207, oxprice: 45.5, oxvat: 19, amount: 8888 }\n    - { oxid: 9213, oxprice: 30.77, oxvat: 19, amount: 10000 }\ndiscounts:\n    - { oxid: discount2for9200, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9200], oxsort: 10 }\n    - { oxid: discount3for9201, oxaddsum: 3, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 20 }\n    - { oxid: discount4for9207, oxaddsum: 4, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9207], oxsort: 30 }\n    - { oxid: discount6for9213, oxaddsum: 6, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9213], oxsort: 40 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9200, oxprice: 0.05, oxactive: 1, oxarticles: [9200, 9207, 9213] }]\n    delivery: [{ oxactive: 1, oxaddsum: 58.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 9999999 }]\nexpected:\n    articles: { 9200: ['85,26', '50.218,14'], 9201: ['70,66', '93.624,50'], 9207: ['43,68', '388.227,84'], 9213: ['28,92', '289.200,00'] }\n    totals: { totalBrutto: '821.270,48', totalNetto: '692.209,52', vats: { 17: '20.900,21', 19: '108.160,75' }, wrapping: { brutto: '973,85', netto: '818,79', vat: '155,06' }, delivery: { brutto: '58,14', netto: '48,86', vat: '9,28' }, grandTotal: '822.302,47' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case145.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87.0, oxvat: 17, amount: 2008 }\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 369 }\n    - { oxid: 9207, oxprice: 45.5, oxvat: 19, amount: 1698 }\n    - { oxid: 9213, oxprice: 30.77, oxvat: 19, amount: 3665 }\nexpected:\n    articles: { 9200: ['87,00', '174.696,00'], 9201: ['72,85', '26.881,65'], 9207: ['45,50', '77.259,00'], 9213: ['30,77', '112.772,05'] }\n    totals: { totalBrutto: '391.608,70', totalNetto: '331.978,55', vats: { 17: '29.289,06', 19: '30.341,09' }, grandTotal: '391.608,70' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case146.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87.0, oxvat: 17, amount: 12 }\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 5 }\n    - { oxid: 9202, oxprice: 16.21, oxvat: 17, amount: 39 }\nexpected:\n    articles: { 9200: ['59,16', '709,92'], 9201: ['49,54', '247,70'], 9202: ['11,02', '429,78'] }\n    totals: { totalBrutto: '1.387,40', totalNetto: '1.185,81', vats: { 17: '201,59' }, grandTotal: '1.387,40' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case147.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87.0, oxvat: 17, amount: 120 }\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 5 }\n    - { oxid: 9202, oxprice: 16.21, oxvat: 17, amount: 39 }\nexpected:\n    articles: { 9200: ['59,16', '7.099,20'], 9201: ['49,54', '247,70'], 9202: ['11,02', '429,78'] }\n    totals: { totalBrutto: '7.776,68', totalNetto: '6.646,74', vats: { 17: '1.129,94' }, grandTotal: '7.776,68' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case148.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 15.93, oxvat: 15, amount: 21 }\n    - { oxid: 9208, oxprice: 70.87, oxvat: 15, amount: 9 }\n    - { oxid: 9213, oxprice: 25.86, oxvat: 0, amount: 10 }\n    - { oxid: 9216, oxprice: 48.25, oxvat: 0, amount: 4 }\n    - { oxid: 9218, oxprice: 58.09, oxvat: 15, amount: 5 }\nexpected:\n    articles: { 9202: ['15,93', '334,53'], 9208: ['70,87', '637,83'], 9213: ['25,86', '258,60'], 9216: ['48,25', '193,00'], 9218: ['58,09', '290,45'] }\n    totals: { totalBrutto: '1.714,41', totalNetto: '1.549,70', vats: { 0: '0,00', 15: '164,71' }, grandTotal: '1.714,41' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case149.yaml",
    "content": "articles:\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 1012 }\n    - { oxid: 9203, oxprice: 33.3, oxvat: 19, amount: 453 }\n    - { oxid: 9211, oxprice: 5.86, oxvat: 16, amount: 88 }\n    - { oxid: 9216, oxprice: 56.45, oxvat: 17, amount: 56 }\n    - { oxid: 9219, oxprice: 24.33, oxvat: 19, amount: 74 }\nexpected:\n    articles: { 9201: ['72,85', '73.724,20'], 9203: ['33,30', '15.084,90'], 9211: ['5,86', '515,68'], 9216: ['56,45', '3.161,20'], 9219: ['24,33', '1.800,42'] }\n    totals: { totalBrutto: '94.286,40', totalNetto: '80.347,91', vats: { 16: '71,13', 17: '11.171,38', 19: '2.695,98' }, grandTotal: '94.286,40' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case15.yaml",
    "content": "articles:\n    - { oxid: 9201, oxprice: 77.9, oxvat: 17, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_9201, oxaddsum: 5.05, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 10 }\nexpected:\n    articles: { 9201: ['107,09', '107,09'] }\n    totals: { totalBrutto: '107,09', totalNetto: '91,53', vats: { 17: '15,56' }, grandTotal: '107,09' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1.47\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case150.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 47.62, oxvat: 0, amount: 1 }\n    - { oxid: 9201, oxprice: 91.82, oxvat: 0, amount: 1 }\n    - { oxid: 9207, oxprice: 63.03, oxvat: 0, amount: 1 }\nexpected:\n    articles: { 9200: ['47,62', '47,62'], 9201: ['91,82', '91,82'], 9207: ['63,03', '63,03'] }\n    totals: { totalBrutto: '202,47', totalNetto: '202,47', vats: ['0,00'], grandTotal: '202,47' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case151.yaml",
    "content": "articles:\n    - { oxid: 9000, oxprice: 50.01, oxvat: 17, amount: 3.3 }\n    - { oxid: 9201, oxprice: 1.0, oxvat: 17, amount: 0.33 }\nexpected:\n    articles: { 9000: ['50,01', '165,03'], 9201: ['1,00', '0,33'] }\n    totals: { totalBrutto: '165,36', totalNetto: '141,33', vats: { 17: '24,03' }, grandTotal: '165,36' }\noptions:\n    config: { blAllowUnevenAmounts: true, blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case152.yaml",
    "content": "articles:\n    0: { oxid: 9200, oxprice: 87, oxvat: 17, amount: 63 }\n    1: { oxid: 9206, oxprice: 103, oxvat: 19, amount: 125 }\n    3: { oxid: 9216, oxprice: 56.45, oxvat: 17, amount: 14 }\n    4: { oxid: 9218, oxprice: 59.6, oxvat: 18, amount: 39 }\ndiscounts:\n    - { oxid: discount2for9200and9206, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9200, 9206], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9216, oxprice: 0.57, oxactive: 1, oxarticles: [9216] }]\n    delivery: [{ oxactive: 1, oxaddsum: 15, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\nexpected:\n    articles: { 9200: ['85,26', '5.371,38'], 9206: ['100,94', '12.617,50'], 9216: ['56,45', '790,30'], 9218: ['59,60', '2.324,40'] }\n    totals: { totalBrutto: '21.103,58', totalNetto: '17.839,16', vats: { 17: '895,29', 18: '354,57', 19: '2.014,56' }, wrapping: { brutto: '7,98', netto: '6,82', vat: '1,16' }, delivery: { brutto: '15,00', netto: '12,61', vat: '2,39' }, grandTotal: '21.126,56' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case153.yaml",
    "content": "articles:\n    0: { oxid: 9200, oxprice: 59.16, oxvat: 17, amount: 1002 }\n    1: { oxid: 9201, oxprice: 49.54, oxvat: 17, amount: 1 }\n    3: { oxid: 9202, oxprice: 11.02, oxvat: 17, amount: 5 }\ndiscounts:\n    - { oxid: discount5for9200, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9200], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9201, oxprice: 8, oxactive: 1, oxarticles: [9201] }, { oxtype: WRAP, oxname: wrapFor9202, oxprice: 0.7, oxactive: 1, oxarticles: [9202] }]\n    delivery: [{ oxactive: 1, oxaddsum: 14.75, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\nexpected:\n    articles: { 9200: ['38,22', '38.296,44'], 9201: ['33,69', '33,69'], 9202: ['7,49', '37,45'] }\n    totals: { totalBrutto: '38.367,58', totalNetto: '32.792,80', vats: { 17: '5.574,78' }, wrapping: { brutto: '7,84', netto: '6,70', vat: '1,14' }, delivery: { brutto: '10,03', netto: '8,57', vat: '1,46' }, grandTotal: '38.385,45' }\noptions:\n    activeCurrencyRate: 0.68\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case154.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 16.48, oxvat: 19, amount: 190 }\n    - { oxid: 9210, oxprice: 27.35, oxvat: 19, amount: 255 }\n    - { oxid: 9213, oxprice: 30.77, oxvat: 19, amount: 14 }\n    - { oxid: 9215, oxprice: 69.13, oxvat: 19, amount: 10 }\ndiscounts:\n    - { oxid: discount1for9202, oxaddsum: 1, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9202], oxsort: 10 }\n    - { oxid: discount2for9210, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9210], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9215, oxprice: 1.5, oxactive: 1, oxarticles: [9215] }]\n    delivery: [{ oxactive: 1, oxaddsum: 58.49, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\nexpected:\n    articles: { 9202: ['16,32', '3.100,80'], 9210: ['26,80', '6.834,00'], 9213: ['30,77', '430,78'], 9215: ['69,13', '691,30'] }\n    totals: { totalBrutto: '11.056,88', totalNetto: '9.291,50', vats: { 19: '1.765,38' }, wrapping: { brutto: '15,00', netto: '12,61', vat: '2,39' }, delivery: { brutto: '58,49', netto: '49,15', vat: '9,34' }, grandTotal: '11.130,37' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case155.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 15.93, oxvat: 15, amount: 58 }\n    - { oxid: 9208, oxprice: 70.87, oxvat: 15, amount: 14 }\n    - { oxid: 9213, oxprice: 25.86, oxvat: 0, amount: 1398 }\n    - { oxid: 9216, oxprice: 48.25, oxvat: 0, amount: 250 }\n    - { oxid: 9218, oxprice: 58.09, oxvat: 15, amount: 12 }\ndiscounts:\n    - { oxid: discount4for9213, oxaddsum: 4, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9213], oxsort: 10 }\n    - { oxid: discount2for9216, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9216], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9218, oxprice: 2.3, oxactive: 1, oxarticles: [9218] }]\n    delivery: [{ oxactive: 1, oxaddsum: 12.82, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\nexpected:\n    articles: { 9202: ['15,93', '923,94'], 9208: ['70,87', '992,18'], 9213: ['24,83', '34.712,34'], 9216: ['47,29', '11.822,50'], 9218: ['58,09', '697,08'] }\n    totals: { totalBrutto: '49.148,04', totalNetto: '48.807,19', vats: { 15: '340,85', 0: '0,00' }, wrapping: { brutto: '27,60', netto: '24,00', vat: '3,60' }, delivery: { brutto: '12,82', netto: '12,82' }, grandTotal: '49.188,46' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case156.yaml",
    "content": "articles:\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 175 }\n    - { oxid: 9203, oxprice: 33.3, oxvat: 19, amount: 12 }\n    - { oxid: 9211, oxprice: 5.86, oxvat: 16, amount: 5874 }\n    - { oxid: 9216, oxprice: 56.45, oxvat: 17, amount: 225 }\n    - { oxid: 9219, oxprice: 24.33, oxvat: 19, amount: 31 }\ndiscounts:\n    - { oxid: discount2for9201, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 10 }\n    - { oxid: discount4for9211, oxaddsum: 4, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9211], oxsort: 20 }\n    - { oxid: discount2for9216, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9216], oxsort: 30 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9219, oxprice: 0.48, oxactive: 1, oxarticles: [9219] }]\n    delivery: [{ oxactive: 1, oxaddsum: 15.03, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\nexpected:\n    articles: { 9201: ['71,39', '12.493,25'], 9203: ['33,30', '399,60'], 9211: ['5,63', '33.070,62'], 9216: ['55,32', '12.447,00'], 9219: ['24,33', '754,23'] }\n    totals: { totalBrutto: '59.164,70', totalNetto: '50.795,22', vats: { 16: '4.561,46', 17: '3.623,80', 19: '184,22' }, wrapping: { brutto: '14,88', netto: '12,50', vat: '2,38' }, delivery: { brutto: '15,03', netto: '12,96', vat: '2,07' }, grandTotal: '59.194,61' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case157.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 119, oxvat: 19, amount: 1 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\n    oxcountryid: 8f241f11096877ac0.98748826\nexpected:\n    articles: { 9202: ['100,00', '100,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '100,00', vats: ['0,00'], grandTotal: '100,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case158.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 119, oxvat: 19, amount: 1 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\n    oxcountryid: 8f241f11096877ac0.98748826\nexpected:\n    articles: { 9202: ['100,00', '100,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '100,00', vats: ['0,00'], grandTotal: '100,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case159.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 100, oxvat: 19, amount: 1 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\n    oxcountryid: 8f241f11096877ac0.98748826\nexpected:\n    articles: { 9202: ['100,00', '100,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '100,00', vats: ['0,00'], grandTotal: '100,00' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case16.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 74.36, oxvat: 17, amount: 1 }\nexpected:\n    articles: { 9200: ['87,00', '87,00'] }\n    totals: { totalBrutto: '87,00', totalNetto: '74,36', vats: { 17: '12,64' }, grandTotal: '87,00' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case160.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 100, oxvat: 19, amount: 1 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\n    oxcountryid: 8f241f11096877ac0.98748826\nexpected:\n    articles: { 9202: ['100,00', '100,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '100,00', vats: ['0,00'], grandTotal: '100,00' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case161.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9006, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount5for9006, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\n    - { oxid: basketdiscount5for9005, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 30 }\n    - { oxid: basketdiscount5for9006, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9005, oxprice: 9, oxactive: 1, oxarticles: [9005] }, { oxtype: WRAP, oxname: testWrap9006, oxprice: 6, oxactive: 1, oxarticles: [9006] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9005: ['113,00', '3.729,00'], 9006: ['70,13', '1.122,08'] }\n    totals: { totalBrutto: '4.851,08', totalNetto: '4.067,29', vats: { 19: '772,79' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '393,00', netto: '330,25', vat: '62,75' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '5.240,08' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case162.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9006, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount5for9006, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\n    - { oxid: basketdiscount5for9005, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 30 }\n    - { oxid: basketdiscount5for9006, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9005, oxprice: 9, oxactive: 1, oxarticles: [9005] }, { oxtype: WRAP, oxname: testWrap9006, oxprice: 6, oxactive: 1, oxarticles: [9006] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9005: ['113,00', '3.729,00'], 9006: ['70,13', '1.122,08'] }\n    totals: { totalBrutto: '4.851,08', totalNetto: '4.067,29', vats: { 19: '772,79' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '393,00', netto: '330,25', vat: '62,75' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '5.240,08' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case163.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9006, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount5for9006, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\n    - { oxid: basketdiscount5for9005, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 30 }\n    - { oxid: basketdiscount5for9006, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9005, oxprice: 9, oxactive: 1, oxarticles: [9005] }, { oxtype: WRAP, oxname: testWrap9006, oxprice: 6, oxactive: 1, oxarticles: [9006] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9005: ['113,00', '3.729,00'], 9006: ['70,13', '1.122,08'] }\n    totals: { totalBrutto: '4.851,08', totalNetto: '4.067,29', vats: { 19: '772,79' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '393,00', netto: '330,25', vat: '62,75' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '5.240,08' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case164.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount5.5for9005, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['1.125,67', '1.125,67'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '1.126,33', totalNetto: '946,50', vats: { 19: '179,83' }, delivery: { brutto: '112,63', netto: '94,65', vat: '17,98' }, payment: { brutto: '10,00', netto: '8,40', vat: '1,60' }, grandTotal: '1.248,96' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case165.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount5.5for9005, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['1.125,67', '1.125,67'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '1.126,33', totalNetto: '946,50', vats: { 19: '179,83' }, delivery: { brutto: '112,63', netto: '94,65', vat: '17,98' }, payment: { brutto: '10,00', netto: '8,40', vat: '1,60' }, grandTotal: '1.248,96' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case166.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount50for9005, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['595,60', '595,60'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '596,26', totalNetto: '501,06', vats: { 19: '95,20' }, delivery: { brutto: '59,63', netto: '50,11', vat: '9,52' }, payment: { brutto: '10,00', netto: '8,40', vat: '1,60' }, grandTotal: '665,89' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case167.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 3 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount50for9005, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['595,60', '1.786,80'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '1.787,46', totalNetto: '1.502,07', vats: { 19: '285,39' }, delivery: { brutto: '178,75', netto: '150,21', vat: '28,54' }, payment: { brutto: '10,00', netto: '8,40', vat: '1,60' }, grandTotal: '1.976,21' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case168.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 3 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount50for9005, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['595,60', '1.786,80'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '1.787,46', totalNetto: '1.502,07', vats: { 19: '285,39' }, delivery: { brutto: '178,75', netto: '150,21', vat: '28,54' }, payment: { brutto: '10,00' }, grandTotal: '1.976,21' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: false, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case169.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 3 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount50for9005, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['595,60', '1.786,80'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '1.787,46', totalNetto: '1.502,07', vats: { 19: '285,39' }, delivery: { brutto: '178,75', netto: '150,21', vat: '28,54' }, payment: { brutto: '10,00', netto: '8,40', vat: '1,60' }, grandTotal: '1.976,21' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case17.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 74.36, oxvat: 17, amount: 1 }\nexpected:\n    articles: { 9200: ['59,16', '59,16'] }\n    totals: { totalBrutto: '59,16', totalNetto: '50,56', vats: { 17: '8,60' }, grandTotal: '59,16' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case170.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 3 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount50for9005, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['595,60', '1.786,80'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '1.787,46', totalNetto: '1.502,07', vats: { 19: '285,39' }, delivery: { brutto: '178,75', netto: '150,21', vat: '28,54' }, payment: { brutto: '10,00' }, grandTotal: '1.976,21' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: false, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case171.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 3 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 18, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount50for9005, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['595,60', '1.786,80'], 9006: ['0,65', '0,65'] }\n    totals: { totalBrutto: '1.787,45', totalNetto: '1.502,06', vats: { 19: '285,29', 18: '0,10' }, delivery: { brutto: '178,75', netto: '150,21', vat: '28,54' }, payment: { brutto: '10,00' }, grandTotal: '1.976,20' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blShowVATForPayCharge: false, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case172.yaml",
    "content": "articles:\n    - { oxid: 1126, oxprice: 34.0, oxvat: 19, amount: 3 }\ndiscounts:\n    - { oxid: testdisc, oxaddsum: 50, oxaddsumtype: '%', oxamount: 3, oxamountto: 99999, oxactive: 1, oxarticles: [1126], oxsort: 10 }\nexpected:\n    articles: { 1126: ['17,00', '51,00'] }\n    totals: { totalBrutto: '51,00', totalNetto: '42,86', vats: { 19: '8,14' }, grandTotal: '51,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, bl_perfLoadSelectLists: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case173.yaml",
    "content": "articles:\n    - { oxid: 1126, oxprice: 34.0, oxvat: 19, amount: 3 }\ndiscounts:\n    - { oxid: testdisc, oxaddsum: 50, oxaddsumtype: '%', oxamount: 3, oxamountto: 99999, oxactive: 0, oxarticles: [1126], oxsort: 10 }\n    - { oxid: _testoxdiscount2, oxaddsum: 50, oxaddsumtype: '%', oxamount: 3, oxamountto: 99999, oxprice: 69, oxpriceto: 999999, oxactive: 1, oxarticles: [1126], oxsort: 20 }\nexpected:\n    articles: { 1126: ['17,00', '51,00'] }\n    totals: { totalBrutto: '51,00', totalNetto: '42,86', vats: { 19: '8,14' }, grandTotal: '51,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, bl_perfLoadSelectLists: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case174.yaml",
    "content": "articles:\n    - { oxid: 1126, oxprice: 34.0, oxvat: 19, amount: 2 }\ndiscounts:\n    - { oxid: testdisc, oxaddsum: 50, oxaddsumtype: '%', oxamount: 3, oxamountto: 99999, oxactive: 0, oxarticles: [1126], oxsort: 10 }\n    - { oxid: _testoxdiscount2, oxaddsum: 50, oxaddsumtype: '%', oxamount: 3, oxamountto: 99999, oxprice: 69, oxpriceto: 999999, oxactive: 1, oxarticles: [1126], oxsort: 20 }\nexpected:\n    articles: { 1126: ['34,00', '68,00'] }\n    totals: { totalBrutto: '68,00', totalNetto: '57,14', vats: { 19: '10,86' }, grandTotal: '68,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, bl_perfLoadSelectLists: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case18.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 74.36, oxvat: 17, amount: 1 }\nexpected:\n    articles: { 9200: ['127,89', '127,89'] }\n    totals: { totalBrutto: '127,89', totalNetto: '109,31', vats: { 17: '18,58' }, grandTotal: '127,89' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1.47\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case19.yaml",
    "content": "articles:\n    - { oxid: 9201, oxprice: 66.58, oxvat: 17, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_9201, oxaddsum: 4.32, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 10 }\nexpected:\n    articles: { 9201: ['73,58', '73,58'] }\n    totals: { totalBrutto: '73,58', totalNetto: '62,89', vats: { 17: '10,69' }, grandTotal: '73,58' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case2.yaml",
    "content": "articles:\n    - { oxid: 9003, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9004, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9003, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9003], oxsort: 10 }\n    - { oxid: shopdiscount5for9004, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9004], oxsort: 20 }\n    - { oxid: basketdiscount5for9003, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9003], oxsort: 30 }\n    - { oxid: basketdiscount5for9004, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9004], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9003, oxprice: 9, oxactive: 1, oxarticles: [9003] }, { oxtype: WRAP, oxname: testWrap9003, oxprice: 6, oxactive: 1, oxarticles: [9004] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxarticles: [9003, 9004] }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9003: ['76,84', '2.535,72'], 9004: ['47,70', '763,20'] }\n    totals: { totalBrutto: '3.298,92', totalNetto: '2.765,92', vats: { 19: '525,52' }, discounts: { absolutebasketdiscount: '3,40' }, wrapping: { brutto: '267,24', netto: '224,57', vat: '42,67' }, delivery: { brutto: '4,08', netto: '3,43', vat: '0,65' }, payment: { brutto: '0,68', netto: '0,57', vat: '0,11' }, voucher: { brutto: '4,08' }, grandTotal: '3.563,44' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case20.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 16.2, oxvat: 0, amount: 1 }\nexpected:\n    articles: { 9202: ['11,02', '11,02'] }\n    totals: { totalBrutto: '11,02', totalNetto: '11,02', vats: ['0,00'], grandTotal: '11,02' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case21.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 16.2, oxvat: 0, amount: 1 }\nexpected:\n    articles: { 9202: ['16,20', '16,20'] }\n    totals: { totalBrutto: '16,20', totalNetto: '16,20', vats: ['0,00'], grandTotal: '16,20' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case22.yaml",
    "content": "articles:\n    - { oxid: 100, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 1001, oxprice: 66, oxvat: 19, amount: 33 }\ndiscounts:\n    - { oxid: shopdiscount5for100, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [100], oxsort: 10 }\n    - { oxid: shopdiscount5for1001, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: basketdiscount5for100, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [100], oxsort: 30 }\n    - { oxid: basketdiscount5for1001, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9005, oxprice: 9, oxactive: 1, oxarticles: [100] }, { oxtype: WRAP, oxname: testWrap9006, oxprice: 6, oxactive: 1, oxarticles: [1001] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 100: ['113,00', '3.729,00'], 1001: ['70,13', '2.314,29'] }\n    totals: { totalBrutto: '6.043,29', totalNetto: '5.069,15', vats: { 19: '963,14' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '495,00', netto: '415,97', vat: '79,03' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '6.534,29' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case23.yaml",
    "content": "articles:\n    - { oxid: 10005, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount5for10005, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 10 }\n    - { oxid: shopdiscount5for1004, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 20 }\n    - { oxid: basketdiscount5for10005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 30 }\n    - { oxid: basketdiscount5for1004, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\n    - { oxid: procdiscountfor10005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 60 }\n    - { oxid: procdiscountfor1004, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 70 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap102, oxprice: 9, oxactive: 1, oxarticles: [10005] }, { oxtype: WRAP, oxname: testWrap1002, oxprice: 6, oxactive: 1, oxarticles: [1004] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 10005: ['1.115,67', '1.115,67'], 1004: ['0,59', '0,59'] }\n    totals: { totalBrutto: '1.116,26', totalNetto: '928,79', vats: { 19: '176,47' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '15,00', netto: '12,60', vat: '2,40' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '1.127,26' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case24.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 5.02, oxvat: 7, amount: 1 }\n    - { oxid: 1112, oxprice: 99, 0: 0, oxvat: 19, amount: 1 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 5.02, oxvat: 8, amount: 1 }\ndiscounts:\n    - { oxid: discount10%, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111], oxsort: 10 }\n    - { oxid: discount5.5, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1112, 1113], oxsort: 20 }\nexpected:\n    articles: { 111: ['5,52', '5,52'], 1112: ['93,56', '93,56'], 1113: ['945,95', '945,95'], 1114: ['5,02', '5,02'] }\n    totals: { totalBrutto: '1.248,35', totalNetto: '1.050,05', vats: { 7: '0,39', 8: '0,40', 19: '197,51' }, grandTotal: '1.248,35' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case25.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\ncosts:\n    wrapping: { 3: { oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 } }\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'] }\n    totals: { totalBrutto: '568,00', totalNetto: '500,00', vats: { 10: '30,00', 19: '38,00' }, delivery: { brutto: '312,40', netto: '275,00', vat: '37,40' }, payment: { brutto: '312,40', netto: '275,00', vat: '37,40' }, giftcard: { brutto: '2,84', netto: '2,50', vat: '0,34' }, grandTotal: '1.195,64' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case26.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 10 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '200,00'], 1002: ['200,00', '200,00'] }\n    totals: { totalBrutto: '400,00', totalNetto: '349,89', vats: { 19: '31,93', 10: '18,18' }, delivery: { brutto: '261,80', netto: '220,00', vat: '41,80' }, payment: { brutto: '327,25', netto: '275,00', vat: '52,25' }, grandTotal: '989,05' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case27.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1002, oxprice: 20.0, oxvat: 10, amount: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['200,00', '200,00'], 1002: ['20,00', '200,00'] }\n    totals: { totalBrutto: '400,00', totalNetto: '349,89', vats: { 19: '31,93', 10: '18,18' }, delivery: { brutto: '261,80', netto: '220,00', vat: '41,80' }, payment: { brutto: '327,25', netto: '275,00', vat: '52,25' }, grandTotal: '989,05' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case28.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 19, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1003, oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1003: ['0,00', '0,00'] }\n    totals: { totalBrutto: '595,00', totalNetto: '500,00', vats: { 19: '95,00' }, delivery: { brutto: '327,25', netto: '275,00', vat: '52,25' }, payment: { brutto: '327,25', netto: '275,00', vat: '52,25' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, grandTotal: '1.252,48' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case29.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 19, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1003, oxitmamount: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1003: ['0,00', '0,00'] }\n    totals: { totalBrutto: '595,00', totalNetto: '500,00', vats: { 19: '95,00' }, delivery: { brutto: '327,25', netto: '275,00', vat: '52,25' }, payment: { brutto: '327,25', netto: '275,00', vat: '52,25' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, grandTotal: '1.252,48' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case3.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9006, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount5for9006, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\n    - { oxid: basketdiscount5for9005, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 30 }\n    - { oxid: basketdiscount5for9006, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9005, oxprice: 9, oxactive: 1, oxarticles: [9005] }, { oxtype: WRAP, oxname: testWrap9006, oxprice: 6, oxactive: 1, oxarticles: [9006] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9005: ['113,00', '3.729,00'], 9006: ['70,13', '1.122,08'] }\n    totals: { totalBrutto: '4.851,08', totalNetto: '4.067,29', vats: { 19: '772,79' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '393,00', netto: '330,25', vat: '62,75' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '5.240,08' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case30.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 19, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\n    - { oxid: discountforbasket55%, oxaddsum: 55, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '267,75', totalNetto: '500,00', vats: { 19: '42,75' }, discounts: { discountforbasket55%: '275,00' }, delivery: { brutto: '327,25', netto: '275,00', vat: '52,25' }, payment: { brutto: '327,25', netto: '275,00', vat: '52,25' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, grandTotal: '925,23' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case31.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\n    - { oxid: discountforbasket55%, oxaddsum: 55, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '255,60', totalNetto: '500,00', vats: { 10: '13,50', 19: '17,10' }, discounts: { discountforbasket55%: '275,00' }, delivery: { brutto: '312,40', netto: '275,00', vat: '37,40' }, payment: { brutto: '312,40', netto: '275,00', vat: '37,40' }, giftcard: { brutto: '2,84', netto: '2,50', vat: '0,34' }, grandTotal: '883,24' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case32.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '511,20', totalNetto: '500,00', vats: { 19: '34,20', 10: '27,00' }, delivery: { brutto: '312,40', netto: '275,00', vat: '37,40' }, payment: { brutto: '312,40', netto: '275,00', vat: '37,40' }, voucher: { brutto: '50,00' }, giftcard: { brutto: '2,84', netto: '2,50', vat: '0,34' }, grandTotal: '1.138,84' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case33.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '511,20', totalNetto: '500,00', vats: { 19: '34,20', 10: '27,00' }, delivery: { brutto: '302,50', netto: '275,00', vat: '27,50' }, payment: { brutto: '302,50', netto: '275,00', vat: '27,50' }, voucher: { brutto: '50,00' }, giftcard: { brutto: '2,75', netto: '2,50', vat: '0,25' }, grandTotal: '1.118,95' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case34.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '511,20', totalNetto: '500,00', vats: { 19: '34,20', 10: '27,00' }, delivery: { brutto: '302,50' }, payment: { brutto: '302,50' }, voucher: { brutto: '50,00' }, giftcard: { brutto: '2,75' }, grandTotal: '1.118,95' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: false, blShowVATForPayCharge: false, blShowVATForWrapping: false, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case35.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 15 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '511,20', totalNetto: '500,00', vats: { 19: '34,20', 10: '27,00' }, delivery: { brutto: '302,50', netto: '275,00', vat: '27,50' }, payment: { brutto: '302,50', netto: '275,00', vat: '27,50' }, voucher: { brutto: '50,00' }, giftcard: { brutto: '2,75', netto: '2,50', vat: '0,25' }, grandTotal: '1.118,95' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case36.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [basketUser] }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: item_discount, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['18,00', '36,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '225,15', totalNetto: '236,00', vats: { 19: '30,78', 11: '3,21' }, discounts: { percentage_discount: '23,60' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, delivery: { brutto: '154,46', netto: '129,80', vat: '24,66' }, payment: { brutto: '154,46', netto: '129,80', vat: '24,66' }, voucher: { brutto: '21,24' }, grandTotal: '537,05' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case37.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [basketUser] }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: item_discount, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['18,00', '36,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '225,15', totalNetto: '236,00', vats: { 19: '30,78', 11: '3,21' }, discounts: { percentage_discount: '23,60' }, giftcard: { brutto: '2,94', netto: '2,50', vat: '0,44' }, delivery: { brutto: '152,88', netto: '129,80', vat: '23,08' }, payment: { brutto: '152,88', netto: '129,80', vat: '23,08' }, voucher: { brutto: '21,24' }, grandTotal: '533,85' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case38.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [basketUser] }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: item_discount, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['54,00', '108,00'], 1002: ['600,00', '600,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '675,44', totalNetto: '708,00', vats: { 19: '92,34', 11: '9,62' }, discounts: { percentage_discount: '70,80' }, giftcard: { brutto: '8,83', netto: '7,50', vat: '1,33' }, delivery: { brutto: '458,63', netto: '389,40', vat: '69,23' }, payment: { brutto: '458,63', netto: '389,40', vat: '69,23' }, voucher: { brutto: '63,72' }, grandTotal: '1.601,53' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 3\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case39.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [basketUser] }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: item_discount, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['59,94', '119,88'], 1002: ['714,00', '714,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '833,88', totalNetto: '573,48', vats: { 19: '92,34', 11: '9,62' }, discounts: { percentage_discount: '83,39' }, giftcard: { brutto: '8,83', netto: '7,50', vat: '1,33' }, delivery: { brutto: '540,17', netto: '458,63', vat: '81,54' }, payment: { brutto: '540,17', netto: '458,63', vat: '81,54' }, voucher: { brutto: '75,05' }, grandTotal: '1.764,61' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 3\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case4.yaml",
    "content": "articles:\n    - { oxid: 9007, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9008, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9007, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9007], oxsort: 10 }\n    - { oxid: shopdiscount5for9008, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9008], oxsort: 20 }\n    - { oxid: basketdiscount5for9007, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9007], oxsort: 30 }\n    - { oxid: basketdiscount5for9008, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9008], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9007, oxprice: 9, oxactive: 1, oxarticles: [9007] }, { oxtype: WRAP, oxname: testWrap9008, oxprice: 6, oxactive: 1, oxarticles: [9008] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9007: ['76,84', '2.535,72'], 9008: ['47,70', '763,20'] }\n    totals: { totalBrutto: '3.298,92', totalNetto: '2.765,92', vats: { 19: '525,52' }, discounts: { absolutebasketdiscount: '3,40' }, wrapping: { brutto: '267,24', netto: '224,57', vat: '42,67' }, delivery: { brutto: '4,08', netto: '3,43', vat: '0,65' }, payment: { brutto: '0,68', netto: '0,57', vat: '0,11' }, voucher: { brutto: '4,08' }, grandTotal: '3.563,44' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case40.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19, OXSHOPID: 2 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [basketUser] }\nshop:\n    - { oxactive: 1, oxparentid: 1, oxname: subshop, activeshop: true }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: item_discount, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['18,00', '36,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '225,15', totalNetto: '236,00', vats: { 19: '30,78', 11: '3,21' }, discounts: { percentage_discount: '23,60' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, delivery: { brutto: '154,46', netto: '129,80', vat: '24,66' }, payment: { brutto: '154,46', netto: '129,80', vat: '24,66' }, voucher: { brutto: '21,24' }, grandTotal: '537,05' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case41.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1, oxfreeshipping: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19, OXSHOPID: 2 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [basketUser] }\nshop:\n    - { oxactive: 1, oxparentid: 1, oxname: subshop, activeshop: true }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: item_discount, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['18,00', '36,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '225,15', totalNetto: '236,00', vats: { 19: '30,78', 11: '3,21' }, discounts: { percentage_discount: '23,60' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, delivery: { brutto: '23,56', netto: '19,80', vat: '3,76' }, payment: { brutto: '154,46', netto: '129,80', vat: '24,66' }, voucher: { brutto: '21,24' }, grandTotal: '406,15' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case42.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 1, scaleprices: [{ oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 }, { oxamount: 3, oxamountto: 4, oxartid: 1001, oxaddperc: 20 }] }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1, oxfreeshipping: 1 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '20,00'], 1002: ['200,00', '200,00'] }\n    totals: { totalBrutto: '234,18', totalNetto: '220,00', vats: { 19: '34,20', 11: '1,98' }, discounts: { percentage_discount: '22,00' }, delivery: { brutto: '2,38', netto: '2,00', vat: '0,38' }, payment: { brutto: '26,18', netto: '22,00', vat: '4,18' }, grandTotal: '262,74' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case43.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, oxfreeshipping: 1, scaleprices: { oxamount: 1, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1, oxfreeshipping: 1 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['18,00', '36,00'], 1002: ['200,00', '200,00'] }\n    totals: { totalBrutto: '250,16', totalNetto: '236,00', vats: { 19: '34,20', 11: '3,56' }, discounts: { percentage_discount: '23,60' }, delivery: { brutto: '0,00' }, payment: { brutto: '27,80', netto: '23,60', vat: '4,20' }, grandTotal: '277,96' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case44.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1.0, oxvat: 20, amount: 1, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 2.0, oxvat: 30, amount: 2 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 1.0, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['1,20', '1,20'], 1002: ['2,60', '5,20'] }\n    totals: { totalBrutto: '6,40', totalNetto: '4,50', vats: { 20: '0,18', 30: '1,08' }, discounts: { percentage_discount: '0,64' }, delivery: { brutto: '4,16', netto: '3,20', vat: '0,96' }, payment: { brutto: '1,30', netto: '1,00', vat: '0,30' }, grandTotal: '11,22' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case45.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1.0, oxvat: 20, amount: 2, scaleprices: { oxamount: 1, oxamountto: 3, oxartid: 1001, oxaddabs: 2.0 } }\n    - { oxid: 1002, oxprice: 2.0, oxvat: 30, amount: 2 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 1.0, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['1,20', '2,40'], 1002: ['2,60', '5,20'] }\n    totals: { totalBrutto: '7,60', totalNetto: '5,40', vats: { 20: '0,36', 30: '1,08' }, discounts: { percentage_discount: '0,76' }, delivery: { brutto: '4,94', netto: '3,80', vat: '1,14' }, payment: { brutto: '1,30', netto: '1,00', vat: '0,30' }, grandTotal: '13,08' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case46.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2 }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6 }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6 }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6 }\ndiscounts:\n    - { oxid: tenpercentdiscount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 1001: ['1.002,55', '2.005,10'], 1002: ['11,56', '23,12'], 1003: ['1.326,89', '7.961,34'], 1004: ['6,66', '39,96'], 1005: ['0,66', '3,96'] }\n    totals: { totalBrutto: '10.033,48', totalNetto: '8.524,80', vats: { 19: '288,13', 13: '2,39', 3: '208,70', 17: '5,23', 33: '0,88' }, discounts: { tenpercentdiscount: '1.003,35' }, grandTotal: '9.030,13' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case47.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2 }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6 }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6 }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6 }\ndiscounts:\n    - { oxid: absdiscount, oxaddsum: 125.55, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['1.002,55', '2.005,10'], 1002: ['11,56', '23,12'], 1003: ['1.326,89', '7.961,34'], 1004: ['6,66', '39,96'], 1005: ['0,66', '3,96'] }\n    totals: { totalBrutto: '10.033,48', totalNetto: '9.353,48', vats: { 19: '316,14', 13: '2,63', 3: '228,98', 17: '5,73', 33: '0,97' }, discounts: { absdiscount: '125,55' }, delivery: { brutto: '3,14', netto: '3,05', vat: '0,09' }, payment: { brutto: '7,59', netto: '7,37', vat: '0,22' }, grandTotal: '9.918,66' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case48.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2 }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6 }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6 }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6 }\ndiscounts:\n    - { oxid: absdiscount, oxaddsum: 125.55, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['842,48', '1.684,96'], 1002: ['10,23', '20,46'], 1003: ['1.288,24', '7.729,44'], 1004: ['5,69', '34,14'], 1005: ['0,50', '3,00'] }\n    totals: { totalBrutto: '9.900,49', totalNetto: '9.472,00', vats: { 19: '315,90', 13: '2,62', 3: '228,81', 17: '5,73', 33: '0,98' }, discounts: { absdiscount: '125,55' }, delivery: { brutto: '3,14', netto: '3,05', vat: '0,09' }, payment: { brutto: '7,59', netto: '7,37', vat: '0,22' }, grandTotal: '9.911,22' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case49.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1382.42, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 13.58, oxvat: 13, amount: 14 }\n    - { oxid: 1003, oxprice: 1756.66, oxvat: 3, amount: 13 }\n    - { oxid: 1004, oxprice: 13.64, oxvat: 17, amount: 62 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9001, oxprice: 3.98, oxactive: 1, oxarticles: [1001] }, { oxtype: WRAP, oxname: testWrap9002, oxprice: 1.47, oxactive: 1, oxarticles: [1002] }, { oxtype: WRAP, oxname: testWrap9003, oxprice: 2.14, oxactive: 1, oxarticles: [1003] }, { oxtype: CARD, oxname: testCard9001, oxprice: 2.97, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['1.382,42', '2.764,84'], 1002: ['13,58', '190,12'], 1003: ['1.756,66', '22.836,58'], 1004: ['13,64', '845,68'] }\n    totals: { totalBrutto: '26.637,22', totalNetto: '25.385,88', vats: { 19: '441,45', 13: '21,87', 3: '665,14', 17: '122,88' }, delivery: { brutto: '3,14' }, payment: { brutto: '7,59' }, wrapping: { brutto: '56,36' }, giftcard: { brutto: '2,97' }, grandTotal: '26.707,28' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForDelivery: false, blShowVATForPayCharge: false, blShowVATForWrapping: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case5.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87, oxvat: 17, amount: 1 }\nexpected:\n    articles: { 9200: ['87,00', '87,00'] }\n    totals: { totalBrutto: '87,00', totalNetto: '74,36', vats: { 17: '12,64' }, grandTotal: '87,00' }\noptions:\n    config: { blEnterNetPrice: false, blViewNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case50.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1382.42, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 13.58, oxvat: 13, amount: 14 }\n    - { oxid: 1003, oxprice: 1756.66, oxvat: 3, amount: 13 }\n    - { oxid: 1004, oxprice: 13.64, oxvat: 17, amount: 62 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9001, oxprice: 3.98, oxactive: 1, oxarticles: [1001] }, { oxtype: WRAP, oxname: testWrap9002, oxprice: 1.47, oxactive: 1, oxarticles: [1002] }, { oxtype: WRAP, oxname: testWrap9003, oxprice: 2.14, oxactive: 1, oxarticles: [1003] }, { oxtype: CARD, oxname: testCard9001, oxprice: 2.97, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['1.382,42', '2.764,84'], 1002: ['13,58', '190,12'], 1003: ['1.756,66', '22.836,58'], 1004: ['13,64', '845,68'] }\n    totals: { totalBrutto: '26.637,22', totalNetto: '25.385,88', vats: { 19: '441,45', 13: '21,87', 3: '665,14', 17: '122,88' }, delivery: { brutto: '3,14', netto: '3,05', vat: '0,09' }, payment: { brutto: '7,59', netto: '7,37', vat: '0,22' }, wrapping: { brutto: '56,36', netto: '51,91', vat: '4,45' }, giftcard: { brutto: '2,97', netto: '2,88', vat: '0,09' }, grandTotal: '26.707,28' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case51.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1382.42, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 13.58, oxvat: 13, amount: 14 }\n    - { oxid: 1003, oxprice: 1756.66, oxvat: 3, amount: 13 }\n    - { oxid: 1004, oxprice: 13.64, oxvat: 17, amount: 62 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9001, oxprice: 3.98, oxactive: 1, oxarticles: [1001] }, { oxtype: WRAP, oxname: testWrap9002, oxprice: 1.47, oxactive: 1, oxarticles: [1002] }, { oxtype: WRAP, oxname: testWrap9003, oxprice: 2.14, oxactive: 1, oxarticles: [1003] }, { oxtype: CARD, oxname: testCard9001, oxprice: 2.97, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['1.382,42', '2.764,84'], 1002: ['13,58', '190,12'], 1003: ['1.756,66', '22.836,58'], 1004: ['13,64', '845,68'] }\n    totals: { totalBrutto: '26.637,22', totalNetto: '25.385,88', vats: { 19: '441,45', 13: '21,87', 3: '665,14', 17: '122,88' }, delivery: { brutto: '3,14', netto: '2,99', vat: '0,15' }, payment: { brutto: '7,59', netto: '7,23', vat: '0,36' }, wrapping: { brutto: '56,36', netto: '51,91', vat: '4,45' }, giftcard: { brutto: '2,97', netto: '2,83', vat: '0,14' }, grandTotal: '26.707,28' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case52.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1382.42, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 13.58, oxvat: 13, amount: 14 }\n    - { oxid: 1003, oxprice: 1756.66, oxvat: 3, amount: 13 }\n    - { oxid: 1004, oxprice: 13.64, oxvat: 17, amount: 62 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9001, oxprice: 3.98, oxactive: 1, oxarticles: [1001] }, { oxtype: WRAP, oxname: testWrap9002, oxprice: 1.47, oxactive: 1, oxarticles: [1002] }, { oxtype: WRAP, oxname: testWrap9003, oxprice: 2.14, oxactive: 1, oxarticles: [1003] }, { oxtype: CARD, oxname: testCard9001, oxprice: 2.97, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['1.161,70', '2.323,40'], 1002: ['12,02', '168,28'], 1003: ['1.705,50', '22.171,50'], 1004: ['11,66', '722,92'] }\n    totals: { totalBrutto: '26.637,48', totalNetto: '25.386,10', vats: { 19: '441,45', 13: '21,88', 3: '665,15', 17: '122,90' }, delivery: { brutto: '3,23', netto: '3,14', vat: '0,09' }, payment: { brutto: '7,82', netto: '7,59', vat: '0,23' }, wrapping: { brutto: '61,38', netto: '56,36', vat: '5,02' }, giftcard: { brutto: '3,06', netto: '2,97', vat: '0,09' }, grandTotal: '26.712,97' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case53.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2, scaleprices: [{ oxaddabs: 1002.55, oxamount: 1, oxamountto: 5, oxartid: 1001 }, { oxaddabs: 1089.65, oxamount: 6, oxamountto: 10, oxartid: 1001 }] }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2, scaleprices: [{ oxaddabs: 11.56, oxamount: 1, oxamountto: 5, oxartid: 1002 }, { oxaddabs: 16.55, oxamount: 6, oxamountto: 10, oxartid: 1002 }] }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6, scaleprices: [{ oxaddabs: 1325.45, oxamount: 1, oxamountto: 5, oxartid: 1003 }, { oxaddabs: 1326.89, oxamount: 6, oxamountto: 10, oxartid: 1003 }] }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6, scaleprices: [{ oxaddabs: 5.65, oxamount: 1, oxamountto: 5, oxartid: 1004 }, { oxaddabs: 5.69, oxamount: 6, oxamountto: 10, oxartid: 1004 }] }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6, scaleprices: [{ oxaddabs: 0.55, oxamount: 1, oxamountto: 5, oxartid: 1005 }, { oxaddabs: 0.66, oxamount: 6, oxamountto: 10, oxartid: 1005 }] }\ndiscounts:\n    - { oxid: tenpercentdiscount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 1001: ['842,48', '1.684,96'], 1002: ['10,23', '20,46'], 1003: ['1.288,24', '7.729,44'], 1004: ['5,69', '34,14'], 1005: ['0,50', '3,00'] }\n    totals: { totalBrutto: '9.030,12', totalNetto: '9.472,00', vats: { 19: '288,13', 13: '2,39', 3: '208,69', 17: '5,22', 33: '0,89' }, discounts: { tenpercentdiscount: '947,20' }, grandTotal: '9.030,12' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case54.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2, scaleprices: [{ oxaddabs: 1002.55, oxamount: 1, oxamountto: 5, oxartid: 1001 }, { oxaddabs: 1089.65, oxamount: 6, oxamountto: 10, oxartid: 1001 }] }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2, scaleprices: [{ oxaddabs: 11.56, oxamount: 1, oxamountto: 5, oxartid: 1002 }, { oxaddabs: 16.55, oxamount: 6, oxamountto: 10, oxartid: 1002 }] }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6, scaleprices: [{ oxaddabs: 1325.45, oxamount: 1, oxamountto: 5, oxartid: 1003 }, { oxaddabs: 1326.89, oxamount: 6, oxamountto: 10, oxartid: 1003 }] }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6, scaleprices: [{ oxaddabs: 5.65, oxamount: 1, oxamountto: 5, oxartid: 1004 }, { oxaddabs: 5.69, oxamount: 6, oxamountto: 10, oxartid: 1004 }] }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6, scaleprices: [{ oxaddabs: 0.55, oxamount: 1, oxamountto: 5, oxartid: 1005 }, { oxaddabs: 0.66, oxamount: 6, oxamountto: 10, oxartid: 1005 }] }\ndiscounts:\n    - { oxid: tenpercentdiscount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 1001: ['1.002,55', '2.005,10'], 1002: ['11,56', '23,12'], 1003: ['1.326,89', '7.961,34'], 1004: ['6,66', '39,96'], 1005: ['0,66', '3,96'] }\n    totals: { totalBrutto: '10.033,48', totalNetto: '8.524,80', vats: { 19: '288,13', 13: '2,39', 3: '208,70', 17: '5,23', 33: '0,88' }, discounts: { tenpercentdiscount: '1.003,35' }, grandTotal: '9.030,13' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case55.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2, scaleprices: [{ oxaddabs: 1002.55, oxamount: 1, oxamountto: 5, oxartid: 1001 }, { oxaddabs: 1089.65, oxamount: 6, oxamountto: 10, oxartid: 1001 }] }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2, scaleprices: [{ oxaddabs: 11.56, oxamount: 1, oxamountto: 5, oxartid: 1002 }, { oxaddabs: 16.55, oxamount: 6, oxamountto: 10, oxartid: 1002 }] }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6, scaleprices: [{ oxaddabs: 1325.45, oxamount: 1, oxamountto: 5, oxartid: 1003 }, { oxaddabs: 1326.89, oxamount: 6, oxamountto: 10, oxartid: 1003 }] }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6, scaleprices: [{ oxaddabs: 5.65, oxamount: 1, oxamountto: 5, oxartid: 1004 }, { oxaddabs: 5.69, oxamount: 6, oxamountto: 10, oxartid: 1004 }] }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6, scaleprices: [{ oxaddabs: 0.55, oxamount: 1, oxamountto: 5, oxartid: 1005 }, { oxaddabs: 0.66, oxamount: 6, oxamountto: 10, oxartid: 1005 }] }\ndiscounts:\n    - { oxid: absdiscount, oxaddsum: 125.55, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['1.002,55', '2.005,10'], 1002: ['11,56', '23,12'], 1003: ['1.326,89', '7.961,34'], 1004: ['6,66', '39,96'], 1005: ['0,66', '3,96'] }\n    totals: { totalBrutto: '10.033,48', totalNetto: '9.353,48', vats: { 19: '316,14', 13: '2,63', 3: '228,98', 17: '5,73', 33: '0,97' }, discounts: { absdiscount: '125,55' }, delivery: { brutto: '3,14', netto: '3,05', vat: '0,09' }, payment: { brutto: '7,59', netto: '7,37', vat: '0,22' }, grandTotal: '9.918,66' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case56.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2, scaleprices: [{ oxaddabs: 1002.55, oxamount: 1, oxamountto: 5, oxartid: 1001 }, { oxaddabs: 1089.65, oxamount: 6, oxamountto: 10, oxartid: 1001 }] }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2, scaleprices: [{ oxaddabs: 11.56, oxamount: 1, oxamountto: 5, oxartid: 1002 }, { oxaddabs: 16.55, oxamount: 6, oxamountto: 10, oxartid: 1002 }] }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6, scaleprices: [{ oxaddabs: 1325.45, oxamount: 1, oxamountto: 5, oxartid: 1003 }, { oxaddabs: 1326.89, oxamount: 6, oxamountto: 10, oxartid: 1003 }] }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6, scaleprices: [{ oxaddabs: 5.65, oxamount: 1, oxamountto: 5, oxartid: 1004 }, { oxaddabs: 5.69, oxamount: 6, oxamountto: 10, oxartid: 1004 }] }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6, scaleprices: [{ oxaddabs: 0.55, oxamount: 1, oxamountto: 5, oxartid: 1005 }, { oxaddabs: 0.66, oxamount: 6, oxamountto: 10, oxartid: 1005 }] }\ndiscounts:\n    - { oxid: absdiscount, oxaddsum: 125.55, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['842,48', '1.684,96'], 1002: ['10,23', '20,46'], 1003: ['1.288,24', '7.729,44'], 1004: ['5,69', '34,14'], 1005: ['0,50', '3,00'] }\n    totals: { totalBrutto: '9.900,49', totalNetto: '9.472,00', vats: { 19: '315,90', 13: '2,62', 3: '228,81', 17: '5,73', 33: '0,98' }, discounts: { absdiscount: '125,55' }, delivery: { brutto: '3,14', netto: '3,05', vat: '0,09' }, payment: { brutto: '7,59', netto: '7,37', vat: '0,22' }, grandTotal: '9.911,22' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case57.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 0.55, oxvat: 19, amount: 1, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddabs: 2.0 } }\n    - { oxid: 1002, oxprice: 5.52, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 945.95, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 4.74, oxvat: 19, amount: 1 }\n    - { oxid: 1005, oxprice: 1.0, oxvat: 19, amount: 5 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.5, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['0,55', '0,55'], 1002: ['5,52', '5,52'], 1003: ['945,95', '945,95'], 1004: ['4,74', '4,74'], 1005: ['1,00', '5,00'] }\n    totals: { totalBrutto: '1.030,04', totalNetto: '961,76', vats: { 19: '164,46' }, discounts: { percentage_discount: '96,18' }, delivery: { brutto: '114,45', netto: '96,18', vat: '18,27' }, payment: { brutto: '8,93', netto: '7,50', vat: '1,43' }, grandTotal: '1.153,42' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case58.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 9.0, oxvat: 19, amount: 4, scaleprices: { oxaddabs: 2.0, oxamount: 3, oxamountto: 5, oxartid: 1001 } }\n    - { oxid: 1002, oxprice: 5.52, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 945.95, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 4.74, oxvat: 19, amount: 1 }\n    - { oxid: 1005, oxprice: 1.0, oxvat: 19, amount: 5 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.5, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['2,00', '8,00'], 1002: ['5,52', '5,52'], 1003: ['945,95', '945,95'], 1004: ['4,74', '4,74'], 1005: ['1,00', '5,00'] }\n    totals: { totalBrutto: '1.038,02', totalNetto: '969,21', vats: { 19: '165,73' }, discounts: { percentage_discount: '96,92' }, delivery: { brutto: '115,33', netto: '96,92', vat: '18,41' }, payment: { brutto: '8,93', netto: '7,50', vat: '1,43' }, grandTotal: '1.162,28' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case59.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 10.55, oxvat: 19, amount: 4, scaleprices: { oxaddabs: 2.0, oxamount: 3, oxamountto: 5, oxartid: 1001 } }\n    - { oxid: 1002, oxprice: 20.52, oxvat: 19, amount: 3, scaleprices: { oxaddabs: 2.0, oxamount: 3, oxamountto: 5, oxartid: 1002 } }\n    - { oxid: 1003, oxprice: 945.95, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 4.74, oxvat: 19, amount: 1 }\n    - { oxid: 1005, oxprice: 1.0, oxvat: 19, amount: 5 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.5, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['2,00', '8,00'], 1002: ['2,00', '6,00'], 1003: ['945,95', '945,95'], 1004: ['4,74', '4,74'], 1005: ['1,00', '5,00'] }\n    totals: { totalBrutto: '1.038,54', totalNetto: '969,69', vats: { 19: '165,82' }, discounts: { percentage_discount: '96,97' }, delivery: { brutto: '115,39', netto: '96,97', vat: '18,42' }, payment: { brutto: '8,93', netto: '7,50', vat: '1,43' }, grandTotal: '1.162,86' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case6.yaml",
    "content": "articles:\n    - { oxid: 9209, oxprice: 42.36, oxvat: 18, amount: 1 }\nexpected:\n    articles: { 9209: ['24,57', '24,57'] }\n    totals: { totalBrutto: '24,57', totalNetto: '20,82', vats: { 18: '3,75' }, grandTotal: '24,57' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.58\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case60.yaml",
    "content": "articles:\n    - { oxid: testarticle, oxprice: 12.0, amount: 2, scaleprices: { oxaddabs: 11.95, oxamount: 2, oxamountto: 2, oxartid: testarticle } }\ndiscounts:\n    - { oxid: _testDiscount, oxactive: 1, oxtitle: 'new discount', oxprice: 12, oxpriceto: 24.99, oxaddsumtype: abs, oxaddsum: 3, oxsort: 10 }\nexpected:\n    articles: { testarticle: ['11,95', '23,90'] }\n    totals: { totalBrutto: '23,90', totalNetto: '17,56', vats: { 19: '3,34' }, discounts: { _testDiscount: '3,00' }, grandTotal: '20,90' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case61.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9002, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9001, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9001], oxsort: 10 }\n    - { oxid: shopdiscount5for9002, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9002], oxsort: 20 }\n    - { oxid: basketdiscount5for9001, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9001], oxsort: 30 }\n    - { oxid: basketdiscount5for9002, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9002], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9001, oxprice: 9, oxactive: 1, oxarticles: [9001] }, { oxtype: WRAP, oxname: testWrap9002, oxprice: 6, oxactive: 1, oxarticles: [9002] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxarticles: [9001, 9002] }]\n    voucherserie: [{ oxserienr: abs_4_voucher_serie, oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9001: ['63,92', '2.109,36'], 9002: ['40,08', '641,28'] }\n    totals: { totalBrutto: '2.750,64', totalNetto: '2.305,18', vats: { 19: '437,98' }, discounts: { absolutebasketdiscount: '3,40' }, wrapping: { brutto: '267,24', netto: '224,57', vat: '42,67' }, delivery: { brutto: '4,08', netto: '3,43', vat: '0,65' }, payment: { brutto: '0,68', netto: '0,57', vat: '0,11' }, voucher: { brutto: '4,08' }, grandTotal: '3.015,16' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case65.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 100.0, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_111, oxaddsum: 15.0, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxprice: 85, oxpriceto: 110, oxactive: 1, oxarticles: [111], oxsort: 10 }\nexpected:\n    articles: { 111: ['68,00', '68,00'] }\n    totals: { totalBrutto: '68,00', totalNetto: '57,14', vats: { 19: '10,86' }, grandTotal: '68,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.8\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case66.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 120.0, oxvat: 20, amount: 1 }\ndiscounts:\n    - { oxid: discount_for_111, oxaddsum: 50.0, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 99999, oxactive: 1, oxarticles: [111], oxsort: 10 }\n    - { oxid: basket, oxaddsum: 50.0, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 99999, oxactive: 1, oxsort: 20 }\nexpected:\n    articles: { 111: ['60,00', '60,00'] }\n    totals: { totalBrutto: '60,00', totalNetto: '25,00', vats: { 20: '5,00' }, discounts: { basket: '30,00' }, grandTotal: '30,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case67.yaml",
    "content": "articles:\n    - { oxid: _testProduct, oxprice: 10.0, oxvat: 19, amount: 36 }\ndiscounts:\n    - { oxid: basket_0, oxaddsum: 6.0, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 199, oxactive: 1, oxsort: 10 }\n    - { oxid: basket_1, oxaddsum: 9.0, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 200, oxpriceto: 299, oxactive: 1, oxsort: 20 }\n    - { oxid: basket_2, oxaddsum: 12.0, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 300, oxpriceto: 99999, oxactive: 1, oxsort: 30 }\nexpected:\n    articles: { _testProduct: ['10,00', '360,00'] }\n    totals: { totalBrutto: '360,00', totalNetto: '266,22', vats: { 19: '50,58' }, discounts: { basket_2: '43,20' }, grandTotal: '316,80' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case68.yaml",
    "content": "articles:\n    - { oxid: _testProduct, oxprice: 10.0, oxvat: 19, amount: 31 }\ndiscounts:\n    - { oxid: basket_0, oxaddsum: 6.0, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 199, oxactive: 1, oxarticles: [_testProduct], oxsort: 10 }\n    - { oxid: basket_1, oxaddsum: 9.0, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 200, oxpriceto: 299, oxactive: 1, oxarticles: [_testProduct], oxsort: 20 }\n    - { oxid: basket_2, oxaddsum: 12.0, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 300, oxpriceto: 99999, oxactive: 1, oxarticles: [_testProduct], oxsort: 30 }\nexpected:\n    articles: { _testProduct: ['8,80', '272,80'] }\n    totals: { totalBrutto: '272,80', totalNetto: '229,24', vats: { 19: '43,56' }, grandTotal: '272,80' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case69.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 1 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '10,00'] }\n    totals: { totalBrutto: '11,90', totalNetto: '10,00', vats: { 19: '1,90' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '35,70' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case7.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87, oxvat: 17, amount: 1 }\nexpected:\n    articles: { 9200: ['59,16', '59,16'] }\n    totals: { totalBrutto: '59,16', totalNetto: '50,56', vats: { 17: '8,60' }, grandTotal: '59,16' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case70.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 50 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '500,00'] }\n    totals: { totalBrutto: '595,00', totalNetto: '500,00', vats: { 19: '95,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '618,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case71.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 50 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '500,00'] }\n    totals: { totalBrutto: '595,00', totalNetto: '500,00', vats: { 19: '95,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '618,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case72.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 200 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '2.000,00'] }\n    totals: { totalBrutto: '2.380,00', totalNetto: '2.000,00', vats: { 19: '380,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '2.403,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case73.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 200 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '2.000,00'] }\n    totals: { totalBrutto: '2.380,00', totalNetto: '2.000,00', vats: { 19: '380,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '2.403,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case74.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 250 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '2.500,00'] }\n    totals: { totalBrutto: '2.975,00', totalNetto: '2.500,00', vats: { 19: '475,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '2.998,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case75.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 250 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '2.500,00'] }\n    totals: { totalBrutto: '2.975,00', totalNetto: '2.500,00', vats: { 19: '475,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '2.998,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case76.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 250 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '2.500,00'] }\n    totals: { totalBrutto: '2.975,00', totalNetto: '2.500,00', vats: { 19: '475,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '2.998,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case77.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 510 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '5.100,00'] }\n    totals: { totalBrutto: '6.069,00', totalNetto: '5.100,00', vats: { 19: '969,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '6.092,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case78.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 510 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '5.100,00'] }\n    totals: { totalBrutto: '6.069,00', totalNetto: '5.100,00', vats: { 19: '969,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '6.092,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case79.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 1000 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '10.000,00'] }\n    totals: { totalBrutto: '11.900,00', totalNetto: '10.000,00', vats: { 19: '1.900,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '11.923,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case8.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87, oxvat: 17, amount: 1 }\nexpected:\n    articles: { 9200: ['127,89', '127,89'] }\n    totals: { totalBrutto: '127,89', totalNetto: '109,31', vats: { 17: '18,58' }, grandTotal: '127,89' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1.47\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case80.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 30.0, oxvat: 25, amount: 15 }\n    - { oxid: 1002, oxprice: 100.0, oxvat: 20, amount: 15 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['30,00', '450,00'], 1002: ['100,00', '1.500,00'] }\n    totals: { totalBrutto: '2.362,50', totalNetto: '1.950,00', vats: { 25: '112,50', 20: '300,00' }, delivery: { brutto: '1.299,38', netto: '1.072,50', vat: '226,88' }, payment: { brutto: '1.299,38', netto: '1.072,50', vat: '226,88' }, grandTotal: '4.961,26' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case81.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 30.0, oxvat: 25, amount: 15 }\n    - { oxid: 1002, oxprice: 100.0, oxvat: 20, amount: 15 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['30,00', '450,00'], 1002: ['100,00', '1.500,00'] }\n    totals: { totalBrutto: '2.362,50', totalNetto: '1.950,00', vats: { 25: '112,50', 20: '300,00' }, delivery: { brutto: '1.299,38', netto: '1.072,50', vat: '226,88' }, payment: { brutto: '1.299,38', netto: '1.072,50', vat: '226,88' }, grandTotal: '4.961,26' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case82.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 30.0, oxvat: 25, amount: 15 }\n    - { oxid: 1002, oxprice: 100.0, oxvat: 20, amount: 15 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['30,00', '450,00'], 1002: ['100,00', '1.500,00'] }\n    totals: { totalBrutto: '2.126,25', totalNetto: '1.950,00', vats: { 25: '101,25', 20: '270,00' }, delivery: { brutto: '1.299,38', netto: '1.072,50', vat: '226,88' }, payment: { brutto: '1.299,38', netto: '1.072,50', vat: '226,88' }, voucher: { brutto: '195,00' }, grandTotal: '4.725,01' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case83.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 30.0, oxvat: 25, amount: 15 }\n    - { oxid: 1002, oxprice: 100.0, oxvat: 20, amount: 15 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['20,40', '306,00'], 1002: ['68,00', '1.020,00'] }\n    totals: { totalBrutto: '1.445,85', totalNetto: '1.326,00', vats: { 25: '68,85', 20: '183,60' }, delivery: { brutto: '883,58', netto: '729,30', vat: '154,28' }, payment: { brutto: '883,58', netto: '729,30', vat: '154,28' }, voucher: { brutto: '132,60' }, grandTotal: '3.213,01' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case84.yaml",
    "content": "articles:\n    - { oxid: rounding_issue_test_article, oxprice: 298.55, oxvat: 19, amount: 200 }\ndiscounts:\n    - { oxid: discount_2_55_forShop, oxaddsum: 2.55, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { rounding_issue_test_article: ['298,55', '59.710,00'] }\n    totals: { totalBrutto: '59.710,00', discounts: { discount_2_55_forShop: '1.522,61' }, totalNetto: '48.896,97', vats: { 19: '9.290,42' }, grandTotal: '58.187,39' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case85.yaml",
    "content": "articles:\n    - { oxid: rounding_issue_test_article, oxprice: 298.55, oxvat: 19, amount: 200 }\ndiscounts:\n    - { oxid: discount_2_55_forBasket, oxaddsum: 2.55, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [rounding_issue_test_article], oxsort: 10 }\nexpected:\n    articles: { rounding_issue_test_article: ['290,94', '58.188,00'] }\n    totals: { totalBrutto: '58.188,00', totalNetto: '48.897,48', vats: { 19: '9.290,52' }, grandTotal: '58.188,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case86.yaml",
    "content": "articles:\n    - { oxid: rounding_issue_test_article, oxprice: 298.55, oxvat: 19, amount: 200 }\ndiscounts:\n    - { oxid: discount_2_55_forShop, oxaddsum: 2.55, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [rounding_issue_test_article], oxsort: 10 }\nexpected:\n    articles: { rounding_issue_test_article: ['290,94', '58.188,00'] }\n    totals: { totalBrutto: '58.188,00', totalNetto: '48.897,48', vats: { 19: '9.290,52' }, grandTotal: '58.188,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case87.yaml",
    "content": "articles:\n    - { oxid: rounding_issue_test_article, oxprice: 298.55, oxvat: 19, amount: 200 }\ndiscounts:\n    - { oxid: discount_2_55_forShop, oxaddsum: 2.55, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { rounding_issue_test_article: ['290,94', '58.188,00'] }\n    totals: { totalBrutto: '58.188,00', totalNetto: '48.897,48', vats: { 19: '9.290,52' }, grandTotal: '58.188,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case88.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 1.0, oxvat: 20, amount: 1 }\n    - { oxid: 1111, oxprice: 95.02, oxvat: 20, amount: 6 }\n    - { oxid: 1112, oxprice: 105.78, oxvat: 20, amount: 7 }\ndiscounts:\n    - { oxid: procdiscountforbasket, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['1,00', '1,00'], 1111: ['95,02', '570,12'], 1112: ['105,78', '740,46'] }\n    totals: { totalBrutto: '1.416,50', totalNetto: '1.311,58', vats: { 20: '236,08' }, discounts: { procdiscountforbasket: '131,16' }, delivery: { brutto: '786,95', netto: '655,79', vat: '131,16' }, payment: { brutto: '1,20', netto: '1,00', vat: '0,20' }, grandTotal: '2.204,65' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case89.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 19, amount: 1 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 5.02, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: procdiscountforbasket, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountfor1113, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114], oxsort: 30 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['5,52', '5,52'], 1113: ['945,95', '945,95'], 1114: ['4,74', '4,74'] }\n    totals: { totalBrutto: '1.024,69', totalNetto: '956,76', vats: { 19: '163,61' }, discounts: { procdiscountforbasket: '95,68' }, delivery: { brutto: '569,27', netto: '478,38', vat: '90,89' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '1.653,46' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case9.yaml",
    "content": "articles:\n    - { oxid: 9207, oxprice: 45.5, oxvat: 19, amount: 1 }\nexpected:\n    articles: { 9207: ['45,50', '45,50'] }\n    totals: { totalBrutto: '45,50', totalNetto: '38,24', vats: { 19: '7,26' }, grandTotal: '45,50' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case90.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 3 }\n    - { oxid: 1112, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1113, oxprice: 0.9, oxvat: 19, amount: 3 }\n    - { oxid: 1114, oxprice: 5.02, oxvat: 55, amount: 1 }\n    - { oxid: 1115, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1114], oxsort: 20 }\n    - { oxid: discountforbasket1112, oxaddsum: -5.2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1112], oxsort: 30 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '1,65'], 1112: ['105,78', '105,78'], 1113: ['0,90', '2,70'], 1114: ['5,52', '5,52'], 1115: ['0,50', '0,50'] }\n    totals: { totalBrutto: '126,18', totalNetto: '116,15', vats: { 19: '18,92', 55: '2,73' }, discounts: { discountforbasket10%: '11,62' }, delivery: { brutto: '69,12', netto: '58,08', vat: '11,04' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '254,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blPaymentVatOnTop: true, blDeliveryVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case91.yaml",
    "content": "articles:\n    - { oxid: _test1001, oxprice: 1002.55, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2 }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6 }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6 }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6 }\ndiscounts:\n    - { oxid: tenpercentdiscount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { _test1001: ['842,48', '1.684,96'], 1002: ['10,23', '20,46'], 1003: ['1.288,24', '7.729,44'], 1004: ['5,69', '34,14'], 1005: ['0,50', '3,00'] }\n    totals: { totalBrutto: '9.030,12', totalNetto: '9.472,00', vats: { 19: '288,13', 13: '2,39', 3: '208,69', 17: '5,22', 33: '0,89' }, discounts: { tenpercentdiscount: '947,20' }, grandTotal: '9.030,12' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case92.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 19, amount: 1 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 5.02, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountfor1113, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114], oxsort: 30 }\n    - { oxid: discountforbasket20%, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 40 }\n    - { oxid: discountforbasket35%, oxaddsum: 35, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['5,52', '5,52'], 1113: ['945,95', '945,95'], 1114: ['4,74', '4,74'] }\n    totals: { totalBrutto: '532,84', totalNetto: '956,76', vats: { 19: '85,08' }, discounts: { discountforbasket10%: '95,68', discountforbasket20%: '172,22', discountforbasket35%: '241,10' }, delivery: { brutto: '569,27', netto: '478,38', vat: '90,89' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '1.161,61' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case93.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 55, amount: 1 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 5.02, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountforproduct1, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountforproduct2, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114], oxsort: 30 }\n    - { oxid: discountforbasket20%, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 40 }\n    - { oxid: discountforbasket35%, oxaddsum: 35, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['5,52', '5,52'], 1113: ['945,95', '945,95'], 1114: ['4,74', '4,74'] }\n    totals: { totalBrutto: '533,76', totalNetto: '956,76', vats: { 19: '84,58', 55: '1,42' }, discounts: { discountforbasket10%: '95,68', discountforbasket20%: '172,22', discountforbasket35%: '241,10' }, delivery: { brutto: '569,27', netto: '478,38', vat: '90,89' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '1.162,53' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case94.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 55, amount: 1 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1115, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1116, oxprice: 1.0, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountfor1113, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114], oxsort: 30 }\n    - { oxid: discountforbasket20%, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 40 }\n    - { oxid: discountforbasket35%, oxaddsum: 35, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\n    - { oxid: discountforbasket1115, oxaddsum: -5.2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1115], oxsort: 60 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['5,52', '5,52'], 1113: ['945,95', '945,95'], 1114: ['95,02', '95,02'], 1115: ['105,78', '105,78'], 1116: ['1,00', '1,00'] }\n    totals: { totalBrutto: '643,52', totalNetto: '1.153,82', vats: { 19: '102,11', 55: '1,42' }, discounts: { discountforbasket10%: '115,38', discountforbasket20%: '207,69', discountforbasket35%: '290,76' }, delivery: { brutto: '686,52', netto: '576,91', vat: '109,61' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '1.389,54' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case95.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 55, amount: 5 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1115, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1116, oxprice: 1.0, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountfor1113, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114], oxsort: 30 }\n    - { oxid: discountforbasket20%, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 40 }\n    - { oxid: discountforbasket35%, oxaddsum: 35, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\n    - { oxid: discountforbasket1115, oxaddsum: -5.2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1115], oxsort: 60 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['5,52', '27,60'], 1113: ['945,95', '945,95'], 1114: ['95,02', '95,02'], 1115: ['105,78', '105,78'], 1116: ['1,00', '1,00'] }\n    totals: { totalBrutto: '659,53', totalNetto: '1.175,90', vats: { 19: '102,11', 55: '7,10' }, discounts: { discountforbasket10%: '117,59', discountforbasket20%: '211,66', discountforbasket35%: '296,33' }, delivery: { brutto: '699,66', netto: '587,95', vat: '111,71' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '1.418,69' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case96.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 55, amount: 5 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1115, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1116, oxprice: 1.0, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountfor1113, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114], oxsort: 30 }\n    - { oxid: discountforbasket1115, oxaddsum: -5.2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1115], oxsort: 40 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['5,52', '27,60'], 1113: ['945,95', '945,95'], 1114: ['95,02', '95,02'], 1115: ['105,78', '105,78'], 1116: ['1,00', '1,00'] }\n    totals: { totalBrutto: '1.268,33', totalNetto: '1.175,90', vats: { 19: '196,36', 55: '13,66' }, discounts: { discountforbasket10%: '117,59' }, delivery: { brutto: '699,66', netto: '587,95', vat: '111,71' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '2.027,49' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case97.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 19, amount: 1 }\n    - { oxid: 1113, oxprice: 1, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 1001, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112, 1114], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 0.55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,50', '0,50'], 1112: ['4,97', '4,97'], 1113: ['0,90', '0,90'], 1114: ['990,99', '990,99'] }\n    totals: { totalBrutto: '1.186,86', totalNetto: '997,36', vats: { 19: '189,50' }, delivery: { brutto: '652,77', netto: '548,55', vat: '104,22' }, payment: { brutto: '0,65', netto: '0,55', vat: '0,10' }, grandTotal: '1.840,28' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case98.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 99, amount: 3 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 99, amount: 2 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 99, amount: 1 }\n    - { oxid: 1114, oxprice: 5.02, oxvat: 99, amount: 1 }\n    - { oxid: 1115, oxprice: 100.55, oxvat: 99, amount: 1 }\n    - { oxid: 1116, oxprice: 5, oxvat: 9, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountfor1113, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114, 1115], oxsort: 30 }\n    - { oxid: procdiscount20%, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 40 }\n    - { oxid: procdiscount35%, oxaddsum: 35, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 0.55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '1,65'], 1112: ['5,52', '11,04'], 1113: ['945,95', '945,95'], 1114: ['4,74', '4,74'], 1115: ['95,02', '95,02'], 1116: ['5,00', '5,00'] }\n    totals: { totalBrutto: '988,26', totalNetto: '1.063,40', vats: { 99: '490,38', 9: '0,21' }, discounts: { discountforbasket10%: '106,34', procdiscount20%: '191,41', procdiscount35%: '267,98' }, delivery: { brutto: '1.163,89', netto: '584,87', vat: '579,02' }, payment: { brutto: '1,09', netto: '0,55', vat: '0,54' }, grandTotal: '2.153,24' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/case99.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.55, oxvat: 33, amount: 1 }\n    - { oxid: 1112, oxprice: 1101.1, oxvat: 33, amount: 1 }\n    - { oxid: 1113, oxprice: 110, oxvat: 33, amount: 1 }\n    - { oxid: 1114, oxprice: 1.0, oxvat: 33, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['1.101,10', '1.101,10'], 1113: ['110,00', '110,00'], 1114: ['1,00', '1,00'] }\n    totals: { totalBrutto: '1.451,54', totalNetto: '1.212,65', vats: { 33: '360,16' }, discounts: { discountforbasket10%: '121,27' }, delivery: { brutto: '887,06', netto: '666,96', vat: '220,10' }, payment: { brutto: '73,15', netto: '55,00', vat: '18,15' }, grandTotal: '2.411,75' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testDeliveryBug4622(1).yaml",
    "content": "articles:\n    - { oxid: vine1, oxprice: 100, oxvat: 19, amount: 1 }\n    - { oxid: coupon, oxprice: 50, oxvat: 19, amount: 2, oxnonmaterial: true }\ncosts:\n    delivery: [{ oxtitle: '10% from total ', oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 1000, oxsort: 1 }]\nexpected:\n    articles: { vine1: ['100,00', '100,00'], coupon: ['50,00', '100,00'] }\n    totals: { totalBrutto: '200,00', totalNetto: '168,07', vats: { 19: '31,93' }, delivery: { brutto: '10,00' }, grandTotal: '210,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net, blExclNonMaterialFromDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testDeliveryBug4622(2).yaml",
    "content": "articles:\n    - { oxid: vine1, oxprice: 100, oxvat: 19, amount: 1 }\n    - { oxid: coupon, oxprice: 50, oxvat: 19, amount: 2, oxnonmaterial: true }\ncosts:\n    delivery: [{ oxtitle: '10% from total ', oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 1000, oxsort: 1 }]\nexpected:\n    articles: { vine1: ['100,00', '100,00'], coupon: ['50,00', '100,00'] }\n    totals: { totalBrutto: '200,00', totalNetto: '168,07', vats: { 19: '31,93' }, delivery: { brutto: '20,00' }, grandTotal: '220,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net, blExclNonMaterialFromDelivery: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testDeliveryBug4622(3).yaml",
    "content": "articles:\n    - { oxid: coupon, oxprice: 50, oxvat: 19, amount: 2, oxnonmaterial: true }\ncosts:\n    delivery: [{ oxtitle: '10% from total ', oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 1000, oxsort: 1 }]\nexpected:\n    articles: { coupon: ['50,00', '100,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '84,03', vats: { 19: '15,97' }, delivery: { brutto: '10,00' }, grandTotal: '110,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net, blExclNonMaterialFromDelivery: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testDeliveryBug4622(4).yaml",
    "content": "articles:\n    - { oxid: coupon, oxprice: 50, oxvat: 19, amount: 2, oxnonmaterial: true }\ncosts:\n    delivery: [{ oxtitle: '10% from total ', oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 1000, oxsort: 1 }]\nexpected:\n    articles: { coupon: ['50,00', '100,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '84,03', vats: { 19: '15,97' }, delivery: { brutto: '0,00' }, grandTotal: '100,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net, blExclNonMaterialFromDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testDeliveryBug4730(1).yaml",
    "content": "categories:\n    - { oxid: vine, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [vine1] }\n    - { oxid: supplies, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [supply1] }\narticles:\n    - { oxid: vine1, oxprice: 5, oxvat: 10, amount: 6 }\n    - { oxid: supply1, oxprice: 10, oxvat: 10, amount: 1 }\ncosts:\n    delivery: [\n        { oxtitle: '1 - 3 Bottles', oxactive: 1, oxaddsum: 4.9, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 1, oxparamend: 3, oxsort: 1, oxcategories: [vine] },\n        { oxtitle: '4 - 11 Bottles', oxactive: 1, oxaddsum: 5.9, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 4, oxparamend: 11, oxsort: 1, oxcategories: [vine] },\n        { oxtitle: 'more than 12 Bottles', oxactive: 1, oxaddsum: 0, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 12, oxparamend: 99999, oxsort: 1, oxcategories: [vine] },\n        { oxtitle: supplies, oxactive: 1, oxaddsum: 2.9, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 0, oxparamend: 99999, oxsort: 4, oxcategories: [supplies] }\n    ]\nexpected:\n    articles: { vine1: ['5,00', '30,00'], supply1: ['10,00', '10,00'] }\n    totals: { totalBrutto: '40,00', totalNetto: '36,36', vats: { 10: '3,64' }, delivery: { brutto: '5,90' }, grandTotal: '45,90' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testDeliveryBug4730(2).yaml",
    "content": "categories:\n    - { oxid: vine, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [vine1] }\n    - { oxid: supplies, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [supply1] }\narticles:\n    - { oxid: vine1, oxprice: 5, oxvat: 10, amount: 2 }\n    - { oxid: supply1, oxprice: 10, oxvat: 10, amount: 1 }\ncosts:\n    delivery: [\n        { oxtitle: '1 - 3 Bottles', oxactive: 1, oxaddsum: 4.9, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 1, oxparamend: 3, oxsort: 1, oxcategories: [vine] },\n        { oxtitle: '4 - 11 Bottles', oxactive: 1, oxaddsum: 5.9, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 4, oxparamend: 11, oxsort: 1, oxcategories: [vine] },\n        { oxtitle: 'more than 12 Bottles', oxactive: 1, oxaddsum: 0, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 12, oxparamend: 99999, oxsort: 1, oxcategories: [vine] },\n        { oxtitle: supplies, oxactive: 1, oxaddsum: 2.9, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 0, oxparamend: 99999, oxsort: 4, oxcategories: [supplies] }\n    ]\nexpected:\n    articles: { vine1: ['5,00', '10,00'], supply1: ['10,00', '10,00'] }\n    totals: { totalBrutto: '20,00', totalNetto: '18,18', vats: { 10: '1,82' }, delivery: { brutto: '4,90' }, grandTotal: '24,90' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testDeliveryByWeight(1).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 1.8, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 2 }\n    - { oxid: 10012, oxprice: 2.0, oxvat: 19, amount: 1, oxweight: 2 }\n    - { oxid: 10013, oxprice: 1.7, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 15.0, oxfinalize: 0, oxparamend: 999, oxfixed: 2, oxsort: 4 }, { oxactive: 1, oxaddsum: 1.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 1.0, oxfinalize: 0, oxparamend: 4.99999999, oxfixed: 2, oxsort: 1 }, { oxactive: 1, oxaddsum: 5.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 5.0, oxfinalize: 0, oxparamend: 14.9999999, oxfixed: 2, oxsort: 2 }]\nexpected:\n    articles: { 10011: ['1,80', '1,80'], 10012: ['2,00', '2,00'], 10013: ['1,70', '1,70'] }\n    totals: { totalBrutto: '5,50', totalNetto: '4,62', vats: { 19: '0,88' }, delivery: { brutto: '12,00' }, grandTotal: '17,50' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testDeliveryByWeight(2).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 1.8, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 2 }\n    - { oxid: 10012, oxprice: 2.0, oxvat: 19, amount: 3, oxweight: 2 }\n    - { oxid: 10013, oxprice: 1.7, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 15.0, oxfinalize: 0, oxparamend: 999, oxfixed: 2, oxsort: 4 }, { oxactive: 1, oxaddsum: 1.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 1.0, oxfinalize: 0, oxparamend: 4.99999999, oxfixed: 2, oxsort: 1 }, { oxactive: 1, oxaddsum: 5.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 5.0, oxfinalize: 0, oxparamend: 14.9999999, oxfixed: 2, oxsort: 2 }]\nexpected:\n    articles: { 10011: ['1,80', '1,80'], 10012: ['2,00', '6,00'], 10013: ['1,70', '1,70'] }\n    totals: { totalBrutto: '9,50', totalNetto: '7,98', vats: { 19: '1,52' }, delivery: { brutto: '14,00' }, grandTotal: '23,50' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testDeliveryByWeight(3).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 1.8, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 2 }\n    - { oxid: 10012, oxprice: 2.0, oxvat: 19, amount: 3, oxweight: 2 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 15.0, oxfinalize: 0, oxparamend: 999, oxfixed: 2, oxsort: 4 }, { oxactive: 1, oxaddsum: 1.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 1.0, oxfinalize: 0, oxparamend: 4.99999999, oxfixed: 2, oxsort: 1 }, { oxactive: 1, oxaddsum: 5.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 5.0, oxfinalize: 0, oxparamend: 14.9999999, oxfixed: 2, oxsort: 2 }]\nexpected:\n    articles: { 10011: ['1,80', '1,80'], 10012: ['2,00', '6,00'] }\n    totals: { totalBrutto: '7,80', totalNetto: '6,55', vats: { 19: '1,25' }, delivery: { brutto: '4,00' }, grandTotal: '11,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testDeliveryByWeight(4).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 1.8, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 2 }\n    - { oxid: 10012, oxprice: 2.0, oxvat: 19, amount: 1, oxweight: 2 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 15.0, oxfinalize: 0, oxparamend: 999, oxfixed: 2, oxsort: 4 }, { oxactive: 1, oxaddsum: 1.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 1.0, oxfinalize: 0, oxparamend: 4.99999999, oxfixed: 2, oxsort: 1 }, { oxactive: 1, oxaddsum: 5.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 5.0, oxfinalize: 0, oxparamend: 14.9999999, oxfixed: 2, oxsort: 2 }]\nexpected:\n    articles: { 10011: ['1,80', '1,80'], 10012: ['2,00', '2,00'] }\n    totals: { totalBrutto: '3,80', totalNetto: '3,19', vats: { 19: '0,61' }, delivery: { brutto: '2,00' }, grandTotal: '5,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testDeliveryByWeight(5).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 1.8, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 2 }\n    - { oxid: 10012, oxprice: 2.0, oxvat: 19, amount: 1, oxweight: 3 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 15.0, oxfinalize: 0, oxparamend: 999, oxfixed: 0, oxsort: 4 }, { oxactive: 1, oxaddsum: 1.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 1.0, oxfinalize: 0, oxparamend: 4.99999999, oxfixed: 0, oxsort: 1 }, { oxactive: 1, oxaddsum: 5.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 5.0, oxfinalize: 0, oxparamend: 14.9999999, oxfixed: 0, oxsort: 2 }]\nexpected:\n    articles: { 10011: ['1,80', '1,80'], 10012: ['2,00', '2,00'] }\n    totals: { totalBrutto: '3,80', totalNetto: '3,19', vats: { 19: '0,61' }, delivery: { brutto: '5,00' }, grandTotal: '8,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testDeliveryByWeight(6).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 1.8, oxvat: 19, amount: 2, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 2 }\n    - { oxid: 10012, oxprice: 2.0, oxvat: 19, amount: 2, oxweight: 3 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 15.0, oxfinalize: 0, oxparamend: 999, oxfixed: 1, oxsort: 4 }, { oxactive: 1, oxaddsum: 1.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 1.0, oxfinalize: 0, oxparamend: 4.99999999, oxfixed: 1, oxsort: 1 }, { oxactive: 1, oxaddsum: 5.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 5.0, oxfinalize: 0, oxparamend: 14.9999999, oxfixed: 1, oxsort: 2 }]\nexpected:\n    articles: { 10011: ['1,80', '3,60'], 10012: ['2,00', '4,00'] }\n    totals: { totalBrutto: '7,60', totalNetto: '6,39', vats: { 19: '1,21' }, delivery: { brutto: '6,00' }, grandTotal: '13,60' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testDeliveryLowerShippingCost(1).yaml",
    "content": "categories:\n    - { oxid: books, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [book] }\n    - { oxid: otherStuff, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [stuff] }\narticles:\n    - { oxid: stuff, oxprice: 20, oxvat: 20, amount: 6 }\n    - { oxid: book, oxprice: 10, oxvat: 10, amount: 1 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 2, oxaddsumtype: abs, oxdeltype: a, oxparam: 0, oxparamend: 99999, oxsort: 1 }, { oxactive: 1, oxaddsum: 3, oxaddsumtype: abs, oxdeltype: a, oxparam: 0, oxparamend: 99999, oxsort: 2, oxcategories: [otherStuff] }]\nexpected:\n    articles: { book: ['10,00', '10,00'], stuff: ['20,00', '120,00'] }\n    totals: { totalBrutto: '130,00', totalNetto: '109,09', vats: { 10: '0,91', 20: '20,00' }, delivery: { brutto: '5,00' }, grandTotal: '135,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testDeliveryLowerShippingCost(2).yaml",
    "content": "categories:\n    - { oxid: books, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [book] }\n    - { oxid: otherStuff, oxparentid: oxrootid, oxshopid: 1, oxactive: 1 }\narticles:\n    - { oxid: book, oxprice: 10, oxvat: 10, amount: 1 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 2, oxaddsumtype: abs, oxdeltype: a, oxparam: 0, oxparamend: 99999, oxsort: 1 }, { oxactive: 1, oxaddsum: 3, oxaddsumtype: abs, oxdeltype: a, oxparam: 0, oxparamend: 99999, oxsort: 2, oxcategories: [otherStuff] }]\nexpected:\n    articles: { book: ['10,00', '10,00'] }\n    totals: { totalBrutto: '10,00', totalNetto: '9,09', vats: { 10: '0,91' }, delivery: { brutto: '2,00' }, grandTotal: '12,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testDeliveryLowerShippingCost(3).yaml",
    "content": "categories:\n    - { oxid: books, oxparentid: oxrootid, oxshopid: 1, oxactive: 1 }\n    - { oxid: otherStuff, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [stuff] }\narticles:\n    - { oxid: stuff, oxprice: 20, oxvat: 20, amount: 6 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 2, oxaddsumtype: abs, oxdeltype: a, oxparam: 0, oxparamend: 99999, oxsort: 1 }, { oxactive: 1, oxaddsum: 3, oxaddsumtype: abs, oxdeltype: a, oxparam: 0, oxparamend: 99999, oxsort: 2, oxcategories: [otherStuff] }]\nexpected:\n    articles: { stuff: ['20,00', '120,00'] }\n    totals: { totalBrutto: '120,00', totalNetto: '100,00', vats: { 20: '20,00' }, delivery: { brutto: '5,00' }, grandTotal: '125,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testDiscountBug5913.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 50.0, oxstock: 100, oxvat: 19, oxartnum: '1000', amount: 2 }\n    - { oxid: 1003, oxprice: 5.0, oxstock: 1, oxvat: 19, oxstockflag: 2, oxartnum: '1003' }\ndiscounts:\n    - { oxid: testitem_discount, oxshopid: 1, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 0, oxactive: 1, oxitmartid: 1003, oxitmamount: 1, oxitmmultiple: 1, oxarticles: [1000], oxsort: 10 }\nexpected:\n    articles: { 1000: ['50,00', '100,00'], 1003: ['0,00', '0,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '84,03', vats: { 19: '15,97' }, grandTotal: '100,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testDiscountSorting.yaml",
    "content": "articles:\n    - { oxid: 10005, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount5for10005, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 70 }\n    - { oxid: shopdiscount5for1004, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 20 }\n    - { oxid: basketdiscount5for10005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 60 }\n    - { oxid: basketdiscount5for1004, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 30 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor10005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 40 }\n    - { oxid: procdiscountfor1004, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap102, oxprice: 9, oxactive: 1, oxarticles: [10005] }, { oxtype: WRAP, oxname: testWrap1002, oxprice: 6, oxactive: 1, oxarticles: [1004] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 10005: ['1.115,95', '1.115,95'], 1004: ['0,59', '0,59'] }\n    totals: { totalBrutto: '1.116,54', totalNetto: '929,03', vats: { 19: '176,51' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '15,00', netto: '12,60', vat: '2,40' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '1.127,54' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testDiscountSortingDBRMIndependency.yaml",
    "content": "articles:\n    - { oxid: 10005, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: shopdiscount5for1004, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 20 }\n    - { oxid: basketdiscount5for1004, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 30 }\n    - { oxid: procdiscountfor10005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 40 }\n    - { oxid: procdiscountfor1004, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 50 }\n    - { oxid: basketdiscount5for10005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 60 }\n    - { oxid: shopdiscount5for10005, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 70 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap102, oxprice: 9, oxactive: 1, oxarticles: [10005] }, { oxtype: WRAP, oxname: testWrap1002, oxprice: 6, oxactive: 1, oxarticles: [1004] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 10005: ['1.115,95', '1.115,95'], 1004: ['0,59', '0,59'] }\n    totals: { totalBrutto: '1.116,54', totalNetto: '929,03', vats: { 19: '176,51' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '15,00', netto: '12,60', vat: '2,40' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '1.127,54' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFewItmDiscounts.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1 }\n    - { oxid: 1003, oxprice: 50.0, oxvat: 5 }\n    - { oxid: 1002, oxprice: 50.0, oxvat: 5 }\ndiscounts:\n    - { oxid: testitem_discount, oxshopid: 1, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 0, oxactive: 1, oxitmartid: 1003, oxitmamount: 1, oxitmmultiple: 0, oxarticles: [1000], oxsort: 10 }\n    - { oxid: testitem_discounts, oxshopid: 1, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 0, oxactive: 1, oxitmartid: 1002, oxitmamount: 1, oxitmmultiple: 0, oxarticles: [1000], oxsort: 20 }\nexpected:\n    articles: { 1000: ['50,00', '50,00'], 1003: ['0,00', '0,00'], 1002: ['0,00', '0,00'] }\n    totals: { totalBrutto: '50,00', totalNetto: '47,62', vats: { 5: '2,38' }, grandTotal: '50,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendItmDiscounts.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 5 }\n    - { oxid: 1003, oxprice: 50.0, oxvat: 5 }\ndiscounts:\n    - { oxid: testitem_discount, oxshopid: 1, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 0, oxactive: 1, oxitmartid: 1003, oxitmamount: 1, oxitmmultiple: 0, oxarticles: [1000], oxsort: 10 }\n    - { oxid: testdiscountfrom200, oxshopid: 1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 200, oxpriceto: 999999, oxactive: 1, oxsort: 20 }\nexpected:\n    articles: { 1000: ['50,00', '250,00'], 1003: ['0,00', '0,00'] }\n    totals: { totalBrutto: '250,00', totalNetto: '214,29', vats: { 5: '10,71' }, discounts: { testdiscountfrom200: '25,00' }, grandTotal: '225,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendNettoPrices.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 3 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: 'Test wrapping [EN] ðÄßü?', oxprice: 0.9, oxactive: 1, oxarticles: [1000] }, { oxtype: CARD, oxname: 'Test card [EN] ðÄßü', oxprice: 0.2, oxactive: 1 }]\nexpected:\n    articles: { 1000: ['52,50', '157,50'] }\n    totals: { totalBrutto: '157,50', totalNetto: '150,00', vats: { 5: '7,50' }, wrapping: { brutto: '2,84' }, giftcard: { brutto: '0,21' }, grandTotal: '160,55' }\noptions:\n    config: { blShowNetPrice: false, blEnterNetPrice: true, blWrappingVatOnTop: true, blDeliveryVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendOrderStep1Calculation2(1).yaml",
    "content": "articles:\n    - { oxid: 10012, oxprice: 98, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1002, oxprice: 67.0, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 75.0, oxvat: 19, amount: 6, oxpricea: 70, oxpriceb: 85, oxpricec: 0, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1, oxpricea: 35, oxpriceb: 45, oxpricec: 55, oxunitname: kg, oxunitquantity: 2, oxweight: 2 }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10012, 1000], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 1.5, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, oxminimumvalue: 75, voucher_count: 1 }]\nexpected:\n    articles: { 10012: ['93,00', '93,00'], 1002: ['60,30', '60,30'], 1003: ['54,00', '324,00'], 1000: ['45,00', '45,00'] }\n    totals: { totalBrutto: '522,30', totalNetto: '441,73', vats: { 10: '8,29', 19: '60,18', 5: '2,10' }, delivery: { brutto: '1,50' }, voucher: { brutto: '10,00' }, grandTotal: '513,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendOrderStep1Calculation2(2).yaml",
    "content": "articles:\n    - { oxid: 10013, oxprice: 100, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1002, oxprice: 67.0, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 75.0, oxvat: 19, amount: 6, oxpricea: 70, oxpriceb: 85, oxpricec: 0, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1, oxpricea: 35, oxpriceb: 45, oxpricec: 55, oxunitname: kg, oxunitquantity: 2, oxweight: 2 }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10013, 1000], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 1.5, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, oxminimumvalue: 75, voucher_count: 1 }]\nexpected:\n    articles: { 10013: ['95,00', '95,00'], 1002: ['60,30', '60,30'], 1003: ['54,00', '324,00'], 1000: ['45,00', '45,00'] }\n    totals: { totalBrutto: '524,30', totalNetto: '443,54', vats: { 10: '8,47', 19: '60,19', 5: '2,10' }, delivery: { brutto: '1,50' }, voucher: { brutto: '10,00' }, grandTotal: '515,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendOrderStep1Calculation2(3).yaml",
    "content": "articles:\n    - { oxid: 10014, oxprice: 102, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1002, oxprice: 67.0, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 75.0, oxvat: 19, amount: 6, oxpricea: 70, oxpriceb: 85, oxpricec: 0, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1, oxpricea: 35, oxpriceb: 45, oxpricec: 55, oxunitname: kg, oxunitquantity: 2, oxweight: 2 }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10014, 1000], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 1.5, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, oxminimumvalue: 75, voucher_count: 1 }]\nexpected:\n    articles: { 10014: ['97,00', '97,00'], 1002: ['60,30', '60,30'], 1003: ['54,00', '324,00'], 1000: ['45,00', '45,00'] }\n    totals: { totalBrutto: '526,30', totalNetto: '445,36', vats: { 10: '8,65', 19: '60,19', 5: '2,10' }, delivery: { brutto: '1,50' }, voucher: { brutto: '10,00' }, grandTotal: '517,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendOrderStep1Calculation2(4).yaml",
    "content": "articles:\n    - { oxid: 10015, oxprice: 101, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1002, oxprice: 67.0, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 75.0, oxvat: 19, amount: 6, oxpricea: 70, oxpriceb: 85, oxpricec: 0, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1, oxpricea: 35, oxpriceb: 45, oxpricec: 55, oxunitname: kg, oxunitquantity: 2, oxweight: 2 }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10015, 1000], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 1.5, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, oxminimumvalue: 75, voucher_count: 1 }]\nexpected:\n    articles: { 10015: ['96,00', '96,00'], 1002: ['60,30', '60,30'], 1003: ['54,00', '324,00'], 1000: ['45,00', '45,00'] }\n    totals: { totalBrutto: '525,30', totalNetto: '444,45', vats: { 10: '8,56', 19: '60,19', 5: '2,10' }, delivery: { brutto: '1,50' }, voucher: { brutto: '10,00' }, grandTotal: '516,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendOrderStep1Calculation2(5).yaml",
    "content": "articles:\n    - { oxid: 10016, oxprice: 101, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1002, oxprice: 67.0, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10016, 1000], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 1.5, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    voucherserie: [{ oxdiscount: 5.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, oxminimumvalue: 75, voucher_count: 1 }]\nexpected:\n    articles: { 10016: ['96,00', '96,00'], 1002: ['67,00', '67,00'] }\n    totals: { totalBrutto: '163,00', totalNetto: '136,40', vats: { 10: '8,29', 19: '10,16' }, delivery: { brutto: '1,50' }, voucher: { brutto: '8,15' }, grandTotal: '156,35' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendOrderStep1CalculationVoucher.yaml",
    "content": "articles:\n    - { oxid: 1245, oxprice: 98, oxvat: 10, amount: 1 }\n    - { oxid: 6565, oxprice: 67, oxvat: 19, amount: 1 }\n    - { oxid: 1553, oxprice: 60, oxvat: 19, amount: 6 }\n    - { oxid: 1224, oxprice: 50, oxvat: 5, amount: 1 }\ndiscounts:\n    - { oxid: product, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxactive: 1, oxarticles: [6565, 1553], oxsort: 10 }\n    - { oxid: prod2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxprice: 0, oxpriceto: 99999, oxactive: 1, oxarticles: [1224, 1245], oxsort: 20 }\ncosts:\n    voucherserie: [{ oxdiscount: 5, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 6565: ['60,30', '60,30'], 1553: ['54,00', '324,00'], 1224: ['45,00', '45,00'], 1245: ['93,00', '93,00'] }\n    totals: { totalBrutto: '522,30', totalNetto: '427,82', vats: { 10: '8,03', 19: '58,29', 5: '2,04' }, voucher: { brutto: '26,12' }, grandTotal: '496,18' }\n    options: { config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: false, blShowVATForDelivery: true }, activeCurrencyRate: 1.0 }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendOrderStep1DeliverySortedWithCategories(1).yaml",
    "content": "categories:\n    - { oxid: testCategory1, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [_test_10012] }\n    - { oxid: testCategory2, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [_test_1002] }\narticles:\n    - { oxid: _test_1002, oxprice: 20, oxvat: 20, amount: 6 }\n    - { oxid: _test_10012, oxprice: 10, oxvat: 10, amount: 1 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 0, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 5, oxparamend: 99999, oxsort: 1, oxcategories: [testCategory2] }, { oxactive: 1, oxaddsum: 2, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 0, oxparamend: 99999, oxsort: 2, oxcategories: [testCategory1] }]\nexpected:\n    articles: { _test_10012: ['10,00', '10,00'], _test_1002: ['20,00', '120,00'] }\n    totals: { totalBrutto: '130,00', totalNetto: '109,09', vats: { 10: '0,91', 20: '20,00' }, delivery: { brutto: '0,00' }, grandTotal: '130,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendOrderStep1DeliverySortedWithCategories(2).yaml",
    "content": "categories:\n    - { oxid: testCategory1, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [_test_10012] }\n    - { oxid: testCategory2, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [_test_1002] }\narticles:\n    - { oxid: _test_1002, oxprice: 20, oxvat: 20, amount: 6 }\n    - { oxid: _test_10012, oxprice: 10, oxvat: 10, amount: 1 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 0, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 5, oxparamend: 99999, oxsort: 2, oxcategories: [testCategory2] }, { oxactive: 1, oxaddsum: 2, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 0, oxparamend: 99999, oxsort: 1, oxcategories: [testCategory1] }]\nexpected:\n    articles: { _test_10012: ['10,00', '10,00'], _test_1002: ['20,00', '120,00'] }\n    totals: { totalBrutto: '130,00', totalNetto: '109,09', vats: { 10: '0,91', 20: '20,00' }, delivery: { brutto: '2,00' }, grandTotal: '132,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendOrdersFractionQuantities1.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 50, oxvat: 5, oxunitname: kg, oxunitquantity: 10, oxweight: 10, amount: 3.4 }\ndiscounts:\n    - { oxid: test, oxshopid: 1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 0, oxpriceto: 999999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 1000: ['45,00', '153,00'] }\n    totals: { totalBrutto: '153,00', totalNetto: '145,71', vats: { 5: '7,29' }, grandTotal: '153,00' }\noptions:\n    config: { blAllowUnevenAmounts: true, blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendOrdersFractionQuantities2.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 75, oxvat: 19, amount: 3.4 }\n    - { oxid: 1001, oxprice: 100, oxvat: 10, amount: 0.3 }\n    - { oxid: 1000, oxprice: 50, oxvat: 19, oxunitname: kg, oxunitquantity: 10, oxweight: 10, amount: 1.5 }\nexpected:\n    articles: { 1003: ['75,00', '225,00'], 1000: ['50,00', '100,00'] }\n    totals: { totalBrutto: '325,00', totalNetto: '273,11', vats: { 19: '51,89' }, grandTotal: '325,00' }\noptions:\n    config: { blAllowUnevenAmounts: false, blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendPriceA.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 70.0, oxpricea: 71, oxpriceb: 85, oxpricec: 0, amount: 1, oxvat: 19, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\nuser:\n    oxid: _testUserA\n    oxactive: 1\n    oxusername: groupAUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [1003, _testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    articles: { 1003: ['71,00', '71,00'] }\n    totals: { totalBrutto: '71,00', totalNetto: '59,66', vats: { 19: '11,34' }, grandTotal: '71,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendPriceA2.yaml",
    "content": "articles:\n    - { oxid: 1002, oxprice: 50.0, oxpricea: 35, oxpriceb: 45, oxpricec: 55, amount: 1, oxvat: 19, oxtitle: 'Wall Clock ROBOT', oxunitname: kg, oxunitquantity: 2, oxweight: 10 }\nuser:\n    oxid: _testUserA\n    oxactive: 1\n    oxusername: groupAUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [1002, _testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    articles: { 1002: ['35,00', '35,00'] }\n    totals: { totalBrutto: '35,00', totalNetto: '29,41', vats: { 19: '5,59' }, grandTotal: '35,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendPriceB.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 70.0, oxpricea: 70, oxpriceb: 85, oxpricec: 0, amount: 1, oxvat: 19, scaleprices: { oxaddabs: 75.0, oxamount: 2, oxamountto: 5, oxartid: 1003 } }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 19, amount: 1 }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [1003, _testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    articles: { 1003: ['85,00', '85,00'], 1112: ['5,02', '5,02'] }\n    totals: { totalBrutto: '90,02', totalNetto: '75,65', vats: { 19: '14,37' }, grandTotal: '90,02' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendPriceB2.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 70.0, oxpricea: 70, oxpriceb: 85, oxpricec: 0, amount: 7, oxvat: 19, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [1003, _testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    articles: { 1003: ['68,00', '476,00'] }\n    totals: { totalBrutto: '476,00', totalNetto: '400,00', vats: { 19: '76,00' }, grandTotal: '476,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendPriceB3.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 70.0, oxpricea: 70, oxpriceb: 85, oxpricec: 0, amount: 7, oxvat: 19, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [1003, _testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10013, 1000], oxsort: 20 }\nexpected:\n    articles: { 1003: ['61,20', '428,40'] }\n    totals: { totalBrutto: '428,40', totalNetto: '360,00', vats: { 19: '68,40' }, grandTotal: '428,40' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendPriceB4.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 70.0, oxpricea: 70, oxpriceb: 85, oxpricec: 0, amount: 1, oxvat: 19, scaleprices: { oxaddabs: 75.0, oxamount: 2, oxamountto: 5, oxartid: 1003 } }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 19, amount: 1 }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [1003, _testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    articles: { 1003: ['85,00', '85,00'], 1112: ['0,00', '0,00'] }\n    totals: { totalBrutto: '85,00', totalNetto: '71,43', vats: { 19: '13,57' }, grandTotal: '85,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: false, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendPriceC.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 75.0, oxpricea: 70, oxpriceb: 85, oxpricec: 0, amount: 3, oxvat: 19, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\nuser:\n    oxid: _testUserC\n    oxactive: 1\n    oxusername: groupCUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [1003, _testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10013, 1000], oxsort: 20 }\nexpected:\n    articles: { 1003: ['67,50', '202,50'] }\n    totals: { totalBrutto: '202,50', totalNetto: '170,17', vats: { 19: '32,33' }, grandTotal: '202,50' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendVatForBillingCountry(1).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 101, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1003, oxprice: 75.0, oxvat: 19, amount: 1, oxpricea: 70, oxpriceb: 85, oxpricec: 0 }\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1, oxpricea: 35, oxpriceb: 45, oxpricec: 55, oxunitname: kg, oxunitquantity: 2, oxweight: 2 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\n    oxcountryid: a7c40f631fc920687.20179984\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 6.9, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 0.0, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 0, oxcountryid: a7c40f631fc920687.20179984 }]\nexpected:\n    articles: { 10011: ['101,00', '101,00'], 1003: ['75,00', '75,00'], 1000: ['50,00', '50,00'] }\n    totals: { totalBrutto: '226,00', totalNetto: '202,47', vats: { 10: '9,18', 19: '11,97', 5: '2,38' }, delivery: { brutto: '6,90' }, grandTotal: '232,90' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket/testFrontendVatForBillingCountry(2).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 101, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1003, oxprice: 75.0, oxvat: 19, amount: 1, oxpricea: 70, oxpriceb: 85, oxpricec: 0 }\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1, oxpricea: 35, oxpriceb: 45, oxpricec: 55, oxunitname: kg, oxunitquantity: 2, oxweight: 2 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\n    oxcountryid: a7c40f6321c6f6109.43859248\ndiscounts:\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10011, 1000], oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 0, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.5, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 0 }]\nexpected:\n    articles: { 10011: ['86,82', '86,82'], 1003: ['63,03', '63,03'], 1000: ['42,62', '42,62'] }\n    totals: { totalBrutto: '192,47', totalNetto: '192,47', vats: ['0,00'], delivery: { brutto: '0,00' }, payment: { brutto: '7,50', netto: '7,50' }, grandTotal: '199,97' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, blDeliveryVatOnTop: false, blPaymentVatOnTop: false, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/PriceB2basket2.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 70.0, oxpricea: 70, oxpriceb: 85, oxpricec: 0, amount: 7, oxvat: 19, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [1003, _testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    articles: { 1003: ['68,00', '476,00'] }\n    totals: { totalBrutto: '476,00', totalNetto: '400,00', vats: { 19: '76,00' }, grandTotal: '476,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case1.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9002, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9001, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9001], oxsort: 10 }\n    - { oxid: shopdiscount5for9002, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9002], oxsort: 20 }\n    - { oxid: basketdiscount5for9001, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9001], oxsort: 30 }\n    - { oxid: basketdiscount5for9002, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9002], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9001, oxprice: 9, oxactive: 1, oxarticles: [9001] }, { oxtype: WRAP, oxname: testWrap9002, oxprice: 6, oxactive: 1, oxarticles: [9002] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxarticles: [9001, 9002] }]\n    voucherserie: [{ oxserienr: abs_4_voucher_serie, oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9001: ['63,92', '2.109,36'], 9002: ['40,08', '641,28'] }\n    totals: { totalBrutto: '2.750,64', totalNetto: '2.305,18', vats: { 19: '437,98' }, discounts: { absolutebasketdiscount: '3,40' }, wrapping: { brutto: '267,24', netto: '224,57', vat: '42,67' }, delivery: { brutto: '4,08', netto: '3,43', vat: '0,65' }, payment: { brutto: '0,68', netto: '0,57', vat: '0,11' }, voucher: { brutto: '4,08' }, grandTotal: '3.015,16' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case10.yaml",
    "content": "articles:\n    - { oxid: 9206, oxprice: 103, oxvat: 19, amount: 1 }\nexpected:\n    articles: { 9206: ['103,00', '103,00'] }\n    totals: { totalBrutto: '103,00', totalNetto: '86,55', vats: { 19: '16,45' }, grandTotal: '103,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case100.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.55, oxvat: 33, amount: 1 }\n    - { oxid: 1112, oxprice: 1101.1, oxvat: 33, amount: 1 }\n    - { oxid: 1113, oxprice: 110, oxvat: 33, amount: 1 }\n    - { oxid: 1114, oxprice: 1.0, oxvat: 33, amount: 1 }\n    - { oxid: 1115, oxprice: 945.95, oxvat: 50, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['1.101,10', '1.101,10'], 1113: ['110,00', '110,00'], 1114: ['1,00', '1,00'], 1115: ['945,95', '945,95'] }\n    totals: { totalBrutto: '2.728,58', totalNetto: '2.158,60', vats: { 33: '360,16', 50: '425,68' }, discounts: { discountforbasket10%: '215,86' }, delivery: { brutto: '1.579,02', netto: '1.187,23', vat: '391,79' }, payment: { brutto: '73,15', netto: '55,00', vat: '18,15' }, grandTotal: '4.380,75' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case101.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.55, oxvat: 33, amount: 1 }\n    - { oxid: 1112, oxprice: 1101.1, oxvat: 33, amount: 1 }\n    - { oxid: 1113, oxprice: 110, oxvat: 33, amount: 1 }\n    - { oxid: 1114, oxprice: 1.0, oxvat: 33, amount: 1 }\n    - { oxid: 1115, oxprice: 945.95, oxvat: 50, amount: 2 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['1.101,10', '1.101,10'], 1113: ['110,00', '110,00'], 1114: ['1,00', '1,00'], 1115: ['945,95', '1.891,90'] }\n    totals: { totalBrutto: '4.005,61', totalNetto: '3.104,55', vats: { 33: '360,16', 50: '851,36' }, discounts: { discountforbasket10%: '310,46' }, delivery: { brutto: '2.561,25', netto: '1.707,50', vat: '853,75' }, payment: { brutto: '82,50', netto: '55,00', vat: '27,50' }, grandTotal: '6.649,36' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case102.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.55, oxvat: 33, amount: 1 }\n    - { oxid: 1112, oxprice: 945.95, oxvat: 50, amount: 2 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['945,95', '1.891,90'] }\n    totals: { totalBrutto: '2.554,72', totalNetto: '1.892,45', vats: { 33: '0,16', 50: '851,36' }, discounts: { discountforbasket10%: '189,25' }, delivery: { brutto: '1.561,28', netto: '1.040,85', vat: '520,43' }, payment: { brutto: '82,50', netto: '55,00', vat: '27,50' }, grandTotal: '4.198,50' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case103.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 945.95, oxvat: 50, amount: 2 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['945,95', '1.891,90'] }\n    totals: { totalBrutto: '2.554,07', totalNetto: '1.891,90', vats: { 50: '851,36' }, discounts: { discountforbasket10%: '189,19' }, delivery: { brutto: '1.560,83', netto: '1.040,55', vat: '520,28' }, payment: { brutto: '82,50', netto: '55,00', vat: '27,50' }, grandTotal: '4.197,40' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case104.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 1, 0: 0, oxvat: 20, amount: 2 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 111: ['1,00', '2,00'] }\n    totals: { totalBrutto: '2,40', totalNetto: '2,00', vats: { 20: '0,40' }, delivery: { brutto: '1,32', netto: '1,10', vat: '0,22' }, payment: { brutto: '0,24', netto: '0,20', vat: '0,04' }, grandTotal: '3,96' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blShowVATForPayCharge: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case105.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 1, 0: 0, oxvat: 20, amount: 2 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 111: ['1,00', '2,00'] }\n    totals: { totalBrutto: '2,16', totalNetto: '2,00', vats: { 20: '0,36' }, discounts: { discountforbasket10%: '0,20' }, delivery: { brutto: '1,32', netto: '1,10', vat: '0,22' }, payment: { brutto: '0,24', netto: '0,20', vat: '0,04' }, grandTotal: '3,72' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blPaymentVatOnTop: true, blDeliveryVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case106.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 1, 0: 0, oxvat: 20, amount: 1 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 111: ['1,20', '1,20'] }\n    totals: { totalBrutto: '1,20', totalNetto: '1,00', vats: { 20: '0,20' }, delivery: { brutto: '0,79', netto: '0,66', vat: '0,13' }, payment: { brutto: '0,14', netto: '0,12', vat: '0,02' }, grandTotal: '2,13' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case107.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 1, 0: 0, oxvat: 20, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 111: ['1,20', '1,20'] }\n    totals: { totalBrutto: '1,20', totalNetto: '0,90', vats: { 20: '0,18' }, discounts: { discountforbasket10%: '0,12' }, delivery: { brutto: '0,79', netto: '0,66', vat: '0,13' }, payment: { brutto: '0,14', netto: '0,12', vat: '0,02' }, grandTotal: '2,01' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case108.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 1, 0: 0, oxvat: 20, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 15 }]\nexpected:\n    articles: { 111: ['1,20', '1,20'] }\n    totals: { totalBrutto: '1,20', totalNetto: '0,90', vats: { 20: '0,18' }, discounts: { discountforbasket10%: '0,12' }, delivery: { brutto: '0,79', netto: '0,66', vat: '0,13' }, payment: { brutto: '0,23', netto: '0,19', vat: '0,04' }, grandTotal: '2,10' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case109.yaml",
    "content": "articles:\n    - { oxid: 333, oxtitle: item1, oxprice: 60, oxvat: 20, amount: 2 }\n    - { oxid: 444, oxtitle: item2, oxprice: 110, oxvat: 10, amount: 1 }\ndiscounts:\n    - { oxid: discount20, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxactive: 1, oxarticles: [333], oxsort: 10 }\n    - { oxid: discount50, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxactive: 1, oxarticles: [444], oxsort: 20 }\n    - { oxid: discount20forBasket, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxactive: 1, oxsort: 30 }\ncosts: {  }\nexpected:\n    articles: { 333: ['48,00', '96,00'], 444: ['55,00', '55,00'] }\n    totals: { totalBrutto: '151,00', discounts: { discount20forBasket: '30,20' }, totalNetto: '104,00', vats: { 20: '12,80', 10: '4,00' }, grandTotal: '120,80' }\noptions:\n    insertMode: brutto\n    viewMode: brutto\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case11.yaml",
    "content": "articles:\n    - { oxid: 9205, oxprice: 25.9, oxvat: 18, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_9205, oxaddsum: 5.31, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9205], oxsort: 10 }\nexpected:\n    articles: { 9205: ['11,53', '11,53'] }\n    totals: { totalBrutto: '11,53', totalNetto: '9,77', vats: { 18: '1,76' }, grandTotal: '11,53' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.56\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case110.yaml",
    "content": "articles:\n    - { oxid: 1, oxprice: 24.72, oxvat: 7, amount: 2 }\n    - { oxid: 2, oxprice: 14.57, oxvat: 7, amount: 1 }\n    - { oxid: 3, oxprice: 1.49, oxvat: 7, amount: 5 }\n    - { oxid: 4, oxprice: 1.65, oxvat: 7, amount: 5 }\n    - { oxid: 5, oxprice: 17.06, oxvat: 7, amount: 1 }\n    - { oxid: 6, oxprice: 1.63, oxvat: 7, amount: 6 }\n    - { oxid: 7, oxprice: 21.57, oxvat: 7, amount: 1 }\n    - { oxid: 8, oxprice: 21.57, oxvat: 7, amount: 1 }\n    - { oxid: 9, oxprice: 24.44, oxvat: 7, amount: 1 }\nexpected:\n    articles: { 1: ['24,72', '49,44'], 2: ['14,57', '14,57'], 3: ['1,49', '7,45'], 4: ['1,65', '8,25'], 5: ['17,06', '17,06'], 6: ['1,63', '9,78'], 7: ['21,57', '21,57'], 8: ['21,57', '21,57'], 9: ['24,44', '24,44'] }\n    totals: { totalBrutto: '186,32', totalNetto: '174,13', vats: { 7: '12,19' }, grandTotal: '186,32' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case111.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 1 }\nexpected:\n    articles: { 111: ['24,95', '24,95'] }\n    totals: { totalBrutto: '24,95', totalNetto: '20,97', vats: { 19: '3,98' }, grandTotal: '24,95' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case112.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['24,95', '2.495,00'] }\n    totals: { totalBrutto: '2.495,00', totalNetto: '2.096,64', vats: { 19: '398,36' }, grandTotal: '2.495,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case113.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['24,95', '2.495,00'] }\n    totals: { totalBrutto: '2.495,00', totalNetto: '2.096,64', vats: { 19: '398,36' }, grandTotal: '2.495,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case114.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['12,48', '1.248,00'] }\n    totals: { totalBrutto: '1.248,00', totalNetto: '1.048,74', vats: { 19: '199,26' }, grandTotal: '1.248,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case115.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 17.55, amount: 250 }\nexpected:\n    articles: { 111: ['12,48', '3.120,00'] }\n    totals: { totalBrutto: '3.120,00', totalNetto: '2.654,19', vats: { '17.55': '465,81' }, grandTotal: '3.120,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case116.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['12,48', '1.248,00'] }\n    totals: { totalBrutto: '1.248,00', totalNetto: '1.048,74', vats: { 19: '199,26' }, grandTotal: '1.248,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case117.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['12,48', '1.248,00'] }\n    totals: { totalBrutto: '1.248,00', totalNetto: '1.048,74', vats: { 19: '199,26' }, grandTotal: '1.248,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case118.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 25, amount: 100 }\nexpected:\n    articles: { 111: ['12,48', '1.248,00'] }\n    totals: { totalBrutto: '1.248,00', totalNetto: '998,40', vats: { 25: '249,60' }, grandTotal: '1.248,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case119.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 25, amount: 250 }\nexpected:\n    articles: { 111: ['12,48', '3.120,00'] }\n    totals: { totalBrutto: '3.120,00', totalNetto: '2.496,00', vats: { 25: '624,00' }, grandTotal: '3.120,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case12.yaml",
    "content": "articles:\n    - { oxid: 9203, oxprice: 29.99, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_9203, oxaddsum: 2.01, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9203], oxsort: 10 }\nexpected:\n    articles: { 9203: ['27,98', '27,98'] }\n    totals: { totalBrutto: '33,30', totalNetto: '27,98', vats: { 19: '5,32' }, grandTotal: '33,30' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case120.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 1 }\nexpected:\n    articles: { 111: ['20,97', '20,97'] }\n    totals: { totalBrutto: '24,95', totalNetto: '20,97', vats: { 19: '3,98' }, grandTotal: '24,95' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: proportional }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case121.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['20,97', '2.097,00'] }\n    totals: { totalBrutto: '2.495,43', totalNetto: '2.097,00', vats: { 19: '398,43' }, grandTotal: '2.495,43' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: proportional }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case122.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['20,97', '2.097,00'] }\n    totals: { totalBrutto: '2.495,43', totalNetto: '2.097,00', vats: { 19: '398,43' }, grandTotal: '2.495,43' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case123.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['10,49', '1.049,00'] }\n    totals: { totalBrutto: '1.248,31', totalNetto: '1.049,00', vats: { 19: '199,31' }, grandTotal: '1.248,31' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case124.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 17.55, amount: 250 }\nexpected:\n    articles: { 111: ['10,62', '2.655,00'] }\n    totals: { totalBrutto: '3.120,95', totalNetto: '2.655,00', vats: { '17.55': '465,95' }, grandTotal: '3.120,95' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case125.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['10,49', '1.049,00'] }\n    totals: { totalBrutto: '1.248,31', totalNetto: '1.049,00', vats: { 19: '199,31' }, grandTotal: '1.248,31' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case126.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 150 }\nexpected:\n    articles: { 111: ['10,49', '1.573,50'] }\n    totals: { totalBrutto: '1.872,47', totalNetto: '1.573,50', vats: { 19: '298,97' }, grandTotal: '1.872,47' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case127.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 150 }\nexpected:\n    articles: { 111: ['10,49', '1.573,50'] }\n    totals: { totalBrutto: '1.872,47', totalNetto: '1.573,50', vats: { 19: '298,97' }, grandTotal: '1.872,47' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case128.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 150 }\nexpected:\n    articles: { 111: ['10,49', '1.573,50'] }\n    totals: { totalBrutto: '1.872,47', totalNetto: '1.573,50', vats: { 19: '298,97' }, grandTotal: '1.872,47' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case129.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 150 }\n    - { oxid: 222, oxprice: 7.99, oxvat: 19, amount: 1 }\nexpected:\n    articles: { 111: ['10,49', '1.573,50'], 222: ['3,36', '3,36'] }\n    totals: { totalBrutto: '1.876,46', totalNetto: '1.576,86', vats: { 19: '299,60' }, grandTotal: '1.876,46' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case13.yaml",
    "content": "articles:\n    - { oxid: 9201, oxprice: 77.9, oxvat: 17, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_9201, oxaddsum: 5.05, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 10 }\nexpected:\n    articles: { 9201: ['72,85', '72,85'] }\n    totals: { totalBrutto: '72,85', totalNetto: '62,26', vats: { 17: '10,59' }, grandTotal: '72,85' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case130.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 150 }\n    - { oxid: 222, oxprice: 7.99, oxvat: 19, amount: 1 }\nexpected:\n    articles: { 111: ['10,49', '1.573,50'], 222: ['3,36', '3,36'] }\n    totals: { totalBrutto: '1.876,46', totalNetto: '1.576,86', vats: { 19: '299,60' }, grandTotal: '1.876,46' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case131.yaml",
    "content": "articles:\n    - { oxid: '4425', oxprice: 879, amount: 1 }\ndiscounts:\n    - { oxid: discount10euro, oxaddsum: 10, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 0, oxactive: 1, oxarticles: [4425], oxsort: 10 }\nexpected:\n    articles: { 4425: ['869,00', '869,00'] }\n    totals: { totalBrutto: '869,00', totalNetto: '730,25', vats: { 19: '138,75' }, grandTotal: '869,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case132.yaml",
    "content": "articles:\n    - { oxid: '3727', oxprice: 5, amount: 1 }\ndiscounts:\n    - { oxid: discount500forShop, oxaddsum: 500, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 3727: ['5,00', '5,00'] }\n    totals: { totalBrutto: '5,00', discounts: { discount500forShop: '5,00' }, totalNetto: '0,00', vats: { 19: '0,00' }, grandTotal: '0,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case133.yaml",
    "content": "articles:\n    - { oxid: '3727', oxprice: 5, amount: 1 }\ndiscounts:\n    - { oxid: discount500forShop, oxaddsum: 500, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 3727: ['0,00', '0,00'] }\n    totals: { totalBrutto: '0,00', totalNetto: '0,00', vats: { 19: '0,00' }, grandTotal: '0,00' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case134.yaml",
    "content": "articles:\n    - { oxid: '3587', oxtitle: newspaper, oxprice: 2.98, amount: 200 }\ndiscounts:\n    - { oxid: discount2forShop, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: ['3587'], oxsort: 10 }\nexpected:\n    articles: { 3587: ['2,92', '584,00'] }\n    totals: { totalBrutto: '584,00', totalNetto: '490,76', vats: { 19: '93,24' }, grandTotal: '584,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case135.yaml",
    "content": "articles:\n    - { oxid: '3587', oxtitle: newspaper, oxprice: 2.98, amount: 200 }\ndiscounts:\n    - { oxid: discount2forShop, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 3587: ['2,92', '584,00'] }\n    totals: { totalBrutto: '584,00', totalNetto: '490,76', vats: { 19: '93,24' }, grandTotal: '584,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case136.yaml",
    "content": "articles:\n    - { oxid: '3587', oxtitle: newspaper, oxprice: 2.98, amount: 200 }\ndiscounts:\n    - { oxid: discount2forBasket, oxaddsum: 2, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: ['3587'], oxsort: 10 }\ncosts: {  }\nexpected:\n    articles: { 3587: ['2,92', '584,00'] }\n    totals: { totalBrutto: '584,00', totalNetto: '490,76', vats: { 19: '93,24' }, grandTotal: '584,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case137.yaml",
    "content": "articles:\n    - { oxid: '3587', oxtitle: newspaper, oxprice: 2.98, amount: 200 }\ndiscounts:\n    - { oxid: discount2forBasket, oxaddsum: 2, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 3587: ['2,98', '596,00'] }\n    totals: { totalBrutto: '596,00', discounts: { discount2forBasket: '11,92' }, totalNetto: '490,82', vats: { 19: '93,26' }, grandTotal: '584,08' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case138.yaml",
    "content": "articles:\n    - { oxid: _tArticle, oxprice: 50, oxweight: 10, oxstock: 100, oxstockflag: 2, oxvat: 10, amount: 2 }\n    - { oxid: 2000, oxprice: 29.9, oxtitle: 'Wall Clock ROBOT', oxstock: 3, oxstockflag: 1, amount: 1 }\n    - { oxid: _t1651, oxprice: 29.9, oxtitle: 'Beer homebrew kit CHEERS!', oxstock: 10000, oxstockflag: 1, amount: 1 }\ndiscounts:\n    - { oxid: testdiscount0, oxactive: 1, oxtitle: 'Test discount 0', oxamount: 1, oxamountto: 99999, oxprice: 1, oxpriceto: 99999, oxaddsumtype: abs, oxaddsum: 5, oxarticles: [2000, _tArticle], oxsort: 10 }\nexpected:\n    articles: { _tArticle: ['45,00', '90,00'], 2000: ['24,90', '24,90'], _t1651: ['29,90', '29,90'] }\n    totals: { totalBrutto: '144,80', totalNetto: '127,87', vats: { 19: '8,75', 10: '8,18' }, discounts: {  }, grandTotal: '144,80' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case139.yaml",
    "content": "articles:\n    - { oxid: testarticle, oxprice: 19, oxweight: 10, oxstock: 100, oxstockflag: 2, amount: 1, scaleprices: {  } }\ndiscounts:\n    - { oxid: testdiscount0, oxactive: 1, oxtitle: 'Test discount 0', oxamount: 1, oxamountto: 99999, oxprice: 1, oxpriceto: 99999, oxaddsumtype: abs, oxaddsum: 5, oxsort: 10 }\n    - { oxid: testdiscount1, oxactive: 1, oxtitle: 'Test discount 1', oxamount: 1, oxamountto: 99999, oxprice: 1, oxpriceto: 99999, oxaddsumtype: abs, oxaddsum: 7, oxsort: 20 }\nexpected:\n    articles: { testarticle: ['19,00', '19,00'] }\n    totals: { totalBrutto: '19,00', totalNetto: '5,88', vats: { 19: '1,12' }, discounts: { testdiscount0: '5,00', testdiscount1: '7,00' }, grandTotal: '7,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case14.yaml",
    "content": "articles:\n    - { oxid: 9201, oxprice: 77.9, oxvat: 17, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_9201, oxaddsum: 5.05, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 10 }\nexpected:\n    articles: { 9201: ['49,54', '49,54'] }\n    totals: { totalBrutto: '49,54', totalNetto: '42,34', vats: { 17: '7,20' }, grandTotal: '49,54' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case140.yaml",
    "content": "articles:\n    - { oxid: testarticle, oxprice: 12.95, amount: 1, scaleprices: { oxaddabs: 11.95, oxamount: 2, oxamountto: 2, oxartid: testarticle } }\ndiscounts:\n    - { oxid: _testDiscount, oxactive: 1, oxtitle: 'new discount', oxprice: 12, oxpriceto: 24.99, oxaddsumtype: abs, oxaddsum: 3, oxsort: 10 }\nexpected:\n    articles: { testarticle: ['12,95', '12,95'] }\n    totals: { totalBrutto: '12,95', totalNetto: '8,36', vats: { 19: '1,59' }, discounts: { _testDiscount: '3,00' }, grandTotal: '9,95' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case141.yaml",
    "content": "articles:\n    - { oxid: testarticle, oxprice: 19, oxweight: 10, oxstock: 100, oxstockflag: 2, oxskipdiscounts: 1, amount: 1, scaleprices: {  } }\ndiscounts:\n    - { oxid: testdiscount0, oxactive: 1, oxtitle: 'Test discount 0', oxamount: 1, oxamountto: 99999, oxprice: 1, oxpriceto: 99999, oxaddsumtype: abs, oxaddsum: 5, oxsort: 10 }\n    - { oxid: testdiscount1, oxactive: 1, oxtitle: 'Test discount 1', oxamount: 1, oxamountto: 99999, oxprice: 1, oxpriceto: 99999, oxaddsumtype: abs, oxaddsum: 7, oxsort: 20 }\nexpected:\n    articles: { testarticle: ['19,00', '19,00'] }\n    totals: { totalBrutto: '19,00', totalNetto: '15,97', vats: { 19: '3,03' }, discounts: {  }, grandTotal: '19,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case142.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87.0, oxvat: 17, amount: 63 }\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 48 }\n    - { oxid: 9206, oxprice: 103.0, oxvat: 19, amount: 99 }\n    - { oxid: 9216, oxprice: 56.45, oxvat: 17, amount: 22 }\nexpected:\n    articles: { 9200: ['87,00', '5.481,00'], 9201: ['72,85', '3.496,80'], 9206: ['103,00', '10.197,00'], 9216: ['56,45', '1.241,90'] }\n    totals: { totalBrutto: '20.416,70', totalNetto: '17.303,70', vats: { 17: '1.484,91', 19: '1.628,09' }, grandTotal: '20.416,70' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case143.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87, oxvat: 17, amount: 20315 }\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 210 }\n    - { oxid: 9202, oxprice: 16.2, oxvat: 17, amount: 56 }\n    - { oxid: 9208, oxprice: 72.11, oxvat: 17, amount: 691 }\n    - { oxid: 9212, oxprice: 16.37, oxvat: 17, amount: 548 }\n    - { oxid: 9216, oxprice: 56.45, oxvat: 17, amount: 36 }\ndiscounts:\n    - { oxid: discount5for9200, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9200], oxsort: 10 }\n    - { oxid: discount2for9201, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 20 }\n    - { oxid: discount3for9208, oxaddsum: 3, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9208], oxsort: 30 }\n    - { oxid: discount1for9212, oxaddsum: 1, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9212], oxsort: 40 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9202, oxprice: 0.63, oxactive: 1, oxarticles: [9202] }, { oxtype: WRAP, oxname: wrapFor9216, oxprice: 0.33, oxactive: 1, oxarticles: [9216] }]\n    delivery: [{ oxactive: 1, oxaddsum: 117, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 9999999 }]\n    payment: [{ oxaddsum: 3, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 2000000, oxchecked: 1 }]\nexpected:\n    articles: { 9200: ['82,65', '1.679.034,75'], 9201: ['71,39', '14.991,90'], 9202: ['16,20', '907,20'], 9208: ['69,95', '48.335,45'], 9212: ['16,21', '8.883,08'], 9216: ['56,45', '2.032,20'] }\n    totals: { totalBrutto: '1.754.184,58', totalNetto: '1.499.303,06', vats: { 17: '254.881,52' }, wrapping: { brutto: '47,16', netto: '40,30', vat: '6,86' }, delivery: { brutto: '117,00', netto: '100,00', vat: '17,00' }, payment: { brutto: '3,00', netto: '2,56', vat: '0,44' }, grandTotal: '1.754.351,74' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case144.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87, oxvat: 17, amount: 589 }\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 1325 }\n    - { oxid: 9207, oxprice: 45.5, oxvat: 19, amount: 8888 }\n    - { oxid: 9213, oxprice: 30.77, oxvat: 19, amount: 10000 }\ndiscounts:\n    - { oxid: discount2for9200, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9200], oxsort: 10 }\n    - { oxid: discount3for9201, oxaddsum: 3, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 20 }\n    - { oxid: discount4for9207, oxaddsum: 4, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9207], oxsort: 30 }\n    - { oxid: discount6for9213, oxaddsum: 6, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9213], oxsort: 40 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9200, oxprice: 0.05, oxactive: 1, oxarticles: [9200, 9207, 9213] }]\n    delivery: [{ oxactive: 1, oxaddsum: 58.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 9999999 }]\nexpected:\n    articles: { 9200: ['85,26', '50.218,14'], 9201: ['70,66', '93.624,50'], 9207: ['43,68', '388.227,84'], 9213: ['28,92', '289.200,00'] }\n    totals: { totalBrutto: '821.270,48', totalNetto: '692.209,52', vats: { 17: '20.900,21', 19: '108.160,75' }, wrapping: { brutto: '973,85', netto: '818,79', vat: '155,06' }, delivery: { brutto: '58,14', netto: '48,86', vat: '9,28' }, grandTotal: '822.302,47' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case145.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87.0, oxvat: 17, amount: 2008 }\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 369 }\n    - { oxid: 9207, oxprice: 45.5, oxvat: 19, amount: 1698 }\n    - { oxid: 9213, oxprice: 30.77, oxvat: 19, amount: 3665 }\nexpected:\n    articles: { 9200: ['87,00', '174.696,00'], 9201: ['72,85', '26.881,65'], 9207: ['45,50', '77.259,00'], 9213: ['30,77', '112.772,05'] }\n    totals: { totalBrutto: '391.608,70', totalNetto: '331.978,55', vats: { 17: '29.289,06', 19: '30.341,09' }, grandTotal: '391.608,70' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case146.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87.0, oxvat: 17, amount: 12 }\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 5 }\n    - { oxid: 9202, oxprice: 16.21, oxvat: 17, amount: 39 }\nexpected:\n    articles: { 9200: ['59,16', '709,92'], 9201: ['49,54', '247,70'], 9202: ['11,02', '429,78'] }\n    totals: { totalBrutto: '1.387,40', totalNetto: '1.185,81', vats: { 17: '201,59' }, grandTotal: '1.387,40' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case147.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87.0, oxvat: 17, amount: 120 }\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 5 }\n    - { oxid: 9202, oxprice: 16.21, oxvat: 17, amount: 39 }\nexpected:\n    articles: { 9200: ['59,16', '7.099,20'], 9201: ['49,54', '247,70'], 9202: ['11,02', '429,78'] }\n    totals: { totalBrutto: '7.776,68', totalNetto: '6.646,74', vats: { 17: '1.129,94' }, grandTotal: '7.776,68' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case148.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 15.93, oxvat: 15, amount: 21 }\n    - { oxid: 9208, oxprice: 70.87, oxvat: 15, amount: 9 }\n    - { oxid: 9213, oxprice: 25.86, oxvat: 0, amount: 10 }\n    - { oxid: 9216, oxprice: 48.25, oxvat: 0, amount: 4 }\n    - { oxid: 9218, oxprice: 58.09, oxvat: 15, amount: 5 }\nexpected:\n    articles: { 9202: ['15,93', '334,53'], 9208: ['70,87', '637,83'], 9213: ['25,86', '258,60'], 9216: ['48,25', '193,00'], 9218: ['58,09', '290,45'] }\n    totals: { totalBrutto: '1.714,41', totalNetto: '1.549,70', vats: { 0: '0,00', 15: '164,71' }, grandTotal: '1.714,41' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case149.yaml",
    "content": "articles:\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 1012 }\n    - { oxid: 9203, oxprice: 33.3, oxvat: 19, amount: 453 }\n    - { oxid: 9211, oxprice: 5.86, oxvat: 16, amount: 88 }\n    - { oxid: 9216, oxprice: 56.45, oxvat: 17, amount: 56 }\n    - { oxid: 9219, oxprice: 24.33, oxvat: 19, amount: 74 }\nexpected:\n    articles: { 9201: ['72,85', '73.724,20'], 9203: ['33,30', '15.084,90'], 9211: ['5,86', '515,68'], 9216: ['56,45', '3.161,20'], 9219: ['24,33', '1.800,42'] }\n    totals: { totalBrutto: '94.286,40', totalNetto: '80.347,91', vats: { 16: '71,13', 17: '11.171,38', 19: '2.695,98' }, grandTotal: '94.286,40' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case15.yaml",
    "content": "articles:\n    - { oxid: 9201, oxprice: 77.9, oxvat: 17, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_9201, oxaddsum: 5.05, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 10 }\nexpected:\n    articles: { 9201: ['107,09', '107,09'] }\n    totals: { totalBrutto: '107,09', totalNetto: '91,53', vats: { 17: '15,56' }, grandTotal: '107,09' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1.47\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case150.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 47.62, oxvat: 0, amount: 1 }\n    - { oxid: 9201, oxprice: 91.82, oxvat: 0, amount: 1 }\n    - { oxid: 9207, oxprice: 63.03, oxvat: 0, amount: 1 }\nexpected:\n    articles: { 9200: ['47,62', '47,62'], 9201: ['91,82', '91,82'], 9207: ['63,03', '63,03'] }\n    totals: { totalBrutto: '202,47', totalNetto: '202,47', vats: ['0,00'], grandTotal: '202,47' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case151.yaml",
    "content": "articles:\n    - { oxid: 9000, oxprice: 50.01, oxvat: 17, amount: 3.3 }\n    - { oxid: 9201, oxprice: 1.0, oxvat: 17, amount: 0.33 }\nexpected:\n    articles: { 9000: ['50,01', '165,03'], 9201: ['1,00', '0,33'] }\n    totals: { totalBrutto: '165,36', totalNetto: '141,33', vats: { 17: '24,03' }, grandTotal: '165,36' }\noptions:\n    config: { blAllowUnevenAmounts: true, blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case152.yaml",
    "content": "articles:\n    0: { oxid: 9200, oxprice: 87, oxvat: 17, amount: 63 }\n    1: { oxid: 9206, oxprice: 103, oxvat: 19, amount: 125 }\n    3: { oxid: 9216, oxprice: 56.45, oxvat: 17, amount: 14 }\n    4: { oxid: 9218, oxprice: 59.6, oxvat: 18, amount: 39 }\ndiscounts:\n    - { oxid: discount2for9200and9206, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9200, 9206], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9216, oxprice: 0.57, oxactive: 1, oxarticles: [9216] }]\n    delivery: [{ oxactive: 1, oxaddsum: 15, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\nexpected:\n    articles: { 9200: ['85,26', '5.371,38'], 9206: ['100,94', '12.617,50'], 9216: ['56,45', '790,30'], 9218: ['59,60', '2.324,40'] }\n    totals: { totalBrutto: '21.103,58', totalNetto: '17.839,16', vats: { 17: '895,29', 18: '354,57', 19: '2.014,56' }, wrapping: { brutto: '7,98', netto: '6,82', vat: '1,16' }, delivery: { brutto: '15,00', netto: '12,61', vat: '2,39' }, grandTotal: '21.126,56' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case153.yaml",
    "content": "articles:\n    0: { oxid: 9200, oxprice: 59.16, oxvat: 17, amount: 1002 }\n    1: { oxid: 9201, oxprice: 49.54, oxvat: 17, amount: 1 }\n    3: { oxid: 9202, oxprice: 11.02, oxvat: 17, amount: 5 }\ndiscounts:\n    - { oxid: discount5for9200, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9200], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9201, oxprice: 8, oxactive: 1, oxarticles: [9201] }, { oxtype: WRAP, oxname: wrapFor9202, oxprice: 0.7, oxactive: 1, oxarticles: [9202] }]\n    delivery: [{ oxactive: 1, oxaddsum: 14.75, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\nexpected:\n    articles: { 9200: ['38,22', '38.296,44'], 9201: ['33,69', '33,69'], 9202: ['7,49', '37,45'] }\n    totals: { totalBrutto: '38.367,58', totalNetto: '32.792,80', vats: { 17: '5.574,78' }, wrapping: { brutto: '7,84', netto: '6,70', vat: '1,14' }, delivery: { brutto: '10,03', netto: '8,57', vat: '1,46' }, grandTotal: '38.385,45' }\noptions:\n    activeCurrencyRate: 0.68\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case154.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 16.48, oxvat: 19, amount: 190 }\n    - { oxid: 9210, oxprice: 27.35, oxvat: 19, amount: 255 }\n    - { oxid: 9213, oxprice: 30.77, oxvat: 19, amount: 14 }\n    - { oxid: 9215, oxprice: 69.13, oxvat: 19, amount: 10 }\ndiscounts:\n    - { oxid: discount1for9202, oxaddsum: 1, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9202], oxsort: 10 }\n    - { oxid: discount2for9210, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9210], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9215, oxprice: 1.5, oxactive: 1, oxarticles: [9215] }]\n    delivery: [{ oxactive: 1, oxaddsum: 58.49, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\nexpected:\n    articles: { 9202: ['16,32', '3.100,80'], 9210: ['26,80', '6.834,00'], 9213: ['30,77', '430,78'], 9215: ['69,13', '691,30'] }\n    totals: { totalBrutto: '11.056,88', totalNetto: '9.291,50', vats: { 19: '1.765,38' }, wrapping: { brutto: '15,00', netto: '12,61', vat: '2,39' }, delivery: { brutto: '58,49', netto: '49,15', vat: '9,34' }, grandTotal: '11.130,37' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case155.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 15.93, oxvat: 15, amount: 58 }\n    - { oxid: 9208, oxprice: 70.87, oxvat: 15, amount: 14 }\n    - { oxid: 9213, oxprice: 25.86, oxvat: 0, amount: 1398 }\n    - { oxid: 9216, oxprice: 48.25, oxvat: 0, amount: 250 }\n    - { oxid: 9218, oxprice: 58.09, oxvat: 15, amount: 12 }\ndiscounts:\n    - { oxid: discount4for9213, oxaddsum: 4, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9213], oxsort: 10 }\n    - { oxid: discount2for9216, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9216], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9218, oxprice: 2.3, oxactive: 1, oxarticles: [9218] }]\n    delivery: [{ oxactive: 1, oxaddsum: 12.82, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\nexpected:\n    articles: { 9202: ['15,93', '923,94'], 9208: ['70,87', '992,18'], 9213: ['24,83', '34.712,34'], 9216: ['47,29', '11.822,50'], 9218: ['58,09', '697,08'] }\n    totals: { totalBrutto: '49.148,04', totalNetto: '48.807,19', vats: { 15: '340,85', 0: '0,00' }, wrapping: { brutto: '27,60', netto: '24,00', vat: '3,60' }, delivery: { brutto: '12,82', netto: '12,82' }, grandTotal: '49.188,46' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case156.yaml",
    "content": "articles:\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 175 }\n    - { oxid: 9203, oxprice: 33.3, oxvat: 19, amount: 12 }\n    - { oxid: 9211, oxprice: 5.86, oxvat: 16, amount: 5874 }\n    - { oxid: 9216, oxprice: 56.45, oxvat: 17, amount: 225 }\n    - { oxid: 9219, oxprice: 24.33, oxvat: 19, amount: 31 }\ndiscounts:\n    - { oxid: discount2for9201, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 10 }\n    - { oxid: discount4for9211, oxaddsum: 4, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9211], oxsort: 20 }\n    - { oxid: discount2for9216, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9216], oxsort: 30 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9219, oxprice: 0.48, oxactive: 1, oxarticles: [9219] }]\n    delivery: [{ oxactive: 1, oxaddsum: 15.03, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\nexpected:\n    articles: { 9201: ['71,39', '12.493,25'], 9203: ['33,30', '399,60'], 9211: ['5,63', '33.070,62'], 9216: ['55,32', '12.447,00'], 9219: ['24,33', '754,23'] }\n    totals: { totalBrutto: '59.164,70', totalNetto: '50.795,22', vats: { 16: '4.561,46', 17: '3.623,80', 19: '184,22' }, wrapping: { brutto: '14,88', netto: '12,50', vat: '2,38' }, delivery: { brutto: '15,03', netto: '12,96', vat: '2,07' }, grandTotal: '59.194,61' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case157.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 119, oxvat: 19, amount: 1 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\n    oxcountryid: 8f241f11096877ac0.98748826\nexpected:\n    articles: { 9202: ['100,00', '100,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '100,00', vats: ['0,00'], grandTotal: '100,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case158.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 119, oxvat: 19, amount: 1 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\n    oxcountryid: 8f241f11096877ac0.98748826\nexpected:\n    articles: { 9202: ['100,00', '100,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '100,00', vats: ['0,00'], grandTotal: '100,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case159.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 100, oxvat: 19, amount: 1 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\n    oxcountryid: 8f241f11096877ac0.98748826\nexpected:\n    articles: { 9202: ['100,00', '100,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '100,00', vats: ['0,00'], grandTotal: '100,00' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case16.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 74.36, oxvat: 17, amount: 1 }\nexpected:\n    articles: { 9200: ['87,00', '87,00'] }\n    totals: { totalBrutto: '87,00', totalNetto: '74,36', vats: { 17: '12,64' }, grandTotal: '87,00' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case160.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 100, oxvat: 19, amount: 1 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\n    oxcountryid: 8f241f11096877ac0.98748826\nexpected:\n    articles: { 9202: ['100,00', '100,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '100,00', vats: ['0,00'], grandTotal: '100,00' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case161.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9006, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount5for9006, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\n    - { oxid: basketdiscount5for9005, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 30 }\n    - { oxid: basketdiscount5for9006, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9005, oxprice: 9, oxactive: 1, oxarticles: [9005] }, { oxtype: WRAP, oxname: testWrap9006, oxprice: 6, oxactive: 1, oxarticles: [9006] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9005: ['113,00', '3.729,00'], 9006: ['70,13', '1.122,08'] }\n    totals: { totalBrutto: '4.851,08', totalNetto: '4.067,29', vats: { 19: '772,79' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '393,00', netto: '330,25', vat: '62,75' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '5.240,08' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case162.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9006, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount5for9006, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\n    - { oxid: basketdiscount5for9005, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 30 }\n    - { oxid: basketdiscount5for9006, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9005, oxprice: 9, oxactive: 1, oxarticles: [9005] }, { oxtype: WRAP, oxname: testWrap9006, oxprice: 6, oxactive: 1, oxarticles: [9006] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9005: ['113,00', '3.729,00'], 9006: ['70,13', '1.122,08'] }\n    totals: { totalBrutto: '4.851,08', totalNetto: '4.067,29', vats: { 19: '772,79' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '393,00', netto: '330,25', vat: '62,75' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '5.240,08' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case163.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9006, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount5for9006, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\n    - { oxid: basketdiscount5for9005, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 30 }\n    - { oxid: basketdiscount5for9006, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9005, oxprice: 9, oxactive: 1, oxarticles: [9005] }, { oxtype: WRAP, oxname: testWrap9006, oxprice: 6, oxactive: 1, oxarticles: [9006] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9005: ['113,00', '3.729,00'], 9006: ['70,13', '1.122,08'] }\n    totals: { totalBrutto: '4.851,08', totalNetto: '4.067,29', vats: { 19: '772,79' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '393,00', netto: '330,25', vat: '62,75' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '5.240,08' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case164.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount5.5for9005, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['1.125,67', '1.125,67'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '1.126,33', totalNetto: '946,50', vats: { 19: '179,83' }, delivery: { brutto: '112,63', netto: '94,65', vat: '17,98' }, payment: { brutto: '10,00', netto: '8,40', vat: '1,60' }, grandTotal: '1.248,96' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case165.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount5.5for9005, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['1.125,67', '1.125,67'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '1.126,33', totalNetto: '946,50', vats: { 19: '179,83' }, delivery: { brutto: '112,63', netto: '94,65', vat: '17,98' }, payment: { brutto: '10,00', netto: '8,40', vat: '1,60' }, grandTotal: '1.248,96' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case166.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount50for9005, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['595,60', '595,60'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '596,26', totalNetto: '501,06', vats: { 19: '95,20' }, delivery: { brutto: '59,63', netto: '50,11', vat: '9,52' }, payment: { brutto: '10,00', netto: '8,40', vat: '1,60' }, grandTotal: '665,89' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case167.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 3 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount50for9005, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['595,60', '1.786,80'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '1.787,46', totalNetto: '1.502,07', vats: { 19: '285,39' }, delivery: { brutto: '178,75', netto: '150,21', vat: '28,54' }, payment: { brutto: '10,00', netto: '8,40', vat: '1,60' }, grandTotal: '1.976,21' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case168.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 3 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount50for9005, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['595,60', '1.786,80'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '1.787,46', totalNetto: '1.502,07', vats: { 19: '285,39' }, delivery: { brutto: '178,75', netto: '150,21', vat: '28,54' }, payment: { brutto: '10,00' }, grandTotal: '1.976,21' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: false, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case169.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 3 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount50for9005, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['595,60', '1.786,80'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '1.787,46', totalNetto: '1.502,07', vats: { 19: '285,39' }, delivery: { brutto: '178,75', netto: '150,21', vat: '28,54' }, payment: { brutto: '10,00', netto: '8,40', vat: '1,60' }, grandTotal: '1.976,21' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case17.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 74.36, oxvat: 17, amount: 1 }\nexpected:\n    articles: { 9200: ['59,16', '59,16'] }\n    totals: { totalBrutto: '59,16', totalNetto: '50,56', vats: { 17: '8,60' }, grandTotal: '59,16' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case170.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 3 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount50for9005, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['595,60', '1.786,80'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '1.787,46', totalNetto: '1.502,07', vats: { 19: '285,39' }, delivery: { brutto: '178,75', netto: '150,21', vat: '28,54' }, payment: { brutto: '10,00' }, grandTotal: '1.976,21' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: false, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case171.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 3 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 18, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount50for9005, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['595,60', '1.786,80'], 9006: ['0,65', '0,65'] }\n    totals: { totalBrutto: '1.787,45', totalNetto: '1.502,06', vats: { 19: '285,29', 18: '0,10' }, delivery: { brutto: '178,75', netto: '150,21', vat: '28,54' }, payment: { brutto: '10,00' }, grandTotal: '1.976,20' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blShowVATForPayCharge: false, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case172.yaml",
    "content": "articles:\n    - { oxid: 1126, oxprice: 34.0, oxvat: 19, amount: 3 }\ndiscounts:\n    - { oxid: testdisc, oxaddsum: 50, oxaddsumtype: '%', oxamount: 3, oxamountto: 99999, oxactive: 1, oxarticles: [1126], oxsort: 10 }\nexpected:\n    articles: { 1126: ['17,00', '51,00'] }\n    totals: { totalBrutto: '51,00', totalNetto: '42,86', vats: { 19: '8,14' }, grandTotal: '51,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, bl_perfLoadSelectLists: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case173.yaml",
    "content": "articles:\n    - { oxid: 1126, oxprice: 34.0, oxvat: 19, amount: 3 }\ndiscounts:\n    - { oxid: testdisc, oxaddsum: 50, oxaddsumtype: '%', oxamount: 3, oxamountto: 99999, oxactive: 0, oxarticles: [1126], oxsort: 10 }\n    - { oxid: _testoxdiscount2, oxaddsum: 50, oxaddsumtype: '%', oxamount: 3, oxamountto: 99999, oxprice: 69, oxpriceto: 999999, oxactive: 1, oxarticles: [1126], oxsort: 20 }\nexpected:\n    articles: { 1126: ['17,00', '51,00'] }\n    totals: { totalBrutto: '51,00', totalNetto: '42,86', vats: { 19: '8,14' }, grandTotal: '51,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, bl_perfLoadSelectLists: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case174.yaml",
    "content": "articles:\n    - { oxid: 1126, oxprice: 34.0, oxvat: 19, amount: 2 }\ndiscounts:\n    - { oxid: testdisc, oxaddsum: 50, oxaddsumtype: '%', oxamount: 3, oxamountto: 99999, oxactive: 0, oxarticles: [1126], oxsort: 10 }\n    - { oxid: _testoxdiscount2, oxaddsum: 50, oxaddsumtype: '%', oxamount: 3, oxamountto: 99999, oxprice: 69, oxpriceto: 999999, oxactive: 1, oxarticles: [1126], oxsort: 20 }\nexpected:\n    articles: { 1126: ['34,00', '68,00'] }\n    totals: { totalBrutto: '68,00', totalNetto: '57,14', vats: { 19: '10,86' }, grandTotal: '68,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, bl_perfLoadSelectLists: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case18.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 74.36, oxvat: 17, amount: 1 }\nexpected:\n    articles: { 9200: ['127,89', '127,89'] }\n    totals: { totalBrutto: '127,89', totalNetto: '109,31', vats: { 17: '18,58' }, grandTotal: '127,89' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1.47\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case19.yaml",
    "content": "articles:\n    - { oxid: 9201, oxprice: 66.58, oxvat: 17, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_9201, oxaddsum: 4.32, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 10 }\nexpected:\n    articles: { 9201: ['73,58', '73,58'] }\n    totals: { totalBrutto: '73,58', totalNetto: '62,89', vats: { 17: '10,69' }, grandTotal: '73,58' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case2.yaml",
    "content": "articles:\n    - { oxid: 9003, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9004, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9003, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9003], oxsort: 10 }\n    - { oxid: shopdiscount5for9004, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9004], oxsort: 20 }\n    - { oxid: basketdiscount5for9003, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9003], oxsort: 30 }\n    - { oxid: basketdiscount5for9004, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9004], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9003, oxprice: 9, oxactive: 1, oxarticles: [9003] }, { oxtype: WRAP, oxname: testWrap9003, oxprice: 6, oxactive: 1, oxarticles: [9004] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxarticles: [9003, 9004] }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9003: ['76,84', '2.535,72'], 9004: ['47,70', '763,20'] }\n    totals: { totalBrutto: '3.298,92', totalNetto: '2.765,92', vats: { 19: '525,52' }, discounts: { absolutebasketdiscount: '3,40' }, wrapping: { brutto: '267,24', netto: '224,57', vat: '42,67' }, delivery: { brutto: '4,08', netto: '3,43', vat: '0,65' }, payment: { brutto: '0,68', netto: '0,57', vat: '0,11' }, voucher: { brutto: '4,08' }, grandTotal: '3.563,44' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case20.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 16.2, oxvat: 0, amount: 1 }\nexpected:\n    articles: { 9202: ['11,02', '11,02'] }\n    totals: { totalBrutto: '11,02', totalNetto: '11,02', vats: ['0,00'], grandTotal: '11,02' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case21.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 16.2, oxvat: 0, amount: 1 }\nexpected:\n    articles: { 9202: ['16,20', '16,20'] }\n    totals: { totalBrutto: '16,20', totalNetto: '16,20', vats: ['0,00'], grandTotal: '16,20' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case22.yaml",
    "content": "articles:\n    - { oxid: 100, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 1001, oxprice: 66, oxvat: 19, amount: 33 }\ndiscounts:\n    - { oxid: shopdiscount5for100, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [100], oxsort: 10 }\n    - { oxid: shopdiscount5for1001, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: basketdiscount5for100, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [100], oxsort: 30 }\n    - { oxid: basketdiscount5for1001, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9005, oxprice: 9, oxactive: 1, oxarticles: [100] }, { oxtype: WRAP, oxname: testWrap9006, oxprice: 6, oxactive: 1, oxarticles: [1001] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 100: ['113,00', '3.729,00'], 1001: ['70,13', '2.314,29'] }\n    totals: { totalBrutto: '6.043,29', totalNetto: '5.069,15', vats: { 19: '963,14' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '495,00', netto: '415,97', vat: '79,03' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '6.534,29' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case23.yaml",
    "content": "articles:\n    - { oxid: 10005, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount5for10005, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 10 }\n    - { oxid: shopdiscount5for1004, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 20 }\n    - { oxid: basketdiscount5for10005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 30 }\n    - { oxid: basketdiscount5for1004, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\n    - { oxid: procdiscountfor10005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 60 }\n    - { oxid: procdiscountfor1004, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 70 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap102, oxprice: 9, oxactive: 1, oxarticles: [10005] }, { oxtype: WRAP, oxname: testWrap1002, oxprice: 6, oxactive: 1, oxarticles: [1004] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 10005: ['1.115,67', '1.115,67'], 1004: ['0,59', '0,59'] }\n    totals: { totalBrutto: '1.116,26', totalNetto: '928,79', vats: { 19: '176,47' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '15,00', netto: '12,60', vat: '2,40' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '1.127,26' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case24.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 5.02, oxvat: 7, amount: 1 }\n    - { oxid: 1112, oxprice: 99, 0: 0, oxvat: 19, amount: 1 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 5.02, oxvat: 8, amount: 1 }\ndiscounts:\n    - { oxid: discount10%, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111], oxsort: 10 }\n    - { oxid: discount5.5, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1112, 1113], oxsort: 20 }\nexpected:\n    articles: { 111: ['5,52', '5,52'], 1112: ['93,56', '93,56'], 1113: ['945,95', '945,95'], 1114: ['5,02', '5,02'] }\n    totals: { totalBrutto: '1.248,35', totalNetto: '1.050,05', vats: { 7: '0,39', 8: '0,40', 19: '197,51' }, grandTotal: '1.248,35' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case25.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\ncosts:\n    wrapping: { 3: { oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 } }\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'] }\n    totals: { totalBrutto: '568,00', totalNetto: '500,00', vats: { 10: '30,00', 19: '38,00' }, delivery: { brutto: '312,40', netto: '275,00', vat: '37,40' }, payment: { brutto: '312,40', netto: '275,00', vat: '37,40' }, giftcard: { brutto: '2,84', netto: '2,50', vat: '0,34' }, grandTotal: '1.195,64' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case26.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 10 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '200,00'], 1002: ['200,00', '200,00'] }\n    totals: { totalBrutto: '400,00', totalNetto: '349,89', vats: { 19: '31,93', 10: '18,18' }, delivery: { brutto: '261,80', netto: '220,00', vat: '41,80' }, payment: { brutto: '327,25', netto: '275,00', vat: '52,25' }, grandTotal: '989,05' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case27.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1002, oxprice: 20.0, oxvat: 10, amount: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['200,00', '200,00'], 1002: ['20,00', '200,00'] }\n    totals: { totalBrutto: '400,00', totalNetto: '349,89', vats: { 19: '31,93', 10: '18,18' }, delivery: { brutto: '261,80', netto: '220,00', vat: '41,80' }, payment: { brutto: '327,25', netto: '275,00', vat: '52,25' }, grandTotal: '989,05' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case28.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 19, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1003, oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1003: ['0,00', '0,00'] }\n    totals: { totalBrutto: '595,00', totalNetto: '500,00', vats: { 19: '95,00' }, delivery: { brutto: '327,25', netto: '275,00', vat: '52,25' }, payment: { brutto: '327,25', netto: '275,00', vat: '52,25' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, grandTotal: '1.252,48' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case29.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 19, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1003, oxitmamount: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1003: ['0,00', '0,00'] }\n    totals: { totalBrutto: '595,00', totalNetto: '500,00', vats: { 19: '95,00' }, delivery: { brutto: '327,25', netto: '275,00', vat: '52,25' }, payment: { brutto: '327,25', netto: '275,00', vat: '52,25' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, grandTotal: '1.252,48' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case3.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9006, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount5for9006, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\n    - { oxid: basketdiscount5for9005, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 30 }\n    - { oxid: basketdiscount5for9006, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9005, oxprice: 9, oxactive: 1, oxarticles: [9005] }, { oxtype: WRAP, oxname: testWrap9006, oxprice: 6, oxactive: 1, oxarticles: [9006] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9005: ['113,00', '3.729,00'], 9006: ['70,13', '1.122,08'] }\n    totals: { totalBrutto: '4.851,08', totalNetto: '4.067,29', vats: { 19: '772,79' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '393,00', netto: '330,25', vat: '62,75' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '5.240,08' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case30.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 19, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\n    - { oxid: discountforbasket55%, oxaddsum: 55, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '267,75', totalNetto: '500,00', vats: { 19: '42,75' }, discounts: { discountforbasket55%: '275,00' }, delivery: { brutto: '327,25', netto: '275,00', vat: '52,25' }, payment: { brutto: '327,25', netto: '275,00', vat: '52,25' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, grandTotal: '925,23' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case31.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\n    - { oxid: discountforbasket55%, oxaddsum: 55, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '255,60', totalNetto: '500,00', vats: { 10: '13,50', 19: '17,10' }, discounts: { discountforbasket55%: '275,00' }, delivery: { brutto: '312,40', netto: '275,00', vat: '37,40' }, payment: { brutto: '312,40', netto: '275,00', vat: '37,40' }, giftcard: { brutto: '2,84', netto: '2,50', vat: '0,34' }, grandTotal: '883,24' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case32.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '511,20', totalNetto: '500,00', vats: { 19: '34,20', 10: '27,00' }, delivery: { brutto: '312,40', netto: '275,00', vat: '37,40' }, payment: { brutto: '312,40', netto: '275,00', vat: '37,40' }, voucher: { brutto: '50,00' }, giftcard: { brutto: '2,84', netto: '2,50', vat: '0,34' }, grandTotal: '1.138,84' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case33.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '511,20', totalNetto: '500,00', vats: { 19: '34,20', 10: '27,00' }, delivery: { brutto: '302,50', netto: '275,00', vat: '27,50' }, payment: { brutto: '302,50', netto: '275,00', vat: '27,50' }, voucher: { brutto: '50,00' }, giftcard: { brutto: '2,75', netto: '2,50', vat: '0,25' }, grandTotal: '1.118,95' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case34.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '511,20', totalNetto: '500,00', vats: { 19: '34,20', 10: '27,00' }, delivery: { brutto: '302,50' }, payment: { brutto: '302,50' }, voucher: { brutto: '50,00' }, giftcard: { brutto: '2,75' }, grandTotal: '1.118,95' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: false, blShowVATForPayCharge: false, blShowVATForWrapping: false, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case35.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 15 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '511,20', totalNetto: '500,00', vats: { 19: '34,20', 10: '27,00' }, delivery: { brutto: '302,50', netto: '275,00', vat: '27,50' }, payment: { brutto: '302,50', netto: '275,00', vat: '27,50' }, voucher: { brutto: '50,00' }, giftcard: { brutto: '2,75', netto: '2,50', vat: '0,25' }, grandTotal: '1.118,95' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case36.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [basketUser] }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: item_discount, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['18,00', '36,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '225,15', totalNetto: '236,00', vats: { 19: '30,78', 11: '3,21' }, discounts: { percentage_discount: '23,60' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, delivery: { brutto: '154,46', netto: '129,80', vat: '24,66' }, payment: { brutto: '154,46', netto: '129,80', vat: '24,66' }, voucher: { brutto: '21,24' }, grandTotal: '537,05' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case37.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [basketUser] }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: item_discount, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['18,00', '36,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '225,15', totalNetto: '236,00', vats: { 19: '30,78', 11: '3,21' }, discounts: { percentage_discount: '23,60' }, giftcard: { brutto: '2,94', netto: '2,50', vat: '0,44' }, delivery: { brutto: '152,88', netto: '129,80', vat: '23,08' }, payment: { brutto: '152,88', netto: '129,80', vat: '23,08' }, voucher: { brutto: '21,24' }, grandTotal: '533,85' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case38.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [basketUser] }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: item_discount, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['54,00', '108,00'], 1002: ['600,00', '600,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '675,44', totalNetto: '708,00', vats: { 19: '92,34', 11: '9,62' }, discounts: { percentage_discount: '70,80' }, giftcard: { brutto: '8,83', netto: '7,50', vat: '1,33' }, delivery: { brutto: '458,63', netto: '389,40', vat: '69,23' }, payment: { brutto: '458,63', netto: '389,40', vat: '69,23' }, voucher: { brutto: '63,72' }, grandTotal: '1.601,53' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 3\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case39.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [basketUser] }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: item_discount, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['59,94', '119,88'], 1002: ['714,00', '714,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '833,88', totalNetto: '573,48', vats: { 19: '92,34', 11: '9,62' }, discounts: { percentage_discount: '83,39' }, giftcard: { brutto: '8,83', netto: '7,50', vat: '1,33' }, delivery: { brutto: '540,17', netto: '458,63', vat: '81,54' }, payment: { brutto: '540,17', netto: '458,63', vat: '81,54' }, voucher: { brutto: '75,05' }, grandTotal: '1.764,61' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 3\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case4.yaml",
    "content": "articles:\n    - { oxid: 9007, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9008, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9007, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9007], oxsort: 10 }\n    - { oxid: shopdiscount5for9008, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9008], oxsort: 20 }\n    - { oxid: basketdiscount5for9007, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9007], oxsort: 30 }\n    - { oxid: basketdiscount5for9008, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9008], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9007, oxprice: 9, oxactive: 1, oxarticles: [9007] }, { oxtype: WRAP, oxname: testWrap9008, oxprice: 6, oxactive: 1, oxarticles: [9008] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9007: ['76,84', '2.535,72'], 9008: ['47,70', '763,20'] }\n    totals: { totalBrutto: '3.298,92', totalNetto: '2.765,92', vats: { 19: '525,52' }, discounts: { absolutebasketdiscount: '3,40' }, wrapping: { brutto: '267,24', netto: '224,57', vat: '42,67' }, delivery: { brutto: '4,08', netto: '3,43', vat: '0,65' }, payment: { brutto: '0,68', netto: '0,57', vat: '0,11' }, voucher: { brutto: '4,08' }, grandTotal: '3.563,44' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case40.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19, OXSHOPID: 2 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [basketUser] }\nshop:\n    - { oxactive: 1, oxparentid: 1, oxname: subshop, activeshop: true }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: item_discount, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['18,00', '36,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '225,15', totalNetto: '236,00', vats: { 19: '30,78', 11: '3,21' }, discounts: { percentage_discount: '23,60' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, delivery: { brutto: '154,46', netto: '129,80', vat: '24,66' }, payment: { brutto: '154,46', netto: '129,80', vat: '24,66' }, voucher: { brutto: '21,24' }, grandTotal: '537,05' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case41.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1, oxfreeshipping: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19, OXSHOPID: 2 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [basketUser] }\nshop:\n    - { oxactive: 1, oxparentid: 1, oxname: subshop, activeshop: true }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: item_discount, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['18,00', '36,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '225,15', totalNetto: '236,00', vats: { 19: '30,78', 11: '3,21' }, discounts: { percentage_discount: '23,60' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, delivery: { brutto: '23,56', netto: '19,80', vat: '3,76' }, payment: { brutto: '154,46', netto: '129,80', vat: '24,66' }, voucher: { brutto: '21,24' }, grandTotal: '406,15' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case42.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 1, scaleprices: [{ oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 }, { oxamount: 3, oxamountto: 4, oxartid: 1001, oxaddperc: 20 }] }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1, oxfreeshipping: 1 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '20,00'], 1002: ['200,00', '200,00'] }\n    totals: { totalBrutto: '234,18', totalNetto: '220,00', vats: { 19: '34,20', 11: '1,98' }, discounts: { percentage_discount: '22,00' }, delivery: { brutto: '2,38', netto: '2,00', vat: '0,38' }, payment: { brutto: '26,18', netto: '22,00', vat: '4,18' }, grandTotal: '262,74' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case43.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, oxfreeshipping: 1, scaleprices: { oxamount: 1, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1, oxfreeshipping: 1 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['18,00', '36,00'], 1002: ['200,00', '200,00'] }\n    totals: { totalBrutto: '250,16', totalNetto: '236,00', vats: { 19: '34,20', 11: '3,56' }, discounts: { percentage_discount: '23,60' }, delivery: { brutto: '0,00' }, payment: { brutto: '27,80', netto: '23,60', vat: '4,20' }, grandTotal: '277,96' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case44.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1.0, oxvat: 20, amount: 1, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 2.0, oxvat: 30, amount: 2 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 1.0, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['1,20', '1,20'], 1002: ['2,60', '5,20'] }\n    totals: { totalBrutto: '6,40', totalNetto: '4,50', vats: { 20: '0,18', 30: '1,08' }, discounts: { percentage_discount: '0,64' }, delivery: { brutto: '4,16', netto: '3,20', vat: '0,96' }, payment: { brutto: '1,30', netto: '1,00', vat: '0,30' }, grandTotal: '11,22' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case45.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1.0, oxvat: 20, amount: 2, scaleprices: { oxamount: 1, oxamountto: 3, oxartid: 1001, oxaddabs: 2.0 } }\n    - { oxid: 1002, oxprice: 2.0, oxvat: 30, amount: 2 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 1.0, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['1,20', '2,40'], 1002: ['2,60', '5,20'] }\n    totals: { totalBrutto: '7,60', totalNetto: '5,40', vats: { 20: '0,36', 30: '1,08' }, discounts: { percentage_discount: '0,76' }, delivery: { brutto: '4,94', netto: '3,80', vat: '1,14' }, payment: { brutto: '1,30', netto: '1,00', vat: '0,30' }, grandTotal: '13,08' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case46.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2 }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6 }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6 }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6 }\ndiscounts:\n    - { oxid: tenpercentdiscount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 1001: ['1.002,55', '2.005,10'], 1002: ['11,56', '23,12'], 1003: ['1.326,89', '7.961,34'], 1004: ['6,66', '39,96'], 1005: ['0,66', '3,96'] }\n    totals: { totalBrutto: '10.033,48', totalNetto: '8.524,80', vats: { 19: '288,13', 13: '2,39', 3: '208,70', 17: '5,23', 33: '0,88' }, discounts: { tenpercentdiscount: '1.003,35' }, grandTotal: '9.030,13' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case47.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2 }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6 }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6 }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6 }\ndiscounts:\n    - { oxid: absdiscount, oxaddsum: 125.55, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['1.002,55', '2.005,10'], 1002: ['11,56', '23,12'], 1003: ['1.326,89', '7.961,34'], 1004: ['6,66', '39,96'], 1005: ['0,66', '3,96'] }\n    totals: { totalBrutto: '10.033,48', totalNetto: '9.353,48', vats: { 19: '316,14', 13: '2,63', 3: '228,98', 17: '5,73', 33: '0,97' }, discounts: { absdiscount: '125,55' }, delivery: { brutto: '3,14', netto: '3,05', vat: '0,09' }, payment: { brutto: '7,59', netto: '7,37', vat: '0,22' }, grandTotal: '9.918,66' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case48.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2 }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6 }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6 }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6 }\ndiscounts:\n    - { oxid: absdiscount, oxaddsum: 125.55, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['842,48', '1.684,96'], 1002: ['10,23', '20,46'], 1003: ['1.288,24', '7.729,44'], 1004: ['5,69', '34,14'], 1005: ['0,50', '3,00'] }\n    totals: { totalBrutto: '9.900,49', totalNetto: '9.472,00', vats: { 19: '315,90', 13: '2,62', 3: '228,81', 17: '5,73', 33: '0,98' }, discounts: { absdiscount: '125,55' }, delivery: { brutto: '3,14', netto: '3,05', vat: '0,09' }, payment: { brutto: '7,59', netto: '7,37', vat: '0,22' }, grandTotal: '9.911,22' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case49.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1382.42, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 13.58, oxvat: 13, amount: 14 }\n    - { oxid: 1003, oxprice: 1756.66, oxvat: 3, amount: 13 }\n    - { oxid: 1004, oxprice: 13.64, oxvat: 17, amount: 62 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9001, oxprice: 3.98, oxactive: 1, oxarticles: [1001] }, { oxtype: WRAP, oxname: testWrap9002, oxprice: 1.47, oxactive: 1, oxarticles: [1002] }, { oxtype: WRAP, oxname: testWrap9003, oxprice: 2.14, oxactive: 1, oxarticles: [1003] }, { oxtype: CARD, oxname: testCard9001, oxprice: 2.97, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['1.382,42', '2.764,84'], 1002: ['13,58', '190,12'], 1003: ['1.756,66', '22.836,58'], 1004: ['13,64', '845,68'] }\n    totals: { totalBrutto: '26.637,22', totalNetto: '25.385,88', vats: { 19: '441,45', 13: '21,87', 3: '665,14', 17: '122,88' }, delivery: { brutto: '3,14' }, payment: { brutto: '7,59' }, wrapping: { brutto: '56,36' }, giftcard: { brutto: '2,97' }, grandTotal: '26.707,28' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForDelivery: false, blShowVATForPayCharge: false, blShowVATForWrapping: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case5.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87, oxvat: 17, amount: 1 }\nexpected:\n    articles: { 9200: ['87,00', '87,00'] }\n    totals: { totalBrutto: '87,00', totalNetto: '74,36', vats: { 17: '12,64' }, grandTotal: '87,00' }\noptions:\n    config: { blEnterNetPrice: false, blViewNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case50.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1382.42, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 13.58, oxvat: 13, amount: 14 }\n    - { oxid: 1003, oxprice: 1756.66, oxvat: 3, amount: 13 }\n    - { oxid: 1004, oxprice: 13.64, oxvat: 17, amount: 62 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9001, oxprice: 3.98, oxactive: 1, oxarticles: [1001] }, { oxtype: WRAP, oxname: testWrap9002, oxprice: 1.47, oxactive: 1, oxarticles: [1002] }, { oxtype: WRAP, oxname: testWrap9003, oxprice: 2.14, oxactive: 1, oxarticles: [1003] }, { oxtype: CARD, oxname: testCard9001, oxprice: 2.97, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['1.382,42', '2.764,84'], 1002: ['13,58', '190,12'], 1003: ['1.756,66', '22.836,58'], 1004: ['13,64', '845,68'] }\n    totals: { totalBrutto: '26.637,22', totalNetto: '25.385,88', vats: { 19: '441,45', 13: '21,87', 3: '665,14', 17: '122,88' }, delivery: { brutto: '3,14', netto: '3,05', vat: '0,09' }, payment: { brutto: '7,59', netto: '7,37', vat: '0,22' }, wrapping: { brutto: '56,36', netto: '51,91', vat: '4,45' }, giftcard: { brutto: '2,97', netto: '2,88', vat: '0,09' }, grandTotal: '26.707,28' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case51.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1382.42, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 13.58, oxvat: 13, amount: 14 }\n    - { oxid: 1003, oxprice: 1756.66, oxvat: 3, amount: 13 }\n    - { oxid: 1004, oxprice: 13.64, oxvat: 17, amount: 62 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9001, oxprice: 3.98, oxactive: 1, oxarticles: [1001] }, { oxtype: WRAP, oxname: testWrap9002, oxprice: 1.47, oxactive: 1, oxarticles: [1002] }, { oxtype: WRAP, oxname: testWrap9003, oxprice: 2.14, oxactive: 1, oxarticles: [1003] }, { oxtype: CARD, oxname: testCard9001, oxprice: 2.97, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['1.382,42', '2.764,84'], 1002: ['13,58', '190,12'], 1003: ['1.756,66', '22.836,58'], 1004: ['13,64', '845,68'] }\n    totals: { totalBrutto: '26.637,22', totalNetto: '25.385,88', vats: { 19: '441,45', 13: '21,87', 3: '665,14', 17: '122,88' }, delivery: { brutto: '3,14', netto: '2,99', vat: '0,15' }, payment: { brutto: '7,59', netto: '7,23', vat: '0,36' }, wrapping: { brutto: '56,36', netto: '51,91', vat: '4,45' }, giftcard: { brutto: '2,97', netto: '2,83', vat: '0,14' }, grandTotal: '26.707,28' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case52.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1382.42, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 13.58, oxvat: 13, amount: 14 }\n    - { oxid: 1003, oxprice: 1756.66, oxvat: 3, amount: 13 }\n    - { oxid: 1004, oxprice: 13.64, oxvat: 17, amount: 62 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9001, oxprice: 3.98, oxactive: 1, oxarticles: [1001] }, { oxtype: WRAP, oxname: testWrap9002, oxprice: 1.47, oxactive: 1, oxarticles: [1002] }, { oxtype: WRAP, oxname: testWrap9003, oxprice: 2.14, oxactive: 1, oxarticles: [1003] }, { oxtype: CARD, oxname: testCard9001, oxprice: 2.97, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['1.161,70', '2.323,40'], 1002: ['12,02', '168,28'], 1003: ['1.705,50', '22.171,50'], 1004: ['11,66', '722,92'] }\n    totals: { totalBrutto: '26.637,48', totalNetto: '25.386,10', vats: { 19: '441,45', 13: '21,88', 3: '665,15', 17: '122,90' }, delivery: { brutto: '3,23', netto: '3,14', vat: '0,09' }, payment: { brutto: '7,82', netto: '7,59', vat: '0,23' }, wrapping: { brutto: '61,38', netto: '56,36', vat: '5,02' }, giftcard: { brutto: '3,06', netto: '2,97', vat: '0,09' }, grandTotal: '26.712,97' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case53.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2, scaleprices: [{ oxaddabs: 1002.55, oxamount: 1, oxamountto: 5, oxartid: 1001 }, { oxaddabs: 1089.65, oxamount: 6, oxamountto: 10, oxartid: 1001 }] }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2, scaleprices: [{ oxaddabs: 11.56, oxamount: 1, oxamountto: 5, oxartid: 1002 }, { oxaddabs: 16.55, oxamount: 6, oxamountto: 10, oxartid: 1002 }] }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6, scaleprices: [{ oxaddabs: 1325.45, oxamount: 1, oxamountto: 5, oxartid: 1003 }, { oxaddabs: 1326.89, oxamount: 6, oxamountto: 10, oxartid: 1003 }] }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6, scaleprices: [{ oxaddabs: 5.65, oxamount: 1, oxamountto: 5, oxartid: 1004 }, { oxaddabs: 5.69, oxamount: 6, oxamountto: 10, oxartid: 1004 }] }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6, scaleprices: [{ oxaddabs: 0.55, oxamount: 1, oxamountto: 5, oxartid: 1005 }, { oxaddabs: 0.66, oxamount: 6, oxamountto: 10, oxartid: 1005 }] }\ndiscounts:\n    - { oxid: tenpercentdiscount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 1001: ['842,48', '1.684,96'], 1002: ['10,23', '20,46'], 1003: ['1.288,24', '7.729,44'], 1004: ['5,69', '34,14'], 1005: ['0,50', '3,00'] }\n    totals: { totalBrutto: '9.030,12', totalNetto: '9.472,00', vats: { 19: '288,13', 13: '2,39', 3: '208,69', 17: '5,22', 33: '0,89' }, discounts: { tenpercentdiscount: '947,20' }, grandTotal: '9.030,12' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case54.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2, scaleprices: [{ oxaddabs: 1002.55, oxamount: 1, oxamountto: 5, oxartid: 1001 }, { oxaddabs: 1089.65, oxamount: 6, oxamountto: 10, oxartid: 1001 }] }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2, scaleprices: [{ oxaddabs: 11.56, oxamount: 1, oxamountto: 5, oxartid: 1002 }, { oxaddabs: 16.55, oxamount: 6, oxamountto: 10, oxartid: 1002 }] }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6, scaleprices: [{ oxaddabs: 1325.45, oxamount: 1, oxamountto: 5, oxartid: 1003 }, { oxaddabs: 1326.89, oxamount: 6, oxamountto: 10, oxartid: 1003 }] }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6, scaleprices: [{ oxaddabs: 5.65, oxamount: 1, oxamountto: 5, oxartid: 1004 }, { oxaddabs: 5.69, oxamount: 6, oxamountto: 10, oxartid: 1004 }] }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6, scaleprices: [{ oxaddabs: 0.55, oxamount: 1, oxamountto: 5, oxartid: 1005 }, { oxaddabs: 0.66, oxamount: 6, oxamountto: 10, oxartid: 1005 }] }\ndiscounts:\n    - { oxid: tenpercentdiscount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 1001: ['1.002,55', '2.005,10'], 1002: ['11,56', '23,12'], 1003: ['1.326,89', '7.961,34'], 1004: ['6,66', '39,96'], 1005: ['0,66', '3,96'] }\n    totals: { totalBrutto: '10.033,48', totalNetto: '8.524,80', vats: { 19: '288,13', 13: '2,39', 3: '208,70', 17: '5,23', 33: '0,88' }, discounts: { tenpercentdiscount: '1.003,35' }, grandTotal: '9.030,13' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case55.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2, scaleprices: [{ oxaddabs: 1002.55, oxamount: 1, oxamountto: 5, oxartid: 1001 }, { oxaddabs: 1089.65, oxamount: 6, oxamountto: 10, oxartid: 1001 }] }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2, scaleprices: [{ oxaddabs: 11.56, oxamount: 1, oxamountto: 5, oxartid: 1002 }, { oxaddabs: 16.55, oxamount: 6, oxamountto: 10, oxartid: 1002 }] }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6, scaleprices: [{ oxaddabs: 1325.45, oxamount: 1, oxamountto: 5, oxartid: 1003 }, { oxaddabs: 1326.89, oxamount: 6, oxamountto: 10, oxartid: 1003 }] }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6, scaleprices: [{ oxaddabs: 5.65, oxamount: 1, oxamountto: 5, oxartid: 1004 }, { oxaddabs: 5.69, oxamount: 6, oxamountto: 10, oxartid: 1004 }] }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6, scaleprices: [{ oxaddabs: 0.55, oxamount: 1, oxamountto: 5, oxartid: 1005 }, { oxaddabs: 0.66, oxamount: 6, oxamountto: 10, oxartid: 1005 }] }\ndiscounts:\n    - { oxid: absdiscount, oxaddsum: 125.55, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['1.002,55', '2.005,10'], 1002: ['11,56', '23,12'], 1003: ['1.326,89', '7.961,34'], 1004: ['6,66', '39,96'], 1005: ['0,66', '3,96'] }\n    totals: { totalBrutto: '10.033,48', totalNetto: '9.353,48', vats: { 19: '316,14', 13: '2,63', 3: '228,98', 17: '5,73', 33: '0,97' }, discounts: { absdiscount: '125,55' }, delivery: { brutto: '3,14', netto: '3,05', vat: '0,09' }, payment: { brutto: '7,59', netto: '7,37', vat: '0,22' }, grandTotal: '9.918,66' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case56.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2, scaleprices: [{ oxaddabs: 1002.55, oxamount: 1, oxamountto: 5, oxartid: 1001 }, { oxaddabs: 1089.65, oxamount: 6, oxamountto: 10, oxartid: 1001 }] }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2, scaleprices: [{ oxaddabs: 11.56, oxamount: 1, oxamountto: 5, oxartid: 1002 }, { oxaddabs: 16.55, oxamount: 6, oxamountto: 10, oxartid: 1002 }] }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6, scaleprices: [{ oxaddabs: 1325.45, oxamount: 1, oxamountto: 5, oxartid: 1003 }, { oxaddabs: 1326.89, oxamount: 6, oxamountto: 10, oxartid: 1003 }] }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6, scaleprices: [{ oxaddabs: 5.65, oxamount: 1, oxamountto: 5, oxartid: 1004 }, { oxaddabs: 5.69, oxamount: 6, oxamountto: 10, oxartid: 1004 }] }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6, scaleprices: [{ oxaddabs: 0.55, oxamount: 1, oxamountto: 5, oxartid: 1005 }, { oxaddabs: 0.66, oxamount: 6, oxamountto: 10, oxartid: 1005 }] }\ndiscounts:\n    - { oxid: absdiscount, oxaddsum: 125.55, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['842,48', '1.684,96'], 1002: ['10,23', '20,46'], 1003: ['1.288,24', '7.729,44'], 1004: ['5,69', '34,14'], 1005: ['0,50', '3,00'] }\n    totals: { totalBrutto: '9.900,49', totalNetto: '9.472,00', vats: { 19: '315,90', 13: '2,62', 3: '228,81', 17: '5,73', 33: '0,98' }, discounts: { absdiscount: '125,55' }, delivery: { brutto: '3,14', netto: '3,05', vat: '0,09' }, payment: { brutto: '7,59', netto: '7,37', vat: '0,22' }, grandTotal: '9.911,22' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case57.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 0.55, oxvat: 19, amount: 1, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddabs: 2.0 } }\n    - { oxid: 1002, oxprice: 5.52, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 945.95, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 4.74, oxvat: 19, amount: 1 }\n    - { oxid: 1005, oxprice: 1.0, oxvat: 19, amount: 5 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.5, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['0,55', '0,55'], 1002: ['5,52', '5,52'], 1003: ['945,95', '945,95'], 1004: ['4,74', '4,74'], 1005: ['1,00', '5,00'] }\n    totals: { totalBrutto: '1.030,04', totalNetto: '961,76', vats: { 19: '164,46' }, discounts: { percentage_discount: '96,18' }, delivery: { brutto: '114,45', netto: '96,18', vat: '18,27' }, payment: { brutto: '8,93', netto: '7,50', vat: '1,43' }, grandTotal: '1.153,42' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case58.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 9.0, oxvat: 19, amount: 4, scaleprices: { oxaddabs: 2.0, oxamount: 3, oxamountto: 5, oxartid: 1001 } }\n    - { oxid: 1002, oxprice: 5.52, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 945.95, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 4.74, oxvat: 19, amount: 1 }\n    - { oxid: 1005, oxprice: 1.0, oxvat: 19, amount: 5 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.5, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['2,00', '8,00'], 1002: ['5,52', '5,52'], 1003: ['945,95', '945,95'], 1004: ['4,74', '4,74'], 1005: ['1,00', '5,00'] }\n    totals: { totalBrutto: '1.038,02', totalNetto: '969,21', vats: { 19: '165,73' }, discounts: { percentage_discount: '96,92' }, delivery: { brutto: '115,33', netto: '96,92', vat: '18,41' }, payment: { brutto: '8,93', netto: '7,50', vat: '1,43' }, grandTotal: '1.162,28' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case59.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 10.55, oxvat: 19, amount: 4, scaleprices: { oxaddabs: 2.0, oxamount: 3, oxamountto: 5, oxartid: 1001 } }\n    - { oxid: 1002, oxprice: 20.52, oxvat: 19, amount: 3, scaleprices: { oxaddabs: 2.0, oxamount: 3, oxamountto: 5, oxartid: 1002 } }\n    - { oxid: 1003, oxprice: 945.95, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 4.74, oxvat: 19, amount: 1 }\n    - { oxid: 1005, oxprice: 1.0, oxvat: 19, amount: 5 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.5, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['2,00', '8,00'], 1002: ['2,00', '6,00'], 1003: ['945,95', '945,95'], 1004: ['4,74', '4,74'], 1005: ['1,00', '5,00'] }\n    totals: { totalBrutto: '1.038,54', totalNetto: '969,69', vats: { 19: '165,82' }, discounts: { percentage_discount: '96,97' }, delivery: { brutto: '115,39', netto: '96,97', vat: '18,42' }, payment: { brutto: '8,93', netto: '7,50', vat: '1,43' }, grandTotal: '1.162,86' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case6.yaml",
    "content": "articles:\n    - { oxid: 9209, oxprice: 42.36, oxvat: 18, amount: 1 }\nexpected:\n    articles: { 9209: ['24,57', '24,57'] }\n    totals: { totalBrutto: '24,57', totalNetto: '20,82', vats: { 18: '3,75' }, grandTotal: '24,57' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.58\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case60.yaml",
    "content": "articles:\n    - { oxid: testarticle, oxprice: 12.0, amount: 2, scaleprices: { oxaddabs: 11.95, oxamount: 2, oxamountto: 2, oxartid: testarticle } }\ndiscounts:\n    - { oxid: _testDiscount, oxactive: 1, oxtitle: 'new discount', oxprice: 12, oxpriceto: 24.99, oxaddsumtype: abs, oxaddsum: 3, oxsort: 10 }\nexpected:\n    articles: { testarticle: ['11,95', '23,90'] }\n    totals: { totalBrutto: '23,90', totalNetto: '17,56', vats: { 19: '3,34' }, discounts: { _testDiscount: '3,00' }, grandTotal: '20,90' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case61.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9002, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9001, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9001], oxsort: 10 }\n    - { oxid: shopdiscount5for9002, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9002], oxsort: 20 }\n    - { oxid: basketdiscount5for9001, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9001], oxsort: 30 }\n    - { oxid: basketdiscount5for9002, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9002], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9001, oxprice: 9, oxactive: 1, oxarticles: [9001] }, { oxtype: WRAP, oxname: testWrap9002, oxprice: 6, oxactive: 1, oxarticles: [9002] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxarticles: [9001, 9002] }]\n    voucherserie: [{ oxserienr: abs_4_voucher_serie, oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9001: ['63,92', '2.109,36'], 9002: ['40,08', '641,28'] }\n    totals: { totalBrutto: '2.750,64', totalNetto: '2.305,18', vats: { 19: '437,98' }, discounts: { absolutebasketdiscount: '3,40' }, wrapping: { brutto: '267,24', netto: '224,57', vat: '42,67' }, delivery: { brutto: '4,08', netto: '3,43', vat: '0,65' }, payment: { brutto: '0,68', netto: '0,57', vat: '0,11' }, voucher: { brutto: '4,08' }, grandTotal: '3.015,16' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case65.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 100.0, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_111, oxaddsum: 15.0, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxprice: 85, oxpriceto: 110, oxactive: 1, oxarticles: [111], oxsort: 10 }\nexpected:\n    articles: { 111: ['68,00', '68,00'] }\n    totals: { totalBrutto: '68,00', totalNetto: '57,14', vats: { 19: '10,86' }, grandTotal: '68,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.8\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case66.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 120.0, oxvat: 20, amount: 1 }\ndiscounts:\n    - { oxid: discount_for_111, oxaddsum: 50.0, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 99999, oxactive: 1, oxarticles: [111], oxsort: 10 }\n    - { oxid: basket, oxaddsum: 50.0, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 99999, oxactive: 1, oxsort: 20 }\nexpected:\n    articles: { 111: ['60,00', '60,00'] }\n    totals: { totalBrutto: '60,00', totalNetto: '25,00', vats: { 20: '5,00' }, discounts: { basket: '30,00' }, grandTotal: '30,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case67.yaml",
    "content": "articles:\n    - { oxid: _testProduct, oxprice: 10.0, oxvat: 19, amount: 36 }\ndiscounts:\n    - { oxid: basket_0, oxaddsum: 6.0, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 199, oxactive: 1, oxsort: 10 }\n    - { oxid: basket_1, oxaddsum: 9.0, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 200, oxpriceto: 299, oxactive: 1, oxsort: 20 }\n    - { oxid: basket_2, oxaddsum: 12.0, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 300, oxpriceto: 99999, oxactive: 1, oxsort: 30 }\nexpected:\n    articles: { _testProduct: ['10,00', '360,00'] }\n    totals: { totalBrutto: '360,00', totalNetto: '266,22', vats: { 19: '50,58' }, discounts: { basket_2: '43,20' }, grandTotal: '316,80' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case68.yaml",
    "content": "articles:\n    - { oxid: _testProduct, oxprice: 10.0, oxvat: 19, amount: 31 }\ndiscounts:\n    - { oxid: basket_0, oxaddsum: 6.0, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 199, oxactive: 1, oxarticles: [_testProduct], oxsort: 10 }\n    - { oxid: basket_1, oxaddsum: 9.0, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 200, oxpriceto: 299, oxactive: 1, oxarticles: [_testProduct], oxsort: 20 }\n    - { oxid: basket_2, oxaddsum: 12.0, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 300, oxpriceto: 99999, oxactive: 1, oxarticles: [_testProduct], oxsort: 30 }\nexpected:\n    articles: { _testProduct: ['8,80', '272,80'] }\n    totals: { totalBrutto: '272,80', totalNetto: '229,24', vats: { 19: '43,56' }, grandTotal: '272,80' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case69.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 1 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '10,00'] }\n    totals: { totalBrutto: '11,90', totalNetto: '10,00', vats: { 19: '1,90' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '35,70' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case7.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87, oxvat: 17, amount: 1 }\nexpected:\n    articles: { 9200: ['59,16', '59,16'] }\n    totals: { totalBrutto: '59,16', totalNetto: '50,56', vats: { 17: '8,60' }, grandTotal: '59,16' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case70.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 50 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '500,00'] }\n    totals: { totalBrutto: '595,00', totalNetto: '500,00', vats: { 19: '95,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '618,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case71.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 50 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '500,00'] }\n    totals: { totalBrutto: '595,00', totalNetto: '500,00', vats: { 19: '95,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '618,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case72.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 200 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '2.000,00'] }\n    totals: { totalBrutto: '2.380,00', totalNetto: '2.000,00', vats: { 19: '380,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '2.403,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case73.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 200 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '2.000,00'] }\n    totals: { totalBrutto: '2.380,00', totalNetto: '2.000,00', vats: { 19: '380,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '2.403,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case74.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 250 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '2.500,00'] }\n    totals: { totalBrutto: '2.975,00', totalNetto: '2.500,00', vats: { 19: '475,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '2.998,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case75.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 250 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '2.500,00'] }\n    totals: { totalBrutto: '2.975,00', totalNetto: '2.500,00', vats: { 19: '475,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '2.998,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case76.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 250 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '2.500,00'] }\n    totals: { totalBrutto: '2.975,00', totalNetto: '2.500,00', vats: { 19: '475,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '2.998,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case77.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 510 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '5.100,00'] }\n    totals: { totalBrutto: '6.069,00', totalNetto: '5.100,00', vats: { 19: '969,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '6.092,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case78.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 510 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '5.100,00'] }\n    totals: { totalBrutto: '6.069,00', totalNetto: '5.100,00', vats: { 19: '969,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '6.092,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case79.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 1000 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '10.000,00'] }\n    totals: { totalBrutto: '11.900,00', totalNetto: '10.000,00', vats: { 19: '1.900,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '11.923,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case8.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87, oxvat: 17, amount: 1 }\nexpected:\n    articles: { 9200: ['127,89', '127,89'] }\n    totals: { totalBrutto: '127,89', totalNetto: '109,31', vats: { 17: '18,58' }, grandTotal: '127,89' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1.47\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case80.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 30.0, oxvat: 25, amount: 15 }\n    - { oxid: 1002, oxprice: 100.0, oxvat: 20, amount: 15 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['30,00', '450,00'], 1002: ['100,00', '1.500,00'] }\n    totals: { totalBrutto: '2.362,50', totalNetto: '1.950,00', vats: { 25: '112,50', 20: '300,00' }, delivery: { brutto: '1.299,38', netto: '1.072,50', vat: '226,88' }, payment: { brutto: '1.299,38', netto: '1.072,50', vat: '226,88' }, grandTotal: '4.961,26' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case81.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 30.0, oxvat: 25, amount: 15 }\n    - { oxid: 1002, oxprice: 100.0, oxvat: 20, amount: 15 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['30,00', '450,00'], 1002: ['100,00', '1.500,00'] }\n    totals: { totalBrutto: '2.362,50', totalNetto: '1.950,00', vats: { 25: '112,50', 20: '300,00' }, delivery: { brutto: '1.299,38', netto: '1.072,50', vat: '226,88' }, payment: { brutto: '1.299,38', netto: '1.072,50', vat: '226,88' }, grandTotal: '4.961,26' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case82.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 30.0, oxvat: 25, amount: 15 }\n    - { oxid: 1002, oxprice: 100.0, oxvat: 20, amount: 15 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['30,00', '450,00'], 1002: ['100,00', '1.500,00'] }\n    totals: { totalBrutto: '2.126,25', totalNetto: '1.950,00', vats: { 25: '101,25', 20: '270,00' }, delivery: { brutto: '1.299,38', netto: '1.072,50', vat: '226,88' }, payment: { brutto: '1.299,38', netto: '1.072,50', vat: '226,88' }, voucher: { brutto: '195,00' }, grandTotal: '4.725,01' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case83.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 30.0, oxvat: 25, amount: 15 }\n    - { oxid: 1002, oxprice: 100.0, oxvat: 20, amount: 15 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['20,40', '306,00'], 1002: ['68,00', '1.020,00'] }\n    totals: { totalBrutto: '1.445,85', totalNetto: '1.326,00', vats: { 25: '68,85', 20: '183,60' }, delivery: { brutto: '883,58', netto: '729,30', vat: '154,28' }, payment: { brutto: '883,58', netto: '729,30', vat: '154,28' }, voucher: { brutto: '132,60' }, grandTotal: '3.213,01' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case84.yaml",
    "content": "articles:\n    - { oxid: rounding_issue_test_article, oxprice: 298.55, oxvat: 19, amount: 200 }\ndiscounts:\n    - { oxid: discount_2_55_forShop, oxaddsum: 2.55, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { rounding_issue_test_article: ['298,55', '59.710,00'] }\n    totals: { totalBrutto: '59.710,00', discounts: { discount_2_55_forShop: '1.522,61' }, totalNetto: '48.896,97', vats: { 19: '9.290,42' }, grandTotal: '58.187,39' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case85.yaml",
    "content": "articles:\n    - { oxid: rounding_issue_test_article, oxprice: 298.55, oxvat: 19, amount: 200 }\ndiscounts:\n    - { oxid: discount_2_55_forBasket, oxaddsum: 2.55, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [rounding_issue_test_article], oxsort: 10 }\nexpected:\n    articles: { rounding_issue_test_article: ['290,94', '58.188,00'] }\n    totals: { totalBrutto: '58.188,00', totalNetto: '48.897,48', vats: { 19: '9.290,52' }, grandTotal: '58.188,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case86.yaml",
    "content": "articles:\n    - { oxid: rounding_issue_test_article, oxprice: 298.55, oxvat: 19, amount: 200 }\ndiscounts:\n    - { oxid: discount_2_55_forShop, oxaddsum: 2.55, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [rounding_issue_test_article], oxsort: 10 }\nexpected:\n    articles: { rounding_issue_test_article: ['290,94', '58.188,00'] }\n    totals: { totalBrutto: '58.188,00', totalNetto: '48.897,48', vats: { 19: '9.290,52' }, grandTotal: '58.188,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case87.yaml",
    "content": "articles:\n    - { oxid: rounding_issue_test_article, oxprice: 298.55, oxvat: 19, amount: 200 }\ndiscounts:\n    - { oxid: discount_2_55_forShop, oxaddsum: 2.55, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { rounding_issue_test_article: ['290,94', '58.188,00'] }\n    totals: { totalBrutto: '58.188,00', totalNetto: '48.897,48', vats: { 19: '9.290,52' }, grandTotal: '58.188,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case88.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 1.0, oxvat: 20, amount: 1 }\n    - { oxid: 1111, oxprice: 95.02, oxvat: 20, amount: 6 }\n    - { oxid: 1112, oxprice: 105.78, oxvat: 20, amount: 7 }\ndiscounts:\n    - { oxid: procdiscountforbasket, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['1,00', '1,00'], 1111: ['95,02', '570,12'], 1112: ['105,78', '740,46'] }\n    totals: { totalBrutto: '1.416,50', totalNetto: '1.311,58', vats: { 20: '236,08' }, discounts: { procdiscountforbasket: '131,16' }, delivery: { brutto: '786,95', netto: '655,79', vat: '131,16' }, payment: { brutto: '1,20', netto: '1,00', vat: '0,20' }, grandTotal: '2.204,65' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case89.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 19, amount: 1 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 5.02, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: procdiscountforbasket, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountfor1113, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114], oxsort: 30 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['5,52', '5,52'], 1113: ['945,95', '945,95'], 1114: ['4,74', '4,74'] }\n    totals: { totalBrutto: '1.024,69', totalNetto: '956,76', vats: { 19: '163,61' }, discounts: { procdiscountforbasket: '95,68' }, delivery: { brutto: '569,27', netto: '478,38', vat: '90,89' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '1.653,46' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case9.yaml",
    "content": "articles:\n    - { oxid: 9207, oxprice: 45.5, oxvat: 19, amount: 1 }\nexpected:\n    articles: { 9207: ['45,50', '45,50'] }\n    totals: { totalBrutto: '45,50', totalNetto: '38,24', vats: { 19: '7,26' }, grandTotal: '45,50' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case90.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 3 }\n    - { oxid: 1112, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1113, oxprice: 0.9, oxvat: 19, amount: 3 }\n    - { oxid: 1114, oxprice: 5.02, oxvat: 55, amount: 1 }\n    - { oxid: 1115, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1114], oxsort: 20 }\n    - { oxid: discountforbasket1112, oxaddsum: -5.2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1112], oxsort: 30 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '1,65'], 1112: ['105,78', '105,78'], 1113: ['0,90', '2,70'], 1114: ['5,52', '5,52'], 1115: ['0,50', '0,50'] }\n    totals: { totalBrutto: '126,18', totalNetto: '116,15', vats: { 19: '18,92', 55: '2,73' }, discounts: { discountforbasket10%: '11,62' }, delivery: { brutto: '69,12', netto: '58,08', vat: '11,04' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '254,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blPaymentVatOnTop: true, blDeliveryVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case91.yaml",
    "content": "articles:\n    - { oxid: _test1001, oxprice: 1002.55, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2 }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6 }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6 }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6 }\ndiscounts:\n    - { oxid: tenpercentdiscount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { _test1001: ['842,48', '1.684,96'], 1002: ['10,23', '20,46'], 1003: ['1.288,24', '7.729,44'], 1004: ['5,69', '34,14'], 1005: ['0,50', '3,00'] }\n    totals: { totalBrutto: '9.030,12', totalNetto: '9.472,00', vats: { 19: '288,13', 13: '2,39', 3: '208,69', 17: '5,22', 33: '0,89' }, discounts: { tenpercentdiscount: '947,20' }, grandTotal: '9.030,12' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case92.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 19, amount: 1 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 5.02, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountfor1113, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114], oxsort: 30 }\n    - { oxid: discountforbasket20%, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 40 }\n    - { oxid: discountforbasket35%, oxaddsum: 35, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['5,52', '5,52'], 1113: ['945,95', '945,95'], 1114: ['4,74', '4,74'] }\n    totals: { totalBrutto: '532,84', totalNetto: '956,76', vats: { 19: '85,08' }, discounts: { discountforbasket10%: '95,68', discountforbasket20%: '172,22', discountforbasket35%: '241,10' }, delivery: { brutto: '569,27', netto: '478,38', vat: '90,89' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '1.161,61' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case93.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 55, amount: 1 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 5.02, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountforproduct1, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountforproduct2, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114], oxsort: 30 }\n    - { oxid: discountforbasket20%, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 40 }\n    - { oxid: discountforbasket35%, oxaddsum: 35, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['5,52', '5,52'], 1113: ['945,95', '945,95'], 1114: ['4,74', '4,74'] }\n    totals: { totalBrutto: '533,76', totalNetto: '956,76', vats: { 19: '84,58', 55: '1,42' }, discounts: { discountforbasket10%: '95,68', discountforbasket20%: '172,22', discountforbasket35%: '241,10' }, delivery: { brutto: '569,27', netto: '478,38', vat: '90,89' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '1.162,53' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case94.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 55, amount: 1 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1115, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1116, oxprice: 1.0, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountfor1113, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114], oxsort: 30 }\n    - { oxid: discountforbasket20%, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 40 }\n    - { oxid: discountforbasket35%, oxaddsum: 35, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\n    - { oxid: discountforbasket1115, oxaddsum: -5.2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1115], oxsort: 60 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['5,52', '5,52'], 1113: ['945,95', '945,95'], 1114: ['95,02', '95,02'], 1115: ['105,78', '105,78'], 1116: ['1,00', '1,00'] }\n    totals: { totalBrutto: '643,52', totalNetto: '1.153,82', vats: { 19: '102,11', 55: '1,42' }, discounts: { discountforbasket10%: '115,38', discountforbasket20%: '207,69', discountforbasket35%: '290,76' }, delivery: { brutto: '686,52', netto: '576,91', vat: '109,61' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '1.389,54' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case95.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 55, amount: 5 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1115, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1116, oxprice: 1.0, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountfor1113, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114], oxsort: 30 }\n    - { oxid: discountforbasket20%, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 40 }\n    - { oxid: discountforbasket35%, oxaddsum: 35, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\n    - { oxid: discountforbasket1115, oxaddsum: -5.2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1115], oxsort: 60 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['5,52', '27,60'], 1113: ['945,95', '945,95'], 1114: ['95,02', '95,02'], 1115: ['105,78', '105,78'], 1116: ['1,00', '1,00'] }\n    totals: { totalBrutto: '659,53', totalNetto: '1.175,90', vats: { 19: '102,11', 55: '7,10' }, discounts: { discountforbasket10%: '117,59', discountforbasket20%: '211,66', discountforbasket35%: '296,33' }, delivery: { brutto: '699,66', netto: '587,95', vat: '111,71' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '1.418,69' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case96.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 55, amount: 5 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1115, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1116, oxprice: 1.0, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountfor1113, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114], oxsort: 30 }\n    - { oxid: discountforbasket1115, oxaddsum: -5.2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1115], oxsort: 40 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['5,52', '27,60'], 1113: ['945,95', '945,95'], 1114: ['95,02', '95,02'], 1115: ['105,78', '105,78'], 1116: ['1,00', '1,00'] }\n    totals: { totalBrutto: '1.268,33', totalNetto: '1.175,90', vats: { 19: '196,36', 55: '13,66' }, discounts: { discountforbasket10%: '117,59' }, delivery: { brutto: '699,66', netto: '587,95', vat: '111,71' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '2.027,49' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case97.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 19, amount: 1 }\n    - { oxid: 1113, oxprice: 1, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 1001, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112, 1114], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 0.55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,50', '0,50'], 1112: ['4,97', '4,97'], 1113: ['0,90', '0,90'], 1114: ['990,99', '990,99'] }\n    totals: { totalBrutto: '1.186,86', totalNetto: '997,36', vats: { 19: '189,50' }, delivery: { brutto: '652,77', netto: '548,55', vat: '104,22' }, payment: { brutto: '0,65', netto: '0,55', vat: '0,10' }, grandTotal: '1.840,28' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case98.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 99, amount: 3 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 99, amount: 2 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 99, amount: 1 }\n    - { oxid: 1114, oxprice: 5.02, oxvat: 99, amount: 1 }\n    - { oxid: 1115, oxprice: 100.55, oxvat: 99, amount: 1 }\n    - { oxid: 1116, oxprice: 5, oxvat: 9, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountfor1113, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114, 1115], oxsort: 30 }\n    - { oxid: procdiscount20%, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 40 }\n    - { oxid: procdiscount35%, oxaddsum: 35, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 0.55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '1,65'], 1112: ['5,52', '11,04'], 1113: ['945,95', '945,95'], 1114: ['4,74', '4,74'], 1115: ['95,02', '95,02'], 1116: ['5,00', '5,00'] }\n    totals: { totalBrutto: '988,26', totalNetto: '1.063,40', vats: { 99: '490,38', 9: '0,21' }, discounts: { discountforbasket10%: '106,34', procdiscount20%: '191,41', procdiscount35%: '267,98' }, delivery: { brutto: '1.163,89', netto: '584,87', vat: '579,02' }, payment: { brutto: '1,09', netto: '0,55', vat: '0,54' }, grandTotal: '2.153,24' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/case99.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.55, oxvat: 33, amount: 1 }\n    - { oxid: 1112, oxprice: 1101.1, oxvat: 33, amount: 1 }\n    - { oxid: 1113, oxprice: 110, oxvat: 33, amount: 1 }\n    - { oxid: 1114, oxprice: 1.0, oxvat: 33, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['1.101,10', '1.101,10'], 1113: ['110,00', '110,00'], 1114: ['1,00', '1,00'] }\n    totals: { totalBrutto: '1.451,54', totalNetto: '1.212,65', vats: { 33: '360,16' }, discounts: { discountforbasket10%: '121,27' }, delivery: { brutto: '887,06', netto: '666,96', vat: '220,10' }, payment: { brutto: '73,15', netto: '55,00', vat: '18,15' }, grandTotal: '2.411,75' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testDeliveryBug4622(1).yaml",
    "content": "articles:\n    - { oxid: vine1, oxprice: 100, oxvat: 19, amount: 1 }\n    - { oxid: coupon, oxprice: 50, oxvat: 19, amount: 2, oxnonmaterial: true }\ncosts:\n    delivery: [{ oxtitle: '10% from total ', oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 1000, oxsort: 1 }]\nexpected:\n    articles: { vine1: ['100,00', '100,00'], coupon: ['50,00', '100,00'] }\n    totals: { totalBrutto: '200,00', totalNetto: '168,07', vats: { 19: '31,93' }, delivery: { brutto: '10,00' }, grandTotal: '210,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net, blExclNonMaterialFromDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testDeliveryBug4622(2).yaml",
    "content": "articles:\n    - { oxid: vine1, oxprice: 100, oxvat: 19, amount: 1 }\n    - { oxid: coupon, oxprice: 50, oxvat: 19, amount: 2, oxnonmaterial: true }\ncosts:\n    delivery: [{ oxtitle: '10% from total ', oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 1000, oxsort: 1 }]\nexpected:\n    articles: { vine1: ['100,00', '100,00'], coupon: ['50,00', '100,00'] }\n    totals: { totalBrutto: '200,00', totalNetto: '168,07', vats: { 19: '31,93' }, delivery: { brutto: '20,00' }, grandTotal: '220,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net, blExclNonMaterialFromDelivery: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testDeliveryBug4622(3).yaml",
    "content": "articles:\n    - { oxid: coupon, oxprice: 50, oxvat: 19, amount: 2, oxnonmaterial: true }\ncosts:\n    delivery: [{ oxtitle: '10% from total ', oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 1000, oxsort: 1 }]\nexpected:\n    articles: { coupon: ['50,00', '100,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '84,03', vats: { 19: '15,97' }, delivery: { brutto: '10,00' }, grandTotal: '110,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net, blExclNonMaterialFromDelivery: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testDeliveryBug4622(4).yaml",
    "content": "articles:\n    - { oxid: coupon, oxprice: 50, oxvat: 19, amount: 2, oxnonmaterial: true }\ncosts:\n    delivery: [{ oxtitle: '10% from total ', oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 1000, oxsort: 1 }]\nexpected:\n    articles: { coupon: ['50,00', '100,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '84,03', vats: { 19: '15,97' }, delivery: { brutto: '0,00' }, grandTotal: '100,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net, blExclNonMaterialFromDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testDeliveryBug4730(1).yaml",
    "content": "categories:\n    - { oxid: vine, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [vine1] }\n    - { oxid: supplies, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [supply1] }\narticles:\n    - { oxid: vine1, oxprice: 5, oxvat: 10, amount: 6 }\n    - { oxid: supply1, oxprice: 10, oxvat: 10, amount: 1 }\ncosts:\n    delivery: [\n        { oxtitle: '1 - 3 Bottles', oxactive: 1, oxaddsum: 4.9, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 1, oxparamend: 3, oxsort: 1, oxcategories: [vine] },\n        { oxtitle: '4 - 11 Bottles', oxactive: 1, oxaddsum: 5.9, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 4, oxparamend: 11, oxsort: 1, oxcategories: [vine] },\n        { oxtitle: 'more than 12 Bottles', oxactive: 1, oxaddsum: 0, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 12, oxparamend: 99999, oxsort: 1, oxcategories: [vine] },\n        { oxtitle: supplies, oxactive: 1, oxaddsum: 2.9, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 0, oxparamend: 99999, oxsort: 4, oxcategories: [supplies] }\n    ]\nexpected:\n    articles: { vine1: ['5,00', '30,00'], supply1: ['10,00', '10,00'] }\n    totals: { totalBrutto: '40,00', totalNetto: '36,36', vats: { 10: '3,64' }, delivery: { brutto: '5,90' }, grandTotal: '45,90' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testDeliveryBug4730(2).yaml",
    "content": "categories:\n    - { oxid: vine, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [vine1] }\n    - { oxid: supplies, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [supply1] }\narticles:\n    - { oxid: vine1, oxprice: 5, oxvat: 10, amount: 2 }\n    - { oxid: supply1, oxprice: 10, oxvat: 10, amount: 1 }\ncosts:\n    delivery: [\n        { oxtitle: '1 - 3 Bottles', oxactive: 1, oxaddsum: 4.9, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 1, oxparamend: 3, oxsort: 1, oxcategories: [vine] },\n        { oxtitle: '4 - 11 Bottles', oxactive: 1, oxaddsum: 5.9, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 4, oxparamend: 11, oxsort: 1, oxcategories: [vine] },\n        { oxtitle: 'more than 12 Bottles', oxactive: 1, oxaddsum: 0, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 12, oxparamend: 99999, oxsort: 1, oxcategories: [vine] },\n        { oxtitle: supplies, oxactive: 1, oxaddsum: 2.9, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 0, oxparamend: 99999, oxsort: 4, oxcategories: [supplies] }\n    ]\nexpected:\n    articles: { vine1: ['5,00', '10,00'], supply1: ['10,00', '10,00'] }\n    totals: { totalBrutto: '20,00', totalNetto: '18,18', vats: { 10: '1,82' }, delivery: { brutto: '4,90' }, grandTotal: '24,90' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testDeliveryByWeight(1).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 1.8, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 2 }\n    - { oxid: 10012, oxprice: 2.0, oxvat: 19, amount: 1, oxweight: 2 }\n    - { oxid: 10013, oxprice: 1.7, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 15.0, oxfinalize: 0, oxparamend: 999, oxfixed: 2, oxsort: 4 }, { oxactive: 1, oxaddsum: 1.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 1.0, oxfinalize: 0, oxparamend: 4.99999999, oxfixed: 2, oxsort: 1 }, { oxactive: 1, oxaddsum: 5.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 5.0, oxfinalize: 0, oxparamend: 14.9999999, oxfixed: 2, oxsort: 2 }]\nexpected:\n    articles: { 10011: ['1,80', '1,80'], 10012: ['2,00', '2,00'], 10013: ['1,70', '1,70'] }\n    totals: { totalBrutto: '5,50', totalNetto: '4,62', vats: { 19: '0,88' }, delivery: { brutto: '12,00' }, grandTotal: '17,50' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testDeliveryByWeight(2).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 1.8, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 2 }\n    - { oxid: 10012, oxprice: 2.0, oxvat: 19, amount: 3, oxweight: 2 }\n    - { oxid: 10013, oxprice: 1.7, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 15.0, oxfinalize: 0, oxparamend: 999, oxfixed: 2, oxsort: 4 }, { oxactive: 1, oxaddsum: 1.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 1.0, oxfinalize: 0, oxparamend: 4.99999999, oxfixed: 2, oxsort: 1 }, { oxactive: 1, oxaddsum: 5.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 5.0, oxfinalize: 0, oxparamend: 14.9999999, oxfixed: 2, oxsort: 2 }]\nexpected:\n    articles: { 10011: ['1,80', '1,80'], 10012: ['2,00', '6,00'], 10013: ['1,70', '1,70'] }\n    totals: { totalBrutto: '9,50', totalNetto: '7,98', vats: { 19: '1,52' }, delivery: { brutto: '14,00' }, grandTotal: '23,50' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testDeliveryByWeight(3).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 1.8, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 2 }\n    - { oxid: 10012, oxprice: 2.0, oxvat: 19, amount: 3, oxweight: 2 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 15.0, oxfinalize: 0, oxparamend: 999, oxfixed: 2, oxsort: 4 }, { oxactive: 1, oxaddsum: 1.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 1.0, oxfinalize: 0, oxparamend: 4.99999999, oxfixed: 2, oxsort: 1 }, { oxactive: 1, oxaddsum: 5.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 5.0, oxfinalize: 0, oxparamend: 14.9999999, oxfixed: 2, oxsort: 2 }]\nexpected:\n    articles: { 10011: ['1,80', '1,80'], 10012: ['2,00', '6,00'] }\n    totals: { totalBrutto: '7,80', totalNetto: '6,55', vats: { 19: '1,25' }, delivery: { brutto: '4,00' }, grandTotal: '11,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testDeliveryByWeight(4).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 1.8, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 2 }\n    - { oxid: 10012, oxprice: 2.0, oxvat: 19, amount: 1, oxweight: 2 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 15.0, oxfinalize: 0, oxparamend: 999, oxfixed: 2, oxsort: 4 }, { oxactive: 1, oxaddsum: 1.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 1.0, oxfinalize: 0, oxparamend: 4.99999999, oxfixed: 2, oxsort: 1 }, { oxactive: 1, oxaddsum: 5.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 5.0, oxfinalize: 0, oxparamend: 14.9999999, oxfixed: 2, oxsort: 2 }]\nexpected:\n    articles: { 10011: ['1,80', '1,80'], 10012: ['2,00', '2,00'] }\n    totals: { totalBrutto: '3,80', totalNetto: '3,19', vats: { 19: '0,61' }, delivery: { brutto: '2,00' }, grandTotal: '5,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testDeliveryByWeight(5).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 1.8, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 2 }\n    - { oxid: 10012, oxprice: 2.0, oxvat: 19, amount: 1, oxweight: 3 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 15.0, oxfinalize: 0, oxparamend: 999, oxfixed: 0, oxsort: 4 }, { oxactive: 1, oxaddsum: 1.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 1.0, oxfinalize: 0, oxparamend: 4.99999999, oxfixed: 0, oxsort: 1 }, { oxactive: 1, oxaddsum: 5.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 5.0, oxfinalize: 0, oxparamend: 14.9999999, oxfixed: 0, oxsort: 2 }]\nexpected:\n    articles: { 10011: ['1,80', '1,80'], 10012: ['2,00', '2,00'] }\n    totals: { totalBrutto: '3,80', totalNetto: '3,19', vats: { 19: '0,61' }, delivery: { brutto: '5,00' }, grandTotal: '8,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testDeliveryByWeight(6).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 1.8, oxvat: 19, amount: 2, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 2 }\n    - { oxid: 10012, oxprice: 2.0, oxvat: 19, amount: 2, oxweight: 3 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 15.0, oxfinalize: 0, oxparamend: 999, oxfixed: 1, oxsort: 4 }, { oxactive: 1, oxaddsum: 1.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 1.0, oxfinalize: 0, oxparamend: 4.99999999, oxfixed: 1, oxsort: 1 }, { oxactive: 1, oxaddsum: 5.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 5.0, oxfinalize: 0, oxparamend: 14.9999999, oxfixed: 1, oxsort: 2 }]\nexpected:\n    articles: { 10011: ['1,80', '3,60'], 10012: ['2,00', '4,00'] }\n    totals: { totalBrutto: '7,60', totalNetto: '6,39', vats: { 19: '1,21' }, delivery: { brutto: '6,00' }, grandTotal: '13,60' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testDeliveryLowerShippingCost(1).yaml",
    "content": "categories:\n    - { oxid: books, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [book] }\n    - { oxid: otherStuff, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [stuff] }\narticles:\n    - { oxid: stuff, oxprice: 20, oxvat: 20, amount: 6 }\n    - { oxid: book, oxprice: 10, oxvat: 10, amount: 1 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 2, oxaddsumtype: abs, oxdeltype: a, oxparam: 0, oxparamend: 99999, oxsort: 1 }, { oxactive: 1, oxaddsum: 3, oxaddsumtype: abs, oxdeltype: a, oxparam: 0, oxparamend: 99999, oxsort: 2, oxcategories: [otherStuff] }]\nexpected:\n    articles: { book: ['10,00', '10,00'], stuff: ['20,00', '120,00'] }\n    totals: { totalBrutto: '130,00', totalNetto: '109,09', vats: { 10: '0,91', 20: '20,00' }, delivery: { brutto: '5,00' }, grandTotal: '135,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testDeliveryLowerShippingCost(2).yaml",
    "content": "categories:\n    - { oxid: books, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [book] }\n    - { oxid: otherStuff, oxparentid: oxrootid, oxshopid: 1, oxactive: 1 }\narticles:\n    - { oxid: book, oxprice: 10, oxvat: 10, amount: 1 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 2, oxaddsumtype: abs, oxdeltype: a, oxparam: 0, oxparamend: 99999, oxsort: 1 }, { oxactive: 1, oxaddsum: 3, oxaddsumtype: abs, oxdeltype: a, oxparam: 0, oxparamend: 99999, oxsort: 2, oxcategories: [otherStuff] }]\nexpected:\n    articles: { book: ['10,00', '10,00'] }\n    totals: { totalBrutto: '10,00', totalNetto: '9,09', vats: { 10: '0,91' }, delivery: { brutto: '2,00' }, grandTotal: '12,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testDeliveryLowerShippingCost(3).yaml",
    "content": "categories:\n    - { oxid: books, oxparentid: oxrootid, oxshopid: 1, oxactive: 1 }\n    - { oxid: otherStuff, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [stuff] }\narticles:\n    - { oxid: stuff, oxprice: 20, oxvat: 20, amount: 6 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 2, oxaddsumtype: abs, oxdeltype: a, oxparam: 0, oxparamend: 99999, oxsort: 1 }, { oxactive: 1, oxaddsum: 3, oxaddsumtype: abs, oxdeltype: a, oxparam: 0, oxparamend: 99999, oxsort: 2, oxcategories: [otherStuff] }]\nexpected:\n    articles: { stuff: ['20,00', '120,00'] }\n    totals: { totalBrutto: '120,00', totalNetto: '100,00', vats: { 20: '20,00' }, delivery: { brutto: '5,00' }, grandTotal: '125,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testDiscountBug5913.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 50.0, oxstock: 100, oxvat: 19, oxartnum: '1000', amount: 2 }\n    - { oxid: 1003, oxprice: 5.0, oxstock: 1, oxvat: 19, oxstockflag: 2, oxartnum: '1003' }\ndiscounts:\n    - { oxid: testitem_discount, oxshopid: 1, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 0, oxactive: 1, oxitmartid: 1003, oxitmamount: 1, oxitmmultiple: 1, oxarticles: [1000], oxsort: 10 }\nexpected:\n    articles: { 1000: ['50,00', '100,00'], 1003: ['0,00', '0,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '84,03', vats: { 19: '15,97' }, grandTotal: '100,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testDiscountSorting.yaml",
    "content": "articles:\n    - { oxid: 10005, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount5for10005, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 70 }\n    - { oxid: shopdiscount5for1004, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 20 }\n    - { oxid: basketdiscount5for10005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 60 }\n    - { oxid: basketdiscount5for1004, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 30 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor10005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 40 }\n    - { oxid: procdiscountfor1004, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap102, oxprice: 9, oxactive: 1, oxarticles: [10005] }, { oxtype: WRAP, oxname: testWrap1002, oxprice: 6, oxactive: 1, oxarticles: [1004] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 10005: ['1.115,95', '1.115,95'], 1004: ['0,59', '0,59'] }\n    totals: { totalBrutto: '1.116,54', totalNetto: '929,03', vats: { 19: '176,51' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '15,00', netto: '12,60', vat: '2,40' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '1.127,54' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testDiscountSortingDBRMIndependency.yaml",
    "content": "articles:\n    - { oxid: 10005, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: shopdiscount5for1004, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 20 }\n    - { oxid: basketdiscount5for1004, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 30 }\n    - { oxid: procdiscountfor10005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 40 }\n    - { oxid: procdiscountfor1004, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 50 }\n    - { oxid: basketdiscount5for10005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 60 }\n    - { oxid: shopdiscount5for10005, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 70 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap102, oxprice: 9, oxactive: 1, oxarticles: [10005] }, { oxtype: WRAP, oxname: testWrap1002, oxprice: 6, oxactive: 1, oxarticles: [1004] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 10005: ['1.115,95', '1.115,95'], 1004: ['0,59', '0,59'] }\n    totals: { totalBrutto: '1.116,54', totalNetto: '929,03', vats: { 19: '176,51' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '15,00', netto: '12,60', vat: '2,40' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '1.127,54' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFewItmDiscounts.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1 }\n    - { oxid: 1003, oxprice: 50.0, oxvat: 5 }\n    - { oxid: 1002, oxprice: 50.0, oxvat: 5 }\ndiscounts:\n    - { oxid: testitem_discount, oxshopid: 1, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 0, oxactive: 1, oxitmartid: 1003, oxitmamount: 1, oxitmmultiple: 0, oxarticles: [1000], oxsort: 10 }\n    - { oxid: testitem_discounts, oxshopid: 1, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 0, oxactive: 1, oxitmartid: 1002, oxitmamount: 1, oxitmmultiple: 0, oxarticles: [1000], oxsort: 20 }\nexpected:\n    articles: { 1000: ['50,00', '50,00'], 1003: ['0,00', '0,00'], 1002: ['0,00', '0,00'] }\n    totals: { totalBrutto: '50,00', totalNetto: '47,62', vats: { 5: '2,38' }, grandTotal: '50,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendItmDiscounts.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 5 }\n    - { oxid: 1003, oxprice: 50.0, oxvat: 5 }\ndiscounts:\n    - { oxid: testitem_discount, oxshopid: 1, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 0, oxactive: 1, oxitmartid: 1003, oxitmamount: 1, oxitmmultiple: 0, oxarticles: [1000], oxsort: 10 }\n    - { oxid: testdiscountfrom200, oxshopid: 1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 200, oxpriceto: 999999, oxactive: 1, oxsort: 20 }\nexpected:\n    articles: { 1000: ['50,00', '250,00'], 1003: ['0,00', '0,00'] }\n    totals: { totalBrutto: '250,00', totalNetto: '214,29', vats: { 5: '10,71' }, discounts: { testdiscountfrom200: '25,00' }, grandTotal: '225,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendNettoPrices.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 3 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: 'Test wrapping [EN] ðÄßü?', oxprice: 0.9, oxactive: 1, oxarticles: [1000] }, { oxtype: CARD, oxname: 'Test card [EN] ðÄßü', oxprice: 0.2, oxactive: 1 }]\nexpected:\n    articles: { 1000: ['52,50', '157,50'] }\n    totals: { totalBrutto: '157,50', totalNetto: '150,00', vats: { 5: '7,50' }, wrapping: { brutto: '2,84' }, giftcard: { brutto: '0,21' }, grandTotal: '160,55' }\noptions:\n    config: { blShowNetPrice: false, blEnterNetPrice: true, blWrappingVatOnTop: true, blDeliveryVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendOrderStep1Calculation2(1).yaml",
    "content": "articles:\n    - { oxid: 10012, oxprice: 98, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1002, oxprice: 67.0, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 75.0, oxvat: 19, amount: 6, oxpricea: 70, oxpriceb: 85, oxpricec: 0, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1, oxpricea: 35, oxpriceb: 45, oxpricec: 55, oxunitname: kg, oxunitquantity: 2, oxweight: 2 }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10012, 1000], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 1.5, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, oxminimumvalue: 75, voucher_count: 1 }]\nexpected:\n    articles: { 10012: ['93,00', '93,00'], 1002: ['60,30', '60,30'], 1003: ['54,00', '324,00'], 1000: ['45,00', '45,00'] }\n    totals: { totalBrutto: '522,30', totalNetto: '441,73', vats: { 10: '8,29', 19: '60,18', 5: '2,10' }, delivery: { brutto: '1,50' }, voucher: { brutto: '10,00' }, grandTotal: '513,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendOrderStep1Calculation2(2).yaml",
    "content": "articles:\n    - { oxid: 10013, oxprice: 100, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1002, oxprice: 67.0, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 75.0, oxvat: 19, amount: 6, oxpricea: 70, oxpriceb: 85, oxpricec: 0, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1, oxpricea: 35, oxpriceb: 45, oxpricec: 55, oxunitname: kg, oxunitquantity: 2, oxweight: 2 }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10013, 1000], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 1.5, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, oxminimumvalue: 75, voucher_count: 1 }]\nexpected:\n    articles: { 10013: ['95,00', '95,00'], 1002: ['60,30', '60,30'], 1003: ['54,00', '324,00'], 1000: ['45,00', '45,00'] }\n    totals: { totalBrutto: '524,30', totalNetto: '443,54', vats: { 10: '8,47', 19: '60,19', 5: '2,10' }, delivery: { brutto: '1,50' }, voucher: { brutto: '10,00' }, grandTotal: '515,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendOrderStep1Calculation2(3).yaml",
    "content": "articles:\n    - { oxid: 10014, oxprice: 102, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1002, oxprice: 67.0, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 75.0, oxvat: 19, amount: 6, oxpricea: 70, oxpriceb: 85, oxpricec: 0, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1, oxpricea: 35, oxpriceb: 45, oxpricec: 55, oxunitname: kg, oxunitquantity: 2, oxweight: 2 }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10014, 1000], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 1.5, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, oxminimumvalue: 75, voucher_count: 1 }]\nexpected:\n    articles: { 10014: ['97,00', '97,00'], 1002: ['60,30', '60,30'], 1003: ['54,00', '324,00'], 1000: ['45,00', '45,00'] }\n    totals: { totalBrutto: '526,30', totalNetto: '445,36', vats: { 10: '8,65', 19: '60,19', 5: '2,10' }, delivery: { brutto: '1,50' }, voucher: { brutto: '10,00' }, grandTotal: '517,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendOrderStep1Calculation2(4).yaml",
    "content": "articles:\n    - { oxid: 10015, oxprice: 101, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1002, oxprice: 67.0, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 75.0, oxvat: 19, amount: 6, oxpricea: 70, oxpriceb: 85, oxpricec: 0, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1, oxpricea: 35, oxpriceb: 45, oxpricec: 55, oxunitname: kg, oxunitquantity: 2, oxweight: 2 }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10015, 1000], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 1.5, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, oxminimumvalue: 75, voucher_count: 1 }]\nexpected:\n    articles: { 10015: ['96,00', '96,00'], 1002: ['60,30', '60,30'], 1003: ['54,00', '324,00'], 1000: ['45,00', '45,00'] }\n    totals: { totalBrutto: '525,30', totalNetto: '444,45', vats: { 10: '8,56', 19: '60,19', 5: '2,10' }, delivery: { brutto: '1,50' }, voucher: { brutto: '10,00' }, grandTotal: '516,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendOrderStep1Calculation2(5).yaml",
    "content": "articles:\n    - { oxid: 10016, oxprice: 101, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1002, oxprice: 67.0, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10016, 1000], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 1.5, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    voucherserie: [{ oxdiscount: 5.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, oxminimumvalue: 75, voucher_count: 1 }]\nexpected:\n    articles: { 10016: ['96,00', '96,00'], 1002: ['67,00', '67,00'] }\n    totals: { totalBrutto: '163,00', totalNetto: '136,40', vats: { 10: '8,29', 19: '10,16' }, delivery: { brutto: '1,50' }, voucher: { brutto: '8,15' }, grandTotal: '156,35' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendOrderStep1CalculationVoucher.yaml",
    "content": "articles:\n    - { oxid: 1245, oxprice: 98, oxvat: 10, amount: 1 }\n    - { oxid: 6565, oxprice: 67, oxvat: 19, amount: 1 }\n    - { oxid: 1553, oxprice: 60, oxvat: 19, amount: 6 }\n    - { oxid: 1224, oxprice: 50, oxvat: 5, amount: 1 }\ndiscounts:\n    - { oxid: product, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxactive: 1, oxarticles: [6565, 1553], oxsort: 10 }\n    - { oxid: prod2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxprice: 0, oxpriceto: 99999, oxactive: 1, oxarticles: [1224, 1245], oxsort: 20 }\ncosts:\n    voucherserie: [{ oxdiscount: 5, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 6565: ['60,30', '60,30'], 1553: ['54,00', '324,00'], 1224: ['45,00', '45,00'], 1245: ['93,00', '93,00'] }\n    totals: { totalBrutto: '522,30', totalNetto: '427,82', vats: { 10: '8,03', 19: '58,29', 5: '2,04' }, voucher: { brutto: '26,12' }, grandTotal: '496,18' }\n    options: { config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: false, blShowVATForDelivery: true }, activeCurrencyRate: 1.0 }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendOrderStep1DeliverySortedWithCategories(1).yaml",
    "content": "categories:\n    - { oxid: testCategory1, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [_test_10012] }\n    - { oxid: testCategory2, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [_test_1002] }\narticles:\n    - { oxid: _test_1002, oxprice: 20, oxvat: 20, amount: 6 }\n    - { oxid: _test_10012, oxprice: 10, oxvat: 10, amount: 1 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 0, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 5, oxparamend: 99999, oxsort: 1, oxcategories: [testCategory2] }, { oxactive: 1, oxaddsum: 2, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 0, oxparamend: 99999, oxsort: 2, oxcategories: [testCategory1] }]\nexpected:\n    articles: { _test_10012: ['10,00', '10,00'], _test_1002: ['20,00', '120,00'] }\n    totals: { totalBrutto: '130,00', totalNetto: '109,09', vats: { 10: '0,91', 20: '20,00' }, delivery: { brutto: '0,00' }, grandTotal: '130,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendOrderStep1DeliverySortedWithCategories(2).yaml",
    "content": "categories:\n    - { oxid: testCategory1, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [_test_10012] }\n    - { oxid: testCategory2, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [_test_1002] }\narticles:\n    - { oxid: _test_1002, oxprice: 20, oxvat: 20, amount: 6 }\n    - { oxid: _test_10012, oxprice: 10, oxvat: 10, amount: 1 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 0, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 5, oxparamend: 99999, oxsort: 2, oxcategories: [testCategory2] }, { oxactive: 1, oxaddsum: 2, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 0, oxparamend: 99999, oxsort: 1, oxcategories: [testCategory1] }]\nexpected:\n    articles: { _test_10012: ['10,00', '10,00'], _test_1002: ['20,00', '120,00'] }\n    totals: { totalBrutto: '130,00', totalNetto: '109,09', vats: { 10: '0,91', 20: '20,00' }, delivery: { brutto: '2,00' }, grandTotal: '132,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendOrdersFractionQuantities1.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 50, oxvat: 5, oxunitname: kg, oxunitquantity: 10, oxweight: 10, amount: 3.4 }\ndiscounts:\n    - { oxid: test, oxshopid: 1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 0, oxpriceto: 999999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 1000: ['45,00', '153,00'] }\n    totals: { totalBrutto: '153,00', totalNetto: '145,71', vats: { 5: '7,29' }, grandTotal: '153,00' }\noptions:\n    config: { blAllowUnevenAmounts: true, blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendOrdersFractionQuantities2.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 75, oxvat: 19, amount: 3.4 }\n    - { oxid: 1001, oxprice: 100, oxvat: 10, amount: 0.3 }\n    - { oxid: 1000, oxprice: 50, oxvat: 19, oxunitname: kg, oxunitquantity: 10, oxweight: 10, amount: 1.5 }\nexpected:\n    articles: { 1003: ['75,00', '225,00'], 1000: ['50,00', '100,00'] }\n    totals: { totalBrutto: '325,00', totalNetto: '273,11', vats: { 19: '51,89' }, grandTotal: '325,00' }\noptions:\n    config: { blAllowUnevenAmounts: false, blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendPriceA.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 70.0, oxpricea: 71, oxpriceb: 85, oxpricec: 0, amount: 1, oxvat: 19, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\nuser:\n    oxid: _testUserA\n    oxactive: 1\n    oxusername: groupAUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [1003, _testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    articles: { 1003: ['71,00', '71,00'] }\n    totals: { totalBrutto: '71,00', totalNetto: '59,66', vats: { 19: '11,34' }, grandTotal: '71,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendPriceA2.yaml",
    "content": "articles:\n    - { oxid: 1002, oxprice: 50.0, oxpricea: 35, oxpriceb: 45, oxpricec: 55, amount: 1, oxvat: 19, oxtitle: 'Wall Clock ROBOT', oxunitname: kg, oxunitquantity: 2, oxweight: 10 }\nuser:\n    oxid: _testUserA\n    oxactive: 1\n    oxusername: groupAUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [1002, _testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    articles: { 1002: ['35,00', '35,00'] }\n    totals: { totalBrutto: '35,00', totalNetto: '29,41', vats: { 19: '5,59' }, grandTotal: '35,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendPriceB.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 70.0, oxpricea: 70, oxpriceb: 85, oxpricec: 0, amount: 1, oxvat: 19, scaleprices: { oxaddabs: 75.0, oxamount: 2, oxamountto: 5, oxartid: 1003 } }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 19, amount: 1 }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [1003, _testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    articles: { 1003: ['85,00', '85,00'], 1112: ['5,02', '5,02'] }\n    totals: { totalBrutto: '90,02', totalNetto: '75,65', vats: { 19: '14,37' }, grandTotal: '90,02' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendPriceB2.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 70.0, oxpricea: 70, oxpriceb: 85, oxpricec: 0, amount: 7, oxvat: 19, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [1003, _testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    articles: { 1003: ['68,00', '476,00'] }\n    totals: { totalBrutto: '476,00', totalNetto: '400,00', vats: { 19: '76,00' }, grandTotal: '476,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendPriceB3.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 70.0, oxpricea: 70, oxpriceb: 85, oxpricec: 0, amount: 7, oxvat: 19, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [1003, _testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10013, 1000], oxsort: 20 }\nexpected:\n    articles: { 1003: ['61,20', '428,40'] }\n    totals: { totalBrutto: '428,40', totalNetto: '360,00', vats: { 19: '68,40' }, grandTotal: '428,40' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendPriceB4.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 70.0, oxpricea: 70, oxpriceb: 85, oxpricec: 0, amount: 1, oxvat: 19, scaleprices: { oxaddabs: 75.0, oxamount: 2, oxamountto: 5, oxartid: 1003 } }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 19, amount: 1 }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [1003, _testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    articles: { 1003: ['85,00', '85,00'], 1112: ['0,00', '0,00'] }\n    totals: { totalBrutto: '85,00', totalNetto: '71,43', vats: { 19: '13,57' }, grandTotal: '85,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: false, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendPriceC.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 75.0, oxpricea: 70, oxpriceb: 85, oxpricec: 0, amount: 3, oxvat: 19, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\nuser:\n    oxid: _testUserC\n    oxactive: 1\n    oxusername: groupCUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [1003, _testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10013, 1000], oxsort: 20 }\nexpected:\n    articles: { 1003: ['67,50', '202,50'] }\n    totals: { totalBrutto: '202,50', totalNetto: '170,17', vats: { 19: '32,33' }, grandTotal: '202,50' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendVatForBillingCountry(1).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 101, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1003, oxprice: 75.0, oxvat: 19, amount: 1, oxpricea: 70, oxpriceb: 85, oxpricec: 0 }\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1, oxpricea: 35, oxpriceb: 45, oxpricec: 55, oxunitname: kg, oxunitquantity: 2, oxweight: 2 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\n    oxcountryid: a7c40f631fc920687.20179984\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 6.9, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 0.0, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 0, oxcountryid: a7c40f631fc920687.20179984 }]\nexpected:\n    articles: { 10011: ['101,00', '101,00'], 1003: ['75,00', '75,00'], 1000: ['50,00', '50,00'] }\n    totals: { totalBrutto: '226,00', totalNetto: '202,47', vats: { 10: '9,18', 19: '11,97', 5: '2,38' }, delivery: { brutto: '6,90' }, grandTotal: '232,90' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php83/testFrontendVatForBillingCountry(2).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 101, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1003, oxprice: 75.0, oxvat: 19, amount: 1, oxpricea: 70, oxpriceb: 85, oxpricec: 0 }\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1, oxpricea: 35, oxpriceb: 45, oxpricec: 55, oxunitname: kg, oxunitquantity: 2, oxweight: 2 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\n    oxcountryid: a7c40f6321c6f6109.43859248\ndiscounts:\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10011, 1000], oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 0, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.5, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 0 }]\nexpected:\n    articles: { 10011: ['86,82', '86,82'], 1003: ['63,03', '63,03'], 1000: ['42,62', '42,62'] }\n    totals: { totalBrutto: '192,47', totalNetto: '192,47', vats: ['0,00'], delivery: { brutto: '0,00' }, payment: { brutto: '7,50', netto: '7,50' }, grandTotal: '199,97' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, blDeliveryVatOnTop: false, blPaymentVatOnTop: false, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/PriceB2basket2.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 70.0, oxpricea: 70, oxpriceb: 85, oxpricec: 0, amount: 7, oxvat: 19, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [1003, _testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    articles: { 1003: ['68,00', '476,00'] }\n    totals: { totalBrutto: '476,00', totalNetto: '400,00', vats: { 19: '76,00' }, grandTotal: '476,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case1.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9002, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9001, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9001], oxsort: 10 }\n    - { oxid: shopdiscount5for9002, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9002], oxsort: 20 }\n    - { oxid: basketdiscount5for9001, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9001], oxsort: 30 }\n    - { oxid: basketdiscount5for9002, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9002], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9001, oxprice: 9, oxactive: 1, oxarticles: [9001] }, { oxtype: WRAP, oxname: testWrap9002, oxprice: 6, oxactive: 1, oxarticles: [9002] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxarticles: [9001, 9002] }]\n    voucherserie: [{ oxserienr: abs_4_voucher_serie, oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9001: ['63,92', '2.109,36'], 9002: ['40,08', '641,28'] }\n    totals: { totalBrutto: '2.750,64', totalNetto: '2.305,18', vats: { 19: '437,98' }, discounts: { absolutebasketdiscount: '3,40' }, wrapping: { brutto: '267,24', netto: '224,57', vat: '42,67' }, delivery: { brutto: '4,08', netto: '3,43', vat: '0,65' }, payment: { brutto: '0,68', netto: '0,57', vat: '0,11' }, voucher: { brutto: '4,08' }, grandTotal: '3.015,16' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case10.yaml",
    "content": "articles:\n    - { oxid: 9206, oxprice: 103, oxvat: 19, amount: 1 }\nexpected:\n    articles: { 9206: ['103,00', '103,00'] }\n    totals: { totalBrutto: '103,00', totalNetto: '86,55', vats: { 19: '16,45' }, grandTotal: '103,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case100.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.55, oxvat: 33, amount: 1 }\n    - { oxid: 1112, oxprice: 1101.1, oxvat: 33, amount: 1 }\n    - { oxid: 1113, oxprice: 110, oxvat: 33, amount: 1 }\n    - { oxid: 1114, oxprice: 1.0, oxvat: 33, amount: 1 }\n    - { oxid: 1115, oxprice: 945.95, oxvat: 50, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['1.101,10', '1.101,10'], 1113: ['110,00', '110,00'], 1114: ['1,00', '1,00'], 1115: ['945,95', '945,95'] }\n    totals: { totalBrutto: '2.728,58', totalNetto: '2.158,60', vats: { 33: '360,16', 50: '425,68' }, discounts: { discountforbasket10%: '215,86' }, delivery: { brutto: '1.579,02', netto: '1.187,23', vat: '391,79' }, payment: { brutto: '73,15', netto: '55,00', vat: '18,15' }, grandTotal: '4.380,75' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case101.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.55, oxvat: 33, amount: 1 }\n    - { oxid: 1112, oxprice: 1101.1, oxvat: 33, amount: 1 }\n    - { oxid: 1113, oxprice: 110, oxvat: 33, amount: 1 }\n    - { oxid: 1114, oxprice: 1.0, oxvat: 33, amount: 1 }\n    - { oxid: 1115, oxprice: 945.95, oxvat: 50, amount: 2 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['1.101,10', '1.101,10'], 1113: ['110,00', '110,00'], 1114: ['1,00', '1,00'], 1115: ['945,95', '1.891,90'] }\n    totals: { totalBrutto: '4.005,61', totalNetto: '3.104,55', vats: { 33: '360,16', 50: '851,36' }, discounts: { discountforbasket10%: '310,46' }, delivery: { brutto: '2.561,25', netto: '1.707,50', vat: '853,75' }, payment: { brutto: '82,50', netto: '55,00', vat: '27,50' }, grandTotal: '6.649,36' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case102.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.55, oxvat: 33, amount: 1 }\n    - { oxid: 1112, oxprice: 945.95, oxvat: 50, amount: 2 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['945,95', '1.891,90'] }\n    totals: { totalBrutto: '2.554,72', totalNetto: '1.892,45', vats: { 33: '0,16', 50: '851,36' }, discounts: { discountforbasket10%: '189,25' }, delivery: { brutto: '1.561,28', netto: '1.040,85', vat: '520,43' }, payment: { brutto: '82,50', netto: '55,00', vat: '27,50' }, grandTotal: '4.198,50' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case103.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 945.95, oxvat: 50, amount: 2 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['945,95', '1.891,90'] }\n    totals: { totalBrutto: '2.554,07', totalNetto: '1.891,90', vats: { 50: '851,36' }, discounts: { discountforbasket10%: '189,19' }, delivery: { brutto: '1.560,83', netto: '1.040,55', vat: '520,28' }, payment: { brutto: '82,50', netto: '55,00', vat: '27,50' }, grandTotal: '4.197,40' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case104.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 1, 0: 0, oxvat: 20, amount: 2 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 111: ['1,00', '2,00'] }\n    totals: { totalBrutto: '2,40', totalNetto: '2,00', vats: { 20: '0,40' }, delivery: { brutto: '1,32', netto: '1,10', vat: '0,22' }, payment: { brutto: '0,24', netto: '0,20', vat: '0,04' }, grandTotal: '3,96' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blShowVATForPayCharge: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case105.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 1, 0: 0, oxvat: 20, amount: 2 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 111: ['1,00', '2,00'] }\n    totals: { totalBrutto: '2,16', totalNetto: '2,00', vats: { 20: '0,36' }, discounts: { discountforbasket10%: '0,20' }, delivery: { brutto: '1,32', netto: '1,10', vat: '0,22' }, payment: { brutto: '0,24', netto: '0,20', vat: '0,04' }, grandTotal: '3,72' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blPaymentVatOnTop: true, blDeliveryVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case106.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 1, 0: 0, oxvat: 20, amount: 1 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 111: ['1,20', '1,20'] }\n    totals: { totalBrutto: '1,20', totalNetto: '1,00', vats: { 20: '0,20' }, delivery: { brutto: '0,79', netto: '0,66', vat: '0,13' }, payment: { brutto: '0,14', netto: '0,12', vat: '0,02' }, grandTotal: '2,13' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case107.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 1, 0: 0, oxvat: 20, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 111: ['1,20', '1,20'] }\n    totals: { totalBrutto: '1,20', totalNetto: '0,90', vats: { 20: '0,18' }, discounts: { discountforbasket10%: '0,12' }, delivery: { brutto: '0,79', netto: '0,66', vat: '0,13' }, payment: { brutto: '0,14', netto: '0,12', vat: '0,02' }, grandTotal: '2,01' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case108.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 1, 0: 0, oxvat: 20, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 15 }]\nexpected:\n    articles: { 111: ['1,20', '1,20'] }\n    totals: { totalBrutto: '1,20', totalNetto: '0,90', vats: { 20: '0,18' }, discounts: { discountforbasket10%: '0,12' }, delivery: { brutto: '0,79', netto: '0,66', vat: '0,13' }, payment: { brutto: '0,23', netto: '0,19', vat: '0,04' }, grandTotal: '2,10' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case109.yaml",
    "content": "articles:\n    - { oxid: 333, oxtitle: item1, oxprice: 60, oxvat: 20, amount: 2 }\n    - { oxid: 444, oxtitle: item2, oxprice: 110, oxvat: 10, amount: 1 }\ndiscounts:\n    - { oxid: discount20, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxactive: 1, oxarticles: [333], oxsort: 10 }\n    - { oxid: discount50, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxactive: 1, oxarticles: [444], oxsort: 20 }\n    - { oxid: discount20forBasket, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxactive: 1, oxsort: 30 }\ncosts: {  }\nexpected:\n    articles: { 333: ['48,00', '96,00'], 444: ['55,00', '55,00'] }\n    totals: { totalBrutto: '151,00', discounts: { discount20forBasket: '30,20' }, totalNetto: '104,00', vats: { 20: '12,80', 10: '4,00' }, grandTotal: '120,80' }\noptions:\n    insertMode: brutto\n    viewMode: brutto\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case11.yaml",
    "content": "articles:\n    - { oxid: 9205, oxprice: 25.9, oxvat: 18, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_9205, oxaddsum: 5.31, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9205], oxsort: 10 }\nexpected:\n    articles: { 9205: ['11,53', '11,53'] }\n    totals: { totalBrutto: '11,53', totalNetto: '9,77', vats: { 18: '1,76' }, grandTotal: '11,53' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.56\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case110.yaml",
    "content": "articles:\n    - { oxid: 1, oxprice: 24.72, oxvat: 7, amount: 2 }\n    - { oxid: 2, oxprice: 14.57, oxvat: 7, amount: 1 }\n    - { oxid: 3, oxprice: 1.49, oxvat: 7, amount: 5 }\n    - { oxid: 4, oxprice: 1.65, oxvat: 7, amount: 5 }\n    - { oxid: 5, oxprice: 17.06, oxvat: 7, amount: 1 }\n    - { oxid: 6, oxprice: 1.63, oxvat: 7, amount: 6 }\n    - { oxid: 7, oxprice: 21.57, oxvat: 7, amount: 1 }\n    - { oxid: 8, oxprice: 21.57, oxvat: 7, amount: 1 }\n    - { oxid: 9, oxprice: 24.44, oxvat: 7, amount: 1 }\nexpected:\n    articles: { 1: ['24,72', '49,44'], 2: ['14,57', '14,57'], 3: ['1,49', '7,45'], 4: ['1,65', '8,25'], 5: ['17,06', '17,06'], 6: ['1,63', '9,78'], 7: ['21,57', '21,57'], 8: ['21,57', '21,57'], 9: ['24,44', '24,44'] }\n    totals: { totalBrutto: '186,32', totalNetto: '174,13', vats: { 7: '12,19' }, grandTotal: '186,32' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case111.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 1 }\nexpected:\n    articles: { 111: ['24,95', '24,95'] }\n    totals: { totalBrutto: '24,95', totalNetto: '20,97', vats: { 19: '3,98' }, grandTotal: '24,95' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case112.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['24,95', '2.495,00'] }\n    totals: { totalBrutto: '2.495,00', totalNetto: '2.096,64', vats: { 19: '398,36' }, grandTotal: '2.495,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case113.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['24,95', '2.495,00'] }\n    totals: { totalBrutto: '2.495,00', totalNetto: '2.096,64', vats: { 19: '398,36' }, grandTotal: '2.495,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case114.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['12,48', '1.248,00'] }\n    totals: { totalBrutto: '1.248,00', totalNetto: '1.048,74', vats: { 19: '199,26' }, grandTotal: '1.248,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case115.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 17.55, amount: 250 }\nexpected:\n    articles: { 111: ['12,48', '3.120,00'] }\n    totals: { totalBrutto: '3.120,00', totalNetto: '2.654,19', vats: { '17.55': '465,81' }, grandTotal: '3.120,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case116.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['12,48', '1.248,00'] }\n    totals: { totalBrutto: '1.248,00', totalNetto: '1.048,74', vats: { 19: '199,26' }, grandTotal: '1.248,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case117.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['12,48', '1.248,00'] }\n    totals: { totalBrutto: '1.248,00', totalNetto: '1.048,74', vats: { 19: '199,26' }, grandTotal: '1.248,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case118.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 25, amount: 100 }\nexpected:\n    articles: { 111: ['12,48', '1.248,00'] }\n    totals: { totalBrutto: '1.248,00', totalNetto: '998,40', vats: { 25: '249,60' }, grandTotal: '1.248,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case119.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 25, amount: 250 }\nexpected:\n    articles: { 111: ['12,48', '3.120,00'] }\n    totals: { totalBrutto: '3.120,00', totalNetto: '2.496,00', vats: { 25: '624,00' }, grandTotal: '3.120,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case12.yaml",
    "content": "articles:\n    - { oxid: 9203, oxprice: 29.99, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_9203, oxaddsum: 2.01, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9203], oxsort: 10 }\nexpected:\n    articles: { 9203: ['27,98', '27,98'] }\n    totals: { totalBrutto: '33,30', totalNetto: '27,98', vats: { 19: '5,32' }, grandTotal: '33,30' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case120.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 1 }\nexpected:\n    articles: { 111: ['20,97', '20,97'] }\n    totals: { totalBrutto: '24,95', totalNetto: '20,97', vats: { 19: '3,98' }, grandTotal: '24,95' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: proportional }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case121.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['20,97', '2.097,00'] }\n    totals: { totalBrutto: '2.495,43', totalNetto: '2.097,00', vats: { 19: '398,43' }, grandTotal: '2.495,43' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: proportional }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case122.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['20,97', '2.097,00'] }\n    totals: { totalBrutto: '2.495,43', totalNetto: '2.097,00', vats: { 19: '398,43' }, grandTotal: '2.495,43' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case123.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['10,49', '1.049,00'] }\n    totals: { totalBrutto: '1.248,31', totalNetto: '1.049,00', vats: { 19: '199,31' }, grandTotal: '1.248,31' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case124.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 17.55, amount: 250 }\nexpected:\n    articles: { 111: ['10,62', '2.655,00'] }\n    totals: { totalBrutto: '3.120,95', totalNetto: '2.655,00', vats: { '17.55': '465,95' }, grandTotal: '3.120,95' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case125.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 100 }\nexpected:\n    articles: { 111: ['10,49', '1.049,00'] }\n    totals: { totalBrutto: '1.248,31', totalNetto: '1.049,00', vats: { 19: '199,31' }, grandTotal: '1.248,31' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case126.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 150 }\nexpected:\n    articles: { 111: ['10,49', '1.573,50'] }\n    totals: { totalBrutto: '1.872,47', totalNetto: '1.573,50', vats: { 19: '298,97' }, grandTotal: '1.872,47' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case127.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 150 }\nexpected:\n    articles: { 111: ['10,49', '1.573,50'] }\n    totals: { totalBrutto: '1.872,47', totalNetto: '1.573,50', vats: { 19: '298,97' }, grandTotal: '1.872,47' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case128.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 150 }\nexpected:\n    articles: { 111: ['10,49', '1.573,50'] }\n    totals: { totalBrutto: '1.872,47', totalNetto: '1.573,50', vats: { 19: '298,97' }, grandTotal: '1.872,47' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case129.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 150 }\n    - { oxid: 222, oxprice: 7.99, oxvat: 19, amount: 1 }\nexpected:\n    articles: { 111: ['10,49', '1.573,50'], 222: ['3,36', '3,36'] }\n    totals: { totalBrutto: '1.876,46', totalNetto: '1.576,86', vats: { 19: '299,60' }, grandTotal: '1.876,46' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case13.yaml",
    "content": "articles:\n    - { oxid: 9201, oxprice: 77.9, oxvat: 17, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_9201, oxaddsum: 5.05, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 10 }\nexpected:\n    articles: { 9201: ['72,85', '72,85'] }\n    totals: { totalBrutto: '72,85', totalNetto: '62,26', vats: { 17: '10,59' }, grandTotal: '72,85' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case130.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 24.95, oxvat: 19, amount: 150 }\n    - { oxid: 222, oxprice: 7.99, oxvat: 19, amount: 1 }\nexpected:\n    articles: { 111: ['10,49', '1.573,50'], 222: ['3,36', '3,36'] }\n    totals: { totalBrutto: '1.876,46', totalNetto: '1.576,86', vats: { 19: '299,60' }, grandTotal: '1.876,46' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.5\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case131.yaml",
    "content": "articles:\n    - { oxid: '4425', oxprice: 879, amount: 1 }\ndiscounts:\n    - { oxid: discount10euro, oxaddsum: 10, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 0, oxactive: 1, oxarticles: [4425], oxsort: 10 }\nexpected:\n    articles: { 4425: ['869,00', '869,00'] }\n    totals: { totalBrutto: '869,00', totalNetto: '730,25', vats: { 19: '138,75' }, grandTotal: '869,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case132.yaml",
    "content": "articles:\n    - { oxid: '3727', oxprice: 5, amount: 1 }\ndiscounts:\n    - { oxid: discount500forShop, oxaddsum: 500, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 3727: ['5,00', '5,00'] }\n    totals: { totalBrutto: '5,00', discounts: { discount500forShop: '5,00' }, totalNetto: '0,00', vats: { 19: '0,00' }, grandTotal: '0,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case133.yaml",
    "content": "articles:\n    - { oxid: '3727', oxprice: 5, amount: 1 }\ndiscounts:\n    - { oxid: discount500forShop, oxaddsum: 500, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 3727: ['0,00', '0,00'] }\n    totals: { totalBrutto: '0,00', totalNetto: '0,00', vats: { 19: '0,00' }, grandTotal: '0,00' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case134.yaml",
    "content": "articles:\n    - { oxid: '3587', oxtitle: newspaper, oxprice: 2.98, amount: 200 }\ndiscounts:\n    - { oxid: discount2forShop, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: ['3587'], oxsort: 10 }\nexpected:\n    articles: { 3587: ['2,92', '584,00'] }\n    totals: { totalBrutto: '584,00', totalNetto: '490,76', vats: { 19: '93,24' }, grandTotal: '584,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case135.yaml",
    "content": "articles:\n    - { oxid: '3587', oxtitle: newspaper, oxprice: 2.98, amount: 200 }\ndiscounts:\n    - { oxid: discount2forShop, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 3587: ['2,92', '584,00'] }\n    totals: { totalBrutto: '584,00', totalNetto: '490,76', vats: { 19: '93,24' }, grandTotal: '584,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case136.yaml",
    "content": "articles:\n    - { oxid: '3587', oxtitle: newspaper, oxprice: 2.98, amount: 200 }\ndiscounts:\n    - { oxid: discount2forBasket, oxaddsum: 2, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: ['3587'], oxsort: 10 }\ncosts: {  }\nexpected:\n    articles: { 3587: ['2,92', '584,00'] }\n    totals: { totalBrutto: '584,00', totalNetto: '490,76', vats: { 19: '93,24' }, grandTotal: '584,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case137.yaml",
    "content": "articles:\n    - { oxid: '3587', oxtitle: newspaper, oxprice: 2.98, amount: 200 }\ndiscounts:\n    - { oxid: discount2forBasket, oxaddsum: 2, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 3587: ['2,98', '596,00'] }\n    totals: { totalBrutto: '596,00', discounts: { discount2forBasket: '11,92' }, totalNetto: '490,82', vats: { 19: '93,26' }, grandTotal: '584,08' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case138.yaml",
    "content": "articles:\n    - { oxid: _tArticle, oxprice: 50, oxweight: 10, oxstock: 100, oxstockflag: 2, oxvat: 10, amount: 2 }\n    - { oxid: 2000, oxprice: 29.9, oxtitle: 'Wall Clock ROBOT', oxstock: 3, oxstockflag: 1, amount: 1 }\n    - { oxid: _t1651, oxprice: 29.9, oxtitle: 'Beer homebrew kit CHEERS!', oxstock: 10000, oxstockflag: 1, amount: 1 }\ndiscounts:\n    - { oxid: testdiscount0, oxactive: 1, oxtitle: 'Test discount 0', oxamount: 1, oxamountto: 99999, oxprice: 1, oxpriceto: 99999, oxaddsumtype: abs, oxaddsum: 5, oxarticles: [2000, _tArticle], oxsort: 10 }\nexpected:\n    articles: { _tArticle: ['45,00', '90,00'], 2000: ['24,90', '24,90'], _t1651: ['29,90', '29,90'] }\n    totals: { totalBrutto: '144,80', totalNetto: '127,87', vats: { 19: '8,75', 10: '8,18' }, discounts: {  }, grandTotal: '144,80' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case139.yaml",
    "content": "articles:\n    - { oxid: testarticle, oxprice: 19, oxweight: 10, oxstock: 100, oxstockflag: 2, amount: 1, scaleprices: {  } }\ndiscounts:\n    - { oxid: testdiscount0, oxactive: 1, oxtitle: 'Test discount 0', oxamount: 1, oxamountto: 99999, oxprice: 1, oxpriceto: 99999, oxaddsumtype: abs, oxaddsum: 5, oxsort: 10 }\n    - { oxid: testdiscount1, oxactive: 1, oxtitle: 'Test discount 1', oxamount: 1, oxamountto: 99999, oxprice: 1, oxpriceto: 99999, oxaddsumtype: abs, oxaddsum: 7, oxsort: 20 }\nexpected:\n    articles: { testarticle: ['19,00', '19,00'] }\n    totals: { totalBrutto: '19,00', totalNetto: '5,88', vats: { 19: '1,12' }, discounts: { testdiscount0: '5,00', testdiscount1: '7,00' }, grandTotal: '7,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case14.yaml",
    "content": "articles:\n    - { oxid: 9201, oxprice: 77.9, oxvat: 17, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_9201, oxaddsum: 5.05, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 10 }\nexpected:\n    articles: { 9201: ['49,54', '49,54'] }\n    totals: { totalBrutto: '49,54', totalNetto: '42,34', vats: { 17: '7,20' }, grandTotal: '49,54' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case140.yaml",
    "content": "articles:\n    - { oxid: testarticle, oxprice: 12.95, amount: 1, scaleprices: { oxaddabs: 11.95, oxamount: 2, oxamountto: 2, oxartid: testarticle } }\ndiscounts:\n    - { oxid: _testDiscount, oxactive: 1, oxtitle: 'new discount', oxprice: 12, oxpriceto: 24.99, oxaddsumtype: abs, oxaddsum: 3, oxsort: 10 }\nexpected:\n    articles: { testarticle: ['12,95', '12,95'] }\n    totals: { totalBrutto: '12,95', totalNetto: '8,36', vats: { 19: '1,59' }, discounts: { _testDiscount: '3,00' }, grandTotal: '9,95' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case141.yaml",
    "content": "articles:\n    - { oxid: testarticle, oxprice: 19, oxweight: 10, oxstock: 100, oxstockflag: 2, oxskipdiscounts: 1, amount: 1, scaleprices: {  } }\ndiscounts:\n    - { oxid: testdiscount0, oxactive: 1, oxtitle: 'Test discount 0', oxamount: 1, oxamountto: 99999, oxprice: 1, oxpriceto: 99999, oxaddsumtype: abs, oxaddsum: 5, oxsort: 10 }\n    - { oxid: testdiscount1, oxactive: 1, oxtitle: 'Test discount 1', oxamount: 1, oxamountto: 99999, oxprice: 1, oxpriceto: 99999, oxaddsumtype: abs, oxaddsum: 7, oxsort: 20 }\nexpected:\n    articles: { testarticle: ['19,00', '19,00'] }\n    totals: { totalBrutto: '19,00', totalNetto: '15,97', vats: { 19: '3,03' }, discounts: {  }, grandTotal: '19,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case142.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87.0, oxvat: 17, amount: 63 }\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 48 }\n    - { oxid: 9206, oxprice: 103.0, oxvat: 19, amount: 99 }\n    - { oxid: 9216, oxprice: 56.45, oxvat: 17, amount: 22 }\nexpected:\n    articles: { 9200: ['87,00', '5.481,00'], 9201: ['72,85', '3.496,80'], 9206: ['103,00', '10.197,00'], 9216: ['56,45', '1.241,90'] }\n    totals: { totalBrutto: '20.416,70', totalNetto: '17.303,70', vats: { 17: '1.484,91', 19: '1.628,09' }, grandTotal: '20.416,70' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case143.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87, oxvat: 17, amount: 20315 }\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 210 }\n    - { oxid: 9202, oxprice: 16.2, oxvat: 17, amount: 56 }\n    - { oxid: 9208, oxprice: 72.11, oxvat: 17, amount: 691 }\n    - { oxid: 9212, oxprice: 16.37, oxvat: 17, amount: 548 }\n    - { oxid: 9216, oxprice: 56.45, oxvat: 17, amount: 36 }\ndiscounts:\n    - { oxid: discount5for9200, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9200], oxsort: 10 }\n    - { oxid: discount2for9201, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 20 }\n    - { oxid: discount3for9208, oxaddsum: 3, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9208], oxsort: 30 }\n    - { oxid: discount1for9212, oxaddsum: 1, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9212], oxsort: 40 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9202, oxprice: 0.63, oxactive: 1, oxarticles: [9202] }, { oxtype: WRAP, oxname: wrapFor9216, oxprice: 0.33, oxactive: 1, oxarticles: [9216] }]\n    delivery: [{ oxactive: 1, oxaddsum: 117, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 9999999 }]\n    payment: [{ oxaddsum: 3, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 2000000, oxchecked: 1 }]\nexpected:\n    articles: { 9200: ['82,65', '1.679.034,75'], 9201: ['71,39', '14.991,90'], 9202: ['16,20', '907,20'], 9208: ['69,95', '48.335,45'], 9212: ['16,21', '8.883,08'], 9216: ['56,45', '2.032,20'] }\n    totals: { totalBrutto: '1.754.184,58', totalNetto: '1.499.303,06', vats: { 17: '254.881,52' }, wrapping: { brutto: '47,16', netto: '40,30', vat: '6,86' }, delivery: { brutto: '117,00', netto: '100,00', vat: '17,00' }, payment: { brutto: '3,00', netto: '2,56', vat: '0,44' }, grandTotal: '1.754.351,74' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case144.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87, oxvat: 17, amount: 589 }\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 1325 }\n    - { oxid: 9207, oxprice: 45.5, oxvat: 19, amount: 8888 }\n    - { oxid: 9213, oxprice: 30.77, oxvat: 19, amount: 10000 }\ndiscounts:\n    - { oxid: discount2for9200, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9200], oxsort: 10 }\n    - { oxid: discount3for9201, oxaddsum: 3, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 20 }\n    - { oxid: discount4for9207, oxaddsum: 4, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9207], oxsort: 30 }\n    - { oxid: discount6for9213, oxaddsum: 6, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9213], oxsort: 40 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9200, oxprice: 0.05, oxactive: 1, oxarticles: [9200, 9207, 9213] }]\n    delivery: [{ oxactive: 1, oxaddsum: 58.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 9999999 }]\nexpected:\n    articles: { 9200: ['85,26', '50.218,14'], 9201: ['70,66', '93.624,50'], 9207: ['43,68', '388.227,84'], 9213: ['28,92', '289.200,00'] }\n    totals: { totalBrutto: '821.270,48', totalNetto: '692.209,52', vats: { 17: '20.900,21', 19: '108.160,75' }, wrapping: { brutto: '973,85', netto: '818,79', vat: '155,06' }, delivery: { brutto: '58,14', netto: '48,86', vat: '9,28' }, grandTotal: '822.302,47' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case145.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87.0, oxvat: 17, amount: 2008 }\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 369 }\n    - { oxid: 9207, oxprice: 45.5, oxvat: 19, amount: 1698 }\n    - { oxid: 9213, oxprice: 30.77, oxvat: 19, amount: 3665 }\nexpected:\n    articles: { 9200: ['87,00', '174.696,00'], 9201: ['72,85', '26.881,65'], 9207: ['45,50', '77.259,00'], 9213: ['30,77', '112.772,05'] }\n    totals: { totalBrutto: '391.608,70', totalNetto: '331.978,55', vats: { 17: '29.289,06', 19: '30.341,09' }, grandTotal: '391.608,70' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case146.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87.0, oxvat: 17, amount: 12 }\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 5 }\n    - { oxid: 9202, oxprice: 16.21, oxvat: 17, amount: 39 }\nexpected:\n    articles: { 9200: ['59,16', '709,92'], 9201: ['49,54', '247,70'], 9202: ['11,02', '429,78'] }\n    totals: { totalBrutto: '1.387,40', totalNetto: '1.185,81', vats: { 17: '201,59' }, grandTotal: '1.387,40' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case147.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87.0, oxvat: 17, amount: 120 }\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 5 }\n    - { oxid: 9202, oxprice: 16.21, oxvat: 17, amount: 39 }\nexpected:\n    articles: { 9200: ['59,16', '7.099,20'], 9201: ['49,54', '247,70'], 9202: ['11,02', '429,78'] }\n    totals: { totalBrutto: '7.776,68', totalNetto: '6.646,74', vats: { 17: '1.129,94' }, grandTotal: '7.776,68' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case148.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 15.93, oxvat: 15, amount: 21 }\n    - { oxid: 9208, oxprice: 70.87, oxvat: 15, amount: 9 }\n    - { oxid: 9213, oxprice: 25.86, oxvat: 0, amount: 10 }\n    - { oxid: 9216, oxprice: 48.25, oxvat: 0, amount: 4 }\n    - { oxid: 9218, oxprice: 58.09, oxvat: 15, amount: 5 }\nexpected:\n    articles: { 9202: ['15,93', '334,53'], 9208: ['70,87', '637,83'], 9213: ['25,86', '258,60'], 9216: ['48,25', '193,00'], 9218: ['58,09', '290,45'] }\n    totals: { totalBrutto: '1.714,41', totalNetto: '1.549,70', vats: { 0: '0,00', 15: '164,71' }, grandTotal: '1.714,41' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case149.yaml",
    "content": "articles:\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 1012 }\n    - { oxid: 9203, oxprice: 33.3, oxvat: 19, amount: 453 }\n    - { oxid: 9211, oxprice: 5.86, oxvat: 16, amount: 88 }\n    - { oxid: 9216, oxprice: 56.45, oxvat: 17, amount: 56 }\n    - { oxid: 9219, oxprice: 24.33, oxvat: 19, amount: 74 }\nexpected:\n    articles: { 9201: ['72,85', '73.724,20'], 9203: ['33,30', '15.084,90'], 9211: ['5,86', '515,68'], 9216: ['56,45', '3.161,20'], 9219: ['24,33', '1.800,42'] }\n    totals: { totalBrutto: '94.286,40', totalNetto: '80.347,91', vats: { 16: '71,13', 17: '11.171,38', 19: '2.695,98' }, grandTotal: '94.286,40' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case15.yaml",
    "content": "articles:\n    - { oxid: 9201, oxprice: 77.9, oxvat: 17, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_9201, oxaddsum: 5.05, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 10 }\nexpected:\n    articles: { 9201: ['107,09', '107,09'] }\n    totals: { totalBrutto: '107,09', totalNetto: '91,53', vats: { 17: '15,56' }, grandTotal: '107,09' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1.47\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case150.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 47.62, oxvat: 0, amount: 1 }\n    - { oxid: 9201, oxprice: 91.82, oxvat: 0, amount: 1 }\n    - { oxid: 9207, oxprice: 63.03, oxvat: 0, amount: 1 }\nexpected:\n    articles: { 9200: ['47,62', '47,62'], 9201: ['91,82', '91,82'], 9207: ['63,03', '63,03'] }\n    totals: { totalBrutto: '202,47', totalNetto: '202,47', vats: ['0,00'], grandTotal: '202,47' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case151.yaml",
    "content": "articles:\n    - { oxid: 9000, oxprice: 50.01, oxvat: 17, amount: 3.3 }\n    - { oxid: 9201, oxprice: 1.0, oxvat: 17, amount: 0.33 }\nexpected:\n    articles: { 9000: ['50,01', '165,03'], 9201: ['1,00', '0,33'] }\n    totals: { totalBrutto: '165,36', totalNetto: '141,33', vats: { 17: '24,03' }, grandTotal: '165,36' }\noptions:\n    config: { blAllowUnevenAmounts: true, blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case152.yaml",
    "content": "articles:\n    0: { oxid: 9200, oxprice: 87, oxvat: 17, amount: 63 }\n    1: { oxid: 9206, oxprice: 103, oxvat: 19, amount: 125 }\n    3: { oxid: 9216, oxprice: 56.45, oxvat: 17, amount: 14 }\n    4: { oxid: 9218, oxprice: 59.6, oxvat: 18, amount: 39 }\ndiscounts:\n    - { oxid: discount2for9200and9206, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9200, 9206], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9216, oxprice: 0.57, oxactive: 1, oxarticles: [9216] }]\n    delivery: [{ oxactive: 1, oxaddsum: 15, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\nexpected:\n    articles: { 9200: ['85,26', '5.371,38'], 9206: ['100,94', '12.617,50'], 9216: ['56,45', '790,30'], 9218: ['59,60', '2.324,40'] }\n    totals: { totalBrutto: '21.103,58', totalNetto: '17.839,16', vats: { 17: '895,29', 18: '354,57', 19: '2.014,56' }, wrapping: { brutto: '7,98', netto: '6,82', vat: '1,16' }, delivery: { brutto: '15,00', netto: '12,61', vat: '2,39' }, grandTotal: '21.126,56' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case153.yaml",
    "content": "articles:\n    0: { oxid: 9200, oxprice: 59.16, oxvat: 17, amount: 1002 }\n    1: { oxid: 9201, oxprice: 49.54, oxvat: 17, amount: 1 }\n    3: { oxid: 9202, oxprice: 11.02, oxvat: 17, amount: 5 }\ndiscounts:\n    - { oxid: discount5for9200, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9200], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9201, oxprice: 8, oxactive: 1, oxarticles: [9201] }, { oxtype: WRAP, oxname: wrapFor9202, oxprice: 0.7, oxactive: 1, oxarticles: [9202] }]\n    delivery: [{ oxactive: 1, oxaddsum: 14.75, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\nexpected:\n    articles: { 9200: ['38,22', '38.296,44'], 9201: ['33,69', '33,69'], 9202: ['7,49', '37,45'] }\n    totals: { totalBrutto: '38.367,58', totalNetto: '32.792,80', vats: { 17: '5.574,78' }, wrapping: { brutto: '7,84', netto: '6,70', vat: '1,14' }, delivery: { brutto: '10,03', netto: '8,57', vat: '1,46' }, grandTotal: '38.385,45' }\noptions:\n    activeCurrencyRate: 0.68\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case154.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 16.48, oxvat: 19, amount: 190 }\n    - { oxid: 9210, oxprice: 27.35, oxvat: 19, amount: 255 }\n    - { oxid: 9213, oxprice: 30.77, oxvat: 19, amount: 14 }\n    - { oxid: 9215, oxprice: 69.13, oxvat: 19, amount: 10 }\ndiscounts:\n    - { oxid: discount1for9202, oxaddsum: 1, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9202], oxsort: 10 }\n    - { oxid: discount2for9210, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9210], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9215, oxprice: 1.5, oxactive: 1, oxarticles: [9215] }]\n    delivery: [{ oxactive: 1, oxaddsum: 58.49, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\nexpected:\n    articles: { 9202: ['16,32', '3.100,80'], 9210: ['26,80', '6.834,00'], 9213: ['30,77', '430,78'], 9215: ['69,13', '691,30'] }\n    totals: { totalBrutto: '11.056,88', totalNetto: '9.291,50', vats: { 19: '1.765,38' }, wrapping: { brutto: '15,00', netto: '12,61', vat: '2,39' }, delivery: { brutto: '58,49', netto: '49,15', vat: '9,34' }, grandTotal: '11.130,37' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case155.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 15.93, oxvat: 15, amount: 58 }\n    - { oxid: 9208, oxprice: 70.87, oxvat: 15, amount: 14 }\n    - { oxid: 9213, oxprice: 25.86, oxvat: 0, amount: 1398 }\n    - { oxid: 9216, oxprice: 48.25, oxvat: 0, amount: 250 }\n    - { oxid: 9218, oxprice: 58.09, oxvat: 15, amount: 12 }\ndiscounts:\n    - { oxid: discount4for9213, oxaddsum: 4, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9213], oxsort: 10 }\n    - { oxid: discount2for9216, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9216], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9218, oxprice: 2.3, oxactive: 1, oxarticles: [9218] }]\n    delivery: [{ oxactive: 1, oxaddsum: 12.82, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\nexpected:\n    articles: { 9202: ['15,93', '923,94'], 9208: ['70,87', '992,18'], 9213: ['24,83', '34.712,34'], 9216: ['47,29', '11.822,50'], 9218: ['58,09', '697,08'] }\n    totals: { totalBrutto: '49.148,04', totalNetto: '48.807,19', vats: { 15: '340,85', 0: '0,00' }, wrapping: { brutto: '27,60', netto: '24,00', vat: '3,60' }, delivery: { brutto: '12,82', netto: '12,82' }, grandTotal: '49.188,46' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case156.yaml",
    "content": "articles:\n    - { oxid: 9201, oxprice: 72.85, oxvat: 17, amount: 175 }\n    - { oxid: 9203, oxprice: 33.3, oxvat: 19, amount: 12 }\n    - { oxid: 9211, oxprice: 5.86, oxvat: 16, amount: 5874 }\n    - { oxid: 9216, oxprice: 56.45, oxvat: 17, amount: 225 }\n    - { oxid: 9219, oxprice: 24.33, oxvat: 19, amount: 31 }\ndiscounts:\n    - { oxid: discount2for9201, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 10 }\n    - { oxid: discount4for9211, oxaddsum: 4, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9211], oxsort: 20 }\n    - { oxid: discount2for9216, oxaddsum: 2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9216], oxsort: 30 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: wrapFor9219, oxprice: 0.48, oxactive: 1, oxarticles: [9219] }]\n    delivery: [{ oxactive: 1, oxaddsum: 15.03, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\nexpected:\n    articles: { 9201: ['71,39', '12.493,25'], 9203: ['33,30', '399,60'], 9211: ['5,63', '33.070,62'], 9216: ['55,32', '12.447,00'], 9219: ['24,33', '754,23'] }\n    totals: { totalBrutto: '59.164,70', totalNetto: '50.795,22', vats: { 16: '4.561,46', 17: '3.623,80', 19: '184,22' }, wrapping: { brutto: '14,88', netto: '12,50', vat: '2,38' }, delivery: { brutto: '15,03', netto: '12,96', vat: '2,07' }, grandTotal: '59.194,61' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case157.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 119, oxvat: 19, amount: 1 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\n    oxcountryid: 8f241f11096877ac0.98748826\nexpected:\n    articles: { 9202: ['100,00', '100,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '100,00', vats: ['0,00'], grandTotal: '100,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case158.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 119, oxvat: 19, amount: 1 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\n    oxcountryid: 8f241f11096877ac0.98748826\nexpected:\n    articles: { 9202: ['100,00', '100,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '100,00', vats: ['0,00'], grandTotal: '100,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case159.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 100, oxvat: 19, amount: 1 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\n    oxcountryid: 8f241f11096877ac0.98748826\nexpected:\n    articles: { 9202: ['100,00', '100,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '100,00', vats: ['0,00'], grandTotal: '100,00' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case16.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 74.36, oxvat: 17, amount: 1 }\nexpected:\n    articles: { 9200: ['87,00', '87,00'] }\n    totals: { totalBrutto: '87,00', totalNetto: '74,36', vats: { 17: '12,64' }, grandTotal: '87,00' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case160.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 100, oxvat: 19, amount: 1 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\n    oxcountryid: 8f241f11096877ac0.98748826\nexpected:\n    articles: { 9202: ['100,00', '100,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '100,00', vats: ['0,00'], grandTotal: '100,00' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case161.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9006, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount5for9006, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\n    - { oxid: basketdiscount5for9005, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 30 }\n    - { oxid: basketdiscount5for9006, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9005, oxprice: 9, oxactive: 1, oxarticles: [9005] }, { oxtype: WRAP, oxname: testWrap9006, oxprice: 6, oxactive: 1, oxarticles: [9006] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9005: ['113,00', '3.729,00'], 9006: ['70,13', '1.122,08'] }\n    totals: { totalBrutto: '4.851,08', totalNetto: '4.067,29', vats: { 19: '772,79' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '393,00', netto: '330,25', vat: '62,75' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '5.240,08' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case162.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9006, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount5for9006, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\n    - { oxid: basketdiscount5for9005, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 30 }\n    - { oxid: basketdiscount5for9006, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9005, oxprice: 9, oxactive: 1, oxarticles: [9005] }, { oxtype: WRAP, oxname: testWrap9006, oxprice: 6, oxactive: 1, oxarticles: [9006] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9005: ['113,00', '3.729,00'], 9006: ['70,13', '1.122,08'] }\n    totals: { totalBrutto: '4.851,08', totalNetto: '4.067,29', vats: { 19: '772,79' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '393,00', netto: '330,25', vat: '62,75' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '5.240,08' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case163.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9006, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount5for9006, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\n    - { oxid: basketdiscount5for9005, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 30 }\n    - { oxid: basketdiscount5for9006, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9005, oxprice: 9, oxactive: 1, oxarticles: [9005] }, { oxtype: WRAP, oxname: testWrap9006, oxprice: 6, oxactive: 1, oxarticles: [9006] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9005: ['113,00', '3.729,00'], 9006: ['70,13', '1.122,08'] }\n    totals: { totalBrutto: '4.851,08', totalNetto: '4.067,29', vats: { 19: '772,79' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '393,00', netto: '330,25', vat: '62,75' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '5.240,08' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case164.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount5.5for9005, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['1.125,67', '1.125,67'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '1.126,33', totalNetto: '946,50', vats: { 19: '179,83' }, delivery: { brutto: '112,63', netto: '94,65', vat: '17,98' }, payment: { brutto: '10,00', netto: '8,40', vat: '1,60' }, grandTotal: '1.248,96' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case165.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount5.5for9005, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['1.125,67', '1.125,67'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '1.126,33', totalNetto: '946,50', vats: { 19: '179,83' }, delivery: { brutto: '112,63', netto: '94,65', vat: '17,98' }, payment: { brutto: '10,00', netto: '8,40', vat: '1,60' }, grandTotal: '1.248,96' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case166.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount50for9005, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['595,60', '595,60'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '596,26', totalNetto: '501,06', vats: { 19: '95,20' }, delivery: { brutto: '59,63', netto: '50,11', vat: '9,52' }, payment: { brutto: '10,00', netto: '8,40', vat: '1,60' }, grandTotal: '665,89' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case167.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 3 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount50for9005, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['595,60', '1.786,80'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '1.787,46', totalNetto: '1.502,07', vats: { 19: '285,39' }, delivery: { brutto: '178,75', netto: '150,21', vat: '28,54' }, payment: { brutto: '10,00', netto: '8,40', vat: '1,60' }, grandTotal: '1.976,21' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case168.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 3 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount50for9005, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['595,60', '1.786,80'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '1.787,46', totalNetto: '1.502,07', vats: { 19: '285,39' }, delivery: { brutto: '178,75', netto: '150,21', vat: '28,54' }, payment: { brutto: '10,00' }, grandTotal: '1.976,21' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: false, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case169.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 3 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount50for9005, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['595,60', '1.786,80'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '1.787,46', totalNetto: '1.502,07', vats: { 19: '285,39' }, delivery: { brutto: '178,75', netto: '150,21', vat: '28,54' }, payment: { brutto: '10,00', netto: '8,40', vat: '1,60' }, grandTotal: '1.976,21' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case17.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 74.36, oxvat: 17, amount: 1 }\nexpected:\n    articles: { 9200: ['59,16', '59,16'] }\n    totals: { totalBrutto: '59,16', totalNetto: '50,56', vats: { 17: '8,60' }, grandTotal: '59,16' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case170.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 3 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount50for9005, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['595,60', '1.786,80'], 9006: ['0,66', '0,66'] }\n    totals: { totalBrutto: '1.787,46', totalNetto: '1.502,07', vats: { 19: '285,39' }, delivery: { brutto: '178,75', netto: '150,21', vat: '28,54' }, payment: { brutto: '10,00' }, grandTotal: '1.976,21' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: false, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case171.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 1001, oxvat: 19, amount: 3 }\n    - { oxid: 9006, oxprice: 0.5, oxvat: 18, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount50for9005, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount-10for9006, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 2, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9005: ['595,60', '1.786,80'], 9006: ['0,65', '0,65'] }\n    totals: { totalBrutto: '1.787,45', totalNetto: '1.502,06', vats: { 19: '285,29', 18: '0,10' }, delivery: { brutto: '178,75', netto: '150,21', vat: '28,54' }, payment: { brutto: '10,00' }, grandTotal: '1.976,20' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blShowVATForPayCharge: false, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case172.yaml",
    "content": "articles:\n    - { oxid: 1126, oxprice: 34.0, oxvat: 19, amount: 3 }\ndiscounts:\n    - { oxid: testdisc, oxaddsum: 50, oxaddsumtype: '%', oxamount: 3, oxamountto: 99999, oxactive: 1, oxarticles: [1126], oxsort: 10 }\nexpected:\n    articles: { 1126: ['17,00', '51,00'] }\n    totals: { totalBrutto: '51,00', totalNetto: '42,86', vats: { 19: '8,14' }, grandTotal: '51,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, bl_perfLoadSelectLists: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case173.yaml",
    "content": "articles:\n    - { oxid: 1126, oxprice: 34.0, oxvat: 19, amount: 3 }\ndiscounts:\n    - { oxid: testdisc, oxaddsum: 50, oxaddsumtype: '%', oxamount: 3, oxamountto: 99999, oxactive: 0, oxarticles: [1126], oxsort: 10 }\n    - { oxid: _testoxdiscount2, oxaddsum: 50, oxaddsumtype: '%', oxamount: 3, oxamountto: 99999, oxprice: 69, oxpriceto: 999999, oxactive: 1, oxarticles: [1126], oxsort: 20 }\nexpected:\n    articles: { 1126: ['17,00', '51,00'] }\n    totals: { totalBrutto: '51,00', totalNetto: '42,86', vats: { 19: '8,14' }, grandTotal: '51,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, bl_perfLoadSelectLists: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case174.yaml",
    "content": "articles:\n    - { oxid: 1126, oxprice: 34.0, oxvat: 19, amount: 2 }\ndiscounts:\n    - { oxid: testdisc, oxaddsum: 50, oxaddsumtype: '%', oxamount: 3, oxamountto: 99999, oxactive: 0, oxarticles: [1126], oxsort: 10 }\n    - { oxid: _testoxdiscount2, oxaddsum: 50, oxaddsumtype: '%', oxamount: 3, oxamountto: 99999, oxprice: 69, oxpriceto: 999999, oxactive: 1, oxarticles: [1126], oxsort: 20 }\nexpected:\n    articles: { 1126: ['34,00', '68,00'] }\n    totals: { totalBrutto: '68,00', totalNetto: '57,14', vats: { 19: '10,86' }, grandTotal: '68,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, bl_perfLoadSelectLists: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case18.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 74.36, oxvat: 17, amount: 1 }\nexpected:\n    articles: { 9200: ['127,89', '127,89'] }\n    totals: { totalBrutto: '127,89', totalNetto: '109,31', vats: { 17: '18,58' }, grandTotal: '127,89' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1.47\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case19.yaml",
    "content": "articles:\n    - { oxid: 9201, oxprice: 66.58, oxvat: 17, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_9201, oxaddsum: 4.32, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9201], oxsort: 10 }\nexpected:\n    articles: { 9201: ['73,58', '73,58'] }\n    totals: { totalBrutto: '73,58', totalNetto: '62,89', vats: { 17: '10,69' }, grandTotal: '73,58' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case2.yaml",
    "content": "articles:\n    - { oxid: 9003, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9004, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9003, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9003], oxsort: 10 }\n    - { oxid: shopdiscount5for9004, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9004], oxsort: 20 }\n    - { oxid: basketdiscount5for9003, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9003], oxsort: 30 }\n    - { oxid: basketdiscount5for9004, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9004], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9003, oxprice: 9, oxactive: 1, oxarticles: [9003] }, { oxtype: WRAP, oxname: testWrap9003, oxprice: 6, oxactive: 1, oxarticles: [9004] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxarticles: [9003, 9004] }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9003: ['76,84', '2.535,72'], 9004: ['47,70', '763,20'] }\n    totals: { totalBrutto: '3.298,92', totalNetto: '2.765,92', vats: { 19: '525,52' }, discounts: { absolutebasketdiscount: '3,40' }, wrapping: { brutto: '267,24', netto: '224,57', vat: '42,67' }, delivery: { brutto: '4,08', netto: '3,43', vat: '0,65' }, payment: { brutto: '0,68', netto: '0,57', vat: '0,11' }, voucher: { brutto: '4,08' }, grandTotal: '3.563,44' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case20.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 16.2, oxvat: 0, amount: 1 }\nexpected:\n    articles: { 9202: ['11,02', '11,02'] }\n    totals: { totalBrutto: '11,02', totalNetto: '11,02', vats: ['0,00'], grandTotal: '11,02' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case21.yaml",
    "content": "articles:\n    - { oxid: 9202, oxprice: 16.2, oxvat: 0, amount: 1 }\nexpected:\n    articles: { 9202: ['16,20', '16,20'] }\n    totals: { totalBrutto: '16,20', totalNetto: '16,20', vats: ['0,00'], grandTotal: '16,20' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case22.yaml",
    "content": "articles:\n    - { oxid: 100, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 1001, oxprice: 66, oxvat: 19, amount: 33 }\ndiscounts:\n    - { oxid: shopdiscount5for100, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [100], oxsort: 10 }\n    - { oxid: shopdiscount5for1001, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: basketdiscount5for100, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [100], oxsort: 30 }\n    - { oxid: basketdiscount5for1001, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9005, oxprice: 9, oxactive: 1, oxarticles: [100] }, { oxtype: WRAP, oxname: testWrap9006, oxprice: 6, oxactive: 1, oxarticles: [1001] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 100: ['113,00', '3.729,00'], 1001: ['70,13', '2.314,29'] }\n    totals: { totalBrutto: '6.043,29', totalNetto: '5.069,15', vats: { 19: '963,14' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '495,00', netto: '415,97', vat: '79,03' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '6.534,29' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case23.yaml",
    "content": "articles:\n    - { oxid: 10005, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount5for10005, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 10 }\n    - { oxid: shopdiscount5for1004, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 20 }\n    - { oxid: basketdiscount5for10005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 30 }\n    - { oxid: basketdiscount5for1004, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\n    - { oxid: procdiscountfor10005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 60 }\n    - { oxid: procdiscountfor1004, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 70 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap102, oxprice: 9, oxactive: 1, oxarticles: [10005] }, { oxtype: WRAP, oxname: testWrap1002, oxprice: 6, oxactive: 1, oxarticles: [1004] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 10005: ['1.115,67', '1.115,67'], 1004: ['0,59', '0,59'] }\n    totals: { totalBrutto: '1.116,26', totalNetto: '928,79', vats: { 19: '176,47' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '15,00', netto: '12,60', vat: '2,40' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '1.127,26' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case24.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 5.02, oxvat: 7, amount: 1 }\n    - { oxid: 1112, oxprice: 99, 0: 0, oxvat: 19, amount: 1 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 5.02, oxvat: 8, amount: 1 }\ndiscounts:\n    - { oxid: discount10%, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111], oxsort: 10 }\n    - { oxid: discount5.5, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1112, 1113], oxsort: 20 }\nexpected:\n    articles: { 111: ['5,52', '5,52'], 1112: ['93,56', '93,56'], 1113: ['945,95', '945,95'], 1114: ['5,02', '5,02'] }\n    totals: { totalBrutto: '1.248,35', totalNetto: '1.050,05', vats: { 7: '0,39', 8: '0,40', 19: '197,51' }, grandTotal: '1.248,35' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case25.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\ncosts:\n    wrapping: { 3: { oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 } }\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'] }\n    totals: { totalBrutto: '568,00', totalNetto: '500,00', vats: { 10: '30,00', 19: '38,00' }, delivery: { brutto: '312,40', netto: '275,00', vat: '37,40' }, payment: { brutto: '312,40', netto: '275,00', vat: '37,40' }, giftcard: { brutto: '2,84', netto: '2,50', vat: '0,34' }, grandTotal: '1.195,64' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case26.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 10 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '200,00'], 1002: ['200,00', '200,00'] }\n    totals: { totalBrutto: '400,00', totalNetto: '349,89', vats: { 19: '31,93', 10: '18,18' }, delivery: { brutto: '261,80', netto: '220,00', vat: '41,80' }, payment: { brutto: '327,25', netto: '275,00', vat: '52,25' }, grandTotal: '989,05' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case27.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1002, oxprice: 20.0, oxvat: 10, amount: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['200,00', '200,00'], 1002: ['20,00', '200,00'] }\n    totals: { totalBrutto: '400,00', totalNetto: '349,89', vats: { 19: '31,93', 10: '18,18' }, delivery: { brutto: '261,80', netto: '220,00', vat: '41,80' }, payment: { brutto: '327,25', netto: '275,00', vat: '52,25' }, grandTotal: '989,05' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case28.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 19, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1003, oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1003: ['0,00', '0,00'] }\n    totals: { totalBrutto: '595,00', totalNetto: '500,00', vats: { 19: '95,00' }, delivery: { brutto: '327,25', netto: '275,00', vat: '52,25' }, payment: { brutto: '327,25', netto: '275,00', vat: '52,25' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, grandTotal: '1.252,48' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case29.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 19, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1003, oxitmamount: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1003: ['0,00', '0,00'] }\n    totals: { totalBrutto: '595,00', totalNetto: '500,00', vats: { 19: '95,00' }, delivery: { brutto: '327,25', netto: '275,00', vat: '52,25' }, payment: { brutto: '327,25', netto: '275,00', vat: '52,25' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, grandTotal: '1.252,48' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case3.yaml",
    "content": "articles:\n    - { oxid: 9005, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9006, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 10 }\n    - { oxid: shopdiscount5for9006, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 20 }\n    - { oxid: basketdiscount5for9005, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9005], oxsort: 30 }\n    - { oxid: basketdiscount5for9006, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9006], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9005, oxprice: 9, oxactive: 1, oxarticles: [9005] }, { oxtype: WRAP, oxname: testWrap9006, oxprice: 6, oxactive: 1, oxarticles: [9006] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9005: ['113,00', '3.729,00'], 9006: ['70,13', '1.122,08'] }\n    totals: { totalBrutto: '4.851,08', totalNetto: '4.067,29', vats: { 19: '772,79' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '393,00', netto: '330,25', vat: '62,75' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '5.240,08' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case30.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 19, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\n    - { oxid: discountforbasket55%, oxaddsum: 55, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '267,75', totalNetto: '500,00', vats: { 19: '42,75' }, discounts: { discountforbasket55%: '275,00' }, delivery: { brutto: '327,25', netto: '275,00', vat: '52,25' }, payment: { brutto: '327,25', netto: '275,00', vat: '52,25' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, grandTotal: '925,23' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case31.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\n    - { oxid: discountforbasket55%, oxaddsum: 55, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '255,60', totalNetto: '500,00', vats: { 10: '13,50', 19: '17,10' }, discounts: { discountforbasket55%: '275,00' }, delivery: { brutto: '312,40', netto: '275,00', vat: '37,40' }, payment: { brutto: '312,40', netto: '275,00', vat: '37,40' }, giftcard: { brutto: '2,84', netto: '2,50', vat: '0,34' }, grandTotal: '883,24' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case32.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '511,20', totalNetto: '500,00', vats: { 19: '34,20', 10: '27,00' }, delivery: { brutto: '312,40', netto: '275,00', vat: '37,40' }, payment: { brutto: '312,40', netto: '275,00', vat: '37,40' }, voucher: { brutto: '50,00' }, giftcard: { brutto: '2,84', netto: '2,50', vat: '0,34' }, grandTotal: '1.138,84' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case33.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '511,20', totalNetto: '500,00', vats: { 19: '34,20', 10: '27,00' }, delivery: { brutto: '302,50', netto: '275,00', vat: '27,50' }, payment: { brutto: '302,50', netto: '275,00', vat: '27,50' }, voucher: { brutto: '50,00' }, giftcard: { brutto: '2,75', netto: '2,50', vat: '0,25' }, grandTotal: '1.118,95' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case34.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '511,20', totalNetto: '500,00', vats: { 19: '34,20', 10: '27,00' }, delivery: { brutto: '302,50' }, payment: { brutto: '302,50' }, voucher: { brutto: '50,00' }, giftcard: { brutto: '2,75' }, grandTotal: '1.118,95' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: false, blShowVATForPayCharge: false, blShowVATForWrapping: false, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case35.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 15 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '511,20', totalNetto: '500,00', vats: { 19: '34,20', 10: '27,00' }, delivery: { brutto: '302,50', netto: '275,00', vat: '27,50' }, payment: { brutto: '302,50', netto: '275,00', vat: '27,50' }, voucher: { brutto: '50,00' }, giftcard: { brutto: '2,75', netto: '2,50', vat: '0,25' }, grandTotal: '1.118,95' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case36.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [basketUser] }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: item_discount, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['18,00', '36,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '225,15', totalNetto: '236,00', vats: { 19: '30,78', 11: '3,21' }, discounts: { percentage_discount: '23,60' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, delivery: { brutto: '154,46', netto: '129,80', vat: '24,66' }, payment: { brutto: '154,46', netto: '129,80', vat: '24,66' }, voucher: { brutto: '21,24' }, grandTotal: '537,05' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case37.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [basketUser] }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: item_discount, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['18,00', '36,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '225,15', totalNetto: '236,00', vats: { 19: '30,78', 11: '3,21' }, discounts: { percentage_discount: '23,60' }, giftcard: { brutto: '2,94', netto: '2,50', vat: '0,44' }, delivery: { brutto: '152,88', netto: '129,80', vat: '23,08' }, payment: { brutto: '152,88', netto: '129,80', vat: '23,08' }, voucher: { brutto: '21,24' }, grandTotal: '533,85' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case38.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [basketUser] }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: item_discount, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['54,00', '108,00'], 1002: ['600,00', '600,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '675,44', totalNetto: '708,00', vats: { 19: '92,34', 11: '9,62' }, discounts: { percentage_discount: '70,80' }, giftcard: { brutto: '8,83', netto: '7,50', vat: '1,33' }, delivery: { brutto: '458,63', netto: '389,40', vat: '69,23' }, payment: { brutto: '458,63', netto: '389,40', vat: '69,23' }, voucher: { brutto: '63,72' }, grandTotal: '1.601,53' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 3\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case39.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [basketUser] }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: item_discount, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['59,94', '119,88'], 1002: ['714,00', '714,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '833,88', totalNetto: '573,48', vats: { 19: '92,34', 11: '9,62' }, discounts: { percentage_discount: '83,39' }, giftcard: { brutto: '8,83', netto: '7,50', vat: '1,33' }, delivery: { brutto: '540,17', netto: '458,63', vat: '81,54' }, payment: { brutto: '540,17', netto: '458,63', vat: '81,54' }, voucher: { brutto: '75,05' }, grandTotal: '1.764,61' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 3\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case4.yaml",
    "content": "articles:\n    - { oxid: 9007, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9008, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9007, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9007], oxsort: 10 }\n    - { oxid: shopdiscount5for9008, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9008], oxsort: 20 }\n    - { oxid: basketdiscount5for9007, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9007], oxsort: 30 }\n    - { oxid: basketdiscount5for9008, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9008], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9007, oxprice: 9, oxactive: 1, oxarticles: [9007] }, { oxtype: WRAP, oxname: testWrap9008, oxprice: 6, oxactive: 1, oxarticles: [9008] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9007: ['76,84', '2.535,72'], 9008: ['47,70', '763,20'] }\n    totals: { totalBrutto: '3.298,92', totalNetto: '2.765,92', vats: { 19: '525,52' }, discounts: { absolutebasketdiscount: '3,40' }, wrapping: { brutto: '267,24', netto: '224,57', vat: '42,67' }, delivery: { brutto: '4,08', netto: '3,43', vat: '0,65' }, payment: { brutto: '0,68', netto: '0,57', vat: '0,11' }, voucher: { brutto: '4,08' }, grandTotal: '3.563,44' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case40.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19, OXSHOPID: 2 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [basketUser] }\nshop:\n    - { oxactive: 1, oxparentid: 1, oxname: subshop, activeshop: true }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: item_discount, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['18,00', '36,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '225,15', totalNetto: '236,00', vats: { 19: '30,78', 11: '3,21' }, discounts: { percentage_discount: '23,60' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, delivery: { brutto: '154,46', netto: '129,80', vat: '24,66' }, payment: { brutto: '154,46', netto: '129,80', vat: '24,66' }, voucher: { brutto: '21,24' }, grandTotal: '537,05' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case41.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1, oxfreeshipping: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19, OXSHOPID: 2 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [basketUser] }\nshop:\n    - { oxactive: 1, oxparentid: 1, oxname: subshop, activeshop: true }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: item_discount, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['18,00', '36,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }\n    totals: { totalBrutto: '225,15', totalNetto: '236,00', vats: { 19: '30,78', 11: '3,21' }, discounts: { percentage_discount: '23,60' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, delivery: { brutto: '23,56', netto: '19,80', vat: '3,76' }, payment: { brutto: '154,46', netto: '129,80', vat: '24,66' }, voucher: { brutto: '21,24' }, grandTotal: '406,15' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case42.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 1, scaleprices: [{ oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 }, { oxamount: 3, oxamountto: 4, oxartid: 1001, oxaddperc: 20 }] }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1, oxfreeshipping: 1 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['20,00', '20,00'], 1002: ['200,00', '200,00'] }\n    totals: { totalBrutto: '234,18', totalNetto: '220,00', vats: { 19: '34,20', 11: '1,98' }, discounts: { percentage_discount: '22,00' }, delivery: { brutto: '2,38', netto: '2,00', vat: '0,38' }, payment: { brutto: '26,18', netto: '22,00', vat: '4,18' }, grandTotal: '262,74' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case43.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 11, amount: 2, oxfreeshipping: 1, scaleprices: { oxamount: 1, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1, oxfreeshipping: 1 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['18,00', '36,00'], 1002: ['200,00', '200,00'] }\n    totals: { totalBrutto: '250,16', totalNetto: '236,00', vats: { 19: '34,20', 11: '3,56' }, discounts: { percentage_discount: '23,60' }, delivery: { brutto: '0,00' }, payment: { brutto: '27,80', netto: '23,60', vat: '4,20' }, grandTotal: '277,96' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case44.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1.0, oxvat: 20, amount: 1, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddperc: 10 } }\n    - { oxid: 1002, oxprice: 2.0, oxvat: 30, amount: 2 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 1.0, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['1,20', '1,20'], 1002: ['2,60', '5,20'] }\n    totals: { totalBrutto: '6,40', totalNetto: '4,50', vats: { 20: '0,18', 30: '1,08' }, discounts: { percentage_discount: '0,64' }, delivery: { brutto: '4,16', netto: '3,20', vat: '0,96' }, payment: { brutto: '1,30', netto: '1,00', vat: '0,30' }, grandTotal: '11,22' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case45.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1.0, oxvat: 20, amount: 2, scaleprices: { oxamount: 1, oxamountto: 3, oxartid: 1001, oxaddabs: 2.0 } }\n    - { oxid: 1002, oxprice: 2.0, oxvat: 30, amount: 2 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 1.0, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['1,20', '2,40'], 1002: ['2,60', '5,20'] }\n    totals: { totalBrutto: '7,60', totalNetto: '5,40', vats: { 20: '0,36', 30: '1,08' }, discounts: { percentage_discount: '0,76' }, delivery: { brutto: '4,94', netto: '3,80', vat: '1,14' }, payment: { brutto: '1,30', netto: '1,00', vat: '0,30' }, grandTotal: '13,08' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case46.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2 }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6 }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6 }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6 }\ndiscounts:\n    - { oxid: tenpercentdiscount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 1001: ['1.002,55', '2.005,10'], 1002: ['11,56', '23,12'], 1003: ['1.326,89', '7.961,34'], 1004: ['6,66', '39,96'], 1005: ['0,66', '3,96'] }\n    totals: { totalBrutto: '10.033,48', totalNetto: '8.524,80', vats: { 19: '288,13', 13: '2,39', 3: '208,70', 17: '5,23', 33: '0,88' }, discounts: { tenpercentdiscount: '1.003,35' }, grandTotal: '9.030,13' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case47.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2 }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6 }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6 }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6 }\ndiscounts:\n    - { oxid: absdiscount, oxaddsum: 125.55, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['1.002,55', '2.005,10'], 1002: ['11,56', '23,12'], 1003: ['1.326,89', '7.961,34'], 1004: ['6,66', '39,96'], 1005: ['0,66', '3,96'] }\n    totals: { totalBrutto: '10.033,48', totalNetto: '9.353,48', vats: { 19: '316,14', 13: '2,63', 3: '228,98', 17: '5,73', 33: '0,97' }, discounts: { absdiscount: '125,55' }, delivery: { brutto: '3,14', netto: '3,05', vat: '0,09' }, payment: { brutto: '7,59', netto: '7,37', vat: '0,22' }, grandTotal: '9.918,66' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case48.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2 }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6 }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6 }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6 }\ndiscounts:\n    - { oxid: absdiscount, oxaddsum: 125.55, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['842,48', '1.684,96'], 1002: ['10,23', '20,46'], 1003: ['1.288,24', '7.729,44'], 1004: ['5,69', '34,14'], 1005: ['0,50', '3,00'] }\n    totals: { totalBrutto: '9.900,49', totalNetto: '9.472,00', vats: { 19: '315,90', 13: '2,62', 3: '228,81', 17: '5,73', 33: '0,98' }, discounts: { absdiscount: '125,55' }, delivery: { brutto: '3,14', netto: '3,05', vat: '0,09' }, payment: { brutto: '7,59', netto: '7,37', vat: '0,22' }, grandTotal: '9.911,22' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case49.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1382.42, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 13.58, oxvat: 13, amount: 14 }\n    - { oxid: 1003, oxprice: 1756.66, oxvat: 3, amount: 13 }\n    - { oxid: 1004, oxprice: 13.64, oxvat: 17, amount: 62 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9001, oxprice: 3.98, oxactive: 1, oxarticles: [1001] }, { oxtype: WRAP, oxname: testWrap9002, oxprice: 1.47, oxactive: 1, oxarticles: [1002] }, { oxtype: WRAP, oxname: testWrap9003, oxprice: 2.14, oxactive: 1, oxarticles: [1003] }, { oxtype: CARD, oxname: testCard9001, oxprice: 2.97, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['1.382,42', '2.764,84'], 1002: ['13,58', '190,12'], 1003: ['1.756,66', '22.836,58'], 1004: ['13,64', '845,68'] }\n    totals: { totalBrutto: '26.637,22', totalNetto: '25.385,88', vats: { 19: '441,45', 13: '21,87', 3: '665,14', 17: '122,88' }, delivery: { brutto: '3,14' }, payment: { brutto: '7,59' }, wrapping: { brutto: '56,36' }, giftcard: { brutto: '2,97' }, grandTotal: '26.707,28' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForDelivery: false, blShowVATForPayCharge: false, blShowVATForWrapping: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case5.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87, oxvat: 17, amount: 1 }\nexpected:\n    articles: { 9200: ['87,00', '87,00'] }\n    totals: { totalBrutto: '87,00', totalNetto: '74,36', vats: { 17: '12,64' }, grandTotal: '87,00' }\noptions:\n    config: { blEnterNetPrice: false, blViewNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case50.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1382.42, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 13.58, oxvat: 13, amount: 14 }\n    - { oxid: 1003, oxprice: 1756.66, oxvat: 3, amount: 13 }\n    - { oxid: 1004, oxprice: 13.64, oxvat: 17, amount: 62 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9001, oxprice: 3.98, oxactive: 1, oxarticles: [1001] }, { oxtype: WRAP, oxname: testWrap9002, oxprice: 1.47, oxactive: 1, oxarticles: [1002] }, { oxtype: WRAP, oxname: testWrap9003, oxprice: 2.14, oxactive: 1, oxarticles: [1003] }, { oxtype: CARD, oxname: testCard9001, oxprice: 2.97, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['1.382,42', '2.764,84'], 1002: ['13,58', '190,12'], 1003: ['1.756,66', '22.836,58'], 1004: ['13,64', '845,68'] }\n    totals: { totalBrutto: '26.637,22', totalNetto: '25.385,88', vats: { 19: '441,45', 13: '21,87', 3: '665,14', 17: '122,88' }, delivery: { brutto: '3,14', netto: '3,05', vat: '0,09' }, payment: { brutto: '7,59', netto: '7,37', vat: '0,22' }, wrapping: { brutto: '56,36', netto: '51,91', vat: '4,45' }, giftcard: { brutto: '2,97', netto: '2,88', vat: '0,09' }, grandTotal: '26.707,28' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case51.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1382.42, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 13.58, oxvat: 13, amount: 14 }\n    - { oxid: 1003, oxprice: 1756.66, oxvat: 3, amount: 13 }\n    - { oxid: 1004, oxprice: 13.64, oxvat: 17, amount: 62 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9001, oxprice: 3.98, oxactive: 1, oxarticles: [1001] }, { oxtype: WRAP, oxname: testWrap9002, oxprice: 1.47, oxactive: 1, oxarticles: [1002] }, { oxtype: WRAP, oxname: testWrap9003, oxprice: 2.14, oxactive: 1, oxarticles: [1003] }, { oxtype: CARD, oxname: testCard9001, oxprice: 2.97, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['1.382,42', '2.764,84'], 1002: ['13,58', '190,12'], 1003: ['1.756,66', '22.836,58'], 1004: ['13,64', '845,68'] }\n    totals: { totalBrutto: '26.637,22', totalNetto: '25.385,88', vats: { 19: '441,45', 13: '21,87', 3: '665,14', 17: '122,88' }, delivery: { brutto: '3,14', netto: '2,99', vat: '0,15' }, payment: { brutto: '7,59', netto: '7,23', vat: '0,36' }, wrapping: { brutto: '56,36', netto: '51,91', vat: '4,45' }, giftcard: { brutto: '2,97', netto: '2,83', vat: '0,14' }, grandTotal: '26.707,28' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case52.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1382.42, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 13.58, oxvat: 13, amount: 14 }\n    - { oxid: 1003, oxprice: 1756.66, oxvat: 3, amount: 13 }\n    - { oxid: 1004, oxprice: 13.64, oxvat: 17, amount: 62 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9001, oxprice: 3.98, oxactive: 1, oxarticles: [1001] }, { oxtype: WRAP, oxname: testWrap9002, oxprice: 1.47, oxactive: 1, oxarticles: [1002] }, { oxtype: WRAP, oxname: testWrap9003, oxprice: 2.14, oxactive: 1, oxarticles: [1003] }, { oxtype: CARD, oxname: testCard9001, oxprice: 2.97, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['1.161,70', '2.323,40'], 1002: ['12,02', '168,28'], 1003: ['1.705,50', '22.171,50'], 1004: ['11,66', '722,92'] }\n    totals: { totalBrutto: '26.637,48', totalNetto: '25.386,10', vats: { 19: '441,45', 13: '21,88', 3: '665,15', 17: '122,90' }, delivery: { brutto: '3,23', netto: '3,14', vat: '0,09' }, payment: { brutto: '7,82', netto: '7,59', vat: '0,23' }, wrapping: { brutto: '61,38', netto: '56,36', vat: '5,02' }, giftcard: { brutto: '3,06', netto: '2,97', vat: '0,09' }, grandTotal: '26.712,97' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case53.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2, scaleprices: [{ oxaddabs: 1002.55, oxamount: 1, oxamountto: 5, oxartid: 1001 }, { oxaddabs: 1089.65, oxamount: 6, oxamountto: 10, oxartid: 1001 }] }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2, scaleprices: [{ oxaddabs: 11.56, oxamount: 1, oxamountto: 5, oxartid: 1002 }, { oxaddabs: 16.55, oxamount: 6, oxamountto: 10, oxartid: 1002 }] }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6, scaleprices: [{ oxaddabs: 1325.45, oxamount: 1, oxamountto: 5, oxartid: 1003 }, { oxaddabs: 1326.89, oxamount: 6, oxamountto: 10, oxartid: 1003 }] }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6, scaleprices: [{ oxaddabs: 5.65, oxamount: 1, oxamountto: 5, oxartid: 1004 }, { oxaddabs: 5.69, oxamount: 6, oxamountto: 10, oxartid: 1004 }] }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6, scaleprices: [{ oxaddabs: 0.55, oxamount: 1, oxamountto: 5, oxartid: 1005 }, { oxaddabs: 0.66, oxamount: 6, oxamountto: 10, oxartid: 1005 }] }\ndiscounts:\n    - { oxid: tenpercentdiscount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 1001: ['842,48', '1.684,96'], 1002: ['10,23', '20,46'], 1003: ['1.288,24', '7.729,44'], 1004: ['5,69', '34,14'], 1005: ['0,50', '3,00'] }\n    totals: { totalBrutto: '9.030,12', totalNetto: '9.472,00', vats: { 19: '288,13', 13: '2,39', 3: '208,69', 17: '5,22', 33: '0,89' }, discounts: { tenpercentdiscount: '947,20' }, grandTotal: '9.030,12' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case54.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2, scaleprices: [{ oxaddabs: 1002.55, oxamount: 1, oxamountto: 5, oxartid: 1001 }, { oxaddabs: 1089.65, oxamount: 6, oxamountto: 10, oxartid: 1001 }] }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2, scaleprices: [{ oxaddabs: 11.56, oxamount: 1, oxamountto: 5, oxartid: 1002 }, { oxaddabs: 16.55, oxamount: 6, oxamountto: 10, oxartid: 1002 }] }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6, scaleprices: [{ oxaddabs: 1325.45, oxamount: 1, oxamountto: 5, oxartid: 1003 }, { oxaddabs: 1326.89, oxamount: 6, oxamountto: 10, oxartid: 1003 }] }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6, scaleprices: [{ oxaddabs: 5.65, oxamount: 1, oxamountto: 5, oxartid: 1004 }, { oxaddabs: 5.69, oxamount: 6, oxamountto: 10, oxartid: 1004 }] }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6, scaleprices: [{ oxaddabs: 0.55, oxamount: 1, oxamountto: 5, oxartid: 1005 }, { oxaddabs: 0.66, oxamount: 6, oxamountto: 10, oxartid: 1005 }] }\ndiscounts:\n    - { oxid: tenpercentdiscount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 1001: ['1.002,55', '2.005,10'], 1002: ['11,56', '23,12'], 1003: ['1.326,89', '7.961,34'], 1004: ['6,66', '39,96'], 1005: ['0,66', '3,96'] }\n    totals: { totalBrutto: '10.033,48', totalNetto: '8.524,80', vats: { 19: '288,13', 13: '2,39', 3: '208,70', 17: '5,23', 33: '0,88' }, discounts: { tenpercentdiscount: '1.003,35' }, grandTotal: '9.030,13' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case55.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2, scaleprices: [{ oxaddabs: 1002.55, oxamount: 1, oxamountto: 5, oxartid: 1001 }, { oxaddabs: 1089.65, oxamount: 6, oxamountto: 10, oxartid: 1001 }] }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2, scaleprices: [{ oxaddabs: 11.56, oxamount: 1, oxamountto: 5, oxartid: 1002 }, { oxaddabs: 16.55, oxamount: 6, oxamountto: 10, oxartid: 1002 }] }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6, scaleprices: [{ oxaddabs: 1325.45, oxamount: 1, oxamountto: 5, oxartid: 1003 }, { oxaddabs: 1326.89, oxamount: 6, oxamountto: 10, oxartid: 1003 }] }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6, scaleprices: [{ oxaddabs: 5.65, oxamount: 1, oxamountto: 5, oxartid: 1004 }, { oxaddabs: 5.69, oxamount: 6, oxamountto: 10, oxartid: 1004 }] }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6, scaleprices: [{ oxaddabs: 0.55, oxamount: 1, oxamountto: 5, oxartid: 1005 }, { oxaddabs: 0.66, oxamount: 6, oxamountto: 10, oxartid: 1005 }] }\ndiscounts:\n    - { oxid: absdiscount, oxaddsum: 125.55, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['1.002,55', '2.005,10'], 1002: ['11,56', '23,12'], 1003: ['1.326,89', '7.961,34'], 1004: ['6,66', '39,96'], 1005: ['0,66', '3,96'] }\n    totals: { totalBrutto: '10.033,48', totalNetto: '9.353,48', vats: { 19: '316,14', 13: '2,63', 3: '228,98', 17: '5,73', 33: '0,97' }, discounts: { absdiscount: '125,55' }, delivery: { brutto: '3,14', netto: '3,05', vat: '0,09' }, payment: { brutto: '7,59', netto: '7,37', vat: '0,22' }, grandTotal: '9.918,66' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case56.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1002.55, oxvat: 19, amount: 2, scaleprices: [{ oxaddabs: 1002.55, oxamount: 1, oxamountto: 5, oxartid: 1001 }, { oxaddabs: 1089.65, oxamount: 6, oxamountto: 10, oxartid: 1001 }] }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2, scaleprices: [{ oxaddabs: 11.56, oxamount: 1, oxamountto: 5, oxartid: 1002 }, { oxaddabs: 16.55, oxamount: 6, oxamountto: 10, oxartid: 1002 }] }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6, scaleprices: [{ oxaddabs: 1325.45, oxamount: 1, oxamountto: 5, oxartid: 1003 }, { oxaddabs: 1326.89, oxamount: 6, oxamountto: 10, oxartid: 1003 }] }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6, scaleprices: [{ oxaddabs: 5.65, oxamount: 1, oxamountto: 5, oxartid: 1004 }, { oxaddabs: 5.69, oxamount: 6, oxamountto: 10, oxartid: 1004 }] }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6, scaleprices: [{ oxaddabs: 0.55, oxamount: 1, oxamountto: 5, oxartid: 1005 }, { oxaddabs: 0.66, oxamount: 6, oxamountto: 10, oxartid: 1005 }] }\ndiscounts:\n    - { oxid: absdiscount, oxaddsum: 125.55, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 3.14, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.59, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 1001: ['842,48', '1.684,96'], 1002: ['10,23', '20,46'], 1003: ['1.288,24', '7.729,44'], 1004: ['5,69', '34,14'], 1005: ['0,50', '3,00'] }\n    totals: { totalBrutto: '9.900,49', totalNetto: '9.472,00', vats: { 19: '315,90', 13: '2,62', 3: '228,81', 17: '5,73', 33: '0,98' }, discounts: { absdiscount: '125,55' }, delivery: { brutto: '3,14', netto: '3,05', vat: '0,09' }, payment: { brutto: '7,59', netto: '7,37', vat: '0,22' }, grandTotal: '9.911,22' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case57.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 0.55, oxvat: 19, amount: 1, scaleprices: { oxamount: 2, oxamountto: 3, oxartid: 1001, oxaddabs: 2.0 } }\n    - { oxid: 1002, oxprice: 5.52, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 945.95, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 4.74, oxvat: 19, amount: 1 }\n    - { oxid: 1005, oxprice: 1.0, oxvat: 19, amount: 5 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.5, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['0,55', '0,55'], 1002: ['5,52', '5,52'], 1003: ['945,95', '945,95'], 1004: ['4,74', '4,74'], 1005: ['1,00', '5,00'] }\n    totals: { totalBrutto: '1.030,04', totalNetto: '961,76', vats: { 19: '164,46' }, discounts: { percentage_discount: '96,18' }, delivery: { brutto: '114,45', netto: '96,18', vat: '18,27' }, payment: { brutto: '8,93', netto: '7,50', vat: '1,43' }, grandTotal: '1.153,42' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case58.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 9.0, oxvat: 19, amount: 4, scaleprices: { oxaddabs: 2.0, oxamount: 3, oxamountto: 5, oxartid: 1001 } }\n    - { oxid: 1002, oxprice: 5.52, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 945.95, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 4.74, oxvat: 19, amount: 1 }\n    - { oxid: 1005, oxprice: 1.0, oxvat: 19, amount: 5 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.5, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['2,00', '8,00'], 1002: ['5,52', '5,52'], 1003: ['945,95', '945,95'], 1004: ['4,74', '4,74'], 1005: ['1,00', '5,00'] }\n    totals: { totalBrutto: '1.038,02', totalNetto: '969,21', vats: { 19: '165,73' }, discounts: { percentage_discount: '96,92' }, delivery: { brutto: '115,33', netto: '96,92', vat: '18,41' }, payment: { brutto: '8,93', netto: '7,50', vat: '1,43' }, grandTotal: '1.162,28' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case59.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 10.55, oxvat: 19, amount: 4, scaleprices: { oxaddabs: 2.0, oxamount: 3, oxamountto: 5, oxartid: 1001 } }\n    - { oxid: 1002, oxprice: 20.52, oxvat: 19, amount: 3, scaleprices: { oxaddabs: 2.0, oxamount: 3, oxamountto: 5, oxartid: 1002 } }\n    - { oxid: 1003, oxprice: 945.95, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 4.74, oxvat: 19, amount: 1 }\n    - { oxid: 1005, oxprice: 1.0, oxvat: 19, amount: 5 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.5, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['2,00', '8,00'], 1002: ['2,00', '6,00'], 1003: ['945,95', '945,95'], 1004: ['4,74', '4,74'], 1005: ['1,00', '5,00'] }\n    totals: { totalBrutto: '1.038,54', totalNetto: '969,69', vats: { 19: '165,82' }, discounts: { percentage_discount: '96,97' }, delivery: { brutto: '115,39', netto: '96,97', vat: '18,42' }, payment: { brutto: '8,93', netto: '7,50', vat: '1,43' }, grandTotal: '1.162,86' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case6.yaml",
    "content": "articles:\n    - { oxid: 9209, oxprice: 42.36, oxvat: 18, amount: 1 }\nexpected:\n    articles: { 9209: ['24,57', '24,57'] }\n    totals: { totalBrutto: '24,57', totalNetto: '20,82', vats: { 18: '3,75' }, grandTotal: '24,57' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.58\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case60.yaml",
    "content": "articles:\n    - { oxid: testarticle, oxprice: 12.0, amount: 2, scaleprices: { oxaddabs: 11.95, oxamount: 2, oxamountto: 2, oxartid: testarticle } }\ndiscounts:\n    - { oxid: _testDiscount, oxactive: 1, oxtitle: 'new discount', oxprice: 12, oxpriceto: 24.99, oxaddsumtype: abs, oxaddsum: 3, oxsort: 10 }\nexpected:\n    articles: { testarticle: ['11,95', '23,90'] }\n    totals: { totalBrutto: '23,90', totalNetto: '17,56', vats: { 19: '3,34' }, discounts: { _testDiscount: '3,00' }, grandTotal: '20,90' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case61.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 9002, oxprice: 66, oxvat: 19, amount: 16 }\ndiscounts:\n    - { oxid: shopdiscount5for9001, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9001], oxsort: 10 }\n    - { oxid: shopdiscount5for9002, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [9002], oxsort: 20 }\n    - { oxid: basketdiscount5for9001, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9001], oxsort: 30 }\n    - { oxid: basketdiscount5for9002, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [9002], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9001, oxprice: 9, oxactive: 1, oxarticles: [9001] }, { oxtype: WRAP, oxname: testWrap9002, oxprice: 6, oxactive: 1, oxarticles: [9002] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxarticles: [9001, 9002] }]\n    voucherserie: [{ oxserienr: abs_4_voucher_serie, oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 9001: ['63,92', '2.109,36'], 9002: ['40,08', '641,28'] }\n    totals: { totalBrutto: '2.750,64', totalNetto: '2.305,18', vats: { 19: '437,98' }, discounts: { absolutebasketdiscount: '3,40' }, wrapping: { brutto: '267,24', netto: '224,57', vat: '42,67' }, delivery: { brutto: '4,08', netto: '3,43', vat: '0,65' }, payment: { brutto: '0,68', netto: '0,57', vat: '0,11' }, voucher: { brutto: '4,08' }, grandTotal: '3.015,16' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case65.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 100.0, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: abs_discount_for_111, oxaddsum: 15.0, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxprice: 85, oxpriceto: 110, oxactive: 1, oxarticles: [111], oxsort: 10 }\nexpected:\n    articles: { 111: ['68,00', '68,00'] }\n    totals: { totalBrutto: '68,00', totalNetto: '57,14', vats: { 19: '10,86' }, grandTotal: '68,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.8\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case66.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 120.0, oxvat: 20, amount: 1 }\ndiscounts:\n    - { oxid: discount_for_111, oxaddsum: 50.0, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 99999, oxactive: 1, oxarticles: [111], oxsort: 10 }\n    - { oxid: basket, oxaddsum: 50.0, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 99999, oxactive: 1, oxsort: 20 }\nexpected:\n    articles: { 111: ['60,00', '60,00'] }\n    totals: { totalBrutto: '60,00', totalNetto: '25,00', vats: { 20: '5,00' }, discounts: { basket: '30,00' }, grandTotal: '30,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case67.yaml",
    "content": "articles:\n    - { oxid: _testProduct, oxprice: 10.0, oxvat: 19, amount: 36 }\ndiscounts:\n    - { oxid: basket_0, oxaddsum: 6.0, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 199, oxactive: 1, oxsort: 10 }\n    - { oxid: basket_1, oxaddsum: 9.0, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 200, oxpriceto: 299, oxactive: 1, oxsort: 20 }\n    - { oxid: basket_2, oxaddsum: 12.0, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 300, oxpriceto: 99999, oxactive: 1, oxsort: 30 }\nexpected:\n    articles: { _testProduct: ['10,00', '360,00'] }\n    totals: { totalBrutto: '360,00', totalNetto: '266,22', vats: { 19: '50,58' }, discounts: { basket_2: '43,20' }, grandTotal: '316,80' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case68.yaml",
    "content": "articles:\n    - { oxid: _testProduct, oxprice: 10.0, oxvat: 19, amount: 31 }\ndiscounts:\n    - { oxid: basket_0, oxaddsum: 6.0, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 199, oxactive: 1, oxarticles: [_testProduct], oxsort: 10 }\n    - { oxid: basket_1, oxaddsum: 9.0, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 200, oxpriceto: 299, oxactive: 1, oxarticles: [_testProduct], oxsort: 20 }\n    - { oxid: basket_2, oxaddsum: 12.0, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 300, oxpriceto: 99999, oxactive: 1, oxarticles: [_testProduct], oxsort: 30 }\nexpected:\n    articles: { _testProduct: ['8,80', '272,80'] }\n    totals: { totalBrutto: '272,80', totalNetto: '229,24', vats: { 19: '43,56' }, grandTotal: '272,80' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case69.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 1 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '10,00'] }\n    totals: { totalBrutto: '11,90', totalNetto: '10,00', vats: { 19: '1,90' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '35,70' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case7.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87, oxvat: 17, amount: 1 }\nexpected:\n    articles: { 9200: ['59,16', '59,16'] }\n    totals: { totalBrutto: '59,16', totalNetto: '50,56', vats: { 17: '8,60' }, grandTotal: '59,16' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case70.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 50 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '500,00'] }\n    totals: { totalBrutto: '595,00', totalNetto: '500,00', vats: { 19: '95,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '618,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case71.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 50 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '500,00'] }\n    totals: { totalBrutto: '595,00', totalNetto: '500,00', vats: { 19: '95,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '618,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case72.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 200 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '2.000,00'] }\n    totals: { totalBrutto: '2.380,00', totalNetto: '2.000,00', vats: { 19: '380,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '2.403,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case73.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 200 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '2.000,00'] }\n    totals: { totalBrutto: '2.380,00', totalNetto: '2.000,00', vats: { 19: '380,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '2.403,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case74.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 250 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '2.500,00'] }\n    totals: { totalBrutto: '2.975,00', totalNetto: '2.500,00', vats: { 19: '475,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '2.998,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case75.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 250 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '2.500,00'] }\n    totals: { totalBrutto: '2.975,00', totalNetto: '2.500,00', vats: { 19: '475,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '2.998,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case76.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 250 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '2.500,00'] }\n    totals: { totalBrutto: '2.975,00', totalNetto: '2.500,00', vats: { 19: '475,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '2.998,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case77.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 510 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '5.100,00'] }\n    totals: { totalBrutto: '6.069,00', totalNetto: '5.100,00', vats: { 19: '969,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '6.092,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case78.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 510 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '5.100,00'] }\n    totals: { totalBrutto: '6.069,00', totalNetto: '5.100,00', vats: { 19: '969,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '6.092,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case79.yaml",
    "content": "articles:\n    - { oxid: 9001, oxprice: 10, oxvat: 19, amount: 1000 }\ncosts:\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 10, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 10, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 9001: ['10,00', '10.000,00'] }\n    totals: { totalBrutto: '11.900,00', totalNetto: '10.000,00', vats: { 19: '1.900,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '1,90' }, payment: { brutto: '11,90', netto: '10,00', vat: '1,90' }, grandTotal: '11.923,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case8.yaml",
    "content": "articles:\n    - { oxid: 9200, oxprice: 87, oxvat: 17, amount: 1 }\nexpected:\n    articles: { 9200: ['127,89', '127,89'] }\n    totals: { totalBrutto: '127,89', totalNetto: '109,31', vats: { 17: '18,58' }, grandTotal: '127,89' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1.47\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case80.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 30.0, oxvat: 25, amount: 15 }\n    - { oxid: 1002, oxprice: 100.0, oxvat: 20, amount: 15 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['30,00', '450,00'], 1002: ['100,00', '1.500,00'] }\n    totals: { totalBrutto: '2.362,50', totalNetto: '1.950,00', vats: { 25: '112,50', 20: '300,00' }, delivery: { brutto: '1.299,38', netto: '1.072,50', vat: '226,88' }, payment: { brutto: '1.299,38', netto: '1.072,50', vat: '226,88' }, grandTotal: '4.961,26' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case81.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 30.0, oxvat: 25, amount: 15 }\n    - { oxid: 1002, oxprice: 100.0, oxvat: 20, amount: 15 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    articles: { 1001: ['30,00', '450,00'], 1002: ['100,00', '1.500,00'] }\n    totals: { totalBrutto: '2.362,50', totalNetto: '1.950,00', vats: { 25: '112,50', 20: '300,00' }, delivery: { brutto: '1.299,38', netto: '1.072,50', vat: '226,88' }, payment: { brutto: '1.299,38', netto: '1.072,50', vat: '226,88' }, grandTotal: '4.961,26' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case82.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 30.0, oxvat: 25, amount: 15 }\n    - { oxid: 1002, oxprice: 100.0, oxvat: 20, amount: 15 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['30,00', '450,00'], 1002: ['100,00', '1.500,00'] }\n    totals: { totalBrutto: '2.126,25', totalNetto: '1.950,00', vats: { 25: '101,25', 20: '270,00' }, delivery: { brutto: '1.299,38', netto: '1.072,50', vat: '226,88' }, payment: { brutto: '1.299,38', netto: '1.072,50', vat: '226,88' }, voucher: { brutto: '195,00' }, grandTotal: '4.725,01' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case83.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 30.0, oxvat: 25, amount: 15 }\n    - { oxid: 1002, oxprice: 100.0, oxvat: 20, amount: 15 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55.0, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 1001: ['20,40', '306,00'], 1002: ['68,00', '1.020,00'] }\n    totals: { totalBrutto: '1.445,85', totalNetto: '1.326,00', vats: { 25: '68,85', 20: '183,60' }, delivery: { brutto: '883,57', netto: '729,30', vat: '154,27' }, payment: { brutto: '883,57', netto: '729,30', vat: '154,27' }, voucher: { brutto: '132,60' }, grandTotal: '3.212,99' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: proportional, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 0.68\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case84.yaml",
    "content": "articles:\n    - { oxid: rounding_issue_test_article, oxprice: 298.55, oxvat: 19, amount: 200 }\ndiscounts:\n    - { oxid: discount_2_55_forShop, oxaddsum: 2.55, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { rounding_issue_test_article: ['298,55', '59.710,00'] }\n    totals: { totalBrutto: '59.710,00', discounts: { discount_2_55_forShop: '1.522,60' }, totalNetto: '48.896,98', vats: { 19: '9.290,42' }, grandTotal: '58.187,40' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case85.yaml",
    "content": "articles:\n    - { oxid: rounding_issue_test_article, oxprice: 298.55, oxvat: 19, amount: 200 }\ndiscounts:\n    - { oxid: discount_2_55_forBasket, oxaddsum: 2.55, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [rounding_issue_test_article], oxsort: 10 }\nexpected:\n    articles: { rounding_issue_test_article: ['290,94', '58.188,00'] }\n    totals: { totalBrutto: '58.188,00', totalNetto: '48.897,48', vats: { 19: '9.290,52' }, grandTotal: '58.188,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case86.yaml",
    "content": "articles:\n    - { oxid: rounding_issue_test_article, oxprice: 298.55, oxvat: 19, amount: 200 }\ndiscounts:\n    - { oxid: discount_2_55_forShop, oxaddsum: 2.55, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [rounding_issue_test_article], oxsort: 10 }\nexpected:\n    articles: { rounding_issue_test_article: ['290,94', '58.188,00'] }\n    totals: { totalBrutto: '58.188,00', totalNetto: '48.897,48', vats: { 19: '9.290,52' }, grandTotal: '58.188,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case87.yaml",
    "content": "articles:\n    - { oxid: rounding_issue_test_article, oxprice: 298.55, oxvat: 19, amount: 200 }\ndiscounts:\n    - { oxid: discount_2_55_forShop, oxaddsum: 2.55, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { rounding_issue_test_article: ['290,94', '58.188,00'] }\n    totals: { totalBrutto: '58.188,00', totalNetto: '48.897,48', vats: { 19: '9.290,52' }, grandTotal: '58.188,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case88.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 1.0, oxvat: 20, amount: 1 }\n    - { oxid: 1111, oxprice: 95.02, oxvat: 20, amount: 6 }\n    - { oxid: 1112, oxprice: 105.78, oxvat: 20, amount: 7 }\ndiscounts:\n    - { oxid: procdiscountforbasket, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['1,00', '1,00'], 1111: ['95,02', '570,12'], 1112: ['105,78', '740,46'] }\n    totals: { totalBrutto: '1.416,50', totalNetto: '1.311,58', vats: { 20: '236,08' }, discounts: { procdiscountforbasket: '131,16' }, delivery: { brutto: '786,95', netto: '655,79', vat: '131,16' }, payment: { brutto: '1,20', netto: '1,00', vat: '0,20' }, grandTotal: '2.204,65' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case89.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 19, amount: 1 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 5.02, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: procdiscountforbasket, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountfor1113, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114], oxsort: 30 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['5,52', '5,52'], 1113: ['945,95', '945,95'], 1114: ['4,74', '4,74'] }\n    totals: { totalBrutto: '1.024,69', totalNetto: '956,76', vats: { 19: '163,61' }, discounts: { procdiscountforbasket: '95,68' }, delivery: { brutto: '569,27', netto: '478,38', vat: '90,89' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '1.653,46' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case9.yaml",
    "content": "articles:\n    - { oxid: 9207, oxprice: 45.5, oxvat: 19, amount: 1 }\nexpected:\n    articles: { 9207: ['45,50', '45,50'] }\n    totals: { totalBrutto: '45,50', totalNetto: '38,24', vats: { 19: '7,26' }, grandTotal: '45,50' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case90.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 3 }\n    - { oxid: 1112, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1113, oxprice: 0.9, oxvat: 19, amount: 3 }\n    - { oxid: 1114, oxprice: 5.02, oxvat: 55, amount: 1 }\n    - { oxid: 1115, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1114], oxsort: 20 }\n    - { oxid: discountforbasket1112, oxaddsum: -5.2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1112], oxsort: 30 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '1,65'], 1112: ['105,78', '105,78'], 1113: ['0,90', '2,70'], 1114: ['5,52', '5,52'], 1115: ['0,50', '0,50'] }\n    totals: { totalBrutto: '126,18', totalNetto: '116,15', vats: { 19: '18,92', 55: '2,73' }, discounts: { discountforbasket10%: '11,62' }, delivery: { brutto: '69,10', netto: '58,07', vat: '11,03' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '254,78' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blPaymentVatOnTop: true, blDeliveryVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case91.yaml",
    "content": "articles:\n    - { oxid: _test1001, oxprice: 1002.55, oxvat: 19, amount: 2 }\n    - { oxid: 1002, oxprice: 11.56, oxvat: 13, amount: 2 }\n    - { oxid: 1003, oxprice: 1326.89, oxvat: 3, amount: 6 }\n    - { oxid: 1004, oxprice: 6.66, oxvat: 17, amount: 6 }\n    - { oxid: 1005, oxprice: 0.66, oxvat: 33, amount: 6 }\ndiscounts:\n    - { oxid: tenpercentdiscount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { _test1001: ['842,48', '1.684,96'], 1002: ['10,23', '20,46'], 1003: ['1.288,24', '7.729,44'], 1004: ['5,69', '34,14'], 1005: ['0,50', '3,00'] }\n    totals: { totalBrutto: '9.030,12', totalNetto: '9.472,00', vats: { 19: '288,13', 13: '2,39', 3: '208,69', 17: '5,22', 33: '0,89' }, discounts: { tenpercentdiscount: '947,20' }, grandTotal: '9.030,12' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case92.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 19, amount: 1 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 5.02, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountfor1113, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114], oxsort: 30 }\n    - { oxid: discountforbasket20%, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 40 }\n    - { oxid: discountforbasket35%, oxaddsum: 35, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['5,52', '5,52'], 1113: ['945,95', '945,95'], 1114: ['4,74', '4,74'] }\n    totals: { totalBrutto: '532,84', totalNetto: '956,76', vats: { 19: '85,08' }, discounts: { discountforbasket10%: '95,68', discountforbasket20%: '172,22', discountforbasket35%: '241,10' }, delivery: { brutto: '569,27', netto: '478,38', vat: '90,89' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '1.161,61' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case93.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 55, amount: 1 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 5.02, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountforproduct1, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountforproduct2, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114], oxsort: 30 }\n    - { oxid: discountforbasket20%, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 40 }\n    - { oxid: discountforbasket35%, oxaddsum: 35, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['5,52', '5,52'], 1113: ['945,95', '945,95'], 1114: ['4,74', '4,74'] }\n    totals: { totalBrutto: '533,76', totalNetto: '956,76', vats: { 19: '84,58', 55: '1,42' }, discounts: { discountforbasket10%: '95,68', discountforbasket20%: '172,22', discountforbasket35%: '241,10' }, delivery: { brutto: '569,27', netto: '478,38', vat: '90,89' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '1.162,53' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case94.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 55, amount: 1 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1115, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1116, oxprice: 1.0, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountfor1113, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114], oxsort: 30 }\n    - { oxid: discountforbasket20%, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 40 }\n    - { oxid: discountforbasket35%, oxaddsum: 35, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\n    - { oxid: discountforbasket1115, oxaddsum: -5.2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1115], oxsort: 60 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['5,52', '5,52'], 1113: ['945,95', '945,95'], 1114: ['95,02', '95,02'], 1115: ['105,78', '105,78'], 1116: ['1,00', '1,00'] }\n    totals: { totalBrutto: '643,52', totalNetto: '1.153,82', vats: { 19: '102,11', 55: '1,42' }, discounts: { discountforbasket10%: '115,38', discountforbasket20%: '207,69', discountforbasket35%: '290,76' }, delivery: { brutto: '686,52', netto: '576,91', vat: '109,61' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '1.389,54' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case95.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 55, amount: 5 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1115, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1116, oxprice: 1.0, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountfor1113, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114], oxsort: 30 }\n    - { oxid: discountforbasket20%, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 40 }\n    - { oxid: discountforbasket35%, oxaddsum: 35, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\n    - { oxid: discountforbasket1115, oxaddsum: -5.2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1115], oxsort: 60 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['5,52', '27,60'], 1113: ['945,95', '945,95'], 1114: ['95,02', '95,02'], 1115: ['105,78', '105,78'], 1116: ['1,00', '1,00'] }\n    totals: { totalBrutto: '659,53', totalNetto: '1.175,90', vats: { 19: '102,11', 55: '7,10' }, discounts: { discountforbasket10%: '117,59', discountforbasket20%: '211,66', discountforbasket35%: '296,33' }, delivery: { brutto: '699,66', netto: '587,95', vat: '111,71' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '1.418,69' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case96.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 55, amount: 5 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1115, oxprice: 100.55, oxvat: 19, amount: 1 }\n    - { oxid: 1116, oxprice: 1.0, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountfor1113, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114], oxsort: 30 }\n    - { oxid: discountforbasket1115, oxaddsum: -5.2, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1115], oxsort: 40 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 50, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['5,52', '27,60'], 1113: ['945,95', '945,95'], 1114: ['95,02', '95,02'], 1115: ['105,78', '105,78'], 1116: ['1,00', '1,00'] }\n    totals: { totalBrutto: '1.268,33', totalNetto: '1.175,90', vats: { 19: '196,36', 55: '13,66' }, discounts: { discountforbasket10%: '117,59' }, delivery: { brutto: '699,66', netto: '587,95', vat: '111,71' }, payment: { brutto: '59,50', netto: '50,00', vat: '9,50' }, grandTotal: '2.027,49' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case97.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 19, amount: 1 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 19, amount: 1 }\n    - { oxid: 1113, oxprice: 1, oxvat: 19, amount: 1 }\n    - { oxid: 1114, oxprice: 1001, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112, 1114], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 0.55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,50', '0,50'], 1112: ['4,97', '4,97'], 1113: ['0,90', '0,90'], 1114: ['990,99', '990,99'] }\n    totals: { totalBrutto: '1.186,86', totalNetto: '997,36', vats: { 19: '189,50' }, delivery: { brutto: '652,77', netto: '548,55', vat: '104,22' }, payment: { brutto: '0,65', netto: '0,55', vat: '0,10' }, grandTotal: '1.840,28' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case98.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.5, oxvat: 99, amount: 3 }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 99, amount: 2 }\n    - { oxid: 1113, oxprice: 1001, oxvat: 99, amount: 1 }\n    - { oxid: 1114, oxprice: 5.02, oxvat: 99, amount: 1 }\n    - { oxid: 1115, oxprice: 100.55, oxvat: 99, amount: 1 }\n    - { oxid: 1116, oxprice: 5, oxvat: 9, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor111, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [111, 1112], oxsort: 20 }\n    - { oxid: procdiscountfor1113, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1113, 1114, 1115], oxsort: 30 }\n    - { oxid: procdiscount20%, oxaddsum: 20, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 40 }\n    - { oxid: procdiscount35%, oxaddsum: 35, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 0.55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '1,65'], 1112: ['5,52', '11,04'], 1113: ['945,95', '945,95'], 1114: ['4,74', '4,74'], 1115: ['95,02', '95,02'], 1116: ['5,00', '5,00'] }\n    totals: { totalBrutto: '988,26', totalNetto: '1.063,40', vats: { 99: '490,38', 9: '0,21' }, discounts: { discountforbasket10%: '106,34', procdiscount20%: '191,41', procdiscount35%: '267,98' }, delivery: { brutto: '1.163,89', netto: '584,87', vat: '579,02' }, payment: { brutto: '1,09', netto: '0,55', vat: '0,54' }, grandTotal: '2.153,24' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/case99.yaml",
    "content": "articles:\n    - { oxid: 111, oxprice: 0.55, oxvat: 33, amount: 1 }\n    - { oxid: 1112, oxprice: 1101.1, oxvat: 33, amount: 1 }\n    - { oxid: 1113, oxprice: 110, oxvat: 33, amount: 1 }\n    - { oxid: 1114, oxprice: 1.0, oxvat: 33, amount: 1 }\ndiscounts:\n    - { oxid: discountforbasket10%, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0.1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    articles: { 111: ['0,55', '0,55'], 1112: ['1.101,10', '1.101,10'], 1113: ['110,00', '110,00'], 1114: ['1,00', '1,00'] }\n    totals: { totalBrutto: '1.451,55', totalNetto: '1.212,65', vats: { 33: '360,16' }, discounts: { discountforbasket10%: '121,26' }, delivery: { brutto: '887,06', netto: '666,96', vat: '220,10' }, payment: { brutto: '73,15', netto: '55,00', vat: '18,15' }, grandTotal: '2.411,76' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testDeliveryBug4622(1).yaml",
    "content": "articles:\n    - { oxid: vine1, oxprice: 100, oxvat: 19, amount: 1 }\n    - { oxid: coupon, oxprice: 50, oxvat: 19, amount: 2, oxnonmaterial: true }\ncosts:\n    delivery: [{ oxtitle: '10% from total ', oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 1000, oxsort: 1 }]\nexpected:\n    articles: { vine1: ['100,00', '100,00'], coupon: ['50,00', '100,00'] }\n    totals: { totalBrutto: '200,00', totalNetto: '168,07', vats: { 19: '31,93' }, delivery: { brutto: '10,00' }, grandTotal: '210,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net, blExclNonMaterialFromDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testDeliveryBug4622(2).yaml",
    "content": "articles:\n    - { oxid: vine1, oxprice: 100, oxvat: 19, amount: 1 }\n    - { oxid: coupon, oxprice: 50, oxvat: 19, amount: 2, oxnonmaterial: true }\ncosts:\n    delivery: [{ oxtitle: '10% from total ', oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 1000, oxsort: 1 }]\nexpected:\n    articles: { vine1: ['100,00', '100,00'], coupon: ['50,00', '100,00'] }\n    totals: { totalBrutto: '200,00', totalNetto: '168,07', vats: { 19: '31,93' }, delivery: { brutto: '20,00' }, grandTotal: '220,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net, blExclNonMaterialFromDelivery: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testDeliveryBug4622(3).yaml",
    "content": "articles:\n    - { oxid: coupon, oxprice: 50, oxvat: 19, amount: 2, oxnonmaterial: true }\ncosts:\n    delivery: [{ oxtitle: '10% from total ', oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 1000, oxsort: 1 }]\nexpected:\n    articles: { coupon: ['50,00', '100,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '84,03', vats: { 19: '15,97' }, delivery: { brutto: '10,00' }, grandTotal: '110,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net, blExclNonMaterialFromDelivery: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testDeliveryBug4622(4).yaml",
    "content": "articles:\n    - { oxid: coupon, oxprice: 50, oxvat: 19, amount: 2, oxnonmaterial: true }\ncosts:\n    delivery: [{ oxtitle: '10% from total ', oxactive: 1, oxaddsum: 10, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 1000, oxsort: 1 }]\nexpected:\n    articles: { coupon: ['50,00', '100,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '84,03', vats: { 19: '15,97' }, delivery: { brutto: '0,00' }, grandTotal: '100,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net, blExclNonMaterialFromDelivery: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testDeliveryBug4730(1).yaml",
    "content": "categories:\n    - { oxid: vine, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [vine1] }\n    - { oxid: supplies, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [supply1] }\narticles:\n    - { oxid: vine1, oxprice: 5, oxvat: 10, amount: 6 }\n    - { oxid: supply1, oxprice: 10, oxvat: 10, amount: 1 }\ncosts:\n    delivery: [\n        { oxtitle: '1 - 3 Bottles', oxactive: 1, oxaddsum: 4.9, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 1, oxparamend: 3, oxsort: 1, oxcategories: [vine] },\n        { oxtitle: '4 - 11 Bottles', oxactive: 1, oxaddsum: 5.9, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 4, oxparamend: 11, oxsort: 1, oxcategories: [vine] },\n        { oxtitle: 'more than 12 Bottles', oxactive: 1, oxaddsum: 0, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 12, oxparamend: 99999, oxsort: 1, oxcategories: [vine] },\n        { oxtitle: supplies, oxactive: 1, oxaddsum: 2.9, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 0, oxparamend: 99999, oxsort: 4, oxcategories: [supplies] }\n    ]\nexpected:\n    articles: { vine1: ['5,00', '30,00'], supply1: ['10,00', '10,00'] }\n    totals: { totalBrutto: '40,00', totalNetto: '36,36', vats: { 10: '3,64' }, delivery: { brutto: '5,90' }, grandTotal: '45,90' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testDeliveryBug4730(2).yaml",
    "content": "categories:\n    - { oxid: vine, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [vine1] }\n    - { oxid: supplies, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [supply1] }\narticles:\n    - { oxid: vine1, oxprice: 5, oxvat: 10, amount: 2 }\n    - { oxid: supply1, oxprice: 10, oxvat: 10, amount: 1 }\ncosts:\n    delivery: [\n        { oxtitle: '1 - 3 Bottles', oxactive: 1, oxaddsum: 4.9, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 1, oxparamend: 3, oxsort: 1, oxcategories: [vine] },\n        { oxtitle: '4 - 11 Bottles', oxactive: 1, oxaddsum: 5.9, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 4, oxparamend: 11, oxsort: 1, oxcategories: [vine] },\n        { oxtitle: 'more than 12 Bottles', oxactive: 1, oxaddsum: 0, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 12, oxparamend: 99999, oxsort: 1, oxcategories: [vine] },\n        { oxtitle: supplies, oxactive: 1, oxaddsum: 2.9, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 0, oxparamend: 99999, oxsort: 4, oxcategories: [supplies] }\n    ]\nexpected:\n    articles: { vine1: ['5,00', '10,00'], supply1: ['10,00', '10,00'] }\n    totals: { totalBrutto: '20,00', totalNetto: '18,18', vats: { 10: '1,82' }, delivery: { brutto: '4,90' }, grandTotal: '24,90' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testDeliveryByWeight(1).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 1.8, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 2 }\n    - { oxid: 10012, oxprice: 2.0, oxvat: 19, amount: 1, oxweight: 2 }\n    - { oxid: 10013, oxprice: 1.7, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 15.0, oxfinalize: 0, oxparamend: 999, oxfixed: 2, oxsort: 4 }, { oxactive: 1, oxaddsum: 1.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 1.0, oxfinalize: 0, oxparamend: 4.99999999, oxfixed: 2, oxsort: 1 }, { oxactive: 1, oxaddsum: 5.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 5.0, oxfinalize: 0, oxparamend: 14.9999999, oxfixed: 2, oxsort: 2 }]\nexpected:\n    articles: { 10011: ['1,80', '1,80'], 10012: ['2,00', '2,00'], 10013: ['1,70', '1,70'] }\n    totals: { totalBrutto: '5,50', totalNetto: '4,62', vats: { 19: '0,88' }, delivery: { brutto: '12,00' }, grandTotal: '17,50' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testDeliveryByWeight(2).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 1.8, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 2 }\n    - { oxid: 10012, oxprice: 2.0, oxvat: 19, amount: 3, oxweight: 2 }\n    - { oxid: 10013, oxprice: 1.7, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 15.0, oxfinalize: 0, oxparamend: 999, oxfixed: 2, oxsort: 4 }, { oxactive: 1, oxaddsum: 1.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 1.0, oxfinalize: 0, oxparamend: 4.99999999, oxfixed: 2, oxsort: 1 }, { oxactive: 1, oxaddsum: 5.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 5.0, oxfinalize: 0, oxparamend: 14.9999999, oxfixed: 2, oxsort: 2 }]\nexpected:\n    articles: { 10011: ['1,80', '1,80'], 10012: ['2,00', '6,00'], 10013: ['1,70', '1,70'] }\n    totals: { totalBrutto: '9,50', totalNetto: '7,98', vats: { 19: '1,52' }, delivery: { brutto: '14,00' }, grandTotal: '23,50' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testDeliveryByWeight(3).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 1.8, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 2 }\n    - { oxid: 10012, oxprice: 2.0, oxvat: 19, amount: 3, oxweight: 2 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 15.0, oxfinalize: 0, oxparamend: 999, oxfixed: 2, oxsort: 4 }, { oxactive: 1, oxaddsum: 1.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 1.0, oxfinalize: 0, oxparamend: 4.99999999, oxfixed: 2, oxsort: 1 }, { oxactive: 1, oxaddsum: 5.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 5.0, oxfinalize: 0, oxparamend: 14.9999999, oxfixed: 2, oxsort: 2 }]\nexpected:\n    articles: { 10011: ['1,80', '1,80'], 10012: ['2,00', '6,00'] }\n    totals: { totalBrutto: '7,80', totalNetto: '6,55', vats: { 19: '1,25' }, delivery: { brutto: '4,00' }, grandTotal: '11,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testDeliveryByWeight(4).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 1.8, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 2 }\n    - { oxid: 10012, oxprice: 2.0, oxvat: 19, amount: 1, oxweight: 2 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 15.0, oxfinalize: 0, oxparamend: 999, oxfixed: 2, oxsort: 4 }, { oxactive: 1, oxaddsum: 1.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 1.0, oxfinalize: 0, oxparamend: 4.99999999, oxfixed: 2, oxsort: 1 }, { oxactive: 1, oxaddsum: 5.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 5.0, oxfinalize: 0, oxparamend: 14.9999999, oxfixed: 2, oxsort: 2 }]\nexpected:\n    articles: { 10011: ['1,80', '1,80'], 10012: ['2,00', '2,00'] }\n    totals: { totalBrutto: '3,80', totalNetto: '3,19', vats: { 19: '0,61' }, delivery: { brutto: '2,00' }, grandTotal: '5,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testDeliveryByWeight(5).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 1.8, oxvat: 19, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 2 }\n    - { oxid: 10012, oxprice: 2.0, oxvat: 19, amount: 1, oxweight: 3 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 15.0, oxfinalize: 0, oxparamend: 999, oxfixed: 0, oxsort: 4 }, { oxactive: 1, oxaddsum: 1.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 1.0, oxfinalize: 0, oxparamend: 4.99999999, oxfixed: 0, oxsort: 1 }, { oxactive: 1, oxaddsum: 5.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 5.0, oxfinalize: 0, oxparamend: 14.9999999, oxfixed: 0, oxsort: 2 }]\nexpected:\n    articles: { 10011: ['1,80', '1,80'], 10012: ['2,00', '2,00'] }\n    totals: { totalBrutto: '3,80', totalNetto: '3,19', vats: { 19: '0,61' }, delivery: { brutto: '5,00' }, grandTotal: '8,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testDeliveryByWeight(6).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 1.8, oxvat: 19, amount: 2, oxpricea: 0, oxpriceb: 0, oxpricec: 0, oxweight: 2 }\n    - { oxid: 10012, oxprice: 2.0, oxvat: 19, amount: 2, oxweight: 3 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 15.0, oxfinalize: 0, oxparamend: 999, oxfixed: 1, oxsort: 4 }, { oxactive: 1, oxaddsum: 1.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 1.0, oxfinalize: 0, oxparamend: 4.99999999, oxfixed: 1, oxsort: 1 }, { oxactive: 1, oxaddsum: 5.0, oxaddsumtype: abs, oxdeltype: w, oxparam: 5.0, oxfinalize: 0, oxparamend: 14.9999999, oxfixed: 1, oxsort: 2 }]\nexpected:\n    articles: { 10011: ['1,80', '3,60'], 10012: ['2,00', '4,00'] }\n    totals: { totalBrutto: '7,60', totalNetto: '6,39', vats: { 19: '1,21' }, delivery: { brutto: '6,00' }, grandTotal: '13,60' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testDeliveryLowerShippingCost(1).yaml",
    "content": "categories:\n    - { oxid: books, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [book] }\n    - { oxid: otherStuff, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [stuff] }\narticles:\n    - { oxid: stuff, oxprice: 20, oxvat: 20, amount: 6 }\n    - { oxid: book, oxprice: 10, oxvat: 10, amount: 1 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 2, oxaddsumtype: abs, oxdeltype: a, oxparam: 0, oxparamend: 99999, oxsort: 1 }, { oxactive: 1, oxaddsum: 3, oxaddsumtype: abs, oxdeltype: a, oxparam: 0, oxparamend: 99999, oxsort: 2, oxcategories: [otherStuff] }]\nexpected:\n    articles: { book: ['10,00', '10,00'], stuff: ['20,00', '120,00'] }\n    totals: { totalBrutto: '130,00', totalNetto: '109,09', vats: { 10: '0,91', 20: '20,00' }, delivery: { brutto: '5,00' }, grandTotal: '135,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testDeliveryLowerShippingCost(2).yaml",
    "content": "categories:\n    - { oxid: books, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [book] }\n    - { oxid: otherStuff, oxparentid: oxrootid, oxshopid: 1, oxactive: 1 }\narticles:\n    - { oxid: book, oxprice: 10, oxvat: 10, amount: 1 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 2, oxaddsumtype: abs, oxdeltype: a, oxparam: 0, oxparamend: 99999, oxsort: 1 }, { oxactive: 1, oxaddsum: 3, oxaddsumtype: abs, oxdeltype: a, oxparam: 0, oxparamend: 99999, oxsort: 2, oxcategories: [otherStuff] }]\nexpected:\n    articles: { book: ['10,00', '10,00'] }\n    totals: { totalBrutto: '10,00', totalNetto: '9,09', vats: { 10: '0,91' }, delivery: { brutto: '2,00' }, grandTotal: '12,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testDeliveryLowerShippingCost(3).yaml",
    "content": "categories:\n    - { oxid: books, oxparentid: oxrootid, oxshopid: 1, oxactive: 1 }\n    - { oxid: otherStuff, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [stuff] }\narticles:\n    - { oxid: stuff, oxprice: 20, oxvat: 20, amount: 6 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 2, oxaddsumtype: abs, oxdeltype: a, oxparam: 0, oxparamend: 99999, oxsort: 1 }, { oxactive: 1, oxaddsum: 3, oxaddsumtype: abs, oxdeltype: a, oxparam: 0, oxparamend: 99999, oxsort: 2, oxcategories: [otherStuff] }]\nexpected:\n    articles: { stuff: ['20,00', '120,00'] }\n    totals: { totalBrutto: '120,00', totalNetto: '100,00', vats: { 20: '20,00' }, delivery: { brutto: '5,00' }, grandTotal: '125,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testDiscountBug5913.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 50.0, oxstock: 100, oxvat: 19, oxartnum: '1000', amount: 2 }\n    - { oxid: 1003, oxprice: 5.0, oxstock: 1, oxvat: 19, oxstockflag: 2, oxartnum: '1003' }\ndiscounts:\n    - { oxid: testitem_discount, oxshopid: 1, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 0, oxactive: 1, oxitmartid: 1003, oxitmamount: 1, oxitmmultiple: 1, oxarticles: [1000], oxsort: 10 }\nexpected:\n    articles: { 1000: ['50,00', '100,00'], 1003: ['0,00', '0,00'] }\n    totals: { totalBrutto: '100,00', totalNetto: '84,03', vats: { 19: '15,97' }, grandTotal: '100,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testDiscountSorting.yaml",
    "content": "articles:\n    - { oxid: 10005, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: shopdiscount5for10005, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 70 }\n    - { oxid: shopdiscount5for1004, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 20 }\n    - { oxid: basketdiscount5for10005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 60 }\n    - { oxid: basketdiscount5for1004, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 30 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: procdiscountfor10005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 40 }\n    - { oxid: procdiscountfor1004, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap102, oxprice: 9, oxactive: 1, oxarticles: [10005] }, { oxtype: WRAP, oxname: testWrap1002, oxprice: 6, oxactive: 1, oxarticles: [1004] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 10005: ['1.115,95', '1.115,95'], 1004: ['0,59', '0,59'] }\n    totals: { totalBrutto: '1.116,54', totalNetto: '929,03', vats: { 19: '176,51' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '15,00', netto: '12,60', vat: '2,40' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '1.127,54' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testDiscountSortingDBRMIndependency.yaml",
    "content": "articles:\n    - { oxid: 10005, oxprice: 1001, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 0.5, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: shopdiscount5for1004, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 20 }\n    - { oxid: basketdiscount5for1004, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 30 }\n    - { oxid: procdiscountfor10005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 40 }\n    - { oxid: procdiscountfor1004, oxaddsum: -10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 50 }\n    - { oxid: basketdiscount5for10005, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 60 }\n    - { oxid: shopdiscount5for10005, oxaddsum: 5.5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [10005], oxsort: 70 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap102, oxprice: 9, oxactive: 1, oxarticles: [10005] }, { oxtype: WRAP, oxname: testWrap1002, oxprice: 6, oxactive: 1, oxarticles: [1004] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 10005: ['1.115,95', '1.115,95'], 1004: ['0,59', '0,59'] }\n    totals: { totalBrutto: '1.116,54', totalNetto: '929,03', vats: { 19: '176,51' }, discounts: { absolutebasketdiscount: '5,00' }, wrapping: { brutto: '15,00', netto: '12,60', vat: '2,40' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '1.127,54' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFewItmDiscounts.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1 }\n    - { oxid: 1003, oxprice: 50.0, oxvat: 5 }\n    - { oxid: 1002, oxprice: 50.0, oxvat: 5 }\ndiscounts:\n    - { oxid: testitem_discount, oxshopid: 1, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 0, oxactive: 1, oxitmartid: 1003, oxitmamount: 1, oxitmmultiple: 0, oxarticles: [1000], oxsort: 10 }\n    - { oxid: testitem_discounts, oxshopid: 1, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 0, oxactive: 1, oxitmartid: 1002, oxitmamount: 1, oxitmmultiple: 0, oxarticles: [1000], oxsort: 20 }\nexpected:\n    articles: { 1000: ['50,00', '50,00'], 1003: ['0,00', '0,00'], 1002: ['0,00', '0,00'] }\n    totals: { totalBrutto: '50,00', totalNetto: '47,62', vats: { 5: '2,38' }, grandTotal: '50,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendItmDiscounts.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 5 }\n    - { oxid: 1003, oxprice: 50.0, oxvat: 5 }\ndiscounts:\n    - { oxid: testitem_discount, oxshopid: 1, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxprice: 0, oxpriceto: 0, oxactive: 1, oxitmartid: 1003, oxitmamount: 1, oxitmmultiple: 0, oxarticles: [1000], oxsort: 10 }\n    - { oxid: testdiscountfrom200, oxshopid: 1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 200, oxpriceto: 999999, oxactive: 1, oxsort: 20 }\nexpected:\n    articles: { 1000: ['50,00', '250,00'], 1003: ['0,00', '0,00'] }\n    totals: { totalBrutto: '250,00', totalNetto: '214,29', vats: { 5: '10,71' }, discounts: { testdiscountfrom200: '25,00' }, grandTotal: '225,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendNettoPrices.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 3 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: 'Test wrapping [EN] ðÄßü?', oxprice: 0.9, oxactive: 1, oxarticles: [1000] }, { oxtype: CARD, oxname: 'Test card [EN] ðÄßü', oxprice: 0.2, oxactive: 1 }]\nexpected:\n    articles: { 1000: ['52,50', '157,50'] }\n    totals: { totalBrutto: '157,50', totalNetto: '150,00', vats: { 5: '7,50' }, wrapping: { brutto: '2,84' }, giftcard: { brutto: '0,21' }, grandTotal: '160,55' }\noptions:\n    config: { blShowNetPrice: false, blEnterNetPrice: true, blWrappingVatOnTop: true, blDeliveryVatOnTop: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendOrderStep1Calculation2(1).yaml",
    "content": "articles:\n    - { oxid: 10012, oxprice: 98, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1002, oxprice: 67.0, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 75.0, oxvat: 19, amount: 6, oxpricea: 70, oxpriceb: 85, oxpricec: 0, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1, oxpricea: 35, oxpriceb: 45, oxpricec: 55, oxunitname: kg, oxunitquantity: 2, oxweight: 2 }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10012, 1000], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 1.5, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, oxminimumvalue: 75, voucher_count: 1 }]\nexpected:\n    articles: { 10012: ['93,00', '93,00'], 1002: ['60,30', '60,30'], 1003: ['54,00', '324,00'], 1000: ['45,00', '45,00'] }\n    totals: { totalBrutto: '522,30', totalNetto: '441,73', vats: { 10: '8,29', 19: '60,18', 5: '2,10' }, delivery: { brutto: '1,50' }, voucher: { brutto: '10,00' }, grandTotal: '513,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendOrderStep1Calculation2(2).yaml",
    "content": "articles:\n    - { oxid: 10013, oxprice: 100, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1002, oxprice: 67.0, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 75.0, oxvat: 19, amount: 6, oxpricea: 70, oxpriceb: 85, oxpricec: 0, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1, oxpricea: 35, oxpriceb: 45, oxpricec: 55, oxunitname: kg, oxunitquantity: 2, oxweight: 2 }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10013, 1000], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 1.5, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, oxminimumvalue: 75, voucher_count: 1 }]\nexpected:\n    articles: { 10013: ['95,00', '95,00'], 1002: ['60,30', '60,30'], 1003: ['54,00', '324,00'], 1000: ['45,00', '45,00'] }\n    totals: { totalBrutto: '524,30', totalNetto: '443,54', vats: { 10: '8,47', 19: '60,19', 5: '2,10' }, delivery: { brutto: '1,50' }, voucher: { brutto: '10,00' }, grandTotal: '515,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendOrderStep1Calculation2(3).yaml",
    "content": "articles:\n    - { oxid: 10014, oxprice: 102, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1002, oxprice: 67.0, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 75.0, oxvat: 19, amount: 6, oxpricea: 70, oxpriceb: 85, oxpricec: 0, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1, oxpricea: 35, oxpriceb: 45, oxpricec: 55, oxunitname: kg, oxunitquantity: 2, oxweight: 2 }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10014, 1000], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 1.5, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, oxminimumvalue: 75, voucher_count: 1 }]\nexpected:\n    articles: { 10014: ['97,00', '97,00'], 1002: ['60,30', '60,30'], 1003: ['54,00', '324,00'], 1000: ['45,00', '45,00'] }\n    totals: { totalBrutto: '526,30', totalNetto: '445,36', vats: { 10: '8,65', 19: '60,19', 5: '2,10' }, delivery: { brutto: '1,50' }, voucher: { brutto: '10,00' }, grandTotal: '517,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendOrderStep1Calculation2(4).yaml",
    "content": "articles:\n    - { oxid: 10015, oxprice: 101, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1002, oxprice: 67.0, oxvat: 19, amount: 1 }\n    - { oxid: 1003, oxprice: 75.0, oxvat: 19, amount: 6, oxpricea: 70, oxpriceb: 85, oxpricec: 0, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1, oxpricea: 35, oxpriceb: 45, oxpricec: 55, oxunitname: kg, oxunitquantity: 2, oxweight: 2 }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10015, 1000], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 1.5, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, oxminimumvalue: 75, voucher_count: 1 }]\nexpected:\n    articles: { 10015: ['96,00', '96,00'], 1002: ['60,30', '60,30'], 1003: ['54,00', '324,00'], 1000: ['45,00', '45,00'] }\n    totals: { totalBrutto: '525,30', totalNetto: '444,45', vats: { 10: '8,56', 19: '60,19', 5: '2,10' }, delivery: { brutto: '1,50' }, voucher: { brutto: '10,00' }, grandTotal: '516,80' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendOrderStep1Calculation2(5).yaml",
    "content": "articles:\n    - { oxid: 10016, oxprice: 101, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1002, oxprice: 67.0, oxvat: 19, amount: 1 }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10016, 1000], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 1.5, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    voucherserie: [{ oxdiscount: 5.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, oxminimumvalue: 75, voucher_count: 1 }]\nexpected:\n    articles: { 10016: ['96,00', '96,00'], 1002: ['67,00', '67,00'] }\n    totals: { totalBrutto: '163,00', totalNetto: '136,40', vats: { 10: '8,29', 19: '10,16' }, delivery: { brutto: '1,50' }, voucher: { brutto: '8,15' }, grandTotal: '156,35' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendOrderStep1CalculationVoucher.yaml",
    "content": "articles:\n    - { oxid: 1245, oxprice: 98, oxvat: 10, amount: 1 }\n    - { oxid: 6565, oxprice: 67, oxvat: 19, amount: 1 }\n    - { oxid: 1553, oxprice: 60, oxvat: 19, amount: 6 }\n    - { oxid: 1224, oxprice: 50, oxvat: 5, amount: 1 }\ndiscounts:\n    - { oxid: product, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxactive: 1, oxarticles: [6565, 1553], oxsort: 10 }\n    - { oxid: prod2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxprice: 0, oxpriceto: 99999, oxactive: 1, oxarticles: [1224, 1245], oxsort: 20 }\ncosts:\n    voucherserie: [{ oxdiscount: 5, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    articles: { 6565: ['60,30', '60,30'], 1553: ['54,00', '324,00'], 1224: ['45,00', '45,00'], 1245: ['93,00', '93,00'] }\n    totals: { totalBrutto: '522,30', totalNetto: '427,82', vats: { 10: '8,03', 19: '58,29', 5: '2,04' }, voucher: { brutto: '26,12' }, grandTotal: '496,18' }\n    options: { config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: false, blShowVATForDelivery: true }, activeCurrencyRate: 1.0 }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendOrderStep1DeliverySortedWithCategories(1).yaml",
    "content": "categories:\n    - { oxid: testCategory1, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [_test_10012] }\n    - { oxid: testCategory2, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [_test_1002] }\narticles:\n    - { oxid: _test_1002, oxprice: 20, oxvat: 20, amount: 6 }\n    - { oxid: _test_10012, oxprice: 10, oxvat: 10, amount: 1 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 0, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 5, oxparamend: 99999, oxsort: 1, oxcategories: [testCategory2] }, { oxactive: 1, oxaddsum: 2, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 0, oxparamend: 99999, oxsort: 2, oxcategories: [testCategory1] }]\nexpected:\n    articles: { _test_10012: ['10,00', '10,00'], _test_1002: ['20,00', '120,00'] }\n    totals: { totalBrutto: '130,00', totalNetto: '109,09', vats: { 10: '0,91', 20: '20,00' }, delivery: { brutto: '0,00' }, grandTotal: '130,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendOrderStep1DeliverySortedWithCategories(2).yaml",
    "content": "categories:\n    - { oxid: testCategory1, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [_test_10012] }\n    - { oxid: testCategory2, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [_test_1002] }\narticles:\n    - { oxid: _test_1002, oxprice: 20, oxvat: 20, amount: 6 }\n    - { oxid: _test_10012, oxprice: 10, oxvat: 10, amount: 1 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 0, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 5, oxparamend: 99999, oxsort: 2, oxcategories: [testCategory2] }, { oxactive: 1, oxaddsum: 2, oxaddsumtype: abs, oxdeltype: a, oxfinalize: 1, oxparam: 0, oxparamend: 99999, oxsort: 1, oxcategories: [testCategory1] }]\nexpected:\n    articles: { _test_10012: ['10,00', '10,00'], _test_1002: ['20,00', '120,00'] }\n    totals: { totalBrutto: '130,00', totalNetto: '109,09', vats: { 10: '0,91', 20: '20,00' }, delivery: { brutto: '2,00' }, grandTotal: '132,00' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendOrdersFractionQuantities1.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 50, oxvat: 5, oxunitname: kg, oxunitquantity: 10, oxweight: 10, amount: 3.4 }\ndiscounts:\n    - { oxid: test, oxshopid: 1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 0, oxpriceto: 999999, oxactive: 1, oxsort: 10 }\nexpected:\n    articles: { 1000: ['45,00', '153,00'] }\n    totals: { totalBrutto: '153,00', totalNetto: '145,71', vats: { 5: '7,29' }, grandTotal: '153,00' }\noptions:\n    config: { blAllowUnevenAmounts: true, blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendOrdersFractionQuantities2.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 75, oxvat: 19, amount: 3.4 }\n    - { oxid: 1001, oxprice: 100, oxvat: 10, amount: 0.3 }\n    - { oxid: 1000, oxprice: 50, oxvat: 19, oxunitname: kg, oxunitquantity: 10, oxweight: 10, amount: 1.5 }\nexpected:\n    articles: { 1003: ['75,00', '225,00'], 1000: ['50,00', '100,00'] }\n    totals: { totalBrutto: '325,00', totalNetto: '273,11', vats: { 19: '51,89' }, grandTotal: '325,00' }\noptions:\n    config: { blAllowUnevenAmounts: false, blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendPriceA.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 70.0, oxpricea: 71, oxpriceb: 85, oxpricec: 0, amount: 1, oxvat: 19, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\nuser:\n    oxid: _testUserA\n    oxactive: 1\n    oxusername: groupAUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [1003, _testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    articles: { 1003: ['71,00', '71,00'] }\n    totals: { totalBrutto: '71,00', totalNetto: '59,66', vats: { 19: '11,34' }, grandTotal: '71,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendPriceA2.yaml",
    "content": "articles:\n    - { oxid: 1002, oxprice: 50.0, oxpricea: 35, oxpriceb: 45, oxpricec: 55, amount: 1, oxvat: 19, oxtitle: 'Wall Clock ROBOT', oxunitname: kg, oxunitquantity: 2, oxweight: 10 }\nuser:\n    oxid: _testUserA\n    oxactive: 1\n    oxusername: groupAUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [1002, _testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    articles: { 1002: ['35,00', '35,00'] }\n    totals: { totalBrutto: '35,00', totalNetto: '29,41', vats: { 19: '5,59' }, grandTotal: '35,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendPriceB.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 70.0, oxpricea: 70, oxpriceb: 85, oxpricec: 0, amount: 1, oxvat: 19, scaleprices: { oxaddabs: 75.0, oxamount: 2, oxamountto: 5, oxartid: 1003 } }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 19, amount: 1 }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [1003, _testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    articles: { 1003: ['85,00', '85,00'], 1112: ['5,02', '5,02'] }\n    totals: { totalBrutto: '90,02', totalNetto: '75,65', vats: { 19: '14,37' }, grandTotal: '90,02' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendPriceB2.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 70.0, oxpricea: 70, oxpriceb: 85, oxpricec: 0, amount: 7, oxvat: 19, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [1003, _testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    articles: { 1003: ['68,00', '476,00'] }\n    totals: { totalBrutto: '476,00', totalNetto: '400,00', vats: { 19: '76,00' }, grandTotal: '476,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendPriceB3.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 70.0, oxpricea: 70, oxpriceb: 85, oxpricec: 0, amount: 7, oxvat: 19, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [1003, _testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10013, 1000], oxsort: 20 }\nexpected:\n    articles: { 1003: ['61,20', '428,40'] }\n    totals: { totalBrutto: '428,40', totalNetto: '360,00', vats: { 19: '68,40' }, grandTotal: '428,40' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendPriceB4.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 70.0, oxpricea: 70, oxpriceb: 85, oxpricec: 0, amount: 1, oxvat: 19, scaleprices: { oxaddabs: 75.0, oxamount: 2, oxamountto: 5, oxartid: 1003 } }\n    - { oxid: 1112, oxprice: 5.02, oxvat: 19, amount: 1 }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [1003, _testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    articles: { 1003: ['85,00', '85,00'], 1112: ['0,00', '0,00'] }\n    totals: { totalBrutto: '85,00', totalNetto: '71,43', vats: { 19: '13,57' }, grandTotal: '85,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: false, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendPriceC.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 75.0, oxpricea: 70, oxpriceb: 85, oxpricec: 0, amount: 3, oxvat: 19, scaleprices: { oxamount: 6, oxamountto: 999999, oxartid: 1003, oxaddperc: 20 } }\nuser:\n    oxid: _testUserC\n    oxactive: 1\n    oxusername: groupCUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [1003, _testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\ndiscounts:\n    - { oxid: discount1, oxaddsum: 10, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxprice: 100, oxpriceto: 99999, oxactive: 1, oxarticles: [1002, 1003], oxsort: 10 }\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10013, 1000], oxsort: 20 }\nexpected:\n    articles: { 1003: ['67,50', '202,50'] }\n    totals: { totalBrutto: '202,50', totalNetto: '170,17', vats: { 19: '32,33' }, grandTotal: '202,50' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 19 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendVatForBillingCountry(1).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 101, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1003, oxprice: 75.0, oxvat: 19, amount: 1, oxpricea: 70, oxpriceb: 85, oxpricec: 0 }\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1, oxpricea: 35, oxpriceb: 45, oxpricec: 55, oxunitname: kg, oxunitquantity: 2, oxweight: 2 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\n    oxcountryid: a7c40f631fc920687.20179984\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 6.9, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 0.0, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 0, oxcountryid: a7c40f631fc920687.20179984 }]\nexpected:\n    articles: { 10011: ['101,00', '101,00'], 1003: ['75,00', '75,00'], 1000: ['50,00', '50,00'] }\n    totals: { totalBrutto: '226,00', totalNetto: '202,47', vats: { 10: '9,18', 19: '11,97', 5: '2,38' }, delivery: { brutto: '6,90' }, grandTotal: '232,90' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, sAdditionalServVATCalcMethod: biggest_net }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/basket_php84/testFrontendVatForBillingCountry(2).yaml",
    "content": "articles:\n    - { oxid: 10011, oxprice: 101, oxvat: 10, amount: 1, oxpricea: 0, oxpriceb: 0, oxpricec: 0 }\n    - { oxid: 1003, oxprice: 75.0, oxvat: 19, amount: 1, oxpricea: 70, oxpriceb: 85, oxpricec: 0 }\n    - { oxid: 1000, oxprice: 50.0, oxvat: 5, amount: 1, oxpricea: 35, oxpriceb: 45, oxpricec: 55, oxunitname: kg, oxunitquantity: 2, oxweight: 2 }\nuser:\n    oxactive: 1\n    oxusername: basketUser\n    oxcountryid: a7c40f6321c6f6109.43859248\ndiscounts:\n    - { oxid: discount2, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [10011, 1000], oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 0, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 7.5, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 0 }]\nexpected:\n    articles: { 10011: ['86,82', '86,82'], 1003: ['63,03', '63,03'], 1000: ['42,62', '42,62'] }\n    totals: { totalBrutto: '192,47', totalNetto: '192,47', vats: ['0,00'], delivery: { brutto: '0,00' }, payment: { brutto: '7,50', netto: '7,50' }, grandTotal: '199,97' }\noptions:\n    activeCurrencyRate: 1\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blShowVATForWrapping: false, blShowVATForDelivery: false, blDeliveryVatOnTop: false, blPaymentVatOnTop: false, sAdditionalServVATCalcMethod: biggest_net, blShowVATForPayCharge: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/delivery_cost/rules/first_applicable_rule_cost_per_cart.yaml",
    "content": "articles:\n  - { oxid: parent_product_id }\n  - { oxid: variant_product_id, oxparentid: parent_product_id, oxprice: 10.0, amount: 10 }\ncategories:\n  - { oxid: category_id_1, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [ variant_product_id ] }\nuser:\n  oxid: testUser\n  oxactive: 1\n  oxusername: testUser\ncosts:\n  delivery: [\n    { oxactive: 1, oxtitle: 'first', oxaddsum: 11, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 100, oxsort: 100, oxfixed: 0 }\n    { oxactive: 1, oxtitle: 'second', oxaddsum: 22, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparam: 100, oxparamend: 200, oxsort: 200, oxfixed: 0 }\n    { oxactive: 1, oxtitle: 'third', oxaddsum: 33, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 200, oxsort: 300, oxfixed: 0 }\n  ]\nexpected:\n  costs: { totals: { delivery: { brutto: 11.0 } } }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/delivery_cost/rules/first_applicable_rule_cost_per_item.yaml",
    "content": "articles:\n  - { oxid: parent_product_id }\n  - { oxid: variant_product_id, oxparentid: parent_product_id, oxprice: 10.0, amount: 10 }\ncategories:\n  - { oxid: category_id_1, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [ variant_product_id ] }\nuser:\n  oxid: testUser\n  oxactive: 1\n  oxusername: testUser\ncosts:\n  delivery: [\n    { oxactive: 1, oxtitle: 'first', oxaddsum: 11, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 100, oxsort: 100, oxfixed: 2 }\n    { oxactive: 1, oxtitle: 'second', oxaddsum: 22, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparam: 100, oxparamend: 200, oxsort: 200, oxfixed: 2 }\n    { oxactive: 1, oxtitle: 'third', oxaddsum: 33, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 200, oxsort: 300, oxfixed: 2 }\n  ]\nexpected:\n  costs: { totals: { delivery: { brutto: 110.0 } } }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/delivery_cost/rules/first_applicable_rule_cost_per_product.yaml",
    "content": "articles:\n  - { oxid: parent_product_id }\n  - { oxid: variant_product_id, oxparentid: parent_product_id, oxprice: 10.0, amount: 10 }\ncategories:\n  - { oxid: category_id_1, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [ variant_product_id ] }\nuser:\n  oxid: testUser\n  oxactive: 1\n  oxusername: testUser\ncosts:\n  delivery: [\n    { oxactive: 1, oxtitle: 'first', oxaddsum: 11, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 100, oxsort: 100, oxfixed: 1 }\n    { oxactive: 1, oxtitle: 'second', oxaddsum: 22, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparam: 100, oxparamend: 200, oxsort: 200, oxfixed: 1 }\n    { oxactive: 1, oxtitle: 'third', oxaddsum: 33, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 200, oxsort: 300, oxfixed: 1 }\n  ]\nexpected:\n  costs: { totals: { delivery: { brutto: 11.0 } } }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/delivery_cost/rules/multiple_rules_2_applied.yaml",
    "content": "articles:\n  - { oxid: parent_product_id }\n  - { oxid: variant_product_id, oxparentid: parent_product_id, oxprice: 10.0, amount: 5 }\ncategories:\n  - { oxid: category_id_1, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [ variant_product_id ] }\nuser:\n  oxid: testUser\n  oxactive: 1\n  oxusername: testUser\ncosts:\n  delivery: [\n    { oxactive: 1, oxtitle: 'first', oxaddsum: 11, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 0, oxparam: 0, oxparamend: 100, oxsort: 100, oxfixed: 0 }\n    { oxactive: 1, oxtitle: 'second', oxaddsum: 22, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 0, oxparam: 100, oxparamend: 200, oxsort: 200, oxfixed: 0 }\n    { oxactive: 1, oxtitle: 'third', oxaddsum: 33, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 0, oxparam: 0, oxparamend: 200, oxsort: 300, oxfixed: 0 }\n  ]\nexpected:\n  costs: { totals: { delivery: { brutto: 44.0 } } }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/delivery_cost/rules/multiple_rules_3_applied.yaml",
    "content": "articles:\n  - { oxid: parent_product_id }\n  - { oxid: variant_product_id, oxparentid: parent_product_id, oxprice: 10.0, amount: 10 }\ncategories:\n  - { oxid: category_id_1, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [ variant_product_id ] }\nuser:\n  oxid: testUser\n  oxactive: 1\n  oxusername: testUser\ncosts:\n  delivery: [\n    { oxactive: 1, oxtitle: 'first', oxaddsum: 11, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 0, oxparam: 0, oxparamend: 100, oxsort: 100, oxfixed: 0 }\n    { oxactive: 1, oxtitle: 'second', oxaddsum: 22, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 0, oxparam: 100, oxparamend: 200, oxsort: 200, oxfixed: 0 }\n    { oxactive: 1, oxtitle: 'third', oxaddsum: 33, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 0, oxparam: 0, oxparamend: 200, oxsort: 300, oxfixed: 0 }\n  ]\nexpected:\n  costs: { totals: { delivery: { brutto: 66.0 } } }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/delivery_cost/rules_assigned_to/category.yaml",
    "content": "articles:\n  - { oxid: parent_product_id }\n  - { oxid: variant_product_id, oxparentid: parent_product_id, oxprice: 10.0, amount: 10 }\ncategories:\n  - { oxid: category_id_1, oxparentid: oxrootid, oxshopid: 1, oxactive: 1 }\n  - { oxid: category_id_2, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [ variant_product_id ] }\n  - { oxid: category_id_3, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [ variant_product_id ] }\nuser:\n  oxid: testUser\n  oxactive: 1\n  oxusername: testUser\ncosts:\n  delivery: [\n    { oxactive: 1, oxtitle: 'assigned-to-wrong-category', oxaddsum: 11, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 100, oxsort: 100, oxfixed: 0, oxcategories: [ category_id_1 ] }\n    { oxactive: 1, oxtitle: 'correct-category-but-min-amount-too-high', oxaddsum: 22, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparam: 200, oxparamend: 600, oxsort: 200, oxfixed: 0, oxcategories: [ category_id_1, category_id_2, category_id_3 ] }\n    { oxactive: 1, oxtitle: 'ok-rule', oxaddsum: 333, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 600, oxsort: 300, oxfixed: 0, oxcategories: [ category_id_1, category_id_2, category_id_3 ] }\n  ]\nexpected:\n  costs: { totals: { delivery: { brutto: 333.0 } } }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/delivery_cost/rules_assigned_to/product.yaml",
    "content": "articles:\n  - { oxid: parent_product_id }\n  - { oxid: variant_product_id, oxparentid: parent_product_id, oxprice: 10.0, amount: 10 }\ncategories:\n  - { oxid: category_id_1, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [ variant_product_id ] }\n  - { oxid: category_id_2, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [ variant_product_id ] }\n  - { oxid: category_id_3, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [ variant_product_id ] }\nuser:\n  oxid: testUser\n  oxactive: 1\n  oxusername: testUser\ncosts:\n  delivery: [\n    { oxactive: 1, oxtitle: 'assigned-to-wrong-product', oxaddsum: 11, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 100, oxsort: 100, oxfixed: 0, oxarticles: [ wrong_product_id ] }\n    { oxactive: 1, oxtitle: 'correct-product-but-min-amount-too-high', oxaddsum: 22, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparam: 200, oxparamend: 600, oxsort: 200, oxfixed: 0, oxarticles: [ wrong_product_id, variant_product_id ] }\n    { oxactive: 1, oxtitle: 'ok-rule', oxaddsum: 333, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparam: 0, oxparamend: 600, oxsort: 300, oxfixed: 0, oxarticles: [ wrong_product_id, variant_product_id ] }\n  ]\nexpected:\n  costs: { totals: { delivery: { brutto: 333.0 } } }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case1.yaml",
    "content": "articles:\n    - { oxid: '111', oxtitle: '111', oxprice: 1, oxvat: 19, oxstock: 999, amount: 1 }\ndiscounts:\n    - { oxid: discount10for111, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 4.64, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 59.5, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    1: { articles: { 111: ['1,00', '1,00'] }, totals: { totalBrutto: '1,07', discount: '0,10', totalNetto: '1,00', vats: { 19: '0,17' }, delivery: { brutto: '4,64' }, payment: { brutto: '59,50' }, grandTotal: '65,21' } }\n    2: { articles: { 111: ['1,00', '1,00'] }, totals: { totalBrutto: '1,07', discount: '0,10', totalNetto: '1,00', vats: { 19: '0,17' }, delivery: { brutto: '4,64' }, payment: { brutto: '59,50' }, grandTotal: '65,21' } }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\nactions:\n    changeConfigs: { blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case10.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    1: { articles: { 1001: ['20,00', '300,00'] }, totals: { totalBrutto: '297,00', totalNetto: '300,00', vats: { 10: '27,00' }, delivery: { brutto: '11,00', netto: '10,00', vat: '1,00' }, payment: { brutto: '302,50', netto: '275,00', vat: '27,50' }, discount: '0,00', voucher: { brutto: '30,00' }, giftcard: { brutto: '2,75', netto: '2,50', vat: '0,25' }, grandTotal: '613,25' } }\n    2: { articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'], 1006: ['10,00', '10,00'] }, totals: { totalBrutto: '521,91', totalNetto: '510,00', vats: { 19: '35,91', 10: '27,00' }, delivery: { brutto: '11,00', netto: '11,00', vat: '27,50' }, discount: '0,00', payment: { brutto: '302,50', netto: '275,00', vat: '27,50' }, voucher: { brutto: '51,00' }, giftcard: { brutto: '2,75', netto: '2,50', vat: '0,25' }, grandTotal: '838,16' } }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\nactions:\n    addArticles: [{ oxid: '1006', oxtitle: '1006', oxprice: 10, oxvat: 19, oxstock: 999, amount: 1 }, { oxid: '1002', oxtitle: '1002', oxprice: 200, oxvat: 19, oxstock: 999, amount: 1 }]\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case11.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    1: { articles: { 1001: ['20,00', '300,00'] }, totals: { totalBrutto: '297,00', totalNetto: '300,00', vats: { 10: '27,00' }, delivery: { brutto: '11,00', netto: '10,00', vat: '1,00' }, payment: { brutto: '302,50', netto: '275,00', vat: '27,50' }, discount: '0,00', voucher: { brutto: '30,00' }, giftcard: { brutto: '2,75', netto: '2,50', vat: '0,25' }, grandTotal: '613,25' } }\n    2: { articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'], 1006: ['10,00', '10,00'] }, totals: { totalBrutto: '521,91', totalNetto: '510,00', vats: { 19: '35,91', 10: '27,00' }, delivery: { brutto: '11,00', netto: '10,00', vat: '1,00' }, discount: '0,00', payment: { brutto: '302,50', netto: '275,00', vat: '27,50' }, voucher: { brutto: '51,00' }, giftcard: { brutto: '2,75', netto: '2,50', vat: '0,25' }, grandTotal: '838,16' } }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\nactions:\n    changeConfigs: { blShowNetPrice: false }\n    addArticles: [{ oxid: '1006', oxtitle: '1006', oxprice: 10, oxvat: 19, oxstock: 999, amount: 1 }, { oxid: '1002', oxtitle: '1002', oxprice: 200, oxvat: 19, oxstock: 999, amount: 1 }]\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case12.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    1: { articles: { 1001: ['20,00', '300,00'] }, totals: { totalBrutto: '297,00', totalNetto: '300,00', vats: { 10: '27,00' }, delivery: { brutto: '11,00', netto: '10,00', vat: '1,00' }, payment: { brutto: '302,50', netto: '275,00', vat: '27,50' }, discount: '0,00', voucher: { brutto: '30,00' }, giftcard: { brutto: '2,75', netto: '2,50', vat: '0,25' }, grandTotal: '613,25' } }\n    2: { articles: { 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'], 1006: ['10,00', '10,00'] }, totals: { totalBrutto: '224,91', totalNetto: '210,00', vats: { 19: '35,91' }, delivery: { brutto: '11,90', netto: '10,00', vat: '0,90' }, discount: '0,00', payment: { brutto: '327,25', netto: '275,00', vat: '52,25' }, voucher: { brutto: '21,00' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, grandTotal: '567,04' } }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\nactions:\n    changeConfigs: { blShowNetPrice: false }\n    removeArticles: ['1001']\n    addArticles: [{ oxid: '1006', oxtitle: '1006', oxprice: 10, oxvat: 19, oxstock: 999, amount: 1 }, { oxid: '1002', oxtitle: '1002', oxprice: 200, oxvat: 19, oxstock: 999, amount: 1 }]\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case13.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    1: { articles: { 1001: ['20,00', '300,00'] }, totals: { totalBrutto: '297,00', totalNetto: '300,00', vats: { 10: '27,00' }, delivery: { brutto: '11,00', netto: '10,00', vat: '1,00' }, payment: { brutto: '302,50', netto: '275,00', vat: '27,50' }, discount: '0,00', voucher: { brutto: '30,00' }, giftcard: { brutto: '2,75', netto: '2,50', vat: '0,25' }, grandTotal: '613,25' } }\n    2: { articles: { 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'], 1006: ['10,00', '10,00'] }, totals: { totalBrutto: '224,91', totalNetto: '210,00', vats: { 19: '35,91' }, delivery: { brutto: '11,90', netto: '10,00', vat: '0,90' }, discount: '0,00', payment: { brutto: '327,25', netto: '275,00', vat: '52,25' }, voucher: { brutto: '21,00' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, grandTotal: '567,04' } }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\nactions:\n    removeArticles: ['1001']\n    addArticles: [{ oxid: '1006', oxtitle: '1006', oxprice: 10, oxvat: 19, oxstock: 999, amount: 1 }, { oxid: '1002', oxtitle: '1002', oxprice: 200, oxvat: 19, oxstock: 999, amount: 1 }]\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case14.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    1: { articles: { 1001: ['20,00', '300,00'] }, totals: { totalBrutto: '297,00', totalNetto: '300,00', vats: { 10: '27,00' }, delivery: { brutto: '11,00', netto: '10,00', vat: '1,00' }, payment: { brutto: '302,50', netto: '275,00', vat: '27,50' }, discount: '0,00', voucher: { brutto: '30,00' }, giftcard: { brutto: '2,75', netto: '2,50', vat: '0,25' }, grandTotal: '613,25' } }\n    2: { articles: { 1001: ['20,00', '200,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'], 1006: ['10,00', '10,00'] }, totals: { totalBrutto: '422,91', totalNetto: '410,00', vats: { 19: '35,91', 10: '18,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '0,90' }, discount: '0,00', payment: { brutto: '327,25', netto: '275,00', vat: '52,25' }, voucher: { brutto: '41,00' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, grandTotal: '765,04' } }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\nactions:\n    changeArticles: [{ oxid: '1001', amount: 10 }]\n    addArticles: [{ oxid: '1006', oxtitle: '1006', oxprice: 10, oxvat: 19, oxstock: 999, amount: 1 }, { oxid: '1002', oxtitle: '1002', oxprice: 200, oxvat: 19, oxstock: 999, amount: 1 }]\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case15.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    1: { articles: { 1001: ['20,00', '300,00'] }, totals: { totalBrutto: '297,00', totalNetto: '300,00', vats: { 10: '27,00' }, delivery: { brutto: '11,00', netto: '10,00', vat: '1,00' }, payment: { brutto: '302,50', netto: '275,00', vat: '27,50' }, discount: '0,00', voucher: { brutto: '30,00' }, giftcard: { brutto: '2,75', netto: '2,50', vat: '0,25' }, grandTotal: '613,25' } }\n    2: { articles: { 1001: ['20,00', '200,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'], 1006: ['10,00', '10,00'] }, totals: { totalBrutto: '422,91', totalNetto: '410,00', vats: { 19: '35,91', 10: '18,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '0,90' }, discount: '0,00', payment: { brutto: '327,25', netto: '275,00', vat: '52,25' }, voucher: { brutto: '41,00' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, grandTotal: '765,04' } }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\nactions:\n    changeConfigs: { blShowNetPrice: false }\n    changeArticles: [{ oxid: '1001', amount: 10 }]\n    addArticles: [{ oxid: '1006', oxtitle: '1006', oxprice: 10, oxvat: 19, oxstock: 999, amount: 1 }, { oxid: '1002', oxtitle: '1002', oxprice: 200, oxvat: 19, oxstock: 999, amount: 1 }]\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case16.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    1: { articles: { 1001: ['20,00', '300,00'] }, totals: { totalBrutto: '297,00', totalNetto: '300,00', vats: { 10: '27,00' }, delivery: { brutto: '11,00', netto: '10,00', vat: '1,00' }, payment: { brutto: '302,50', netto: '275,00', vat: '27,50' }, discount: '0,00', voucher: { brutto: '30,00' }, giftcard: { brutto: '2,75', netto: '2,50', vat: '0,25' }, grandTotal: '613,25' } }\n    2: { articles: { 1001: ['20,00', '200,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }, totals: { totalBrutto: '412,20', totalNetto: '400,00', vats: { 19: '34,20', 10: '18,00' }, delivery: { brutto: '11,90', netto: '10,00', vat: '0,90' }, discount: '0,00', payment: { brutto: '327,25', netto: '275,00', vat: '52,25' }, voucher: { brutto: '40,00' }, giftcard: { brutto: '2,98', netto: '2,50', vat: '0,48' }, grandTotal: '754,33' } }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\nactions:\n    changeConfigs: { blShowNetPrice: false }\n    changeArticles: [{ oxid: '1001', amount: 10 }]\n    addArticles: [{ oxid: '1002', oxtitle: '1002', oxprice: 200, oxvat: 19, oxstock: 999, amount: 1 }]\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case18.yaml",
    "content": "articles:\n    - { oxid: '111', oxtitle: '111', oxprice: 1002.55, oxvat: 19, oxstock: 999, amount: 2 }\n    - { oxid: '222', oxtitle: '222', oxprice: 11.56, oxvat: 13, oxstock: 999, amount: 2 }\n    - { oxid: '333', oxtitle: '333', oxprice: 1326.89, oxvat: 3, oxstock: 999, amount: 6 }\n    - { oxid: '444', oxtitle: '444', oxprice: 6.66, oxvat: 17, oxstock: 999, amount: 6 }\n    - { oxid: '555', oxtitle: '555', oxprice: 0.66, oxvat: 33, oxstock: 999, amount: 6 }\ndiscounts:\n    - { oxid: discount10for111, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 0.0, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 0.0, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    1: { articles: { 111: ['1.002,55', '2.005,10'], 222: ['11,56', '23,12'], 333: ['1.326,89', '7.961,34'], 444: ['6,66', '39,96'], 555: ['0,66', '3,96'] }, totals: { totalBrutto: '10.033,48', discount: '1.003,35', totalNetto: '8.524,80', vats: { 19: '288,13', 13: '2,39', 3: '208,70', 17: '5,23', 33: '0,88' }, delivery: { brutto: '0,00' }, payment: { brutto: '0,00' }, grandTotal: '9.030,13' } }\n    2: { articles: { 111: ['1.002,55', '1.002,55'], 222: ['11,56', '23,12'], 333: ['1.326,89', '3.980,67'], 444: ['6,66', '26,64'], 555: ['0,66', '3,30'] }, totals: { totalBrutto: '5.036,28', discount: '503,63', totalNetto: '4.277,63', vats: { 19: '144,06', 13: '2,39', 3: '104,35', 17: '3,48', 33: '0,74' }, delivery: { brutto: '0,00' }, payment: { brutto: '0,00' }, grandTotal: '4.532,65' } }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\nactions:\n    changeConfigs: { blShowNetPrice: false }\n    changeArticles: [{ oxid: '111', amount: 1 }, { oxid: '333', amount: 3 }, { oxid: '444', amount: 4 }, { oxid: '555', amount: 5 }]\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case19.yaml",
    "content": "articles:\n    - { oxid: '111', oxtitle: '111', oxprice: 1002.55, oxvat: 19, oxstock: 999, amount: 2 }\n    - { oxid: '222', oxtitle: '222', oxprice: 11.56, oxvat: 13, oxstock: 999, amount: 2 }\n    - { oxid: '333', oxtitle: '333', oxprice: 1326.89, oxvat: 3, oxstock: 999, amount: 6 }\n    - { oxid: '444', oxtitle: '444', oxprice: 6.66, oxvat: 17, oxstock: 999, amount: 6 }\n    - { oxid: '555', oxtitle: '555', oxprice: 0.66, oxvat: 33, oxstock: 999, amount: 6 }\ndiscounts:\n    - { oxid: discount10for111, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxtitle: 'Shipping costs for Example Set2: UPS 24 hrs Express: $12.90', oxaddsum: 0.0, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999, oxsort: '5000', oxfixed: 0 }]\n    payment: [{ oxid: oxidpayadvance, oxdesc: 'Cash in advance', oxaddsum: 0, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxactive: 1 }]\nexpected:\n    1: { articles: { 111: ['1.002,55', '2.005,10'], 222: ['11,56', '23,12'], 333: ['1.326,89', '7.961,34'], 444: ['6,66', '39,96'], 555: ['0,66', '3,96'] }, totals: { totalBrutto: '10.033,48', discount: '1.003,35', totalNetto: '8.524,80', vats: { 19: '288,13', 13: '2,39', 3: '208,70', 17: '5,23', 33: '0,88' }, delivery: { brutto: '0,00' }, payment: { brutto: '0,00' }, grandTotal: '9.030,13' } }\n    2: { articles: { 111: ['1.002,55', '1.002,55'], 666: ['101,33', '202,66'], 777: ['11,66', '34,98'], 888: ['18,65', '74,60'], 999: ['0,55', '2,75'] }, totals: { totalBrutto: '1.317,54', discount: '131,75', totalNetto: '1.009,46', vats: { 19: '144,06', 13: '20,98', 3: '0,92', 17: '9,76', 33: '0,61' }, delivery: { brutto: '0,00' }, payment: { brutto: '0,00' }, grandTotal: '1.185,79' } }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\nactions:\n    changeConfigs: { blShowNetPrice: false }\n    removeArticles: ['222', '333', '444', '555']\n    changeArticles: [{ oxid: '111', amount: 1 }]\n    addArticles: [{ oxid: '666', oxtitle: '666', oxprice: 101.33, oxvat: 13, oxstock: 999, amount: 2 }, { oxid: '777', oxtitle: '777', oxprice: 11.66, oxvat: 3, oxstock: 999, amount: 3 }, { oxid: '888', oxtitle: '888', oxprice: 18.65, oxvat: 17, oxstock: 999, amount: 4 }, { oxid: '999', oxtitle: '999', oxprice: 0.55, oxvat: 33, oxstock: 999, amount: 5 }]\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case2.yaml",
    "content": "articles:\n    - { oxid: '111', oxtitle: '111', oxprice: 1, oxvat: 19, oxstock: 999, amount: 1 }\ndiscounts:\n    - { oxid: discount10for111, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 4.64, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 59.5, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    1: { articles: { 111: ['1,19', '1,19'] }, totals: { totalBrutto: '1,19', discount: '0,12', totalNetto: '0,90', vats: { 19: '0,17' }, delivery: { brutto: '4,64' }, payment: { brutto: '59,50' }, grandTotal: '65,21' } }\n    2: { articles: { 111: ['1,19', '1,19'] }, totals: { totalBrutto: '1,19', discount: '0,12', totalNetto: '0,90', vats: { 19: '0,17' }, delivery: { brutto: '4,64' }, payment: { brutto: '59,50' }, grandTotal: '65,21' } }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\nactions:\n    changeConfigs: { blShowNetPrice: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case20.yaml",
    "content": "articles:\n    - { oxid: '111', oxtitle: '111', oxprice: 1002.55, oxvat: 19, oxstock: 999, amount: 2 }\n    - { oxid: '222', oxtitle: '222', oxprice: 11.56, oxvat: 13, oxstock: 999, amount: 2 }\n    - { oxid: '333', oxtitle: '333', oxprice: 1326.89, oxvat: 3, oxstock: 999, amount: 6 }\n    - { oxid: '444', oxtitle: '444', oxprice: 6.66, oxvat: 17, oxstock: 999, amount: 6 }\n    - { oxid: '555', oxtitle: '555', oxprice: 0.66, oxvat: 33, oxstock: 999, amount: 6 }\ndiscounts:\n    - { oxid: discount10for111, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxtitle: 'Shipping costs for Example Set2: UPS 24 hrs Express: $12.90', oxaddsum: 0.0, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999, oxsort: '5000', oxfixed: 0 }]\n    payment: [{ oxaddsum: 0.0, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    1: { articles: { 111: ['1.002,55', '2.005,10'], 222: ['11,56', '23,12'], 333: ['1.326,89', '7.961,34'], 444: ['6,66', '39,96'], 555: ['0,66', '3,96'] }, totals: { totalBrutto: '10.033,48', discount: '1.003,35', totalNetto: '8.524,80', vats: { 19: '288,13', 13: '2,39', 3: '208,70', 17: '5,23', 33: '0,88' }, delivery: { brutto: '0,00' }, payment: { brutto: '0,00' }, grandTotal: '9.030,13' } }\n    2: { articles: { 888: ['1.002,55', '1.002,55'], 666: ['8,65', '34,60'], 777: ['27,55', '137,75'] }, totals: { totalBrutto: '1.174,90', discount: '117,49', totalNetto: '878,07', vats: { 19: '144,06', 17: '4,52', 33: '30,76' }, delivery: { brutto: '0,00' }, payment: { brutto: '0,00' }, grandTotal: '1.057,41' } }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\nactions:\n    changeConfigs: { blShowNetPrice: false }\n    removeArticles: ['111', '222', '333', '444', '555']\n    addArticles: { 0: { oxid: '666', oxtitle: '666', oxprice: 8.65, oxvat: 17, oxstock: 999, amount: 4 }, 1: { oxid: '777', oxtitle: '777', oxprice: 27.55, oxvat: 33, oxstock: 999, amount: 5 }, 3: { oxid: '888', oxtitle: '888', oxprice: 1002.55, oxvat: 19, oxstock: 999, amount: 1 } }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case21.yaml",
    "content": "articles:\n    - { oxid: '111', oxtitle: '111', oxprice: 1002.55, oxvat: 19, oxstock: 999, amount: 2 }\n    - { oxid: '222', oxtitle: '222', oxprice: 11.56, oxvat: 13, oxstock: 999, amount: 2 }\n    - { oxid: '333', oxtitle: '333', oxprice: 1326.89, oxvat: 3, oxstock: 999, amount: 6 }\n    - { oxid: '444', oxtitle: '444', oxprice: 6.66, oxvat: 17, oxstock: 999, amount: 6 }\n    - { oxid: '555', oxtitle: '555', oxprice: 0.66, oxvat: 33, oxstock: 999, amount: 6 }\ndiscounts:\n    - { oxid: discount10for111, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 0.0, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 0.0, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    1: { articles: { 111: ['1.002,55', '2.005,10'], 222: ['11,56', '23,12'], 333: ['1.326,89', '7.961,34'], 444: ['6,66', '39,96'], 555: ['0,66', '3,96'] }, totals: { totalBrutto: '10.033,48', discount: '1.003,35', totalNetto: '8.524,80', vats: { 19: '288,13', 13: '2,39', 3: '208,70', 17: '5,23', 33: '0,88' }, delivery: { brutto: '0,00' }, payment: { brutto: '0,00' }, grandTotal: '9.030,13' } }\n    2: { articles: { 111: ['1.002,55', '2.005,10'], 222: ['11,56', '23,12'], 333: ['1.326,89', '7.961,34'], 444: ['6,66', '39,96'], 555: ['0,66', '3,96'] }, totals: { totalBrutto: '10.033,48', discount: '1.003,35', totalNetto: '8.524,80', vats: { 19: '288,13', 13: '2,39', 3: '208,70', 17: '5,23', 33: '0,88' }, delivery: { brutto: '0,00' }, payment: { brutto: '0,00' }, grandTotal: '9.030,13' } }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\nactions:\n    changeConfigs: { blShowNetPrice: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case22.yaml",
    "content": "articles:\n    - { oxid: '11', oxtitle: '111', oxprice: 1002.55, oxvat: 19, oxstock: 999, amount: 2 }\n    - { oxid: '222', oxtitle: '222', oxprice: 11.56, oxvat: 13, oxstock: 999, amount: 2 }\n    - { oxid: '333', oxtitle: '333', oxprice: 1326.89, oxvat: 3, oxstock: 999, amount: 6 }\n    - { oxid: '444', oxtitle: '444', oxprice: 6.66, oxvat: 17, oxstock: 999, amount: 6 }\n    - { oxid: '555', oxtitle: '555', oxprice: 0.66, oxvat: 33, oxstock: 999, amount: 6 }\ndiscounts:\n    - { oxid: discount10for111, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: discount_for_prod, oxaddsum: 15, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [666, 777, 888], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 5.0, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999, shippingSetId: oxidstandard }]\n    payment: [{ oxaddsum: 5.0, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    1: { articles: { 11: ['1.002,55', '2.005,10'], 222: ['11,56', '23,12'], 333: ['1.326,89', '7.961,34'], 444: ['6,66', '39,96'], 555: ['0,66', '3,96'] }, totals: { totalBrutto: '10.033,48', discount: '1.003,35', totalNetto: '8.524,80', vats: { 19: '288,13', 13: '2,39', 3: '208,70', 17: '5,23', 33: '0,88' }, delivery: { brutto: '5,00' }, payment: { brutto: '5,00' }, grandTotal: '9.040,13' } }\n    2: { articles: { 666: ['852,17', '5.113,02'], 777: ['9,83', '58,98'], 888: ['1.127,86', '6.767,16'], 444: ['6,66', '39,96'], 555: ['0,66', '3,96'] }, totals: { totalBrutto: '11.983,08', discount: '1.198,31', totalNetto: '9.224,35', vats: { 21: '845,62', 17: '12,94', 14: '791,94', 33: '0,88' }, delivery: { brutto: '5,00' }, payment: { brutto: '5,00' }, grandTotal: '10.794,77' } }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\nactions:\n    changeConfigs: { blEnterNetPrice: false, blShowNetPrice: true }\n    removeArticles: ['11', '222', '333']\n    addArticles: [{ oxid: '666', oxtitle: '666', oxprice: 1002.55, oxvat: 21, oxstock: 999, amount: 6 }, { oxid: '777', oxtitle: '777', oxprice: 11.56, oxvat: 17, oxstock: 999, amount: 6 }, { oxid: '888', oxtitle: '888', oxprice: 1326.89, oxvat: 14, oxstock: 999, amount: 6 }]\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case23.yaml",
    "content": "articles:\n    - { oxid: '111', oxtitle: '111', oxprice: 1002.55, oxvat: 19, oxstock: 999, amount: 2 }\n    - { oxid: '222', oxtitle: '222', oxprice: 11.56, oxvat: 13, oxstock: 999, amount: 2 }\n    - { oxid: '333', oxtitle: '333', oxprice: 1326.89, oxvat: 3, oxstock: 999, amount: 6 }\n    - { oxid: '444', oxtitle: '444', oxprice: 6.66, oxvat: 17, oxstock: 999, amount: 6 }\n    - { oxid: '555', oxtitle: '555', oxprice: 0.66, oxvat: 33, oxstock: 999, amount: 6 }\ndiscounts:\n    - { oxid: discount10for111, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 0.0, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 0.0, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    1: { articles: { 111: ['1.002,55', '2.005,10'], 222: ['11,56', '23,12'], 333: ['1.326,89', '7.961,34'], 444: ['6,66', '39,96'], 555: ['0,66', '3,96'] }, totals: { totalBrutto: '10.033,48', discount: '1.003,35', totalNetto: '8.524,80', vats: { 19: '288,13', 13: '2,39', 3: '208,70', 17: '5,23', 33: '0,88' }, delivery: { brutto: '0,00' }, payment: { brutto: '0,00' }, grandTotal: '9.030,13' } }\n    2: { articles: { 111: ['1.002,55', '2.005,10'], 222: ['11,56', '23,12'], 333: ['1.326,89', '7.961,34'], 444: ['6,66', '39,96'], 555: ['0,66', '3,96'] }, totals: { totalBrutto: '10.033,48', discount: '1.003,35', totalNetto: '8.524,80', vats: { 19: '288,13', 13: '2,39', 3: '208,70', 17: '5,23', 33: '0,88' }, delivery: { brutto: '0,00' }, payment: { brutto: '0,00' }, grandTotal: '9.030,13' } }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\nactions:\n    changeConfigs: { blEnterNetPrice: false, blShowNetPrice: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case24.yaml",
    "content": "articles:\n    - { oxid: '666', oxtitle: '666', oxprice: 1002.55, oxvat: 21, oxstock: 999, amount: 6 }\n    - { oxid: '777', oxtitle: '777', oxprice: 11.56, oxvat: 17, oxstock: 999, amount: 6 }\n    - { oxid: '888', oxtitle: '888', oxprice: 1326.89, oxvat: 14, oxstock: 999, amount: 6 }\n    - { oxid: '444', oxtitle: '444', oxprice: 6.66, oxvat: 17, oxstock: 999, amount: 6 }\n    - { oxid: '555', oxtitle: '555', oxprice: 0.66, oxvat: 33, oxstock: 999, amount: 6 }\ndiscounts:\n    - { oxid: discount10for111, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: discount15fo678, oxaddsum: 15, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [666, 777, 888], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxtitle: 'Shipping costs for Example Set2: UPS 24 hrs Express: $12.90', oxaddsum: 5.0, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999, oxsort: '5000', oxfixed: 0 }]\n    payment: [{ oxaddsum: 5.0, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    1: { articles: { 666: ['852,17', '5.113,02'], 777: ['9,83', '58,98'], 888: ['1.127,86', '6.767,16'], 444: ['6,66', '39,96'], 555: ['0,66', '3,96'] }, totals: { totalBrutto: '11.983,08', discount: '1.198,31', totalNetto: '9.224,35', vats: { 21: '798,65', 17: '12,94', 14: '747,95', 33: '0,88' }, delivery: { brutto: '5,00' }, payment: { brutto: '5,00' }, grandTotal: '10.794,77' } }\n    2: { articles: { 111: ['1.002,55', '4.010,20'], 222: ['11,56', '46,24'], 333: ['1.326,89', '5.307,56'], 444: ['6,66', '26,64'], 555: ['0,66', '2,64'] }, totals: { totalBrutto: '9.393,28', discount: '939,33', totalNetto: '7.729,70', vats: { 19: '576,26', 13: '4,79', 3: '139,13', 17: '3,48', 33: '0,59' }, delivery: { brutto: '5,00' }, payment: { brutto: '5,00' }, grandTotal: '8.463,95' } }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\nactions:\n    changeConfigs: { blEnterNetPrice: false, blShowNetPrice: true }\n    removeArticles: ['666', '777', '888']\n    changeArticles: [{ oxid: '444', amount: 4 }, { oxid: '555', amount: 4 }]\n    addArticles: [{ oxid: '111', oxtitle: '111', oxprice: 1002.55, oxvat: 19, oxstock: 999, amount: 4 }, { oxid: '222', oxtitle: '222', oxprice: 11.56, oxvat: 13, oxstock: 999, amount: 4 }, { oxid: '333', oxtitle: '333', oxprice: 1326.89, oxvat: 3, oxstock: 999, amount: 4 }]\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case26.yaml",
    "content": "articles:\n    - { oxid: '111', oxtitle: '111', oxprice: 1, oxvat: 19, oxstock: 999, amount: 1 }\ndiscounts:\n    - { oxid: discount10for111, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 10.0, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 10.0, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    1: { articles: { 111: ['1,19', '1,19'] }, totals: { totalBrutto: '1,19', discount: '0,12', totalNetto: '0,90', vats: { 19: '0,17' }, delivery: { brutto: '10,00' }, payment: { brutto: '10,00' }, grandTotal: '21,07' } }\n    2: { articles: { 111: ['1,19', '1,19'], 1111: ['4,17', '4,17'] }, totals: { totalBrutto: '5,36', discount: '0,54', totalNetto: '4,05', vats: { 19: '0,77' }, delivery: { brutto: '10,00' }, payment: { brutto: '10,00' }, grandTotal: '24,82' } }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForDelivery: false, blShowVATForPayCharge: false, blShowVATForWrapping: false, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: false, blPaymentVatOnTop: false, blWrappingVatOnTop: false }\nactions:\n    changeConfigs: { blShowNetPrice: true }\n    addArticles: [{ oxid: '1111', oxtitle: '1111', oxprice: 3.5, oxvat: 19, oxstock: 999, amount: 1 }]\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case28.yaml",
    "content": "articles:\n    - { oxid: '111', oxtitle: '111', oxprice: 0.55, oxvat: 33, oxstock: 999, amount: 1 }\n    - { oxid: '1112', oxtitle: '1112', oxprice: 1101.1, oxvat: 33, oxstock: 999, amount: 1 }\n    - { oxid: '1113', oxtitle: '1113', oxprice: 110.0, oxvat: 33, oxstock: 999, amount: 1 }\n    - { oxid: '1114', oxtitle: '1114', oxprice: 1.0, oxvat: 33, oxstock: 999, amount: 1 }\n    - { oxid: '1115', oxtitle: '1115', oxprice: 945.95, oxvat: 50, oxstock: 999, amount: 2 }\ndiscounts:\n    - { oxid: discount10for111, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxtitle: 'Shipping costs for Example Set2:55.00%', oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxid: oxidpayadvance, oxdesc: 'Cash in advance', oxaddsum: 55, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxactive: 1 }]\nexpected:\n    1: { articles: { 111: ['0,55', '0,55'], 1112: ['1.101,10', '1.101,10'], 1113: ['110,00', '110,00'], 1114: ['1,00', '1,00'], 1115: ['945,95', '1.891,90'] }, totals: { totalBrutto: '4.005,61', discount: '310,46', totalNetto: '3.104,55', vats: { 33: '360,16', 50: '851,36' }, delivery: { brutto: '2.561,25', netto: '1.707,50', vat: '853,75' }, payment: { brutto: '82,50', netto: '55,00', vat: '27,50' }, grandTotal: '6.649,36' } }\n    2: { articles: { 111: ['0,55', '0,55'], 1112: ['1.101,10', '1.101,10'], 1113: ['110,00', '110,00'], 1114: ['1,00', '1,00'], 1115: ['945,95', '1.891,90'], 11121: ['3,50', '3,50'] }, totals: { totalBrutto: '4.009,36', discount: '310,81', totalNetto: '3.108,05', vats: { 33: '360,16', 50: '851,36', 19: '0,60' }, delivery: { brutto: '2.564,15', netto: '1.709,43', vat: '854,72' }, payment: { brutto: '82,50', netto: '55,00', vat: '27,50' }, grandTotal: '6.656,01' } }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForPayCharge: true, blShowVATForDelivery: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true }\nactions:\n    changeConfigs: { blShowNetPrice: false }\n    addArticles: [{ oxid: '11121', oxtitle: '11121', oxprice: 3.5, oxvat: 19, oxstock: 999, amount: 1 }]\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case29.yaml",
    "content": "articles:\n    - { oxid: '111', oxtitle: '111', oxprice: 55.55, oxvat: 15.55, oxstock: 999, amount: 6 }\ndiscounts:\n    - { oxid: discount15fo678, oxaddsum: 15, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [111], oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxtitle: 'Shipping costs for Example Set2: UPS 24 hrs Express: $12.90', oxaddsum: 13, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999, oxsort: '5000', oxfixed: 0 }]\n    payment: [{ oxaddsum: 13, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\nexpected:\n    1: { articles: { 111: ['40,86', '245,16'] }, totals: { totalBrutto: '283,28', discount: '0,00', totalNetto: '245,16', vats: { 15: '38,12' }, delivery: { brutto: '31,87' }, payment: { brutto: '35,46' }, grandTotal: '350,61' } }\n    2: { articles: { 111: ['40,86', '40,86'] }, totals: { totalBrutto: '47,21', discount: '0,00', totalNetto: '40,86', vats: { 15: '6,35' }, delivery: { brutto: '5,31' }, payment: { brutto: '5,91' }, grandTotal: '58,43' } }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true }\nactions:\n    changeConfigs: { blEnterNetPrice: false, blShowNetPrice: true }\n    changeArticles: [{ oxid: '111', amount: 1 }]\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case3.yaml",
    "content": "articles:\n    - { oxid: 100, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 1001, oxprice: 66, oxvat: 19, amount: 33 }\ndiscounts:\n    - { oxid: shopdiscount5for100, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [100], oxsort: 10 }\n    - { oxid: shopdiscount5for1001, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: basketdiscount5for100, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [100], oxsort: 30 }\n    - { oxid: basketdiscount5for1001, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9005, oxprice: 9, oxactive: 1, oxarticles: [100] }, { oxtype: WRAP, oxname: testWrap9006, oxprice: 6, oxactive: 1, oxarticles: [1001] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    1: { articles: { 100: ['113,00', '3.729,00'], 1001: ['70,13', '2.314,29'] }, totals: { totalBrutto: '6.043,29', totalNetto: '5.069,15', vats: { 19: '963,14' }, discount: '5,00', wrapping: { brutto: '495,00', netto: '415,97', vat: '79,03' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '6.534,29' } }\n    2: { articles: { 100: ['113,00', '3.729,00'], 1001: ['70,13', '2.314,29'] }, totals: { totalBrutto: '6.043,29', totalNetto: '5.069,15', vats: { 19: '963,14' }, discount: '5,00', wrapping: { brutto: '495,00', netto: '415,97', vat: '79,03' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '6.534,29' } }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\nactions:\n    changeConfigs: { blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case30.yaml",
    "content": "articles:\n    - { oxid: '111', oxtitle: '111', oxprice: 55.55, oxvat: 15.55, oxstock: 999, amount: 6 }\ndiscounts:\n    - { oxid: discount15fo678, oxaddsum: 15, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [111], oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxtitle: 'Shipping costs for Example Set2: UPS 24 hrs Express: $12.90', oxaddsum: 13, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999, oxsort: '5000', oxfixed: 0 }]\n    payment: [{ oxaddsum: 13, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 7 }]\nexpected:\n    1: { articles: { 111: ['40,86', '245,16'] }, totals: { totalBrutto: '283,28', discount: '0,00', totalNetto: '245,16', vats: { 15: '38,12' }, delivery: { brutto: '31,87' }, payment: { brutto: '31,87' }, grandTotal: '347,02' } }\n    2: { articles: { 111: ['40,86', '40,86'] }, totals: { totalBrutto: '47,21', discount: '0,00', totalNetto: '40,86', vats: { 15: '6,35' }, delivery: { brutto: '5,31' }, payment: { brutto: '5,31' }, grandTotal: '57,83' } }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true }\nactions:\n    changeConfigs: { blEnterNetPrice: false, blShowNetPrice: true }\n    changeArticles: [{ oxid: '111', amount: 1 }]\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case31.yaml",
    "content": "articles:\n    - { oxid: '111', oxtitle: '111', oxprice: 55.55, oxvat: 15.55, oxstock: 999, amount: 6 }\ndiscounts:\n    - { oxid: discount15fo678, oxaddsum: 15, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [111], oxsort: 10 }\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 2, oxarticles: [111], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxtitle: 'Shipping costs for Example Set2: UPS 24 hrs Express: $12.90', oxaddsum: 13, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999, oxsort: '5000', oxfixed: 0 }]\n    payment: [{ oxaddsum: 13, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 7 }]\nexpected:\n    1: { articles: { 111: ['40,86', '245,16'] }, totals: { totalBrutto: '283,28', discount: '0,00', totalNetto: '245,16', vats: { 15: '38,12' }, delivery: { brutto: '31,87' }, payment: { brutto: '31,87' }, grandTotal: '347,02' } }\n    2: { articles: { 111: ['40,86', '40,86'] }, totals: { totalBrutto: '47,21', discount: '0,00', totalNetto: '40,86', vats: { 15: '6,35' }, delivery: { brutto: '5,31' }, payment: { brutto: '5,31' }, grandTotal: '57,83' } }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true }\nactions:\n    changeConfigs: { blEnterNetPrice: false, blShowNetPrice: true }\n    changeArticles: [{ oxid: '111', amount: 1 }]\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case32.yaml",
    "content": "articles:\n    - { oxid: '111', oxtitle: '111', oxprice: 55.55, oxvat: 15.55, oxstock: 999, amount: 6 }\n    - { oxid: '222', oxtitle: '222', oxprice: 12.5, oxvat: 19, oxstock: 999, amount: 7 }\ndiscounts:\n    - { oxid: discount15fo678, oxaddsum: 15, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [111, 222], oxsort: 10 }\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 2, oxarticles: [111], oxsort: 20 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9001, oxprice: 2.95, oxactive: 1, oxarticles: [111] }, { oxtype: WRAP, oxname: testWrap9002, oxprice: 2.95, oxactive: 1, oxarticles: [222] }, { oxtype: CARD, oxname: testCard, oxprice: 3, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxtitle: 'Shipping costs for Example Set2: UPS 24 hrs Express: $12.90', oxaddsum: 13, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999, oxsort: '5000', oxfixed: 0 }]\n    payment: [{ oxaddsum: 13, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 21 }]\nexpected:\n    1: { articles: { 111: ['40,86', '245,16'], 222: ['8,93', '62,51'] }, totals: { totalBrutto: '357,67', discount: '0,00', totalNetto: '307,67', vats: { 15: '38,12', 19: '11,88' }, delivery: { brutto: '40,00', neto: '34,62', vat: '5,38' }, payment: { brutto: '44,58', neto: '38,58', vat: '6,00' }, grandTotal: '483,60' } }\n    2: { articles: { 111: ['40,86', '122,58'], 222: ['8,93', '62,51'] }, totals: { totalBrutto: '216,03', discount: '0,00', totalNetto: '185,09', vats: { 15: '19,06', 19: '11,88' }, delivery: { brutto: '24,06', neto: '20,82', vat: '3,24' }, payment: { brutto: '27,65', neto: '23,92', vat: '3,72' }, grandTotal: '300,24' } }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net }\nactions:\n    changeConfigs: { blEnterNetPrice: false, blShowNetPrice: false }\n    changeArticles: [{ oxid: '111', amount: 3 }]\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case39.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 1.0, oxvat: 20, amount: 2 }\n    - { oxid: 1002, oxprice: 2.0, oxvat: 30, amount: 2 }\ndiscounts:\n    - { oxid: percentage_discount, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 50.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 1.0, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\nexpected:\n    1: { articles: { 1001: ['1,20', '2,40'], 1002: ['2,60', '5,20'] }, totals: { totalBrutto: '7,60', totalNetto: '5,40', vats: { 20: '0,36', 30: '1,08' }, discount: '0,76', delivery: { brutto: '4,94', netto: '3,80', vat: '1,14' }, payment: { brutto: '1,30', netto: '1,00', vat: '0,30' }, grandTotal: '13,08' } }\n    2: { articles: { 1001: ['1,20', '2,40'], 1002: ['2,60', '5,20'] }, totals: { totalBrutto: '7,60', totalNetto: '5,40', vats: { 20: '0,36', 30: '1,08' }, discount: '0,76', delivery: { brutto: '4,94', netto: '3,80', vat: '1,14' }, payment: { brutto: '1,30', netto: '1,00', vat: '0,30' }, grandTotal: '13,08' } }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\nactions:\n    changeArticles: [{ oxid: '222', amount: 1 }]\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case5.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 55, oxaddsumtype: '%', oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxaddsumrules: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    1: { articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }, totals: { totalBrutto: '511,20', totalNetto: '500,00', vats: { 19: '34,20', 10: '27,00' }, delivery: { brutto: '302,50', netto: '275,00', vat: '27,50' }, payment: { brutto: '302,50', netto: '275,00', vat: '27,50' }, discount: '0,00', voucher: { brutto: '50,00' }, giftcard: { brutto: '2,75', netto: '2,50', vat: '0,25' }, grandTotal: '1.118,95' } }\n    2: { articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '400,00'], 1004: ['0,00', '0,00'] }, totals: { totalBrutto: '725,40', totalNetto: '700,00', vats: { 19: '68,40', 10: '27,00' }, delivery: { brutto: '458,15' }, discount: '0,00', payment: { brutto: '458,15' }, voucher: { brutto: '70,00' }, giftcard: { brutto: '2,98' }, grandTotal: '1.644,68' } }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\nactions:\n    changeArticles: [{ oxid: '1002', amount: 2 }]\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case6.yaml",
    "content": "articles:\n    - { oxid: 100, oxprice: 100, oxvat: 19, amount: 33 }\n    - { oxid: 1001, oxprice: 66, oxvat: 19, amount: 33 }\ndiscounts:\n    - { oxid: shopdiscount5for100, oxaddsum: 5, oxaddsumtype: abs, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [100], oxsort: 10 }\n    - { oxid: shopdiscount5for1001, oxaddsum: 5, oxaddsumtype: '%', oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: basketdiscount5for100, oxaddsum: 1, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [100], oxsort: 30 }\n    - { oxid: basketdiscount5for1001, oxaddsum: 6, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 40 }\n    - { oxid: absolutebasketdiscount, oxaddsum: 5, oxaddsumtype: abs, oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 50 }\ncosts:\n    wrapping: [{ oxtype: WRAP, oxname: testWrap9005, oxprice: 9, oxactive: 1, oxarticles: [100] }, { oxtype: WRAP, oxname: testWrap9006, oxprice: 6, oxactive: 1, oxarticles: [1001] }]\n    delivery: [{ oxtitle: 6_abs_del, oxactive: 1, oxaddsum: 6, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxtitle: '1 abs payment', oxaddsum: 1, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 6.0, oxdiscounttype: absolute, oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    1: { articles: { 100: ['113,00', '3.729,00'], 1001: ['70,13', '2.314,29'] }, totals: { totalBrutto: '6.043,29', totalNetto: '5.069,15', vats: { 19: '963,14' }, discount: '5,00', wrapping: { brutto: '495,00', netto: '415,97', vat: '79,03' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '6.534,29' } }\n    2: { articles: { 100: ['113,00', '3.729,00'], 1001: ['70,13', '2.314,29'] }, totals: { totalBrutto: '6.043,29', totalNetto: '5.069,15', vats: { 19: '963,14' }, discount: '5,00', wrapping: { brutto: '495,00', netto: '415,97', vat: '79,03' }, delivery: { brutto: '6,00', netto: '5,04', vat: '0,96' }, payment: { brutto: '1,00', netto: '0,84', vat: '0,16' }, voucher: { brutto: '6,00' }, grandTotal: '6.534,29' } }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, blShowVATForWrapping: true, blShowVATForPayCharge: true, blShowVATForDelivery: true }\n    activeCurrencyRate: 1.0\nactions:\n    changeConfigs: { blShowNetPrice: true }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case7.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    1: { articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }, totals: { totalBrutto: '511,20', totalNetto: '500,00', vats: { 19: '34,20', 10: '27,00' }, delivery: { brutto: '302,50', netto: '275,00', vat: '27,50' }, payment: { brutto: '302,50', netto: '275,00', vat: '27,50' }, discount: '0,00', voucher: { brutto: '50,00' }, giftcard: { brutto: '2,75', netto: '2,50', vat: '0,25' }, grandTotal: '1.118,95' } }\n    2: { articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }, totals: { totalBrutto: '511,20', totalNetto: '500,00', vats: { 19: '34,20', 10: '27,00' }, delivery: { brutto: '302,50', netto: '275,00', vat: '27,50' }, discount: '0,00', payment: { brutto: '302,50', netto: '275,00', vat: '27,50' }, voucher: { brutto: '50,00' }, giftcard: { brutto: '2,75', netto: '2,50', vat: '0,25' }, grandTotal: '1.118,95' } }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\nactions:\n    changeConfigs: { blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/case9.yaml",
    "content": "articles:\n    - { oxid: 1001, oxprice: 20.0, oxvat: 10, amount: 15 }\n    - { oxid: 1002, oxprice: 200.0, oxvat: 19, amount: 1 }\n    - { oxid: 1004, oxprice: 200.0, oxvat: 19 }\ndiscounts:\n    - { oxid: discountitm, oxaddsum: 0, oxaddsumtype: itm, oxamount: 1, oxamountto: 99999, oxactive: 1, oxitmartid: 1004, oxitmamount: 1, oxitmultiple: 1, oxarticles: [1002], oxsort: 10 }\ncosts:\n    wrapping: [{ oxtype: CARD, oxname: testCard1001, oxprice: 2.5, oxactive: 1 }]\n    delivery: [{ oxactive: 1, oxaddsum: 55.0, oxaddsumtype: '%', oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 275, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\n    voucherserie: [{ oxdiscount: 10.0, oxdiscounttype: '%', oxallowsameseries: 1, oxallowotherseries: 1, oxallowuseanother: 1, voucher_count: 1 }]\nexpected:\n    1: { articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'] }, totals: { totalBrutto: '511,20', totalNetto: '500,00', vats: { 19: '34,20', 10: '27,00' }, delivery: { brutto: '302,50', netto: '275,00', vat: '27,50' }, payment: { brutto: '302,50', netto: '275,00', vat: '27,50' }, discount: '0,00', voucher: { brutto: '50,00' }, giftcard: { brutto: '2,75', netto: '2,50', vat: '0,25' }, grandTotal: '1.118,95' } }\n    2: { articles: { 1001: ['20,00', '300,00'], 1002: ['200,00', '200,00'], 1004: ['0,00', '0,00'], 1006: ['10,00', '10,00'] }, totals: { totalBrutto: '521,91', totalNetto: '510,00', vats: { 19: '35,91', 10: '27,00' }, delivery: { brutto: '308,55', netto: '280,50', vat: '28,05' }, discount: '0,00', payment: { brutto: '302,50', netto: '275,00', vat: '27,50' }, voucher: { brutto: '51,00' }, giftcard: { brutto: '2,75', netto: '2,50', vat: '0,25' }, grandTotal: '1.135,71' } }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, blShowVATForDelivery: true, blShowVATForPayCharge: true, blShowVATForWrapping: true, sAdditionalServVATCalcMethod: biggest_net, blDeliveryVatOnTop: true, blPaymentVatOnTop: true, blWrappingVatOnTop: true }\n    activeCurrencyRate: 1\nactions:\n    addArticles: [{ oxid: '1006', oxtitle: '1006', oxprice: 10, oxvat: 19, oxstock: 999, amount: 1 }]\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order/oxorder_test_recalc_order_by_use_case.yaml",
    "content": "articles:\n    - { oxid: '1126', oxtitle: 'Bar-Set ABSINTH', oxprice: 34, oxvat: 19, oxstock: 999, amount: 1 }\n    - { oxid: '1127', oxtitle: 'Ice Cubes FLASH', oxprice: 8, oxvat: 19, oxstock: 999, amount: 1 }\ncategories:\n    - { oxid: 30e44ab8593023055.23928895, oxactive: 1, oxtitle: Bar-Equipment, oxarticles: [1126] }\ndiscounts:\n    - { oxid: _testDiscountForArticle, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxamountto: 9999, oxactive: 1, oxarticles: [1126, 1127], oxsort: 10 }\n    - { oxid: _testDiscountForCategory, oxaddsum: 50, oxaddsumtype: '%', oxamount: 1, oxamountto: 9999, oxactive: 1, oxcategories: [30e44ab8593023055.23928895], oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxtitle: 'Shipping costs for Example Set2: UPS 24 hrs Express: $12.90', oxaddsum: 12.9, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999, shippingSetId: oxidstandard, oxsort: '5000', oxfixed: 0 }]\n    payment: [{ oxid: oxidpayadvance, oxdesc: 'Cash in advance', oxaddsum: 0, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1, oxactive: 1 }]\nexpected:\n    1: { articles: { 1126: ['17,00', '17,00'], 1127: ['4,00', '4,00'] }, totals: { totalBrutto: '21,00', discount: '0,00', totalNetto: '17,65', vats: { 19: '3,35' }, delivery: { brutto: '12,90' }, payment: { brutto: '0,00' }, grandTotal: '33,90' } }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order_numbering/case1.yaml",
    "content": "articles:\n    - { oxid: '111', oxtitle: '111', oxprice: 1, oxvat: 19, oxstock: 999, amount: 1 }\n    - { oxid: '111', oxtitle: '111', oxprice: 1, oxvat: 19, oxstock: 999, amount: 1 }\ndiscounts:\n    - { oxid: discount10for111, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: discount10for111, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 4.64, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }, { oxactive: 1, oxaddsum: 4.64, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 59.5, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }, { oxaddsum: 59.5, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\noptions:\n    separateNumbering: false\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/order_numbering/case2.yaml",
    "content": "articles:\n    - { oxid: '111', oxtitle: '111', oxprice: 1, oxvat: 19, oxstock: 999, amount: 1 }\n    - { oxid: '111', oxtitle: '111', oxprice: 1, oxvat: 19, oxstock: 999, amount: 1 }\ndiscounts:\n    - { oxid: discount10for111, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: discount10for111, oxaddsum: 10, oxaddsumtype: '%', oxamount: 1, oxamountto: 99999, oxactive: 1, oxsort: 20 }\ncosts:\n    delivery: [{ oxactive: 1, oxaddsum: 4.64, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }, { oxactive: 1, oxaddsum: 4.64, oxaddsumtype: abs, oxdeltype: p, oxfinalize: 1, oxparamend: 99999 }]\n    payment: [{ oxaddsum: 59.5, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }, { oxaddsum: 59.5, oxaddsumtype: abs, oxfromamount: 0, oxtoamount: 1000000, oxchecked: 1 }]\noptions:\n    separateNumbering: true\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case1.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100.55, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100.55, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,55', price: '80,55' }\n    1001_b: { base_price: '100,55', price: '80,44' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case10.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 5.02, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 5.02, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '5,02', price: '15,02' }\n    1001_b: { base_price: '5,02', price: '5,52' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case100.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 9.99, oxtprice: 10 }\n    - { oxid: '1001', oxprice: 9.99, oxtprice: 10 }\n    - { oxid: '1002', oxprice: 9.99, oxtprice: 10 }\n    - { oxid: '1003', oxprice: 9.99, oxtprice: 10 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '9,99', price: '6,95', rrp_price: '8,70', show_rrp: true }\n    1001: { base_price: '9,99', price: '9,56', rrp_price: '', show_rrp: false }\n    1002: { base_price: '9,99', price: '9,14', rrp_price: '', show_rrp: false }\n    1003: { base_price: '9,99', price: '8,21', rrp_price: '8,70', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case101.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 200, oxtprice: 10 }\n    - { oxid: '1001', oxprice: 200, oxtprice: 10 }\n    - { oxid: '1002', oxprice: 0.05, oxtprice: 10 }\n    - { oxid: '1003', oxprice: 10, oxtprice: 10 }\n    - { oxid: '1004', oxprice: 200, oxtprice: 10 }\n    - { oxid: '1005', oxprice: 200, oxtprice: 10 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\n    - { oxid: percentFor1004, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 50 }\n    - { oxid: percentFor1005, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1005], oxsort: 60 }\nexpected:\n    1000: { base_price: '200,00', price: '184,00', rrp_price: '', show_rrp: false }\n    1001: { base_price: '200,00', price: '253,00', rrp_price: '', show_rrp: false }\n    1002: { base_price: '0,05', price: '0,05', rrp_price: '11,50', show_rrp: true }\n    1003: { base_price: '10,00', price: '10,35', rrp_price: '11,50', show_rrp: true }\n    1004: { base_price: '200,00', price: '241,96', rrp_price: '', show_rrp: false }\n    1005: { base_price: '200,00', price: '217,35', rrp_price: '', show_rrp: false }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case102.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 100, oxtprice: 94.52 }\n    - { oxid: '1001', oxprice: 100, oxtprice: 94.52 }\n    - { oxid: '1002', oxprice: 100, oxtprice: 94.52 }\n    - { oxid: '1003', oxprice: 100, oxtprice: 94.52 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '100,00', price: '92,00', rrp_price: '108,70', show_rrp: true }\n    1001: { base_price: '100,00', price: '126,50', rrp_price: '', show_rrp: false }\n    1002: { base_price: '100,00', price: '120,98', rrp_price: '', show_rrp: false }\n    1003: { base_price: '100,00', price: '108,68', rrp_price: '108,70', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case103.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 0.05, oxtprice: 300 }\n    - { oxid: '1001', oxprice: 0.05, oxtprice: 300 }\n    - { oxid: '1002', oxprice: 0.05, oxtprice: 300 }\n    - { oxid: '1003', oxprice: 0.05, oxtprice: 300 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '0,05', price: '0,05', rrp_price: '345,00', show_rrp: true }\n    1001: { base_price: '0,05', price: '0,07', rrp_price: '345,00', show_rrp: true }\n    1002: { base_price: '0,05', price: '0,06', rrp_price: '345,00', show_rrp: true }\n    1003: { base_price: '0,05', price: '0,06', rrp_price: '345,00', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case104.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 79.9, oxtprice: 79.9 }\n    - { oxid: '1001', oxprice: 79.9, oxtprice: 79.9 }\n    - { oxid: '1002', oxprice: 79.9, oxtprice: 79.9 }\n    - { oxid: '1003', oxprice: 79.9, oxtprice: 79.9 }\n    - { oxid: '1004', oxprice: 89.9, oxtprice: 79.9 }\n    - { oxid: '1005', oxprice: 89.9, oxtprice: 79.9 }\n    - { oxid: '1006', oxprice: 89.9, oxtprice: 79.9 }\n    - { oxid: '1007', oxprice: 89.9, oxtprice: 79.9 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000, 1004], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001, 1005], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002, 1006], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003, 1007], oxsort: 40 }\nexpected:\n    1000: { base_price: '79,90', price: '73,51', rrp_price: '91,89', show_rrp: true }\n    1001: { base_price: '79,90', price: '101,08', rrp_price: '', show_rrp: false }\n    1002: { base_price: '79,90', price: '96,67', rrp_price: '', show_rrp: false }\n    1003: { base_price: '79,90', price: '86,84', rrp_price: '91,89', show_rrp: true }\n    1004: { base_price: '89,90', price: '82,71', rrp_price: '91,89', show_rrp: true }\n    1005: { base_price: '89,90', price: '113,73', rrp_price: '', show_rrp: false }\n    1006: { base_price: '89,90', price: '108,77', rrp_price: '', show_rrp: false }\n    1007: { base_price: '89,90', price: '97,70', rrp_price: '', show_rrp: false }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case105.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 70, oxtprice: 99 }\n    - { oxid: '1001', oxprice: 70, oxtprice: 99 }\n    - { oxid: '1002', oxprice: 70, oxtprice: 99 }\n    - { oxid: '1003', oxprice: 70, oxtprice: 99 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '70,00', price: '64,40', rrp_price: '113,85', show_rrp: true }\n    1001: { base_price: '70,00', price: '88,55', rrp_price: '113,85', show_rrp: true }\n    1002: { base_price: '70,00', price: '84,69', rrp_price: '113,85', show_rrp: true }\n    1003: { base_price: '70,00', price: '76,07', rrp_price: '113,85', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case106.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 150, oxtprice: 299 }\n    - { oxid: '1001', oxprice: 150, oxtprice: 299 }\n    - { oxid: '1002', oxprice: 150, oxtprice: 299 }\n    - { oxid: '1003', oxprice: 150, oxtprice: 299 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '150,00', price: '138,00', rrp_price: '343,85', show_rrp: true }\n    1001: { base_price: '150,00', price: '189,75', rrp_price: '343,85', show_rrp: true }\n    1002: { base_price: '150,00', price: '181,47', rrp_price: '343,85', show_rrp: true }\n    1003: { base_price: '150,00', price: '163,01', rrp_price: '343,85', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case107.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 70, oxtprice: 100 }\n    - { oxid: '1001', oxprice: 70, oxtprice: 100 }\n    - { oxid: '1002', oxprice: 70, oxtprice: 100 }\n    - { oxid: '1003', oxprice: 70, oxtprice: 100 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '70,00', price: '64,40', rrp_price: '115,00', show_rrp: true }\n    1001: { base_price: '70,00', price: '88,55', rrp_price: '115,00', show_rrp: true }\n    1002: { base_price: '70,00', price: '84,69', rrp_price: '115,00', show_rrp: true }\n    1003: { base_price: '70,00', price: '76,07', rrp_price: '115,00', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case108.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 150, oxtprice: 1000 }\n    - { oxid: '1001', oxprice: 150, oxtprice: 1000 }\n    - { oxid: '1002', oxprice: 150, oxtprice: 1000 }\n    - { oxid: '1003', oxprice: 150, oxtprice: 1000 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '150,00', price: '138,00', rrp_price: '1.150,00', show_rrp: true }\n    1001: { base_price: '150,00', price: '189,75', rrp_price: '1.150,00', show_rrp: true }\n    1002: { base_price: '150,00', price: '181,47', rrp_price: '1.150,00', show_rrp: true }\n    1003: { base_price: '150,00', price: '163,01', rrp_price: '1.150,00', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case109.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 9.99, oxtprice: 10 }\n    - { oxid: '1001', oxprice: 9.99, oxtprice: 10 }\n    - { oxid: '1002', oxprice: 9.99, oxtprice: 10 }\n    - { oxid: '1003', oxprice: 9.99, oxtprice: 10 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '9,99', price: '9,19', rrp_price: '11,50', show_rrp: true }\n    1001: { base_price: '9,99', price: '12,64', rrp_price: '', show_rrp: false }\n    1002: { base_price: '9,99', price: '12,09', rrp_price: '', show_rrp: false }\n    1003: { base_price: '9,99', price: '10,86', rrp_price: '11,50', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case11.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 5.02, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 5.02, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '5,02', price: '10,22' }\n    1001_b: { base_price: '5,02', price: '5,28' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case110.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 200, oxtprice: 10 }\n    - { oxid: '1001', oxprice: 200, oxtprice: 10 }\n    - { oxid: '1002', oxprice: 0.05, oxtprice: 10 }\n    - { oxid: '1003', oxprice: 10, oxtprice: 10 }\n    - { oxid: '1004', oxprice: 200, oxtprice: 10 }\n    - { oxid: '1005', oxprice: 200, oxtprice: 10 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\n    - { oxid: percentFor1004, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 50 }\n    - { oxid: percentFor1005, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1005], oxsort: 60 }\nexpected:\n    1000: { base_price: '200,00', price: '160,00', rrp_price: '', show_rrp: false }\n    1001: { base_price: '200,00', price: '220,00', rrp_price: '', show_rrp: false }\n    1002: { base_price: '0,05', price: '0,04', rrp_price: '10,00', show_rrp: true }\n    1003: { base_price: '10,00', price: '9,00', rrp_price: '10,00', show_rrp: true }\n    1004: { base_price: '200,00', price: '210,40', rrp_price: '', show_rrp: false }\n    1005: { base_price: '200,00', price: '189,00', rrp_price: '', show_rrp: false }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case111.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 100, oxtprice: 94.52 }\n    - { oxid: '1001', oxprice: 100, oxtprice: 94.52 }\n    - { oxid: '1002', oxprice: 100, oxtprice: 94.52 }\n    - { oxid: '1003', oxprice: 100, oxtprice: 94.52 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '100,00', price: '80,00', rrp_price: '94,52', show_rrp: true }\n    1001: { base_price: '100,00', price: '110,00', rrp_price: '', show_rrp: false }\n    1002: { base_price: '100,00', price: '105,20', rrp_price: '', show_rrp: false }\n    1003: { base_price: '100,00', price: '94,50', rrp_price: '94,52', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case112.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 0.05, oxtprice: 300 }\n    - { oxid: '1001', oxprice: 0.05, oxtprice: 300 }\n    - { oxid: '1002', oxprice: 0.05, oxtprice: 300 }\n    - { oxid: '1003', oxprice: 0.05, oxtprice: 300 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '0,05', price: '0,04', rrp_price: '300,00', show_rrp: true }\n    1001: { base_price: '0,05', price: '0,06', rrp_price: '300,00', show_rrp: true }\n    1002: { base_price: '0,05', price: '0,05', rrp_price: '300,00', show_rrp: true }\n    1003: { base_price: '0,05', price: '0,05', rrp_price: '300,00', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case113.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 79.9, oxtprice: 79.9 }\n    - { oxid: '1001', oxprice: 79.9, oxtprice: 79.9 }\n    - { oxid: '1002', oxprice: 79.9, oxtprice: 79.9 }\n    - { oxid: '1003', oxprice: 79.9, oxtprice: 79.9 }\n    - { oxid: '1004', oxprice: 89.9, oxtprice: 79.9 }\n    - { oxid: '1005', oxprice: 89.9, oxtprice: 79.9 }\n    - { oxid: '1006', oxprice: 89.9, oxtprice: 79.9 }\n    - { oxid: '1007', oxprice: 89.9, oxtprice: 79.9 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000, 1004], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001, 1005], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002, 1006], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003, 1007], oxsort: 40 }\nexpected:\n    1000: { base_price: '79,90', price: '63,92', rrp_price: '79,90', show_rrp: true }\n    1001: { base_price: '79,90', price: '87,89', rrp_price: '', show_rrp: false }\n    1002: { base_price: '79,90', price: '84,05', rrp_price: '', show_rrp: false }\n    1003: { base_price: '79,90', price: '75,51', rrp_price: '79,90', show_rrp: true }\n    1004: { base_price: '89,90', price: '71,92', rrp_price: '79,90', show_rrp: true }\n    1005: { base_price: '89,90', price: '98,89', rrp_price: '', show_rrp: false }\n    1006: { base_price: '89,90', price: '94,57', rrp_price: '', show_rrp: false }\n    1007: { base_price: '89,90', price: '84,96', rrp_price: '', show_rrp: false }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case114.yaml",
    "content": "articles:\n    - { oxid: tomatoes, oxprice: 1001, oxvat: 20, oxunitname: kg, oxunitquantity: 10, oxweight: 10 }\ndiscounts:\n    - { oxid: percent, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [tomatoes], oxsort: 10 }\nexpected:\n    tomatoes: { base_price: '1.001,00', price: '1.101,10', unit_price: '110,11' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case115.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 70, oxtprice: 99 }\n    - { oxid: '1001', oxprice: 70, oxtprice: 99 }\n    - { oxid: '1002', oxprice: 70, oxtprice: 99 }\n    - { oxid: '1003', oxprice: 70, oxtprice: 99 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 12 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '70,00', price: '56,00', rrp_price: '99,00', show_rrp: true }\n    1001: { base_price: '70,00', price: '77,00', rrp_price: '99,00', show_rrp: true }\n    1002: { base_price: '70,00', price: '73,64', rrp_price: '99,00', show_rrp: true }\n    1003: { base_price: '70,00', price: '66,15', rrp_price: '99,00', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case116.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 150, oxtprice: 299 }\n    - { oxid: '1001', oxprice: 150, oxtprice: 299 }\n    - { oxid: '1002', oxprice: 150, oxtprice: 299 }\n    - { oxid: '1003', oxprice: 150, oxtprice: 299 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '150,00', price: '120,00', rrp_price: '299,00', show_rrp: true }\n    1001: { base_price: '150,00', price: '165,00', rrp_price: '299,00', show_rrp: true }\n    1002: { base_price: '150,00', price: '157,80', rrp_price: '299,00', show_rrp: true }\n    1003: { base_price: '150,00', price: '141,75', rrp_price: '299,00', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case117.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 70, oxtprice: 100 }\n    - { oxid: '1001', oxprice: 70, oxtprice: 100 }\n    - { oxid: '1002', oxprice: 70, oxtprice: 100 }\n    - { oxid: '1003', oxprice: 70, oxtprice: 100 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '70,00', price: '56,00', rrp_price: '100,00', show_rrp: true }\n    1001: { base_price: '70,00', price: '77,00', rrp_price: '100,00', show_rrp: true }\n    1002: { base_price: '70,00', price: '73,64', rrp_price: '100,00', show_rrp: true }\n    1003: { base_price: '70,00', price: '66,15', rrp_price: '100,00', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case118.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 150, oxtprice: 1000 }\n    - { oxid: '1001', oxprice: 150, oxtprice: 1000 }\n    - { oxid: '1002', oxprice: 150, oxtprice: 1000 }\n    - { oxid: '1003', oxprice: 150, oxtprice: 1000 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '150,00', price: '120,00', rrp_price: '1.000,00', show_rrp: true }\n    1001: { base_price: '150,00', price: '165,00', rrp_price: '1.000,00', show_rrp: true }\n    1002: { base_price: '150,00', price: '157,80', rrp_price: '1.000,00', show_rrp: true }\n    1003: { base_price: '150,00', price: '141,75', rrp_price: '1.000,00', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case119.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 9.99, oxtprice: 10 }\n    - { oxid: '1001', oxprice: 9.99, oxtprice: 10 }\n    - { oxid: '1002', oxprice: 9.99, oxtprice: 10 }\n    - { oxid: '1003', oxprice: 9.99, oxtprice: 10 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '9,99', price: '7,99', rrp_price: '10,00', show_rrp: true }\n    1001: { base_price: '9,99', price: '10,99', rrp_price: '', show_rrp: false }\n    1002: { base_price: '9,99', price: '10,51', rrp_price: '', show_rrp: false }\n    1003: { base_price: '9,99', price: '9,44', rrp_price: '10,00', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case12.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 5.02, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 5.02, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '5,02', price: '0,00' }\n    1001_b: { base_price: '5,02', price: '4,74' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case120.yaml",
    "content": "articles:\n    - { oxid: _testId_1, oxprice: 50.8, oxunitquantity: 40, oxunitname: m }\n    - { oxid: _testId_1_childId_1, oxparentid: _testId_1 }\nexpected:\n    _testId_1: { base_price: '50,80', price: '50,80', unit_price: '1,27' }\n    _testId_1_childId_1: { base_price: '50,80', price: '50,80', unit_price: '1,27' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case121.yaml",
    "content": "articles:\n    - { oxid: _testId_1, oxprice: 50.8, oxunitquantity: 40, oxunitname: m }\n    - { oxid: _testId_1_childId_1, oxparentid: _testId_1, oxunitquantity: '20' }\nexpected:\n    _testId_1: { base_price: '50,80', price: '50,80', unit_price: '1,27' }\n    _testId_1_childId_1: { base_price: '50,80', price: '50,80', unit_price: '2,54' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case122.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUserA\n    oxactive: 1\n    oxusername: groupAUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '100,00', price: '99,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case123.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '10,00', price: '10,45' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case124.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUserA\n    oxactive: 1\n    oxusername: groupAUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '100,00', price: '85,05' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case125.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUser\n    oxactive: 1\n    oxusername: user\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '100,55', price: '110,61' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case126.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '10,00', price: '8,98' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case127.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUser\n    oxactive: 1\n    oxusername: user\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '100,55', price: '95,02' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case128.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 99, oxpricea: 9, oxpriceb: 5 }\nuser:\n    oxid: _testUserA\n    oxactive: 1\n    oxusername: groupAUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '9,00', price: '7,65' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case129.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 99, oxpricea: 9, oxpriceb: 5 }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '5,00', price: '4,49' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case13.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 0.5, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 0.5, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '0,50', price: '0,00' }\n    1001_b: { base_price: '0,50', price: '0,40' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case130.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 99, oxpricea: 9, oxpriceb: 5 }\nuser:\n    oxid: _testUser\n    oxactive: 1\n    oxusername: user\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '99,00', price: '93,56' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case131.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUserA\n    oxactive: 1\n    oxusername: groupAUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '100,00', price: '82,50' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case132.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '10,00', price: '8,70' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case133.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUser\n    oxactive: 1\n    oxusername: user\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '100,55', price: '92,17' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case134.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUserA\n    oxactive: 1\n    oxusername: groupAUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '100,00', price: '70,87' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case135.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '10,00', price: '7,48' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case136.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUser\n    oxactive: 1\n    oxusername: user\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '100,55', price: '79,18' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case137.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 99, oxpricea: 9, oxpriceb: 5 }\nuser:\n    oxid: _testUserA\n    oxactive: 1\n    oxusername: groupAUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '9,00', price: '6,38' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case138.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 99, oxpricea: 9, oxpriceb: 5 }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '5,00', price: '3,74' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case139.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 99, oxpricea: 9, oxpriceb: 5 }\nuser:\n    oxid: _testUser\n    oxactive: 1\n    oxusername: user\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '99,00', price: '77,96' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case14.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 0.5, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 0.5, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '0,50', price: '10,50' }\n    1001_b: { base_price: '0,50', price: '0,55' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case140.yaml",
    "content": "articles:\n    1: { oxid: 1001_a, oxprice: 100.55 }\n    2: { oxid: 1001_b, oxprice: 100.55 }\n    3: { oxid: 1002_a, oxprice: 100.55 }\n    4: { oxid: 1002_b, oxprice: 100.55 }\n    5: { oxid: 1003_a, oxprice: 100.55 }\n    6: { oxid: 1003_b, oxprice: 100.55 }\n    7: { oxid: 1004_a, oxprice: 100.55 }\n    8: { oxid: 1004_b, oxprice: 100.55 }\ndiscounts:\n    1: { oxid: absFor1001, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    2: { oxid: percentFor1001, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\n    3: { oxid: absFor1002, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002_a], oxsort: 30 }\n    4: { oxid: percentFor1002, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002_b], oxsort: 40 }\n    5: { oxid: absFor1003, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003_a], oxsort: 50 }\n    6: { oxid: percentFor1003, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003_b], oxsort: 60 }\n    7: { oxid: absFor1004, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004_a], oxsort: 70 }\n    8: { oxid: percentFor1004, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004_b], oxsort: 80 }\nexpected:\n    1001_a: { base_price: '100,55', price: '63,79' }\n    1001_b: { base_price: '100,55', price: '67,03' }\n    1002_a: { base_price: '100,55', price: '93,79' }\n    1002_b: { base_price: '100,55', price: '92,17' }\n    1003_a: { base_price: '100,55', price: '88,99' }\n    1003_b: { base_price: '100,55', price: '88,15' }\n    1004_a: { base_price: '100,55', price: '78,29' }\n    1004_b: { base_price: '100,55', price: '79,18' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case141.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 99, oxpricea: 9, oxpriceb: 5 }\nuser:\n    oxid: _testUserA\n    oxactive: 1\n    oxusername: groupAUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '9,00', price: '9,19' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case142.yaml",
    "content": "articles:\n    1: { oxid: 1001_a, oxprice: 100 }\n    2: { oxid: 1001_b, oxprice: 100 }\n    3: { oxid: 1002_a, oxprice: 100 }\n    4: { oxid: 1002_b, oxprice: 100 }\n    5: { oxid: 1003_a, oxprice: 100 }\n    6: { oxid: 1003_b, oxprice: 100 }\n    7: { oxid: 1004_a, oxprice: 100 }\n    8: { oxid: 1004_b, oxprice: 100 }\ndiscounts:\n    1: { oxid: absFor1001, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    2: { oxid: percentFor1001, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\n    3: { oxid: absFor1002, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002_a], oxsort: 30 }\n    4: { oxid: percentFor1002, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002_b], oxsort: 40 }\n    5: { oxid: absFor1003, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003_a], oxsort: 50 }\n    6: { oxid: percentFor1003, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003_b], oxsort: 60 }\n    7: { oxid: absFor1004, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004_a], oxsort: 70 }\n    8: { oxid: percentFor1004, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004_b], oxsort: 80 }\nexpected:\n    1001_a: { base_price: '100,00', price: '63,33' }\n    1001_b: { base_price: '100,00', price: '66,66' }\n    1002_a: { base_price: '100,00', price: '93,33' }\n    1002_b: { base_price: '100,00', price: '91,66' }\n    1003_a: { base_price: '100,00', price: '88,53' }\n    1003_b: { base_price: '100,00', price: '87,66' }\n    1004_a: { base_price: '100,00', price: '77,83' }\n    1004_b: { base_price: '100,00', price: '78,75' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case143.yaml",
    "content": "articles:\n    1: { oxid: 1001_a, oxprice: 5.02 }\n    2: { oxid: 1001_b, oxprice: 5.02 }\n    3: { oxid: 1002_a, oxprice: 5.02 }\n    4: { oxid: 1002_b, oxprice: 5.02 }\n    5: { oxid: 1003_a, oxprice: 5.02 }\n    6: { oxid: 1003_b, oxprice: 5.02 }\n    7: { oxid: 1004_a, oxprice: 5.02 }\n    8: { oxid: 1004_b, oxprice: 5.02 }\ndiscounts:\n    1: { oxid: absFor1001, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    2: { oxid: percentFor1001, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\n    3: { oxid: absFor1002, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002_a], oxsort: 30 }\n    4: { oxid: percentFor1002, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002_b], oxsort: 40 }\n    5: { oxid: absFor1003, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003_a], oxsort: 50 }\n    6: { oxid: percentFor1003, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003_b], oxsort: 60 }\n    7: { oxid: absFor1004, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004_a], oxsort: 70 }\n    8: { oxid: percentFor1004, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004_b], oxsort: 80 }\nexpected:\n    1001_a: { base_price: '5,02', price: '0,00' }\n    1001_b: { base_price: '5,02', price: '3,34' }\n    1002_a: { base_price: '5,02', price: '14,18' }\n    1002_b: { base_price: '5,02', price: '4,60' }\n    1003_a: { base_price: '5,02', price: '9,38' }\n    1003_b: { base_price: '5,02', price: '4,40' }\n    1004_a: { base_price: '5,02', price: '0,00' }\n    1004_b: { base_price: '5,02', price: '3,95' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case144.yaml",
    "content": "articles:\n    1: { oxid: 1001_a, oxprice: 0.5 }\n    2: { oxid: 1001_b, oxprice: 0.5 }\n    3: { oxid: 1002_a, oxprice: 0.5 }\n    4: { oxid: 1002_b, oxprice: 0.5 }\n    5: { oxid: 1003_a, oxprice: 0.5 }\n    6: { oxid: 1003_b, oxprice: 0.5 }\n    7: { oxid: 1004_a, oxprice: 0.5 }\n    8: { oxid: 1004_b, oxprice: 0.5 }\ndiscounts:\n    1: { oxid: absFor1001, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    2: { oxid: percentFor1001, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\n    3: { oxid: absFor1002, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002_a], oxsort: 30 }\n    4: { oxid: percentFor1002, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002_b], oxsort: 40 }\n    5: { oxid: absFor1003, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003_a], oxsort: 50 }\n    6: { oxid: percentFor1003, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003_b], oxsort: 60 }\n    7: { oxid: absFor1004, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004_a], oxsort: 70 }\n    8: { oxid: percentFor1004, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004_b], oxsort: 80 }\nexpected:\n    1001_a: { base_price: '0,50', price: '0,00' }\n    1001_b: { base_price: '0,50', price: '0,34' }\n    1002_a: { base_price: '0,50', price: '10,42' }\n    1002_b: { base_price: '0,50', price: '0,46' }\n    1003_a: { base_price: '0,50', price: '5,62' }\n    1003_b: { base_price: '0,50', price: '0,44' }\n    1004_a: { base_price: '0,50', price: '0,00' }\n    1004_b: { base_price: '0,50', price: '0,40' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case145.yaml",
    "content": "articles:\n    1: { oxid: 1001_a, oxprice: 1001 }\n    2: { oxid: 1001_b, oxprice: 1001 }\n    3: { oxid: 1002_a, oxprice: 1001 }\n    4: { oxid: 1002_b, oxprice: 1001 }\n    5: { oxid: 1003_a, oxprice: 1001 }\n    6: { oxid: 1003_b, oxprice: 1001 }\n    7: { oxid: 1004_a, oxprice: 1001 }\n    8: { oxid: 1004_b, oxprice: 1001 }\ndiscounts:\n    1: { oxid: absFor1001, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    2: { oxid: percentFor1001, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\n    3: { oxid: absFor1002, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002_a], oxsort: 30 }\n    4: { oxid: percentFor1002, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002_b], oxsort: 40 }\n    5: { oxid: absFor1003, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003_a], oxsort: 50 }\n    6: { oxid: percentFor1003, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003_b], oxsort: 60 }\n    7: { oxid: absFor1004, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004_a], oxsort: 70 }\n    8: { oxid: percentFor1004, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004_b], oxsort: 80 }\nexpected:\n    1001_a: { base_price: '1.001,00', price: '814,17' }\n    1001_b: { base_price: '1.001,00', price: '667,34' }\n    1002_a: { base_price: '1.001,00', price: '844,17' }\n    1002_b: { base_price: '1.001,00', price: '917,59' }\n    1003_a: { base_price: '1.001,00', price: '839,37' }\n    1003_b: { base_price: '1.001,00', price: '877,55' }\n    1004_a: { base_price: '1.001,00', price: '828,67' }\n    1004_b: { base_price: '1.001,00', price: '788,29' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case146.yaml",
    "content": "articles:\n    1: { oxid: 1001_a, oxprice: 100, oxvat: 4 }\n    2: { oxid: 1001_b, oxprice: 100, oxvat: 4 }\n    3: { oxid: 1002_a, oxprice: 99 }\n    4: { oxid: 1002_b, oxprice: 99 }\n    5: { oxid: 1003_a, oxprice: 299 }\n    6: { oxid: 1003_b, oxprice: 299 }\n    7: { oxid: 1004_a, oxprice: 0.6 }\n    8: { oxid: 1004_b, oxprice: 0.6 }\ndiscounts:\n    1: { oxid: absFor1001, oxaddsum: 0, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    2: { oxid: percentFor1001, oxaddsum: 0, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\n    3: { oxid: absFor1002, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002_a], oxsort: 30 }\n    4: { oxid: percentFor1002, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002_b], oxsort: 40 }\n    5: { oxid: absFor1003, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003_a], oxsort: 50 }\n    6: { oxid: percentFor1003, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003_b], oxsort: 60 }\n    7: { oxid: absFor1004, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004_a], oxsort: 70 }\n    8: { oxid: percentFor1004, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004_b], oxsort: 80 }\nexpected:\n    1001_a: { base_price: '100,00', price: '96,15' }\n    1001_b: { base_price: '100,00', price: '96,15' }\n    1002_a: { base_price: '99,00', price: '62,50' }\n    1002_b: { base_price: '99,00', price: '66,00' }\n    1003_a: { base_price: '299,00', price: '254,37' }\n    1003_b: { base_price: '299,00', price: '262,13' }\n    1004_a: { base_price: '0,60', price: '5,70' }\n    1004_b: { base_price: '0,60', price: '0,53' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case147.yaml",
    "content": "articles:\n    - { oxid: _testId_1, oxprice: 0 }\n    - { oxid: _testId_2, oxprice: 0 }\n    - { oxid: _testId_2_child_1, oxprice: 0, oxparentid: _testId_2 }\n    - { oxid: _testId_3, oxprice: 0 }\n    - { oxid: _testId_3_child_1, oxprice: 6, oxparentid: _testId_3 }\n    - { oxid: _testId_4, oxprice: 0 }\n    - { oxid: _testId_4_child_1, oxprice: 0, oxparentid: _testId_4 }\n    - { oxid: _testId_4_child_2, oxprice: 0, oxparentid: _testId_4 }\n    - { oxid: _testId_5, oxprice: 0 }\n    - { oxid: _testId_5_child_1, oxprice: 0, oxparentid: _testId_5 }\n    - { oxid: _testId_5_child_2, oxprice: 6, oxparentid: _testId_5 }\n    - { oxid: _testId_6, oxprice: 0 }\n    - { oxid: _testId_6_child_1, oxprice: 6, oxparentid: _testId_6 }\n    - { oxid: _testId_6_child_2, oxprice: 0, oxparentid: _testId_6 }\n    - { oxid: _testId_7, oxprice: 0 }\n    - { oxid: _testId_7_child_1, oxprice: 6, oxparentid: _testId_7 }\n    - { oxid: _testId_7_child_2, oxprice: 27, oxparentid: _testId_7 }\n    - { oxid: _testId_8, oxprice: 0 }\n    - { oxid: _testId_8_child_1, oxprice: 27, oxparentid: _testId_8 }\n    - { oxid: _testId_8_child_2, oxprice: 6, oxparentid: _testId_8 }\n    - { oxid: _testId_9, oxprice: 0 }\n    - { oxid: _testId_9_child_1, oxprice: 27, oxparentid: _testId_9 }\n    - { oxid: _testId_9_child_2, oxprice: 27, oxparentid: _testId_9 }\nexpected:\n    _testId_1: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '0,00', is_range_price: false }\n    _testId_2: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '0,00', is_range_price: false }\n    _testId_3: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '6,00', is_range_price: false }\n    _testId_4: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '0,00', is_range_price: false }\n    _testId_5: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '6,00', is_range_price: false }\n    _testId_6: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '6,00', is_range_price: false }\n    _testId_7: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '6,00', is_range_price: true }\n    _testId_8: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '6,00', is_range_price: true }\n    _testId_9: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '27,00', is_range_price: false }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blVariantParentBuyable: 0 }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case148.yaml",
    "content": "articles:\n    - { oxid: _testId_1, oxprice: 13 }\n    - { oxid: _testId_2, oxprice: 13 }\n    - { oxid: _testId_2_child_1, oxprice: 0, oxparentid: _testId_2 }\n    - { oxid: _testId_3, oxprice: 13 }\n    - { oxid: _testId_3_child_1, oxprice: 6, oxparentid: _testId_3 }\n    - { oxid: _testId_4, oxprice: 13 }\n    - { oxid: _testId_4_child_1, oxprice: 13, oxparentid: _testId_4 }\n    - { oxid: _testId_5, oxprice: 13 }\n    - { oxid: _testId_5_child_1, oxprice: 27, oxparentid: _testId_5 }\n    - { oxid: _testId_6, oxprice: 13 }\n    - { oxid: _testId_6_child_1, oxprice: 0, oxparentid: _testId_6 }\n    - { oxid: _testId_6_child_2, oxprice: 0, oxparentid: _testId_6 }\n    - { oxid: _testId_7, oxprice: 13 }\n    - { oxid: _testId_7_child_1, oxprice: 0, oxparentid: _testId_7 }\n    - { oxid: _testId_7_child_2, oxprice: 6, oxparentid: _testId_7 }\n    - { oxid: _testId_8, oxprice: 13 }\n    - { oxid: _testId_8_child_1, oxprice: 0, oxparentid: _testId_8 }\n    - { oxid: _testId_8_child_2, oxprice: 13, oxparentid: _testId_8 }\n    - { oxid: _testId_9, oxprice: 13 }\n    - { oxid: _testId_9_child_1, oxprice: 0, oxparentid: _testId_9 }\n    - { oxid: _testId_9_child_2, oxprice: 27, oxparentid: _testId_9 }\n    - { oxid: _testId_10, oxprice: 13 }\n    - { oxid: _testId_10_child_1, oxprice: 6, oxparentid: _testId_10 }\n    - { oxid: _testId_10_child_2, oxprice: 0, oxparentid: _testId_10 }\n    - { oxid: _testId_11, oxprice: 13 }\n    - { oxid: _testId_11_child_1, oxprice: 6, oxparentid: _testId_11 }\n    - { oxid: _testId_11_child_2, oxprice: 27, oxparentid: _testId_11 }\n    - { oxid: _testId_12, oxprice: 13 }\n    - { oxid: _testId_12_child_1, oxprice: 27, oxparentid: _testId_12 }\n    - { oxid: _testId_12_child_2, oxprice: 27, oxparentid: _testId_12 }\nexpected:\n    _testId_1: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '13,00', is_range_price: false }\n    _testId_2: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '13,00', is_range_price: false }\n    _testId_3: { base_price: '13,00', price: '13,00', min_price: '6,00', var_min_price: '6,00', is_range_price: false }\n    _testId_4: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '13,00', is_range_price: false }\n    _testId_5: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '27,00', is_range_price: false }\n    _testId_6: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '13,00', is_range_price: false }\n    _testId_7: { base_price: '13,00', price: '13,00', min_price: '6,00', var_min_price: '6,00', is_range_price: true }\n    _testId_8: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '13,00', is_range_price: false }\n    _testId_9: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '13,00', is_range_price: true }\n    _testId_10: { base_price: '13,00', price: '13,00', min_price: '6,00', var_min_price: '6,00', is_range_price: true }\n    _testId_11: { base_price: '13,00', price: '13,00', min_price: '6,00', var_min_price: '6,00', is_range_price: true }\n    _testId_12: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '27,00', is_range_price: false }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blVariantParentBuyable: 0 }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case149.yaml",
    "content": "articles:\n    - { oxid: _testId_1, oxprice: 0 }\n    - { oxid: _testId_2, oxprice: 0 }\n    - { oxid: _testId_2_child_1, oxprice: 0, oxparentid: _testId_2 }\n    - { oxid: _testId_3, oxprice: 0 }\n    - { oxid: _testId_3_child_1, oxprice: 6, oxparentid: _testId_3 }\n    - { oxid: _testId_4, oxprice: 0 }\n    - { oxid: _testId_4_child_1, oxprice: 0, oxparentid: _testId_4 }\n    - { oxid: _testId_4_child_2, oxprice: 0, oxparentid: _testId_4 }\n    - { oxid: _testId_5, oxprice: 0 }\n    - { oxid: _testId_5_child_1, oxprice: 0, oxparentid: _testId_5 }\n    - { oxid: _testId_5_child_2, oxprice: 6, oxparentid: _testId_5 }\n    - { oxid: _testId_6, oxprice: 0 }\n    - { oxid: _testId_6_child_1, oxprice: 6, oxparentid: _testId_6 }\n    - { oxid: _testId_6_child_2, oxprice: 0, oxparentid: _testId_6 }\n    - { oxid: _testId_7, oxprice: 0 }\n    - { oxid: _testId_7_child_1, oxprice: 6, oxparentid: _testId_7 }\n    - { oxid: _testId_7_child_2, oxprice: 27, oxparentid: _testId_7 }\n    - { oxid: _testId_8, oxprice: 0 }\n    - { oxid: _testId_8_child_1, oxprice: 27, oxparentid: _testId_8 }\n    - { oxid: _testId_8_child_2, oxprice: 6, oxparentid: _testId_8 }\n    - { oxid: _testId_9, oxprice: 0 }\n    - { oxid: _testId_9_child_1, oxprice: 27, oxparentid: _testId_9 }\n    - { oxid: _testId_9_child_2, oxprice: 27, oxparentid: _testId_9 }\nexpected:\n    _testId_1: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '0,00', is_range_price: false }\n    _testId_2: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '0,00', is_range_price: false }\n    _testId_3: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '6,00', is_range_price: false }\n    _testId_4: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '0,00', is_range_price: false }\n    _testId_5: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '6,00', is_range_price: false }\n    _testId_6: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '6,00', is_range_price: false }\n    _testId_7: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '6,00', is_range_price: true }\n    _testId_8: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '6,00', is_range_price: true }\n    _testId_9: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '27,00', is_range_price: false }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blVariantParentBuyable: 0 }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case15.yaml",
    "content": "articles:\n  - { oxid: 1001_a, oxprice: 0.5, oxvat: 20 }\n  - { oxid: 1001_b, oxprice: 0.5, oxvat: 20 }\ndiscounts:\n  - { oxid: abs, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [ 1001_a ], oxsort: 10 }\n  - { oxid: percent, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [ 1001_b ], oxsort: 20 }\nexpected:\n  1001_a: { base_price: '0,50', price: '5,70' }\n  1001_b: { base_price: '0,50', price: '0,53' }\noptions:\n  config: { blEnterNetPrice: false, blShowNetPrice: false }\n  activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case150.yaml",
    "content": "articles:\n    - { oxid: _testId_1, oxprice: 13 }\n    - { oxid: _testId_2, oxprice: 13 }\n    - { oxid: _testId_2_child_1, oxprice: 0, oxparentid: _testId_2 }\n    - { oxid: _testId_3, oxprice: 13 }\n    - { oxid: _testId_3_child_1, oxprice: 6, oxparentid: _testId_3 }\n    - { oxid: _testId_4, oxprice: 13 }\n    - { oxid: _testId_4_child_1, oxprice: 13, oxparentid: _testId_4 }\n    - { oxid: _testId_5, oxprice: 13 }\n    - { oxid: _testId_5_child_1, oxprice: 27, oxparentid: _testId_5 }\n    - { oxid: _testId_6, oxprice: 13 }\n    - { oxid: _testId_6_child_1, oxprice: 0, oxparentid: _testId_6 }\n    - { oxid: _testId_6_child_2, oxprice: 0, oxparentid: _testId_6 }\n    - { oxid: _testId_7, oxprice: 13 }\n    - { oxid: _testId_7_child_1, oxprice: 0, oxparentid: _testId_7 }\n    - { oxid: _testId_7_child_2, oxprice: 6, oxparentid: _testId_7 }\n    - { oxid: _testId_8, oxprice: 13 }\n    - { oxid: _testId_8_child_1, oxprice: 0, oxparentid: _testId_8 }\n    - { oxid: _testId_8_child_2, oxprice: 13, oxparentid: _testId_8 }\n    - { oxid: _testId_9, oxprice: 13 }\n    - { oxid: _testId_9_child_1, oxprice: 0, oxparentid: _testId_9 }\n    - { oxid: _testId_9_child_2, oxprice: 27, oxparentid: _testId_9 }\n    - { oxid: _testId_10, oxprice: 13 }\n    - { oxid: _testId_10_child_1, oxprice: 6, oxparentid: _testId_10 }\n    - { oxid: _testId_10_child_2, oxprice: 0, oxparentid: _testId_10 }\n    - { oxid: _testId_11, oxprice: 13 }\n    - { oxid: _testId_11_child_1, oxprice: 6, oxparentid: _testId_11 }\n    - { oxid: _testId_11_child_2, oxprice: 27, oxparentid: _testId_11 }\n    - { oxid: _testId_12, oxprice: 13 }\n    - { oxid: _testId_12_child_1, oxprice: 27, oxparentid: _testId_12 }\n    - { oxid: _testId_12_child_2, oxprice: 27, oxparentid: _testId_12 }\nexpected:\n    _testId_1: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '13,00', is_range_price: false }\n    _testId_2: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '13,00', is_range_price: false }\n    _testId_3: { base_price: '13,00', price: '13,00', min_price: '6,00', var_min_price: '6,00', is_range_price: false }\n    _testId_4: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '13,00', is_range_price: false }\n    _testId_5: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '27,00', is_range_price: false }\n    _testId_6: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '13,00', is_range_price: false }\n    _testId_7: { base_price: '13,00', price: '13,00', min_price: '6,00', var_min_price: '6,00', is_range_price: true }\n    _testId_8: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '13,00', is_range_price: false }\n    _testId_9: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '13,00', is_range_price: true }\n    _testId_10: { base_price: '13,00', price: '13,00', min_price: '6,00', var_min_price: '6,00', is_range_price: true }\n    _testId_11: { base_price: '13,00', price: '13,00', min_price: '6,00', var_min_price: '6,00', is_range_price: true }\n    _testId_12: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '27,00', is_range_price: false }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blVariantParentBuyable: 0 }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case151.yaml",
    "content": "articles:\n    1: { oxid: _testId_1, oxprice: 0 }\n    2: { oxid: _testId_1_child_1, oxprice: 0, oxparentid: _testId_1, oxactive: false }\n    3: { oxid: _testId_2, oxprice: 0 }\n    4: { oxid: _testId_2_child_1, oxprice: 6, oxparentid: _testId_2, oxactive: false }\n    5: { oxid: _testId_3, oxprice: 0 }\n    6: { oxid: _testId_3_child_1, oxprice: 0, oxparentid: _testId_3, oxactive: false }\n    7: { oxid: _testId_3_child_2, oxprice: 0, oxparentid: _testId_3 }\n    8: { oxid: _testId_4, oxprice: 0 }\n    9: { oxid: _testId_4_child_1, oxprice: 0, oxparentid: _testId_4, oxactive: false }\n    10: { oxid: _testId_4_child_2, oxprice: 6, oxparentid: _testId_4 }\n    11: { oxid: _testId_5, oxprice: 0 }\n    12: { oxid: _testId_5_child_1, oxprice: 6, oxparentid: _testId_5, oxactive: false }\n    13: { oxid: _testId_5_child_2, oxprice: 0, oxparentid: _testId_5 }\n    14: { oxid: _testId_6, oxprice: 0 }\n    15: { oxid: _testId_6_child_1, oxprice: 6, oxparentid: _testId_6, oxactive: false }\n    16: { oxid: _testId_6_child_2, oxprice: 27, oxparentid: _testId_6 }\n    17: { oxid: _testId_7, oxprice: 0 }\n    18: { oxid: _testId_7_child_1, oxprice: 27, oxparentid: _testId_7, oxactive: false }\n    19: { oxid: _testId_7_child_2, oxprice: 6, oxparentid: _testId_7 }\n    20: { oxid: _testId_8, oxprice: 0 }\n    21: { oxid: _testId_8_child_1, oxprice: 27, oxparentid: _testId_8, oxactive: false }\n    22: { oxid: _testId_8_child_2, oxprice: 27, oxparentid: _testId_8 }\nexpected:\n    _testId_1: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '0,00', is_range_price: false }\n    _testId_2: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '0,00', is_range_price: false }\n    _testId_3: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '0,00', is_range_price: false }\n    _testId_4: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '6,00', is_range_price: false }\n    _testId_5: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '0,00', is_range_price: false }\n    _testId_6: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '27,00', is_range_price: false }\n    _testId_7: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '6,00', is_range_price: false }\n    _testId_8: { base_price: '0,00', price: '0,00', min_price: '0,00', var_min_price: '27,00', is_range_price: false }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blVariantParentBuyable: 0 }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case152.yaml",
    "content": "articles:\n    1: { oxid: _testId_1, oxprice: 13 }\n    2: { oxid: _testId_1_child_1, oxprice: 0, oxparentid: _testId_1, oxactive: true }\n    3: { oxid: _testId_2, oxprice: 13 }\n    4: { oxid: _testId_2_child_1, oxprice: 6, oxparentid: _testId_2, oxactive: false }\n    5: { oxid: _testId_3, oxprice: 13 }\n    6: { oxid: _testId_3_child_1, oxprice: 13, oxparentid: _testId_3, oxactive: false }\n    7: { oxid: _testId_4, oxprice: 13 }\n    8: { oxid: _testId_4_child_1, oxprice: 27, oxparentid: _testId_4, oxactive: false }\n    9: { oxid: _testId_5, oxprice: 13 }\n    10: { oxid: _testId_5_child_1, oxprice: 0, oxparentid: _testId_5, oxactive: false }\n    11: { oxid: _testId_5_child_2, oxprice: 0, oxparentid: _testId_5 }\n    12: { oxid: _testId_6, oxprice: 13 }\n    13: { oxid: _testId_6_child_1, oxprice: 0, oxparentid: _testId_6, oxactive: false }\n    14: { oxid: _testId_6_child_2, oxprice: 6, oxparentid: _testId_6 }\n    15: { oxid: _testId_7, oxprice: 13 }\n    16: { oxid: _testId_7_child_1, oxprice: 0, oxparentid: _testId_7, oxactive: false }\n    17: { oxid: _testId_7_child_2, oxprice: 13, oxparentid: _testId_7 }\n    18: { oxid: _testId_8, oxprice: 13 }\n    19: { oxid: _testId_8_child_1, oxprice: 0, oxparentid: _testId_8, oxactive: false }\n    20: { oxid: _testId_8_child_2, oxprice: 27, oxparentid: _testId_8 }\n    21: { oxid: _testId_9, oxprice: 13 }\n    22: { oxid: _testId_9_child_1, oxprice: 6, oxparentid: _testId_9, oxactive: false }\n    23: { oxid: _testId_9_child_2, oxprice: 0, oxparentid: _testId_9 }\n    24: { oxid: _testId_10, oxprice: 13 }\n    25: { oxid: _testId_10_child_1, oxprice: 6, oxparentid: _testId_10, oxactive: false }\n    26: { oxid: _testId_10_child_2, oxprice: 27, oxparentid: _testId_10 }\n    27: { oxid: _testId_11, oxprice: 13 }\n    28: { oxid: _testId_11_child_1, oxprice: 27, oxparentid: _testId_11, oxactive: false }\n    29: { oxid: _testId_11_child_2, oxprice: 27, oxparentid: _testId_11 }\nexpected:\n    _testId_1: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '13,00', is_range_price: false }\n    _testId_2: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '13,00', is_range_price: false }\n    _testId_3: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '13,00', is_range_price: false }\n    _testId_4: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '13,00', is_range_price: false }\n    _testId_5: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '13,00', is_range_price: false }\n    _testId_6: { base_price: '13,00', price: '13,00', min_price: '6,00', var_min_price: '6,00', is_range_price: false }\n    _testId_7: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '13,00', is_range_price: false }\n    _testId_8: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '27,00', is_range_price: false }\n    _testId_9: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '13,00', is_range_price: false }\n    _testId_10: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '27,00', is_range_price: false }\n    _testId_11: { base_price: '13,00', price: '13,00', min_price: '13,00', var_min_price: '27,00', is_range_price: false }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blVariantParentBuyable: 0 }\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case153.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 50.0, oxpricea: 35, oxpriceb: 45, oxpricec: 55 }\nuser:\n    oxid: _testUserA\n    oxactive: 1\n    oxusername: groupAUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    1000: { base_price: '35,00', price: '35,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, dDefaultVAT: 20, blOverrideZeroABCPrices: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case154.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 50.0, oxpricea: 35, oxpriceb: 45, oxpricec: 55 }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    1000: { base_price: '45,00', price: '45,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case155.yaml",
    "content": "articles:\n    - { oxid: 1003, oxprice: 70.0, oxpricea: 70, oxpriceb: 85, oxpricec: 0, scaleprices: { oxaddabs: 75.0, oxamount: 2, oxamountto: 5, oxartid: 1003 } }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    1003: { base_price: '85,00', price: '85,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blOverrideZeroABCPrices: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case156.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 50.0, oxpricea: 35, oxpriceb: 45, oxpricec: 55 }\nuser:\n    oxid: _testUserC\n    oxactive: 1\n    oxusername: groupCUser\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB] }\n    - { oxid: oxidpricec, oxactive: 1, oxtitle: 'Price C', oxobject2group: [_testUserC] }\nexpected:\n    1000: { base_price: '55,00', price: '55,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, dDefaultVAT: 20, blOverrideZeroABCPrices: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case157.yaml",
    "content": "articles:\n    - { oxid: variantDiscountTestParentArticle, oxprice: 50.0 }\n    - { oxid: variantDiscountTestChildArticle, oxparentid: variantDiscountTestParentArticle, oxprice: 50.0 }\ncategories:\n    - { oxid: variantDiscountTestCategory, oxparentid: oxrootid, oxshopid: 1, oxactive: 1, oxarticles: [variantDiscountTestChildArticle] }\ndiscounts:\n    - { oxid: variantDiscountDiscountId, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxcategories: [variantDiscountTestCategory], oxsort: 10 }\nexpected:\n    variantDiscountTestChildArticle: { base_price: '50,00', price: '30,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case16.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 0.5, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 0.5, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '0,50', price: '0,00' }\n    1001_b: { base_price: '0,50', price: '0,47' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case17.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 1001, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 1001, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '1.001,00', price: '981,00' }\n    1001_b: { base_price: '1.001,00', price: '800,80' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case18.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 1001, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 1001, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '1.001,00', price: '1.011,00' }\n    1001_b: { base_price: '1.001,00', price: '1.101,10' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case19.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 1001, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 1001, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '1.001,00', price: '1.006,20' }\n    1001_b: { base_price: '1.001,00', price: '1.053,05' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case2.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100.55, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100.55, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,55', price: '110,55' }\n    1001_b: { base_price: '100,55', price: '110,61' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case20.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 1001, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 1001, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '1.001,00', price: '995,50' }\n    1001_b: { base_price: '1.001,00', price: '945,95' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case21.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 99, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 99, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '99,00', price: '79,00' }\n    1001_b: { base_price: '99,00', price: '79,20' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case22.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 99, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 99, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '99,00', price: '93,50' }\n    1001_b: { base_price: '99,00', price: '93,56' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case23.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 99, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 99, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '99,00', price: '104,20' }\n    1001_b: { base_price: '99,00', price: '104,15' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case24.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100.55, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100.55, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,55', price: '100,66' }\n    1001_b: { base_price: '100,55', price: '96,53' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case25.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100.55, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100.55, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,55', price: '130,66' }\n    1001_b: { base_price: '100,55', price: '132,73' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case26.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100.55, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100.55, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,55', price: '125,86' }\n    1001_b: { base_price: '100,55', price: '126,93' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case27.yaml",
    "content": "articles:\n  - { oxid: 1001_a, oxprice: 100.55, oxvat: 20 }\n  - { oxid: 1001_b, oxprice: 100.55, oxvat: 20 }\ndiscounts:\n  - { oxid: abs, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [ 1001_a ], oxsort: 10 }\n  - { oxid: percent, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [ 1001_b ], oxsort: 20 }\nexpected:\n  1001_a: { base_price: '100,55', price: '125,86' }\n  1001_b: { base_price: '100,55', price: '126,93' }\noptions:\n  config: { blEnterNetPrice: true, blShowNetPrice: false }\n  activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case28.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100.55, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100.55, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,55', price: '115,16' }\n    1001_b: { base_price: '100,55', price: '114,02' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case29.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100, oxvat: 19 }\n    - { oxid: 1001_b, oxprice: 100, oxvat: 19 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,00', price: '99,00' }\n    1001_b: { base_price: '100,00', price: '95,20' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case3.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100.55, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100.55, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,55', price: '105,75' }\n    1001_b: { base_price: '100,55', price: '105,78' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case30.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,00', price: '130,00' }\n    1001_b: { base_price: '100,00', price: '132,00' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case31.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,00', price: '125,20' }\n    1001_b: { base_price: '100,00', price: '126,24' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case32.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,00', price: '114,50' }\n    1001_b: { base_price: '100,00', price: '113,40' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case33.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 5.02, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 5.02, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '5,02', price: '0,00' }\n    1001_b: { base_price: '5,02', price: '4,82' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case34.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 5.02, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 5.02, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '5,02', price: '16,02' }\n    1001_b: { base_price: '5,02', price: '6,62' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case35.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 5.02, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 5.02, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '5,02', price: '11,22' }\n    1001_b: { base_price: '5,02', price: '6,33' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case36.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 5.02, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 5.02, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '5,02', price: '0,52' }\n    1001_b: { base_price: '5,02', price: '5,69' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case37.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 0.5, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 0.5, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '0,50', price: '0,00' }\n    1001_b: { base_price: '0,50', price: '0,48' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case38.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 0.5, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 0.5, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '0,50', price: '10,60' }\n    1001_b: { base_price: '0,50', price: '0,66' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case39.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 0.5, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 0.5, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '0,50', price: '5,80' }\n    1001_b: { base_price: '0,50', price: '0,63' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case4.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100.55, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100.55, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,55', price: '95,05' }\n    1001_b: { base_price: '100,55', price: '95,02' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case40.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 0.5, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 0.5, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '0,50', price: '0,00' }\n    1001_b: { base_price: '0,50', price: '0,57' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case41.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 1001, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 1001, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '1.001,00', price: '1.181,20' }\n    1001_b: { base_price: '1.001,00', price: '960,96' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case42.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 1001, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 1001, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '1.001,00', price: '1.211,20' }\n    1001_b: { base_price: '1.001,00', price: '1.321,32' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case43.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 1001, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 1001, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '1.001,00', price: '1.206,40' }\n    1001_b: { base_price: '1.001,00', price: '1.263,66' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case44.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 1001, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 1001, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '1.001,00', price: '1.195,70' }\n    1001_b: { base_price: '1.001,00', price: '1.135,13' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case45.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 99, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 99, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '99,00', price: '128,80' }\n    1001_b: { base_price: '99,00', price: '130,68' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case46.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100.55, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100.55, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,55', price: '80,55' }\n    1001_b: { base_price: '100,55', price: '80,44' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case47.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100.55, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100.55, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,55', price: '110,55' }\n    1001_b: { base_price: '100,55', price: '110,61' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case48.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100.55, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100.55, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,55', price: '105,75' }\n    1001_b: { base_price: '100,55', price: '105,78' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case49.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100.55, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100.55, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,55', price: '95,05' }\n    1001_b: { base_price: '100,55', price: '95,02' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case5.yaml",
    "content": "articles:\n  - { oxid: 1001_a, oxprice: 100, oxvat: 20 }\n  - { oxid: 1001_b, oxprice: 100, oxvat: 20 }\ndiscounts:\n  - { oxid: abs, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [ 1001_a ], oxsort: 10 }\n  - { oxid: percent, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [ 1001_b ], oxsort: 20 }\nexpected:\n  1001_a: { base_price: '100,00', price: '80,00' }\n  1001_b: { base_price: '100,00', price: '80,00' }\noptions:\n  config: { blEnterNetPrice: false, blShowNetPrice: false }\n  activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case50.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,00', price: '80,00' }\n    1001_b: { base_price: '100,00', price: '80,00' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case51.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,00', price: '110,00' }\n    1001_b: { base_price: '100,00', price: '110,00' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case52.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,00', price: '105,20' }\n    1001_b: { base_price: '100,00', price: '105,20' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case53.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,00', price: '94,50' }\n    1001_b: { base_price: '100,00', price: '94,50' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case54.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUserA\n    oxactive: 1\n    oxusername: groupAUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '100,00', price: '118,80' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case55.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '10,00', price: '12,54' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case56.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUser\n    oxactive: 1\n    oxusername: user\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '100,55', price: '132,73' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case57.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUserA\n    oxactive: 1\n    oxusername: groupAUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '100,00', price: '102,06' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case58.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '10,00', price: '10,77' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case59.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUser\n    oxactive: 1\n    oxusername: user\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '100,55', price: '114,02' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case6.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,00', price: '110,00' }\n    1001_b: { base_price: '100,00', price: '110,00' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case60.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 99, oxpricea: 9, oxpriceb: 5 }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '5,00', price: '5,39' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case61.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 99, oxpricea: 9, oxpriceb: 5 }\nuser:\n    oxid: _testUser\n    oxactive: 1\n    oxusername: user\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '99,00', price: '112,27' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: false, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case62.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUserA\n    oxactive: 1\n    oxusername: groupAUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '100,00', price: '99,00' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case63.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '10,00', price: '10,45' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case64.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUser\n    oxactive: 1\n    oxusername: user\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '100,55', price: '110,61' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case65.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUserA\n    oxactive: 1\n    oxusername: groupAUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '100,00', price: '85,05' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case66.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '10,00', price: '8,98' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case67.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 100.55, oxpricea: 100, oxpriceb: 10 }\nuser:\n    oxid: _testUser\n    oxactive: 1\n    oxusername: user\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '100,55', price: '95,02' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case68.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 99, oxpricea: 9, oxpriceb: 5 }\nuser:\n    oxid: _testUserA\n    oxactive: 1\n    oxusername: groupAUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '9,00', price: '7,65' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case69.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 99, oxpricea: 9, oxpriceb: 5 }\nuser:\n    oxid: _testUserB\n    oxactive: 1\n    oxusername: groupBUser\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '5,00', price: '4,49' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case7.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,00', price: '105,20' }\n    1001_b: { base_price: '100,00', price: '105,20' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case70.yaml",
    "content": "articles:\n    - { oxid: 1000, oxprice: 99, oxpricea: 9, oxpriceb: 5 }\nuser:\n    oxid: _testUser\n    oxactive: 1\n    oxusername: user\ndiscounts:\n    - { oxid: percentForShop, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxsort: 10 }\n    - { oxid: groupADiscount, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpricea], oxsort: 20 }\n    - { oxid: groupBDiscount, oxaddsum: 5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxgroups: [oxidpriceb], oxsort: 30 }\ngroup:\n    - { oxid: oxidpricea, oxactive: 1, oxtitle: 'Price A', oxobject2group: [_testUserA, groupADiscount] }\n    - { oxid: oxidpriceb, oxactive: 1, oxtitle: 'Price B', oxobject2group: [_testUserB, groupBDiscount] }\nexpected:\n    1000: { base_price: '99,00', price: '93,56' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case71.yaml",
    "content": "articles:\n    1: { oxid: 1001_a, oxprice: 5.02 }\n    2: { oxid: 1001_b, oxprice: 5.02 }\n    3: { oxid: 1002_a, oxprice: 5.02 }\n    4: { oxid: 1002_b, oxprice: 5.02 }\n    5: { oxid: 1003_a, oxprice: 5.02 }\n    6: { oxid: 1003_b, oxprice: 5.02 }\n    7: { oxid: 1004_a, oxprice: 5.02 }\n    8: { oxid: 1004_b, oxprice: 5.02 }\ndiscounts:\n    1: { oxid: absFor1001, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    2: { oxid: percentFor1001, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\n    3: { oxid: absFor1002, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002_a], oxsort: 30 }\n    4: { oxid: percentFor1002, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002_b], oxsort: 40 }\n    5: { oxid: absFor1003, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003_a], oxsort: 50 }\n    6: { oxid: percentFor1003, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003_b], oxsort: 60 }\n    7: { oxid: absFor1004, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004_a], oxsort: 70 }\n    8: { oxid: percentFor1004, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004_b], oxsort: 80 }\nexpected:\n    1001_a: { base_price: '5,02', price: '0,00' }\n    1001_b: { base_price: '5,02', price: '4,02' }\n    1002_a: { base_price: '5,02', price: '15,02' }\n    1002_b: { base_price: '5,02', price: '5,52' }\n    1003_a: { base_price: '5,02', price: '10,22' }\n    1003_b: { base_price: '5,02', price: '5,28' }\n    1004_a: { base_price: '5,02', price: '0,00' }\n    1004_b: { base_price: '5,02', price: '4,74' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case72.yaml",
    "content": "articles:\n    1: { oxid: 1001_a, oxprice: 0.5 }\n    2: { oxid: 1001_b, oxprice: 0.5 }\n    3: { oxid: 1002_a, oxprice: 0.5 }\n    4: { oxid: 1002_b, oxprice: 0.5 }\n    5: { oxid: 1003_a, oxprice: 0.5 }\n    6: { oxid: 1003_b, oxprice: 0.5 }\n    7: { oxid: 1004_a, oxprice: 0.5 }\n    8: { oxid: 1004_b, oxprice: 0.5 }\ndiscounts:\n    1: { oxid: absFor1001, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    2: { oxid: percentFor1001, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\n    3: { oxid: absFor1002, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002_a], oxsort: 30 }\n    4: { oxid: percentFor1002, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002_b], oxsort: 40 }\n    5: { oxid: absFor1003, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003_a], oxsort: 50 }\n    6: { oxid: percentFor1003, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003_b], oxsort: 60 }\n    7: { oxid: absFor1004, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004_a], oxsort: 70 }\n    8: { oxid: percentFor1004, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004_b], oxsort: 80 }\nexpected:\n    1001_a: { base_price: '0,50', price: '0,00' }\n    1001_b: { base_price: '0,50', price: '0,40' }\n    1002_a: { base_price: '0,50', price: '10,50' }\n    1002_b: { base_price: '0,50', price: '0,55' }\n    1003_a: { base_price: '0,50', price: '5,70' }\n    1003_b: { base_price: '0,50', price: '0,53' }\n    1004_a: { base_price: '0,50', price: '0,00' }\n    1004_b: { base_price: '0,50', price: '0,47' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case73.yaml",
    "content": "articles:\n    1: { oxid: 1001_a, oxprice: 1001 }\n    2: { oxid: 1001_b, oxprice: 1001 }\n    3: { oxid: 1002_a, oxprice: 1001 }\n    4: { oxid: 1002_b, oxprice: 1001 }\n    5: { oxid: 1003_a, oxprice: 1001 }\n    6: { oxid: 1003_b, oxprice: 1001 }\n    7: { oxid: 1004_a, oxprice: 1001 }\n    8: { oxid: 1004_b, oxprice: 1001 }\ndiscounts:\n    1: { oxid: absFor1001, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    2: { oxid: percentFor1001, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\n    3: { oxid: absFor1002, oxaddsum: -10, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002_a], oxsort: 30 }\n    4: { oxid: percentFor1002, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002_b], oxsort: 40 }\n    5: { oxid: absFor1003, oxaddsum: -5.2, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003_a], oxsort: 50 }\n    6: { oxid: percentFor1003, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003_b], oxsort: 60 }\n    7: { oxid: absFor1004, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004_a], oxsort: 70 }\n    8: { oxid: percentFor1004, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004_b], oxsort: 80 }\nexpected:\n    1001_a: { base_price: '1.001,00', price: '981,00' }\n    1001_b: { base_price: '1.001,00', price: '800,80' }\n    1002_a: { base_price: '1.001,00', price: '1.011,00' }\n    1002_b: { base_price: '1.001,00', price: '1.101,10' }\n    1003_a: { base_price: '1.001,00', price: '1.006,20' }\n    1003_b: { base_price: '1.001,00', price: '1.053,05' }\n    1004_a: { base_price: '1.001,00', price: '995,50' }\n    1004_b: { base_price: '1.001,00', price: '945,95' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true, dDefaultVAT: 20 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case74.yaml",
    "content": "articles:\n    1: { oxid: 1001_a, oxprice: 99, oxvat: 20 }\n    2: { oxid: 1001_b, oxprice: 99, oxvat: 20 }\n    3: { oxid: 1002_a, oxprice: 299, oxvat: 20 }\n    4: { oxid: 1002_b, oxprice: 299, oxvat: 20 }\n    5: { oxid: 1003_a, oxprice: 1, oxvat: 33.55 }\n    6: { oxid: 1003_b, oxprice: 1, oxvat: 33.55 }\ndiscounts:\n    1: { oxid: absFor1001, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    2: { oxid: percentFor1001, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\n    3: { oxid: absFor1002, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002_a], oxsort: 30 }\n    4: { oxid: percentFor1002, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002_b], oxsort: 40 }\n    5: { oxid: absFor1003, oxaddsum: 33.55, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003_a], oxsort: 50 }\n    6: { oxid: percentFor1003, oxaddsum: 33.55, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003_b], oxsort: 60 }\nexpected:\n    1001_a: { base_price: '99,00', price: '93,50' }\n    1001_b: { base_price: '99,00', price: '93,56' }\n    1002_a: { base_price: '299,00', price: '279,00' }\n    1002_b: { base_price: '299,00', price: '239,20' }\n    1003_a: { base_price: '1,00', price: '0,00' }\n    1003_b: { base_price: '1,00', price: '0,66' }\noptions:\n    config: { blEnterNetPrice: true, blShowNetPrice: true }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case75.yaml",
    "content": "articles:\n    - { oxid: _testId_1, oxprice: 100 }\n    - { oxid: _testId_1_child_1, oxprice: 100, oxparentid: _testId_1 }\n    - { oxid: _testId_1_child_2, oxprice: 100, oxparentid: _testId_1 }\n    - { oxid: _testId_2, oxprice: 100 }\n    - { oxid: _testId_2_child_1, oxprice: 120, oxparentid: _testId_2 }\n    - { oxid: _testId_2_child_2, oxprice: 150, oxparentid: _testId_2 }\n    - { oxid: _testId_3, oxprice: 100 }\n    - { oxid: _testId_3_child_1, oxprice: 20, oxparentid: _testId_3 }\n    - { oxid: _testId_3_child_2, oxprice: 50, oxparentid: _testId_3 }\n    - { oxid: _testId_4, oxprice: 100 }\n    - { oxid: _testId_4_child_1, oxprice: 100, oxparentid: _testId_4 }\n    - { oxid: _testId_4_child_2, oxprice: 150, oxparentid: _testId_4 }\n    - { oxid: _testId_5, oxprice: 100 }\n    - { oxid: _testId_5_child_1, oxprice: 100, oxparentid: _testId_5 }\n    - { oxid: _testId_5_child_2, oxprice: 50, oxparentid: _testId_5 }\n    - { oxid: _testId_6, oxprice: 100 }\n    - { oxid: _testId_6_child_1, oxprice: 50, oxparentid: _testId_6 }\n    - { oxid: _testId_6_child_2, oxprice: 50, oxparentid: _testId_6 }\n    - { oxid: _testId_7, oxprice: 40 }\n    - { oxid: _testId_7_child_1, oxprice: 50, oxparentid: _testId_7 }\n    - { oxid: _testId_7_child_2, oxprice: 50, oxparentid: _testId_7 }\nexpected:\n    _testId_1: { base_price: '100,00', price: '100,00', min_price: '100,00', var_min_price: '100,00', is_range_price: false }\n    _testId_2: { base_price: '100,00', price: '100,00', min_price: '100,00', var_min_price: '120,00', is_range_price: true }\n    _testId_3: { base_price: '100,00', price: '100,00', min_price: '20,00', var_min_price: '20,00', is_range_price: true }\n    _testId_4: { base_price: '100,00', price: '100,00', min_price: '100,00', var_min_price: '100,00', is_range_price: true }\n    _testId_5: { base_price: '100,00', price: '100,00', min_price: '50,00', var_min_price: '50,00', is_range_price: true }\n    _testId_6: { base_price: '100,00', price: '100,00', min_price: '50,00', var_min_price: '50,00', is_range_price: false }\n    _testId_7: { base_price: '40,00', price: '40,00', min_price: '40,00', var_min_price: '50,00', is_range_price: false }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blVariantParentBuyable: 0 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case78.yaml",
    "content": "articles:\n    - { oxid: _testId_1, oxprice: 100 }\n    - { oxid: _testId_1_child_1, oxprice: 100, oxparentid: _testId_1 }\n    - { oxid: _testId_1_child_2, oxprice: 100, oxparentid: _testId_1 }\n    - { oxid: _testId_2, oxprice: 100 }\n    - { oxid: _testId_2_child_1, oxprice: 120, oxparentid: _testId_2 }\n    - { oxid: _testId_2_child_2, oxprice: 150, oxparentid: _testId_2 }\n    - { oxid: _testId_3, oxprice: 100 }\n    - { oxid: _testId_3_child_1, oxprice: 20, oxparentid: _testId_3 }\n    - { oxid: _testId_3_child_2, oxprice: 50, oxparentid: _testId_3 }\n    - { oxid: _testId_4, oxprice: 100 }\n    - { oxid: _testId_4_child_1, oxprice: 100, oxparentid: _testId_4 }\n    - { oxid: _testId_4_child_2, oxprice: 150, oxparentid: _testId_4 }\n    - { oxid: _testId_5, oxprice: 100 }\n    - { oxid: _testId_5_child_1, oxprice: 100, oxparentid: _testId_5 }\n    - { oxid: _testId_5_child_2, oxprice: 50, oxparentid: _testId_5 }\n    - { oxid: _testId_6, oxprice: 100 }\n    - { oxid: _testId_6_child_1, oxprice: 50, oxparentid: _testId_6 }\n    - { oxid: _testId_6_child_2, oxprice: 50, oxparentid: _testId_6 }\n    - { oxid: _testId_7, oxprice: 40 }\n    - { oxid: _testId_7_child_1, oxprice: 50, oxparentid: _testId_7 }\n    - { oxid: _testId_7_child_2, oxprice: 50, oxparentid: _testId_7 }\nexpected:\n    _testId_1: { base_price: '100,00', price: '100,00', min_price: '100,00', var_min_price: '100,00', is_range_price: false }\n    _testId_2: { base_price: '100,00', price: '100,00', min_price: '100,00', var_min_price: '120,00', is_range_price: true }\n    _testId_3: { base_price: '100,00', price: '100,00', min_price: '20,00', var_min_price: '20,00', is_range_price: true }\n    _testId_4: { base_price: '100,00', price: '100,00', min_price: '100,00', var_min_price: '100,00', is_range_price: true }\n    _testId_5: { base_price: '100,00', price: '100,00', min_price: '50,00', var_min_price: '50,00', is_range_price: true }\n    _testId_6: { base_price: '100,00', price: '100,00', min_price: '50,00', var_min_price: '50,00', is_range_price: true }\n    _testId_7: { base_price: '40,00', price: '40,00', min_price: '40,00', var_min_price: '50,00', is_range_price: true }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, blVariantParentBuyable: 1 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case8.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 100, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 100, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 5.5, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '100,00', price: '94,50' }\n    1001_b: { base_price: '100,00', price: '94,50' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case83.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 200, oxtprice: 10 }\n    - { oxid: '1001', oxprice: 200, oxtprice: 10 }\n    - { oxid: '1002', oxprice: 0.05, oxtprice: 10 }\n    - { oxid: '1003', oxprice: 10, oxtprice: 10 }\n    - { oxid: '1004', oxprice: 200, oxtprice: 10 }\n    - { oxid: '1005', oxprice: 200, oxtprice: 10 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\n    - { oxid: percentFor1004, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 50 }\n    - { oxid: percentFor1005, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1005], oxsort: 60 }\nexpected:\n    1000: { base_price: '200,00', price: '160,00', rrp_price: '', show_rrp: false }\n    1001: { base_price: '200,00', price: '220,00', rrp_price: '', show_rrp: false }\n    1002: { base_price: '0,05', price: '0,04', rrp_price: '10,00', show_rrp: true }\n    1003: { base_price: '10,00', price: '9,00', rrp_price: '10,00', show_rrp: true }\n    1004: { base_price: '200,00', price: '210,40', rrp_price: '', show_rrp: false }\n    1005: { base_price: '200,00', price: '189,00', rrp_price: '', show_rrp: false }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case84.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 100, oxtprice: 94.52 }\n    - { oxid: '1001', oxprice: 100, oxtprice: 94.52 }\n    - { oxid: '1002', oxprice: 100, oxtprice: 94.52 }\n    - { oxid: '1003', oxprice: 100, oxtprice: 94.52 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '100,00', price: '80,00', rrp_price: '94,52', show_rrp: true }\n    1001: { base_price: '100,00', price: '110,00', rrp_price: '', show_rrp: false }\n    1002: { base_price: '100,00', price: '105,20', rrp_price: '', show_rrp: false }\n    1003: { base_price: '100,00', price: '94,50', rrp_price: '94,52', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case85.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 0.05, oxtprice: 300 }\n    - { oxid: '1001', oxprice: 0.05, oxtprice: 300 }\n    - { oxid: '1002', oxprice: 0.05, oxtprice: 300 }\n    - { oxid: '1003', oxprice: 0.05, oxtprice: 300 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '0,05', price: '0,04', rrp_price: '300,00', show_rrp: true }\n    1001: { base_price: '0,05', price: '0,06', rrp_price: '300,00', show_rrp: true }\n    1002: { base_price: '0,05', price: '0,05', rrp_price: '300,00', show_rrp: true }\n    1003: { base_price: '0,05', price: '0,05', rrp_price: '300,00', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case86.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 79.9, oxtprice: 79.9 }\n    - { oxid: '1001', oxprice: 79.9, oxtprice: 79.9 }\n    - { oxid: '1002', oxprice: 79.9, oxtprice: 79.9 }\n    - { oxid: '1003', oxprice: 79.9, oxtprice: 79.9 }\n    - { oxid: '1004', oxprice: 89.9, oxtprice: 79.9 }\n    - { oxid: '1005', oxprice: 89.9, oxtprice: 79.9 }\n    - { oxid: '1006', oxprice: 89.9, oxtprice: 79.9 }\n    - { oxid: '1007', oxprice: 89.9, oxtprice: 79.9 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000, 1004], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001, 1005], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002, 1006], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003, 1007], oxsort: 40 }\nexpected:\n    1000: { base_price: '79,90', price: '63,92', rrp_price: '79,90', show_rrp: true }\n    1001: { base_price: '79,90', price: '87,89', rrp_price: '', show_rrp: false }\n    1002: { base_price: '79,90', price: '84,05', rrp_price: '', show_rrp: false }\n    1003: { base_price: '79,90', price: '75,51', rrp_price: '79,90', show_rrp: true }\n    1004: { base_price: '89,90', price: '71,92', rrp_price: '79,90', show_rrp: true }\n    1005: { base_price: '89,90', price: '98,89', rrp_price: '', show_rrp: false }\n    1006: { base_price: '89,90', price: '94,57', rrp_price: '', show_rrp: false }\n    1007: { base_price: '89,90', price: '84,96', rrp_price: '', show_rrp: false }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case87.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 70, oxtprice: 99 }\n    - { oxid: '1001', oxprice: 70, oxtprice: 99 }\n    - { oxid: '1002', oxprice: 70, oxtprice: 99 }\n    - { oxid: '1003', oxprice: 70, oxtprice: 99 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '70,00', price: '56,00', rrp_price: '99,00', show_rrp: true }\n    1001: { base_price: '70,00', price: '77,00', rrp_price: '99,00', show_rrp: true }\n    1002: { base_price: '70,00', price: '73,64', rrp_price: '99,00', show_rrp: true }\n    1003: { base_price: '70,00', price: '66,15', rrp_price: '99,00', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case88.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 150, oxtprice: 299 }\n    - { oxid: '1001', oxprice: 150, oxtprice: 299 }\n    - { oxid: '1002', oxprice: 150, oxtprice: 299 }\n    - { oxid: '1003', oxprice: 150, oxtprice: 299 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '150,00', price: '120,00', rrp_price: '299,00', show_rrp: true }\n    1001: { base_price: '150,00', price: '165,00', rrp_price: '299,00', show_rrp: true }\n    1002: { base_price: '150,00', price: '157,80', rrp_price: '299,00', show_rrp: true }\n    1003: { base_price: '150,00', price: '141,75', rrp_price: '299,00', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case89.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 70, oxtprice: 100 }\n    - { oxid: '1001', oxprice: 70, oxtprice: 100 }\n    - { oxid: '1002', oxprice: 70, oxtprice: 100 }\n    - { oxid: '1003', oxprice: 70, oxtprice: 100 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '70,00', price: '56,00', rrp_price: '100,00', show_rrp: true }\n    1001: { base_price: '70,00', price: '77,00', rrp_price: '100,00', show_rrp: true }\n    1002: { base_price: '70,00', price: '73,64', rrp_price: '100,00', show_rrp: true }\n    1003: { base_price: '70,00', price: '66,15', rrp_price: '100,00', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case9.yaml",
    "content": "articles:\n    - { oxid: 1001_a, oxprice: 5.02, oxvat: 20 }\n    - { oxid: 1001_b, oxprice: 5.02, oxvat: 20 }\ndiscounts:\n    - { oxid: abs, oxaddsum: 20, oxaddsumtype: abs, oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_a], oxsort: 10 }\n    - { oxid: percent, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001_b], oxsort: 20 }\nexpected:\n    1001_a: { base_price: '5,02', price: '0,00' }\n    1001_b: { base_price: '5,02', price: '4,02' }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case90.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 150, oxtprice: 1000 }\n    - { oxid: '1001', oxprice: 150, oxtprice: 1000 }\n    - { oxid: '1002', oxprice: 150, oxtprice: 1000 }\n    - { oxid: '1003', oxprice: 150, oxtprice: 1000 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '150,00', price: '120,00', rrp_price: '1.000,00', show_rrp: true }\n    1001: { base_price: '150,00', price: '165,00', rrp_price: '1.000,00', show_rrp: true }\n    1002: { base_price: '150,00', price: '157,80', rrp_price: '1.000,00', show_rrp: true }\n    1003: { base_price: '150,00', price: '141,75', rrp_price: '1.000,00', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case91.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 9.99, oxtprice: 10 }\n    - { oxid: '1001', oxprice: 9.99, oxtprice: 10 }\n    - { oxid: '1002', oxprice: 9.99, oxtprice: 10 }\n    - { oxid: '1003', oxprice: 9.99, oxtprice: 10 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '9,99', price: '7,99', rrp_price: '10,00', show_rrp: true }\n    1001: { base_price: '9,99', price: '10,99', rrp_price: '', show_rrp: false }\n    1002: { base_price: '9,99', price: '10,51', rrp_price: '', show_rrp: false }\n    1003: { base_price: '9,99', price: '9,44', rrp_price: '10,00', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: false, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case92.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 200, oxtprice: 10 }\n    - { oxid: '1001', oxprice: 200, oxtprice: 10 }\n    - { oxid: '1002', oxprice: 0.05, oxtprice: 10 }\n    - { oxid: '1003', oxprice: 10, oxtprice: 10 }\n    - { oxid: '1004', oxprice: 200, oxtprice: 10 }\n    - { oxid: '1005', oxprice: 200, oxtprice: 10 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\n    - { oxid: percentFor1004, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1004], oxsort: 50 }\n    - { oxid: percentFor1005, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1005], oxsort: 60 }\nexpected:\n    1000: { base_price: '200,00', price: '139,13', rrp_price: '', show_rrp: false }\n    1001: { base_price: '200,00', price: '191,30', rrp_price: '', show_rrp: false }\n    1002: { base_price: '0,05', price: '0,03', rrp_price: '8,70', show_rrp: true }\n    1003: { base_price: '10,00', price: '7,83', rrp_price: '8,70', show_rrp: true }\n    1004: { base_price: '200,00', price: '182,95', rrp_price: '', show_rrp: false }\n    1005: { base_price: '200,00', price: '164,34', rrp_price: '', show_rrp: false }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case93.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 100, oxtprice: 94.52 }\n    - { oxid: '1001', oxprice: 100, oxtprice: 94.52 }\n    - { oxid: '1002', oxprice: 100, oxtprice: 94.52 }\n    - { oxid: '1003', oxprice: 100, oxtprice: 94.52 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '100,00', price: '69,57', rrp_price: '82,19', show_rrp: true }\n    1001: { base_price: '100,00', price: '95,66', rrp_price: '', show_rrp: false }\n    1002: { base_price: '100,00', price: '91,48', rrp_price: '', show_rrp: false }\n    1003: { base_price: '100,00', price: '82,18', rrp_price: '82,19', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case94.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 0.05, oxtprice: 300 }\n    - { oxid: '1001', oxprice: 0.05, oxtprice: 300 }\n    - { oxid: '1002', oxprice: 0.05, oxtprice: 300 }\n    - { oxid: '1003', oxprice: 0.05, oxtprice: 300 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '0,05', price: '0,03', rrp_price: '260,87', show_rrp: true }\n    1001: { base_price: '0,05', price: '0,04', rrp_price: '260,87', show_rrp: true }\n    1002: { base_price: '0,05', price: '0,04', rrp_price: '260,87', show_rrp: true }\n    1003: { base_price: '0,05', price: '0,04', rrp_price: '260,87', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case95.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 79.9, oxtprice: 79.9 }\n    - { oxid: '1001', oxprice: 79.9, oxtprice: 79.9 }\n    - { oxid: '1002', oxprice: 79.9, oxtprice: 79.9 }\n    - { oxid: '1003', oxprice: 79.9, oxtprice: 79.9 }\n    - { oxid: '1004', oxprice: 89.9, oxtprice: 79.9 }\n    - { oxid: '1005', oxprice: 89.9, oxtprice: 79.9 }\n    - { oxid: '1006', oxprice: 89.9, oxtprice: 79.9 }\n    - { oxid: '1007', oxprice: 89.9, oxtprice: 79.9 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000, 1004], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001, 1005], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002, 1006], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003, 1007], oxsort: 40 }\nexpected:\n    1000: { base_price: '79,90', price: '55,58', rrp_price: '69,48', show_rrp: true }\n    1001: { base_price: '79,90', price: '76,43', rrp_price: '', show_rrp: false }\n    1002: { base_price: '79,90', price: '73,09', rrp_price: '', show_rrp: false }\n    1003: { base_price: '79,90', price: '65,66', rrp_price: '69,48', show_rrp: true }\n    1004: { base_price: '89,90', price: '62,54', rrp_price: '69,48', show_rrp: true }\n    1005: { base_price: '89,90', price: '85,99', rrp_price: '', show_rrp: false }\n    1006: { base_price: '89,90', price: '82,23', rrp_price: '', show_rrp: false }\n    1007: { base_price: '89,90', price: '73,87', rrp_price: '', show_rrp: false }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case96.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 70, oxtprice: 99 }\n    - { oxid: '1001', oxprice: 70, oxtprice: 99 }\n    - { oxid: '1002', oxprice: 70, oxtprice: 99 }\n    - { oxid: '1003', oxprice: 70, oxtprice: 99 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '70,00', price: '48,70', rrp_price: '86,09', show_rrp: true }\n    1001: { base_price: '70,00', price: '66,96', rrp_price: '86,09', show_rrp: true }\n    1002: { base_price: '70,00', price: '64,04', rrp_price: '86,09', show_rrp: true }\n    1003: { base_price: '70,00', price: '57,52', rrp_price: '86,09', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case97.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 150, oxtprice: 299 }\n    - { oxid: '1001', oxprice: 150, oxtprice: 299 }\n    - { oxid: '1002', oxprice: 150, oxtprice: 299 }\n    - { oxid: '1003', oxprice: 150, oxtprice: 299 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '150,00', price: '104,34', rrp_price: '260,00', show_rrp: true }\n    1001: { base_price: '150,00', price: '143,47', rrp_price: '260,00', show_rrp: true }\n    1002: { base_price: '150,00', price: '137,21', rrp_price: '260,00', show_rrp: true }\n    1003: { base_price: '150,00', price: '123,26', rrp_price: '260,00', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case98.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 70, oxtprice: 100 }\n    - { oxid: '1001', oxprice: 70, oxtprice: 100 }\n    - { oxid: '1002', oxprice: 70, oxtprice: 100 }\n    - { oxid: '1003', oxprice: 70, oxtprice: 100 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '70,00', price: '48,70', rrp_price: '86,96', show_rrp: true }\n    1001: { base_price: '70,00', price: '66,96', rrp_price: '86,96', show_rrp: true }\n    1002: { base_price: '70,00', price: '64,04', rrp_price: '86,96', show_rrp: true }\n    1003: { base_price: '70,00', price: '57,52', rrp_price: '86,96', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Price/testcases/price/case99.yaml",
    "content": "articles:\n    - { oxid: '1000', oxprice: 150, oxtprice: 1000 }\n    - { oxid: '1001', oxprice: 150, oxtprice: 1000 }\n    - { oxid: '1002', oxprice: 150, oxtprice: 1000 }\n    - { oxid: '1003', oxprice: 150, oxtprice: 1000 }\ndiscounts:\n    - { oxid: percentFor1000, oxaddsum: 20, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1000], oxsort: 10 }\n    - { oxid: percentFor1001, oxaddsum: -10, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1001], oxsort: 20 }\n    - { oxid: percentFor1002, oxaddsum: -5.2, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1002], oxsort: 30 }\n    - { oxid: percentFor1003, oxaddsum: 5.5, oxaddsumtype: '%', oxprice: 0, oxpriceto: 99999, oxamount: 0, oxamountto: 99999, oxactive: 1, oxarticles: [1003], oxsort: 40 }\nexpected:\n    1000: { base_price: '150,00', price: '104,34', rrp_price: '869,57', show_rrp: true }\n    1001: { base_price: '150,00', price: '143,47', rrp_price: '869,57', show_rrp: true }\n    1002: { base_price: '150,00', price: '137,21', rrp_price: '869,57', show_rrp: true }\n    1003: { base_price: '150,00', price: '123,26', rrp_price: '869,57', show_rrp: true }\noptions:\n    config: { blEnterNetPrice: false, blShowNetPrice: true, dDefaultVAT: 15 }\n    activeCurrencyRate: 1\n"
  },
  {
    "path": "tests/Integration/Legacy/Search/ProductSearchTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Checkout;\n\nuse OxidEsales\\Eshop\\Application\\Model\\Article;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Application\\Model\\Search;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class ProductSearchTest extends IntegrationTestCase\n{\n    private const CONFIG_KEY_SEARCH_COLUMNS = 'aSearchCols';\n\n    private const SEARCH_STRING_WITH_HITS = 'abc-123';\n\n    private const SEARCH_STRING_WITHOUT_HITS = 'XYZ-987';\n\n    private const ID_PRODUCT_WITH_TITLE_HIT = '1';\n\n    private const ID_PRODUCT_WITH_SEARCH_KEYS_HIT = '2';\n\n    private const ID_PRODUCT_WITHOUT_HITS = '3';\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->prepareProducts();\n    }\n\n    public function testGetSearchArticlesWithoutHitsWillReturnEmptyList(): void\n    {\n        $searchColumns = ['oxtitle'];\n        Registry::getConfig()->setConfigParam(self::CONFIG_KEY_SEARCH_COLUMNS, $searchColumns);\n\n        $productList = oxNew(Search::class)->getSearchArticles(self::SEARCH_STRING_WITHOUT_HITS);\n\n        $this->assertCount(0, $productList);\n    }\n\n    public function testGetSearchArticlesWithHitInOneColumnWillReturnExpected(): void\n    {\n        $searchColumns = ['oxtitle'];\n        Registry::getConfig()->setConfigParam(self::CONFIG_KEY_SEARCH_COLUMNS, $searchColumns);\n\n        $productList = oxNew(Search::class)->getSearchArticles(self::SEARCH_STRING_WITH_HITS);\n\n        $this->assertCount(1, $productList);\n        $productListArray = $productList->getArray();\n        $this->assertSame(self::ID_PRODUCT_WITH_TITLE_HIT, reset($productListArray)->getProductId());\n    }\n\n    public function testGetSearchArticlesWithHitsInTwoColumnsWillReturnExpectedListSize(): void\n    {\n        $searchColumns = ['oxtitle', 'oxsearchkeys'];\n        Registry::getConfig()->setConfigParam(self::CONFIG_KEY_SEARCH_COLUMNS, $searchColumns);\n\n        $productList = oxNew(Search::class)->getSearchArticles(self::SEARCH_STRING_WITH_HITS);\n\n        $this->assertCount(2, $productList);\n    }\n\n    private function prepareProducts(): void\n    {\n        $productWithTitleColumnHit = oxNew(Article::class);\n        $productWithTitleColumnHit->setId(self::ID_PRODUCT_WITH_TITLE_HIT);\n        $productWithTitleColumnHit->oxarticles__oxtitle = new Field($this->getStringWithHit());\n        $productWithTitleColumnHit->oxarticles__oxsearchkeys = new Field($this->getStringWithoutHits());\n        $productWithTitleColumnHit->save();\n\n        $productWithSearchKeysColumnHit = oxNew(Article::class);\n        $productWithSearchKeysColumnHit->setId(self::ID_PRODUCT_WITH_SEARCH_KEYS_HIT);\n        $productWithSearchKeysColumnHit->oxarticles__oxtitle = new Field($this->getStringWithoutHits());\n        $productWithSearchKeysColumnHit->oxarticles__oxsearchkeys = new Field($this->getStringWithHit());\n        $productWithSearchKeysColumnHit->save();\n\n        $productWithoutHits = oxNew(Article::class);\n        $productWithoutHits->setId(self::ID_PRODUCT_WITHOUT_HITS);\n        $productWithoutHits->oxarticles__oxtitle = new Field($this->getStringWithoutHits());\n        $productWithoutHits->oxarticles__oxsearchkeys = new Field($this->getStringWithoutHits());\n        $productWithoutHits->save();\n    }\n\n    private function getStringWithHit(): string\n    {\n        return self::SEARCH_STRING_WITH_HITS;\n    }\n\n    private function getStringWithoutHits(): string\n    {\n        return uniqid('', true);\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/Url/WidgetUrlTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\Url;\n\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\nuse oxRegistry;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nfinal class WidgetUrlTest extends IntegrationTestCase\n{\n    use ContainerTrait;\n\n    private string $shopUrl = 'http://www.example.com/';\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->setParameter('oxid_esales.shop_url', $this->shopUrl);\n    }\n\n    public static function providerGetWidgetUrlAddParametersIdNeed(): array\n    {\n        $basicUrl = 'http://www.example.com/' . 'widget.php';\n        $urlWithoutParams = $basicUrl . '?lang=0';\n\n        $urlParameters = [\n            'param1' => 'value1',\n            'param2' => 'value2',\n        ];\n        $urlWithParams = $basicUrl . '?lang=0&amp;param1=value1&amp;param2=value2';\n\n        $urlLanguageParameters = [\n            'lang' => '1',\n            'param1' => 'value1',\n            'param2' => 'value2',\n        ];\n        $urlWithLanguageParams = $basicUrl . '?lang=1&amp;param1=value1&amp;param2=value2';\n\n        $urlLeveledParameters = [\n            'lang' => '1',\n            'param1' => ['value1', 'value2'],\n        ];\n        $urlWithLeveledParameters = $basicUrl . '?lang=1&amp;param1%5B0%5D=value1&amp;param1%5B1%5D=value2';\n\n        return [[[], $urlWithoutParams],\n            [$urlParameters, $urlWithParams],\n            [$urlLanguageParameters, $urlWithLanguageParams],\n            [$urlLeveledParameters, $urlWithLeveledParameters],\n        ];\n    }\n\n    /**\n     * Testing getShopHomeUrl for widget getter\n     *\n     * @param array $urlParameters parameters to add to url.\n     * @param string $sUrl to check if form url matches expectation.\n     */\n    #[DataProvider('providerGetWidgetUrlAddParametersIdNeed')]\n    public function testGetWidgetUrlWithParameters(array $urlParameters, string $sUrl): void\n    {\n        oxRegistry::getLang()->setBaseLanguage(0);\n\n        $config = oxNew('oxConfig');\n        $config->init();\n\n        $this->assertEquals($sUrl, $config->getWidgetUrl(null, null, $urlParameters));\n    }\n\n    public static function providerGetWidgetUrlAddCorrectLanguage(): array\n    {\n        return [[0], [1]];\n    }\n\n    /**\n     * Testing getShopHomeUrl for widget getter\n     *\n     * @param int $iLang Shop basic language.\n     */\n    #[DataProvider('providerGetWidgetUrlAddCorrectLanguage')]\n    public function testGetWidgetUrlAddCorrectLanguage(int $iLang): void\n    {\n        oxRegistry::getLang()->setBaseLanguage($iLang);\n\n        $config = oxNew('oxConfig');\n        $config->init();\n\n        $this->assertEquals($this->shopUrl . 'widget.php?lang=' . $iLang, $config->getWidgetUrl());\n    }\n\n    /**\n     * Testing getShopHomeUrl for widget getter\n     *\n     * @param int $iLang Shop basic language.\n     */\n    #[DataProvider('providerGetWidgetUrlAddCorrectLanguage')]\n    public function testGetWidgetUrlAddCorrectLanguageWithParameter(int $iLang): void\n    {\n        oxRegistry::getLang()->setBaseLanguage($iLang);\n\n        $config = oxNew('oxConfig');\n        $config->init();\n\n        $this->assertEquals($this->shopUrl . 'widget.php?lang=' . $iLang, $config->getWidgetUrl($iLang));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/User/LoginTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\User;\n\nuse OxidEsales\\Eshop\\Application\\Component\\UserComponent;\nuse OxidEsales\\Eshop\\Core\\Registry;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\IntegrationTestCase;\n\nfinal class LoginTest extends IntegrationTestCase\n{\n    use UserFixtureTrait;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->setShopUserLoginRequestData();\n    }\n\n    public function testLogin(): void\n    {\n        $hash = $this->getHash();\n        $user = $this->addUserFixture($hash);\n\n        $login = oxNew(UserComponent::class)->login();\n\n        $this->assertEquals('payment', $login);\n        $this->assertEquals($user->getId(), Registry::getSession()->getVariable('usr'));\n        $this->assertEquals($hash, $user->getFieldData('oxpassword'));\n        $this->assertEquals($this->getSalt(), $user->getFieldData('oxpasssalt'));\n    }\n\n    public function testLoginWithLegacyHashing(): void\n    {\n        $hash = $this->getLegacyHash();\n        $user = $this->addUserFixture($hash);\n\n        $login = oxNew(UserComponent::class)->login();\n\n        $this->assertEquals('payment', $login);\n        $this->assertEquals($user->getId(), Registry::getSession()->getVariable('usr'),);\n        $this->assertEquals($hash, $user->getFieldData('oxpassword'));\n        $this->assertEquals($this->getSalt(), $user->getFieldData('oxpasssalt'));\n    }\n\n    public function testLoadUserLoggedInWithLegacyHashingWillUpdatePasswordHashAndSetEmptySalt(): void\n    {\n        $hash = $this->getLegacyHash();\n        $user = $this->addUserFixture($hash);\n        oxNew(UserComponent::class)->login();\n\n        $user->load($user->getId());\n\n        $this->assertNotEmpty($user->getFieldData('oxpassword'));\n        $this->assertNotEquals($hash, $user->getFieldData('oxpassword'));\n        $this->assertEmpty($user->getFieldData('oxpasssalt'));\n    }\n\n    public function testLoginFail(): void\n    {\n        $hash = $this->getHash();\n        $user = $this->addUserFixture($hash);\n        $_POST['lgn_pwd'] = uniqid('wrong-pass-', true);\n\n        oxNew(UserComponent::class)->login();\n\n        $this->assertNull(Registry::getSession()->getVariable('usr'));\n        $this->assertEquals($hash, $user->getFieldData('oxpassword'));\n        $this->assertEquals($this->getSalt(), $user->getFieldData('oxpasssalt'));\n    }\n\n    public function testLoadUserAfterLoginFailWithLegacyHashingWillNotUpdateHashAndSalt(): void\n    {\n        $hash = $this->getLegacyHash();\n        $user = $this->addUserFixture($hash);\n        $_POST['lgn_pwd'] = uniqid('wrong-pass-', true);\n        oxNew(UserComponent::class)->login();\n\n        $user->load($user->getId());\n\n        $this->assertNull(Registry::getSession()->getVariable('usr'));\n        $this->assertEquals($hash, $user->getFieldData('oxpassword'));\n        $this->assertEquals($this->getSalt(), $user->getFieldData('oxpasssalt'));\n    }\n}\n"
  },
  {
    "path": "tests/Integration/Legacy/User/UserFixtureTrait.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Integration\\Legacy\\User;\n\nuse OxidEsales\\Eshop\\Application\\Model\\User;\nuse OxidEsales\\Eshop\\Core\\Field;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Bridge\\PasswordServiceBridgeInterface;\n\ntrait UserFixtureTrait\n{\n    private string $salt;\n    private string $username = '_testUserName@oxid-esales.com';\n    private string $pass = '_testPassword';\n\n    private function addUserFixture(string $hash): User\n    {\n        $user = oxNew(User::class);\n        $user->oxuser__oxusername = new Field('_testUserName@oxid-esales.com', Field::T_RAW);\n        $user->oxuser__oxpassword = new Field($hash, Field::T_RAW);\n        $user->oxuser__oxpasssalt = new Field($this->getSalt(), Field::T_RAW);\n        $user->save();\n\n        return $user;\n    }\n\n    private function getSalt(): string\n    {\n        if (!isset($this->salt)) {\n            $this->salt = uniqid('salt-', true);\n        }\n        return $this->salt;\n    }\n\n    private function getLegacyHash(): string\n    {\n        return oxNew(User::class)->encodePassword($this->pass, $this->getSalt());\n    }\n\n    private function getHash(): string\n    {\n        return $this->get(PasswordServiceBridgeInterface::class)->hash($this->pass);\n    }\n\n    private function setShopUserLoginRequestData(): void\n    {\n        $_POST['lgn_usr'] = $this->username;\n        $_POST['lgn_pwd'] = $this->pass;\n    }\n}\n"
  },
  {
    "path": "tests/TestContainerFactory.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests;\n\nuse OxidEsales\\Eshop\\Core\\ShopIdCalculator;\nuse OxidEsales\\Eshop\\Core\\UtilsServer;\nuse OxidEsales\\EshopCommunity\\Internal\\Container\\ContainerProviderInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\ContainerBuilder;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\BasicContextStub;\nuse OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\ContextStub;\nuse Psr\\Container\\ContainerInterface;\nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder as SymfonyContainerBuilder;\n\nclass TestContainerFactory implements ContainerProviderInterface\n{\n    private static $symfonyContainer;\n\n    public function create(): SymfonyContainerBuilder\n    {\n        $shopId = (new ShopIdCalculator(new UtilsServer()))->getShopId();\n        $contextStub = new ContextStub($shopId);\n\n        $container = (new ContainerBuilder($contextStub, $shopId))->getContainer();\n        $container->set(ContextInterface::class, $contextStub);\n        $container->set(BasicContextInterface::class, $contextStub);\n        $container->autowire(BasicContextInterface::class, BasicContextStub::class);\n        $container->autowire(ContextInterface::class, ContextStub::class);\n\n        $this->setAllServicesAsPublic($container);\n\n        return $container;\n    }\n\n    public static function get(): ContainerInterface\n    {\n        if (self::$symfonyContainer === null) {\n            self::$symfonyContainer = (new self())->create();\n            self::$symfonyContainer->compile(true);\n        }\n\n        return self::$symfonyContainer;\n    }\n\n    public static function setContainer(ContainerInterface $container): void\n    {\n        self::$symfonyContainer = $container;\n    }\n\n    public static function resetContainer(): void\n    {\n        self::$symfonyContainer = null;\n    }\n\n    private function setAllServicesAsPublic(SymfonyContainerBuilder $container): void\n    {\n        foreach ($container->getDefinitions() as $definition) {\n            $definition->setPublic(true);\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/BasicContextStub.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContext;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition\\Edition;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse Symfony\\Component\\Filesystem\\Path;\n\nclass BasicContextStub implements BasicContextInterface\n{\n    private string $ceSourcePath;\n    private string $peSourcePath;\n    private string $eeSourcePath;\n    private Edition $edition;\n    private string $generatedServicesFilePath;\n    private string $sourcePath;\n    private string $projectConfigurationDirectory;\n    private string $cacheDirectory;\n    private string $databaseUrl;\n    protected string $activeModuleServicesFilePath;\n    private string $shopBaseUrl;\n    private string $configurableServicesFilePath;\n    private string $shopConfigurableServicesFilePath;\n\n    private BasicContextInterface $basicContext;\n\n    public function __construct()\n    {\n        $this->basicContext = new BasicContext();\n    }\n\n    public function getCommunityEditionSourcePath(): string\n    {\n        return $this->ceSourcePath ?? $this->basicContext->getCommunityEditionSourcePath();\n    }\n\n    public function getContainerCacheFilePath(int $shopId): string\n    {\n        return $this->basicContext->getContainerCacheFilePath($shopId);\n    }\n\n    public function getEdition(): Edition\n    {\n        return $this->edition ?? $this->basicContext->getEdition();\n    }\n\n    public function setEdition(Edition $edition): void\n    {\n        $this->edition = $edition;\n    }\n\n    public function getEnterpriseEditionRootPath(): string\n    {\n        return $this->eeSourcePath ?? $this->basicContext->getEnterpriseEditionRootPath();\n    }\n\n    public function setEnterpriseEditionRootPath(string $enterpriseEditionRootPath): void\n    {\n        $this->eeSourcePath = $enterpriseEditionRootPath;\n    }\n\n    public function getGeneratedServicesFilePath(): string\n    {\n        return $this->generatedServicesFilePath ?? $this->basicContext->getGeneratedServicesFilePath();\n    }\n\n    public function setGeneratedServicesFilePath(string $generatedServicesFilePath): void\n    {\n        $this->generatedServicesFilePath = $generatedServicesFilePath;\n    }\n\n    public function getConfigurableServicesFilePath(): string\n    {\n        return $this->configurableServicesFilePath ?? $this->basicContext->getConfigurableServicesFilePath();\n    }\n\n    public function setConfigurableServicesFilePath(string $configurableServicesFilePath): void\n    {\n        $this->configurableServicesFilePath = $configurableServicesFilePath;\n    }\n\n    public function getShopConfigurableServicesFilePath(int $shopId): string\n    {\n        return $this->shopConfigurableServicesFilePath ?? $this->basicContext->getShopConfigurableServicesFilePath(\n            $shopId\n        );\n    }\n\n    public function setShopConfigurableServicesFilePath(string $shopConfigurableServicesFilePath): void\n    {\n        $this->shopConfigurableServicesFilePath = $shopConfigurableServicesFilePath;\n    }\n\n    public function getProfessionalEditionRootPath(): string\n    {\n        return $this->peSourcePath ?? $this->basicContext->getProfessionalEditionRootPath();\n    }\n\n    public function setProfessionalEditionRootPath(string $professionalEditionRootPath): void\n    {\n        $this->peSourcePath = $professionalEditionRootPath;\n    }\n\n    public function getSourcePath(): string\n    {\n        return $this->sourcePath ?? $this->basicContext->getSourcePath();\n    }\n\n    public function setSourcePath(string $sourcePath): void\n    {\n        $this->sourcePath = $sourcePath;\n    }\n\n    public function getDefaultShopId(): int\n    {\n        return 1;\n    }\n\n    public function getAllShopIds(): array\n    {\n        return [$this->getDefaultShopId()];\n    }\n\n    public function getBackwardsCompatibilityClassMap(): array\n    {\n        return $this->basicContext->getBackwardsCompatibilityClassMap();\n    }\n\n    public function getProjectConfigurationDirectory(): string\n    {\n        return $this->projectConfigurationDirectory ?? $this->basicContext->getProjectConfigurationDirectory();\n    }\n\n    public function setProjectConfigurationDirectory(string $projectConfigurationDirectory): void\n    {\n        $this->projectConfigurationDirectory = $projectConfigurationDirectory;\n    }\n\n    public function getConfigTableName(): string\n    {\n        return 'oxconfig';\n    }\n\n    public function getShopRootPath(): string\n    {\n        return $this->basicContext->getShopRootPath();\n    }\n\n    public function getOutPath(): string\n    {\n        return $this->basicContext->getOutPath();\n    }\n\n    public function getVendorPath(): string\n    {\n        return $this->basicContext->getVendorPath();\n    }\n\n    public function getComposerVendorName(): string\n    {\n        return $this->basicContext->getComposerVendorName();\n    }\n\n    public function getCacheDirectory(): string\n    {\n        return $this->cacheDirectory ?? $this->basicContext->getCacheDirectory();\n    }\n\n    public function setCacheDirectory(string $cacheDirectory): void\n    {\n        $this->cacheDirectory = $cacheDirectory;\n    }\n\n    public function getModuleCacheDirectory(): string\n    {\n        return $this->basicContext->getModuleCacheDirectory();\n    }\n\n    public function getShopConfigurationDirectory(int $shopId): string\n    {\n        return Path::join($this->getProjectConfigurationDirectory(), 'shops', (string)$shopId);\n    }\n\n    public function getActiveModuleServicesFilePath(int $shopId): string\n    {\n        return $this->activeModuleServicesFilePath ?? $this->basicContext->getActiveModuleServicesFilePath($shopId);\n    }\n\n    public function setActiveModuleServicesFilePath(string $path): void\n    {\n        $this->activeModuleServicesFilePath = $path;\n    }\n\n    public function getDatabaseUrl(): string\n    {\n        return $this->databaseUrl ?? $this->basicContext->getDatabaseUrl();\n    }\n\n    public function setDatabaseUrl(string $databaseUrl): string\n    {\n        return $this->databaseUrl = $databaseUrl;\n    }\n\n    public function getShopBaseUrl(): string\n    {\n        return $this->shopBaseUrl ?? $this->basicContext->getShopBaseUrl();\n    }\n\n    public function setShopBaseUrl(string $shopBaseUrl): void\n    {\n        $this->shopBaseUrl = $shopBaseUrl;\n    }\n\n    public function getEditionSourcePath(Edition $edition): string\n    {\n        return match ($edition) {\n            Edition::Community => $this->ceSourcePath ?? $this->basicContext->getEditionSourcePath(Edition::Community),\n            Edition::Professional => $this->peSourcePath ?? $this->basicContext->getEditionSourcePath(Edition::Professional),\n            Edition::Enterprise => $this->eeSourcePath ?? $this->basicContext->getEditionSourcePath(Edition::Enterprise),\n        };\n    }\n\n    public function setCommunityEditionSourcePath(string $path): void\n    {\n        $this->ceSourcePath = $path;\n    }\n\n    public function setProfessionalEditionSourcePath(string $path): void\n    {\n        $this->peSourcePath = $path;\n    }\n\n    public function setEnterpriseEditionSourcePath(string $path): void\n    {\n        $this->eeSourcePath = $path;\n    }\n}"
  },
  {
    "path": "tests/Unit/Internal/Container/DataObject/DIConfigWrapperTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Container\\DataObject;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\DataObject\\DIConfigWrapper;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class DIConfigWrapperTest extends TestCase\n{\n    private string $servicePath1;\n    private string $servicePath2;\n\n    public function setup(): void\n    {\n        $this->servicePath1 = __DIR__ . DIRECTORY_SEPARATOR .\n            '..' . DIRECTORY_SEPARATOR .\n            'TestModule1' . DIRECTORY_SEPARATOR .\n            'services.yaml';\n        $this->servicePath2 = __DIR__ . DIRECTORY_SEPARATOR .\n            '..' . DIRECTORY_SEPARATOR .\n            'TestModule2' . DIRECTORY_SEPARATOR .\n            'services.yaml';\n    }\n\n    public function testCleaningSections(): void\n    {\n        $projectYaml = new DIConfigWrapper(['imports' => []]);\n        // These empty sections should be cleaned away\n        $this->assertCount(0, $projectYaml->getConfigAsArray());\n    }\n\n    public function testGetAllImportFileNames(): void\n    {\n        $configArray = ['imports' => [\n            ['resource' => $this->servicePath1],\n            ['resource' => $this->servicePath2]\n        ]];\n\n        $wrapper = new DIConfigWrapper($configArray);\n        $names = $wrapper->getImportFileNames();\n\n        $this->assertEquals($this->servicePath1, $names[0]);\n        $this->assertEquals($this->servicePath2, $names[1]);\n    }\n\n    public function testAddImport(): void\n    {\n        $configArray = ['imports' => [['resource' => $this->servicePath1]]];\n\n        $wrapper = new DIConfigWrapper($configArray);\n        $wrapper->addImport($this->servicePath2);\n\n        $this->assertCount(2, $wrapper->getConfigAsArray()['imports']);\n    }\n\n    public function testAddFirstImport(): void\n    {\n        $configArray = [];\n\n        $wrapper = new DIConfigWrapper($configArray);\n        $wrapper->addImport($this->servicePath1);\n\n        $expected = ['imports' => [['resource' => $this->servicePath1]]];\n        $this->assertEquals($expected, $wrapper->getConfigAsArray());\n    }\n\n    public function testRemoveImport(): void\n    {\n        $configArray = ['imports' => [\n            ['resource' => $this->servicePath1],\n            ['resource' => $this->servicePath2]\n        ]];\n\n        $wrapper = new DIConfigWrapper($configArray);\n        $wrapper->removeImport($this->servicePath1);\n\n        $this->assertCount(1, $wrapper->getConfigAsArray()['imports']);\n    }\n\n    public function testRemoveLastImport(): void\n    {\n        $configArray = ['imports' => [['resource' => $this->servicePath1]]];\n\n        $wrapper = new DIConfigWrapper($configArray);\n        $wrapper->removeImport($this->servicePath1);\n\n        $this->assertEquals([], $wrapper->getConfigAsArray());\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/ContextStub.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\Context;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\n\nclass ContextStub extends BasicContextStub implements ContextInterface\n{\n    private string $logLevel;\n    private string $logFilePath;\n    private array $shopIds;\n    private array $requiredContactFormFields = [];\n    private string $adminLogFilePath;\n    private bool $doLogAdminQueries;\n    private bool $isAdmin;\n    private array $skipLogTags;\n    private string $adminUserId;\n    private bool $productiveMode;\n    private bool $demoMode;\n    private ContextInterface $context;\n\n    public function __construct(private int $shopId = 1)\n    {\n        $this->context = new Context($this->shopId);\n        parent::__construct();\n    }\n\n    public function setLogLevel(string $logLevel): void\n    {\n        $this->logLevel = $logLevel;\n    }\n\n    public function setLogFilePath(string $logFilePath): void\n    {\n        $this->logFilePath = $logFilePath;\n    }\n\n    public function getLogLevel(): string\n    {\n        return $this->logLevel ?? $this->context->getLogLevel();\n    }\n\n    public function getLogFilePath(): string\n    {\n        return $this->logFilePath ?? $this->context->getLogFilePath();\n    }\n\n    public function getRequiredContactFormFields(): array\n    {\n        return $this->requiredContactFormFields ?? $this->context->getRequiredContactFormFields();\n    }\n\n    public function setRequiredContactFormFields(array $requiredContactFormFields): void\n    {\n        $this->requiredContactFormFields = $requiredContactFormFields;\n    }\n\n    public function getAllShopIds(): array\n    {\n        return $this->shopIds ?? $this->context->getAllShopIds();\n    }\n\n    public function setAllShopIds(array $shopIds): void\n    {\n        $this->shopIds = $shopIds;\n    }\n\n    public function setAdminLogFilePath(string $logFilePath): void\n    {\n        $this->adminLogFilePath = $logFilePath;\n    }\n\n    public function getAdminLogFilePath(): string\n    {\n        return $this->adminLogFilePath ?? $this->context->getAdminLogFilePath();\n    }\n\n    public function setIsEnabledAdminQueryLog(bool $doLogAdminQueries): void\n    {\n        $this->doLogAdminQueries = $doLogAdminQueries;\n    }\n\n    public function isEnabledAdminQueryLog(): bool\n    {\n        return $this->doLogAdminQueries ?? $this->context->isEnabledAdminQueryLog();\n    }\n\n    public function isAdmin(): bool\n    {\n        return $this->isAdmin ?? $this->context->isAdmin();\n    }\n\n    public function setIsAdmin(bool $isAdmin): void\n    {\n        $this->isAdmin = $isAdmin;\n    }\n\n    public function getAdminUserId(): string\n    {\n        return $this->adminUserId ?? $this->context->getAdminUserId();\n    }\n\n    public function setAdminUserId(string $userId): void\n    {\n        $this->adminUserId = $userId;\n    }\n\n    public function getSkipLogTags(): array\n    {\n        return $this->skipLogTags ?? $this->context->getSkipLogTags();\n    }\n\n    public function setSkipLogTags(array $skipLogTags): void\n    {\n        $this->skipLogTags = $skipLogTags;\n    }\n\n    public function isShopInProductiveMode(): bool\n    {\n        return $this->productiveMode ?? $this->context->isShopInProductiveMode();\n    }\n\n    public function setShopInProductiveMode(bool $productiveMode): void\n    {\n        $this->productiveMode = $productiveMode;\n    }\n\n    public function isShopInDemoMode(): bool\n    {\n        return $this->demoMode ?? $this->context->isShopInDemoMode();\n    }\n\n    public function setShopInDemoMode(bool $demoMode): void\n    {\n        $this->demoMode = $demoMode;\n    }\n\n    public function getCurrentShopId(): int\n    {\n        return $this->context->getCurrentShopId();\n    }\n}"
  },
  {
    "path": "tests/Unit/Internal/Domain/Admin/Factory/AdminFactoryTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Admin\\Factory;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\DataObject\\Admin;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Exception\\InvalidEmailException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Exception\\InvalidRightsException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Exception\\InvalidShopException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Factory\\AdminFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\ShopAdapterInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Email\\EmailValidatorService;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Hash\\Service\\PasswordHashServiceInterface;\nuse PHPUnit\\Framework\\TestCase;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\n\nfinal class AdminFactoryTest extends TestCase\n{\n    use ProphecyTrait;\n\n    private $adminFactory;\n\n    private string $password = 'test123';\n\n    private function createAdminFactory(): AdminFactory\n    {\n        $shopAdapter = $this->prophesize(ShopAdapterInterface::class);\n        $shopAdapter->willImplement(ShopAdapterInterface::class);\n        $shopAdapter->validateShopId(1)->willReturn(true);\n        $shopAdapter->generateUniqueId()->willReturn(uniqid());\n\n        $passwordHashService = $this->prophesize(PasswordHashServiceInterface::class);\n        $passwordHashService->hash($this->password)->willReturn(md5($this->password));\n\n        return new AdminFactory(\n            $shopAdapter->reveal(),\n            new EmailValidatorService(),\n            $passwordHashService->reveal()\n        );\n    }\n\n    public function testCreateAdmin(): void\n    {\n        $adminFactory = $this->createAdminFactory();\n\n        $admin = $adminFactory->createAdmin(\n            'testuser@oxideshop.dev',\n            $this->password,\n            Admin::MALL_ADMIN,\n            1\n        );\n\n        $this->assertInstanceOf(\n            Admin::class,\n            $admin\n        );\n\n        $this->assertNotEquals($this->password, $admin->getPasswordHash());\n    }\n\n    public function testFailedCreate(): void\n    {\n        $adminFactory = $this->createAdminFactory();\n\n        $this->expectException(InvalidEmailException::class);\n\n        $adminFactory->createAdmin(\n            'testuser',\n            $this->password,\n            Admin::MALL_ADMIN,\n            1\n        );\n\n        $this->expectException(InvalidRightsException::class);\n\n        $adminFactory->createAdmin(\n            'testuser@oxideshop.dev',\n            $this->password,\n            'admin',\n            1\n        );\n\n        $this->expectException(InvalidShopException::class);\n\n        $adminFactory->createAdmin(\n            'testuser@oxideshop.dev',\n            $this->password,\n            Admin::MALL_ADMIN,\n            12\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Admin/Service/AdminUserServiceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Admin\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\DataObject\\Admin;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Dao\\AdminDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Factory\\AdminFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Service\\AdminUserService;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Admin\\Service\\AdminUserServiceInterface;\nuse PHPUnit\\Framework\\Attributes\\Test;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class AdminUserServiceTest extends TestCase\n{\n    #[Test]\n    public function createAdmin(): void\n    {\n        $email = uniqid();\n        $password = uniqid();\n        $rights = uniqid();\n        $shopId = rand(1, 10);\n\n        $adminStub = $this->createStub(Admin::class);\n\n        $adminFactorySpy = $this->createMock(AdminFactoryInterface::class);\n        $adminFactorySpy\n            ->method('createAdmin')\n            ->with($email, $password, $rights, $shopId)\n            ->willReturn($adminStub);\n\n        $adminDaoMock = $this->createMock(AdminDaoInterface::class);\n        $adminDaoMock\n            ->method('create')\n            ->with($adminStub);\n\n        $sut = $this->getAdminDao(adminDao: $adminDaoMock, adminFactory: $adminFactorySpy);\n\n        $sut->createAdmin($email, $password, $rights, $shopId);\n    }\n\n    private function getAdminDao(\n        ?AdminDaoInterface $adminDao = null,\n        ?AdminFactoryInterface $adminFactory = null\n    ): AdminUserServiceInterface\n    {\n        return new AdminUserService(\n            adminDao: $adminDao ?? $this->createStub(AdminDaoInterface::class),\n            adminFactory: $adminFactory ?? $this->createStub(AdminFactoryInterface::class)\n        );\n    }\n}"
  },
  {
    "path": "tests/Unit/Internal/Domain/Authentication/Generator/RandomTokenGeneratorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Authentication\\Generator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Generator\\RandomTokenGenerator;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class RandomTokenGeneratorTest extends TestCase\n{\n    use ContainerTrait;\n\n    public function testGetAlphanumericTokenWillReturnExpectedType(): void\n    {\n        $token = (new RandomTokenGenerator())->getAlphanumericToken(256);\n\n        $this->assertTrue(ctype_alnum($token));\n    }\n\n    public function testGetAlphanumericTokenWithShortTokenWillReturnsExpectedLength(): void\n    {\n        $tokenLength = 1;\n\n        $token = (new RandomTokenGenerator())->getAlphanumericToken($tokenLength);\n\n        $this->assertEquals($tokenLength, strlen($token));\n    }\n\n    public function testGetAlphanumericTokenWithLongTokenWillReturnsExpectedLength(): void\n    {\n        $tokenLength = 1024;\n\n        $token = (new RandomTokenGenerator())->getAlphanumericToken($tokenLength);\n\n        $this->assertEquals($tokenLength, strlen($token));\n    }\n\n    public function testGetAlphanumericTokenWillPadResultToExpectedLength(): void\n    {\n        $tokens = [];\n        $tokenLength = 3;\n        $tokenGenerator = new RandomTokenGenerator();\n\n        /**\n         * Strings with multiple non-alphanumeric characters (e.g. \"/+E4\", \"+s+w\", \"//w5\", etc.) will be too short after cleaning.\n         * Get some array of results, big enough to contain at least one such combination of characters.\n         */\n        for ($i = 0; $i < 1024; $i++) {\n            $tokens[] = $tokenGenerator->getAlphanumericToken($tokenLength);\n        }\n\n        $arrayOfTokenLengths = array_map('strlen', $tokens);\n        $lengthOfTheShortestToken = min($arrayOfTokenLengths);\n        $this->assertEquals($tokenLength, $lengthOfTheShortestToken);\n    }\n\n    public function testGetAlphanumericTokenWillReturnUniqueValues(): void\n    {\n        $tokens = [];\n        $tokenLength = 3;\n        $tokenGenerator = new RandomTokenGenerator();\n        $iterations = 3;\n\n        for ($i = 0; $i < $iterations; $i++) {\n            $tokens[] = $tokenGenerator->getAlphanumericToken($tokenLength);\n        }\n\n        $numberOfTokens = count($tokens);\n        $uniqueTokens = array_unique($tokens);\n        $this->assertCount($numberOfTokens, $uniqueTokens);\n    }\n\n    public function testGetHexTokenWillReturnExpectedType(): void\n    {\n        $token = (new RandomTokenGenerator())->getHexToken(32);\n\n        $this->assertTrue(ctype_xdigit($token));\n    }\n\n    public function testGetHexTokenWithShortTokenWillReturnExpectedLength(): void\n    {\n        $length = 1;\n        $token = (new RandomTokenGenerator())->getHexToken($length);\n\n        $this->assertEquals($length, strlen($token));\n    }\n\n    public function testGetHexTokenWithLongTokensWillReturnExpectedLength(): void\n    {\n        $length = 1024;\n        $token = (new RandomTokenGenerator())->getHexToken($length);\n\n        $this->assertEquals($length, strlen($token));\n    }\n\n    public function testGetHexTokenWillReturnUniqueValues(): void\n    {\n        $tokens = [];\n        $tokenLength = 3;\n        $tokenGenerator = new RandomTokenGenerator();\n        $iterations = 3;\n\n        for ($i = 0; $i < $iterations; $i++) {\n            $tokens[] = $tokenGenerator->getHexToken($tokenLength);\n        }\n\n        $numberOfTokens = count($tokens);\n        $uniqueTokens = array_unique($tokens);\n        $this->assertCount($numberOfTokens, $uniqueTokens);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Authentication/Service/PasswordVerificationServiceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Authentication\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Authentication\\Policy\\PasswordPolicyInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Authentication\\Service\\PasswordVerificationService;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class PasswordVerificationServiceTest extends TestCase\n{\n    public function testPasswordVerificationVerifiesCorrectPassword(): void\n    {\n        $passwordUtf8 = 'äääää';\n        $passwordHash = password_hash($passwordUtf8, PASSWORD_DEFAULT);\n\n        $passwordVerificationService = $this->getPasswordVerificationService();\n\n        $this->assertTrue(\n            $passwordVerificationService->verifyPassword($passwordUtf8, $passwordHash)\n        );\n    }\n\n    public function testPasswordVerificationDoesNotVerifyWrongPassword(): void\n    {\n        $passwordUtf8 = 'äääää';\n        $passwordHash = password_hash($passwordUtf8, PASSWORD_DEFAULT);\n\n        $passwordVerificationService = $this->getPasswordVerificationService();\n\n        $this->assertFalse(\n            $passwordVerificationService->verifyPassword('WRONG_PASSWORD', $passwordHash)\n        );\n    }\n\n    private function getPasswordVerificationService(): PasswordVerificationService\n    {\n        $passwordPolicyMock = $this->createStub(PasswordPolicyInterface::class);\n\n        return new PasswordVerificationService($passwordPolicyMock);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Contact/Form/ContactFormConfigurationFactoryTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Contact\\Form;\n\nuse PHPUnit\\Framework\\TestCase;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FieldConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FormConfigurationInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FormFieldsConfigurationDataProviderInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form\\ContactFormConfigurationFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\n\nfinal class ContactFormConfigurationFactoryTest extends TestCase\n{\n    public function testConfigurationGetter(): void\n    {\n        $context = $this->createStub(ContextInterface::class);\n\n        $formFieldsConfigurationDataProvider = $this->createStub(FormFieldsConfigurationDataProviderInterface::class);\n        $formFieldsConfigurationDataProvider\n            ->method('getFormFieldsConfiguration')\n            ->willReturn([]);\n\n        $formConfigurationFactory = new ContactFormConfigurationFactory(\n            $formFieldsConfigurationDataProvider,\n            $context\n        );\n\n        $this->assertInstanceOf(\n            FormConfigurationInterface::class,\n            $formConfigurationFactory->getFormConfiguration()\n        );\n    }\n\n    public function testFormFieldsConfiguration(): void\n    {\n        $context = $this->createStub(ContextInterface::class);\n        $context\n            ->method('getRequiredContactFormFields')\n            ->willReturn([\n                'name',\n            ]);\n\n        $formFieldsConfigurationDataProvider = $this->createStub(FormFieldsConfigurationDataProviderInterface::class);\n        $formFieldsConfigurationDataProvider\n            ->method('getFormFieldsConfiguration')\n            ->willReturn([\n                [\n                    'name'              => 'email',\n                    'label'             => 'EMAIL',\n                ],\n                [\n                    'name'              => 'firstName',\n                    'label'             => 'FIRST_NAME',\n                    'required'          => true,\n                ],\n            ]);\n\n        $formConfigurationFactory = new ContactFormConfigurationFactory(\n            $formFieldsConfigurationDataProvider,\n            $context\n        );\n\n        $contactFormConfiguration = $formConfigurationFactory->getFormConfiguration();\n\n        $emailConfiguration = new FieldConfiguration();\n        $emailConfiguration\n            ->setName('email')\n            ->setLabel('EMAIL');\n\n        $firstNameConfiguration = new FieldConfiguration();\n        $firstNameConfiguration\n            ->setName('firstName')\n            ->setLabel('FIRST_NAME')\n            ->isRequired();\n\n        $this->assertEquals(\n            [\n                $emailConfiguration,\n                $firstNameConfiguration,\n            ],\n            $contactFormConfiguration->getFieldConfigurations()\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Contact/Form/ContactFormEmailValidatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Contact\\Form;\n\nuse PHPUnit\\Framework\\TestCase;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Email\\EmailValidatorService;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\Form;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormField;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form\\ContactFormEmailValidator;\n\nfinal class ContactFormEmailValidatorTest extends TestCase\n{\n    public function testInvalidEmailValidation(): void\n    {\n        $validator = $this->getContactFormEmailValidator();\n\n        $invalidEmailField = new FormField();\n        $invalidEmailField->setName('email');\n        $invalidEmailField->setValue('ImSoInvalid');\n\n        $form = new Form();\n        $form->add($invalidEmailField);\n\n        $this->assertFalse(\n            $validator->isValid($form)\n        );\n\n        $this->assertSame(\n            ['ERROR_MESSAGE_INPUT_NOVALIDEMAIL'],\n            $validator->getErrors()\n        );\n    }\n\n    public function testValidEmailValidation(): void\n    {\n        $validator = $this->getContactFormEmailValidator();\n\n        $validEmailField = new FormField();\n        $validEmailField->setName('email');\n        $validEmailField->setValue('someemail@validEmailsClub.com');\n\n        $form = new Form();\n        $form->add($validEmailField);\n\n        $this->assertTrue(\n            $validator->isValid($form)\n        );\n    }\n\n    public function testEmptyEmailIsNotValidIfEmailIsRequired(): void\n    {\n        $validator = $this->getContactFormEmailValidator();\n\n        $emailField = new FormField();\n        $emailField\n            ->setName('email')\n            ->setValue('')\n            ->setIsRequired(true);\n\n        $form = new Form();\n        $form->add($emailField);\n\n        $this->assertFalse(\n            $validator->isValid($form)\n        );\n    }\n\n    public function testEmptyEmailIsValidIfEmailIsRequired(): void\n    {\n        $validator = $this->getContactFormEmailValidator();\n\n        $emailField = new FormField();\n        $emailField\n            ->setName('email')\n            ->setValue('');\n\n        $form = new Form();\n        $form->add($emailField);\n\n        $this->assertTrue(\n            $validator->isValid($form)\n        );\n    }\n\n    private function getContactFormEmailValidator(): ContactFormEmailValidator\n    {\n        return new ContactFormEmailValidator(\n            new EmailValidatorService()\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Contact/Form/ContactFormFactoryTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Contact\\Form;\n\nuse PHPUnit\\Framework\\TestCase;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form\\ContactFormEmailValidator;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form\\ContactFormFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Email\\EmailValidatorService;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormField;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\RequiredFieldsValidator;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FieldConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FormConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FormConfigurationInterface;\n\nfinal class ContactFormFactoryTest extends TestCase\n{\n    public function testFormGetter(): void\n    {\n        $formConfiguration = new FormConfiguration();\n\n        $contactFormFactory = $this->getContactFormFactory($formConfiguration);\n\n        $this->assertInstanceOf(\n            FormInterface::class,\n            $contactFormFactory->getForm()\n        );\n    }\n\n    public function testFromConfigurationHandling(): void\n    {\n        $emailField =  new FormField();\n        $emailField\n            ->setName('email')\n            ->setLabel('EMAIL');\n\n        $firstNameField = new FormField();\n        $firstNameField->setName('firstName');\n\n        $lastNameField = new FormField();\n        $lastNameField\n            ->setName('lastName')\n            ->setIsRequired(true);\n\n        $emailConfiguration = new FieldConfiguration();\n        $emailConfiguration\n            ->setName('email')\n            ->setLabel('EMAIL');\n\n        $firstNameConfiguration = new FieldConfiguration();\n        $firstNameConfiguration\n            ->setName('firstName');\n\n        $lastNameConfiguration = new FieldConfiguration();\n        $lastNameConfiguration\n            ->setName('lastName')\n            ->setIsRequired(true);\n\n        $formConfiguration = new FormConfiguration();\n        $formConfiguration\n            ->addFieldConfiguration($emailConfiguration)\n            ->addFieldConfiguration($firstNameConfiguration)\n            ->addFieldConfiguration($lastNameConfiguration);\n\n        $contactFormFactory = $this->getContactFormFactory($formConfiguration);\n        $form = $contactFormFactory->getForm();\n\n        $this->assertEquals(\n            [\n                'email'     => $emailField,\n                'firstName' => $firstNameField,\n                'lastName'  => $lastNameField,\n            ],\n            $form->getFields()\n        );\n    }\n\n    private function getContactFormFactory(FormConfigurationInterface $formConfiguration): ContactFormFactory\n    {\n        $emailValidatorService = $this->createStub(EmailValidatorService::class);\n\n        return new ContactFormFactory(\n            $formConfiguration,\n            new RequiredFieldsValidator(),\n            new ContactFormEmailValidator($emailValidatorService)\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Contact/Form/ContactFormMessageBuilderTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Contact\\Form;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Contact\\Form\\ContactFormMessageBuilder;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\Form;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormField;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\ShopAdapterInterface;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ContactFormMessageBuilderTest extends TestCase\n{\n    #[DataProvider('fieldsProvider')]\n    public function testContentGetter(string $name, string $value): void\n    {\n        $form = $this->getContactForm();\n        $form->handleRequest([$name => $value]);\n\n        $shopAdapter = $this->createStub(ShopAdapterInterface::class);\n        $shopAdapter\n            ->method('translateString')\n            ->willReturnCallback(function ($arg) {\n                return $arg;\n            });\n        $contactFormMessageBuilder = new ContactFormMessageBuilder($shopAdapter);\n\n        $this->assertStringContainsString(\n            $value,\n            $contactFormMessageBuilder->getContent($form)\n        );\n    }\n\n    public static function fieldsProvider(): array\n    {\n        return [\n            [\n                'email',\n                'marina.ginesta@bcn.cat'\n            ],\n            [\n                'firstName',\n                'Marina'\n            ],\n            [\n                'lastName',\n                'Ginestà'\n            ],\n            [\n                'salutation',\n                'MRS'\n            ],\n            [\n                'message',\n                'I\\'m standing on the rooftop'\n            ],\n        ];\n    }\n\n    private function getContactForm(): Form\n    {\n        $form = new Form();\n\n        $fieldNames = [\n            'email',\n            'firstName',\n            'lastName',\n            'salutation',\n            'message',\n        ];\n\n        foreach ($fieldNames as $fieldName) {\n            $field = new FormField();\n            $field->setName($fieldName);\n            $form->add($field);\n        }\n\n        return $form;\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Email/EmailValidatorServiceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Email\\Email;\n\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Email\\EmailValidatorService;\nuse PHPUnit\\Framework\\TestCase;\n\n#[CoversClass(EmailValidatorService::class)]\nfinal class EmailValidatorServiceTest extends TestCase\n{\n    public static function providerEmailsToValidate(): array\n    {\n        return [\n            ['mathias.krieck@oxid-esales.com', true],\n            ['mytest@com.org', true],\n            ['my+test@com.org', true],\n            ['mytest@oxid-esales.museum', true],\n            ['?mathias.krieck@oxid-esales.com', true],\n            ['my/test@com.org', true],\n            ['mytest@-com.org', false],\n            ['@com.org', false],\n            ['mytestcom.org', false],\n            ['foo.bar@-.-,-,-.oxid-esales.com', false],\n            ['mytest@com', false],\n            ['info@�vyturys.lt', false],\n        ];\n    }\n\n    #[DataProvider('providerEmailsToValidate')]\n    public function testValidateEmailWithValidEmail(string $email, bool $validMail): void\n    {\n        $mailValidator = new EmailValidatorService();\n        $result = $mailValidator->isEmailValid($email);\n        if ($validMail) {\n            $this->assertTrue(\n                $result,\n                'Mail ' . $email . ' validation failed. This mail is valid so should validate.'\n            );\n        } else {\n            $this->assertFalse(\n                $result,\n                'Mail ' . $email . ' was valid. Should not be valid.'\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Media/DataObject/MediaPathTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Media\\DataObject;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class MediaPathTest extends TestCase\n{\n    public function testConstructWithEmptyPathWillThrowException(): void\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n\n        new MediaPath('');\n    }\n\n    public function testConstructWithInvalidCharactersWillThrowException(): void\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n\n        new MediaPath('invalid:path.jpg');\n    }\n\n    public function testConstructWithAbsolutePathWillThrowException(): void\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n\n        new MediaPath('/absolute/path.jpg');\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Media/DataObject/MediaTypeTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Media\\DataObject;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaType;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class MediaTypeTest extends TestCase\n{\n    public function testValidMimeType(): void\n    {\n        $mediaType = new MediaType('image/png');\n\n        $this->assertEquals('image/png', (string)$mediaType);\n    }\n\n    public function testValidMimeTypeWithDotAndPlus(): void\n    {\n        $mediaType = new MediaType('application/vnd.ms-excel.sheet.macroEnabled.12');\n        $this->assertEquals('application/vnd.ms-excel.sheet.macroEnabled.12', (string)$mediaType);\n\n        $mediaType = new MediaType('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');\n        $this->assertEquals('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', (string)$mediaType);\n\n        $mediaType = new MediaType('application/json+zip');\n        $this->assertEquals('application/json+zip', (string)$mediaType);\n    }\n\n    public function testInvalidMimeTypeThrowsException(): void\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n        new MediaType('invalidtype');\n    }\n\n    public function testEmptyMimeTypeIsAccepted(): void\n    {\n        $mediaType = new MediaType('');\n\n        $this->assertEquals('', (string)$mediaType);\n    }\n\n    public function testMimeTypeWithSpaceThrowsException(): void\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n        new MediaType('image/ png');\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Media/MediaUrlGeneratorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Media;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaType;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\MediaUrlGenerator;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Dao\\ShopConfigurationSettingDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\DataObject\\ShopConfigurationSetting;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\ContextStub;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class MediaUrlGeneratorTest extends TestCase\n{\n    private ContextInterface $context;\n    private ShopConfigurationSettingDaoInterface $configDao;\n    private MediaUrlGenerator $urlGenerator;\n\n    protected function setUp(): void\n    {\n        $this->context = new ContextStub();\n        $this->configDao = $this->createStub(ShopConfigurationSettingDaoInterface::class);\n\n        $this->context->setShopBaseUrl('https://shop.example.com/');\n\n        $qualitySetting = $this->createStub(ShopConfigurationSetting::class);\n        $qualitySetting->method('getValue')->willReturn('75');\n\n        $this->configDao->method('get')\n            ->willReturn($qualitySetting);\n    }\n\n    public function testGeneratesUrlForLocalStorage(): void\n    {\n        $this->urlGenerator = new MediaUrlGenerator($this->context, $this->configDao);\n\n        $media = new Media(\n            Id::generate(),\n            new MediaPath('out/pictures/media/products/1001/product.jpg'),\n            new MediaType('image/jpeg')\n        );\n\n        $result = $this->urlGenerator->generateSizedImageUrl($media, '300*200');\n\n        $this->assertEquals(\n            'https://shop.example.com/out/pictures/generated/media/products/1001/300_200_75/product.jpg',\n            $result\n        );\n    }\n\n    public function testGeneratesUrlForCdnStorage(): void\n    {\n        $this->urlGenerator = new MediaUrlGenerator(\n            $this->context,\n            $this->configDao,\n            'https://cdn.example.com/'\n        );\n\n        $media = new Media(\n            Id::generate(),\n            new MediaPath('out/pictures/media/products/1001/product.jpg'),\n            new MediaType('image/jpeg')\n        );\n\n        $result = $this->urlGenerator->generateSizedImageUrl($media, '300*200');\n\n        $this->assertEquals(\n            'https://cdn.example.com/generated/media/products/1001/300_200_75/product.jpg',\n            $result\n        );\n    }\n\n    public function testHandlesDifferentImageSizes(): void\n    {\n        $this->urlGenerator = new MediaUrlGenerator($this->context, $this->configDao);\n\n        $media = new Media(\n            Id::generate(),\n            new MediaPath('out/pictures/media/products/1001/test.png'),\n            new MediaType('image/png')\n        );\n\n        $thumbnail = $this->urlGenerator->generateSizedImageUrl($media, '87*87');\n        $detail = $this->urlGenerator->generateSizedImageUrl($media, '600*600');\n\n        $this->assertEquals(\n            'https://shop.example.com/out/pictures/generated/media/products/1001/87_87_75/test.png',\n            $thumbnail\n        );\n        $this->assertEquals(\n            'https://shop.example.com/out/pictures/generated/media/products/1001/600_600_75/test.png',\n            $detail\n        );\n    }\n\n    public function testGeneratesUrlWithEncodedFilename(): void\n    {\n        $this->urlGenerator = new MediaUrlGenerator($this->context, $this->configDao);\n\n        $media = new Media(\n            Id::generate(),\n            new MediaPath('out/pictures/media/products/1001/Foto 1+#.jpg'),\n            new MediaType('image/jpeg')\n        );\n\n        $result = $this->urlGenerator->generateSizedImageUrl($media, '300*200');\n\n        $this->assertEquals(\n            'https://shop.example.com/out/pictures/generated/media/products/1001/300_200_75/Foto%201%2B%23.jpg',\n            $result\n        );\n    }\n\n    public function testUsesConfiguredImageQuality(): void\n    {\n        $qualitySetting = $this->createStub(ShopConfigurationSetting::class);\n        $qualitySetting->method('getValue')->willReturn('95');\n\n        $configDao = $this->createStub(ShopConfigurationSettingDaoInterface::class);\n        $configDao->method('get')\n            ->willReturn($qualitySetting);\n\n        $this->urlGenerator = new MediaUrlGenerator($this->context, $configDao);\n\n        $media = new Media(\n            Id::generate(),\n            new MediaPath('out/pictures/media/products/1001/hq.jpg'),\n            new MediaType('image/jpeg')\n        );\n\n        $result = $this->urlGenerator->generateSizedImageUrl($media, '400*300');\n\n        $this->assertEquals(\n            'https://shop.example.com/out/pictures/generated/media/products/1001/400_300_95/hq.jpg',\n            $result\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Media/Validator/FileExtensionConstraintValidatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Media\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\FileExtensionMismatchException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\MimeTypeGuessFailedException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\FileExtensionConstraintValidator;\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\HttpFoundation\\File\\UploadedFile;\nuse Symfony\\Component\\Mime\\MimeTypesInterface;\n\nfinal class FileExtensionConstraintValidatorTest extends TestCase\n{\n    #[DoesNotPerformAssertions]\n    public function testValidateDoesNotThrowWhenFileExtensionMatchesDetectedMimeType(): void\n    {\n        $mimeTypes = $this->createStub(MimeTypesInterface::class);\n        $validator = new FileExtensionConstraintValidator($mimeTypes);\n\n        $uploadedFile = $this->createStub(UploadedFile::class);\n        $uploadedFile->method('getPathname')->willReturn('/tmp/file.jpg');\n        $uploadedFile->method('getClientOriginalExtension')->willReturn('JPEG');\n        $mimeTypes->method('guessMimeType')->willReturn('image/jpeg');\n        $mimeTypes->method('getExtensions')->willReturn(['jpg', 'jpeg']);\n\n        $validator->validate($uploadedFile);\n    }\n\n    public function testValidateThrowsWhenFileExtensionIsNotAllowedForMimeType(): void\n    {\n        $mimeTypes = $this->createStub(MimeTypesInterface::class);\n        $validator = new FileExtensionConstraintValidator($mimeTypes);\n\n        $uploadedFile = $this->createStub(UploadedFile::class);\n        $uploadedFile->method('getPathname')->willReturn('/tmp/file.png');\n        $uploadedFile->method('getClientOriginalExtension')->willReturn('png');\n        $mimeTypes->method('guessMimeType')->willReturn('image/jpeg');\n        $mimeTypes->method('getExtensions')->willReturn(['JPG']);\n\n        $this->expectException(FileExtensionMismatchException::class);\n        $validator->validate($uploadedFile);\n    }\n\n    public function testValidateThrowsWhenNoValidExtensionsCanBeResolved(): void\n    {\n        $mimeTypes = $this->createStub(MimeTypesInterface::class);\n        $validator = new FileExtensionConstraintValidator($mimeTypes);\n\n        $uploadedFile = $this->createStub(UploadedFile::class);\n        $uploadedFile->method('getPathname')->willReturn('/tmp/file.jpg');\n        $uploadedFile->method('getClientOriginalExtension')->willReturn('jpg');\n        $mimeTypes->method('guessMimeType')->willReturn('image/jpeg');\n        $mimeTypes->method('getExtensions')->willReturn([]);\n\n        $this->expectException(FileExtensionMismatchException::class);\n        $validator->validate($uploadedFile);\n    }\n\n    public function testValidateThrowsWhenMimeTypeCannotBeGuessed(): void\n    {\n        $mimeTypes = $this->createStub(MimeTypesInterface::class);\n        $validator = new FileExtensionConstraintValidator($mimeTypes);\n\n        $uploadedFile = $this->createStub(UploadedFile::class);\n        $uploadedFile->method('getPathname')->willReturn('/tmp/file.jpg');\n        $uploadedFile->method('getClientOriginalExtension')->willReturn('jpg');\n        $mimeTypes->method('guessMimeType')->willReturn(null);\n\n        $this->expectException(MimeTypeGuessFailedException::class);\n        $validator->validate($uploadedFile);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Media/Validator/FileSizeConstraintValidatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Media\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\FileSizeTooLargeException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\FileSizeTooSmallException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\FileSizeConstraintValidator;\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\HttpFoundation\\File\\UploadedFile;\n\nfinal class FileSizeConstraintValidatorTest extends TestCase\n{\n    #[DoesNotPerformAssertions]\n    public function testValidateDoesNotThrowAtMinAndMaxBoundaries(): void\n    {\n        $validator = new FileSizeConstraintValidator(minSizeKb: 1, maxSizeKb: 2);\n\n        $minFile = $this->createStub(UploadedFile::class);\n        $minFile->method('getSize')->willReturn(1024);\n        $validator->validate($minFile);\n\n        $maxFile = $this->createStub(UploadedFile::class);\n        $maxFile->method('getSize')->willReturn(2048);\n        $validator->validate($maxFile);\n    }\n\n    public function testValidateThrowsWhenFileTooSmall(): void\n    {\n        $validator = new FileSizeConstraintValidator(minSizeKb: 1, maxSizeKb: 10);\n        $uploadedFile = $this->createStub(UploadedFile::class);\n        $uploadedFile->method('getSize')->willReturn(1023);\n\n        $this->expectException(FileSizeTooSmallException::class);\n        $validator->validate($uploadedFile);\n    }\n\n    public function testValidateThrowsWhenFileTooLarge(): void\n    {\n        $validator = new FileSizeConstraintValidator(minSizeKb: 0, maxSizeKb: 2);\n        $uploadedFile = $this->createStub(UploadedFile::class);\n        $uploadedFile->method('getSize')->willReturn(2049);\n\n        $this->expectException(FileSizeTooLargeException::class);\n        $validator->validate($uploadedFile);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Media/Validator/MimeTypeConstraintValidatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Media\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\MimeBaseTypeMismatchException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\MimeGuessMismatchException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\MimeTypeGuessFailedException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\MimeTypeConstraintValidator;\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\HttpFoundation\\File\\UploadedFile;\nuse Symfony\\Component\\Mime\\MimeTypeGuesserInterface;\n\nfinal class MimeTypeConstraintValidatorTest extends TestCase\n{\n    #[DoesNotPerformAssertions]\n    public function testValidateDoesNotThrowWhenMimeTypeMatches(): void\n    {\n        $guesser = $this->createStub(MimeTypeGuesserInterface::class);\n        $validator = new MimeTypeConstraintValidator('image/', $guesser);\n\n        $uploadedFile = $this->createStub(UploadedFile::class);\n        $uploadedFile->method('getPathname')->willReturn('/tmp/valid.jpg');\n        $uploadedFile->method('getClientMimeType')->willReturn('image/jpeg');\n        $guesser->method('guessMimeType')->willReturn('image/jpeg');\n\n        $validator->validate($uploadedFile);\n    }\n\n    public function testValidateThrowsWhenGuessedMimeBaseTypeDoesNotMatchRequiredPrefix(): void\n    {\n        $guesser = $this->createStub(MimeTypeGuesserInterface::class);\n        $validator = new MimeTypeConstraintValidator('image/', $guesser);\n\n        $uploadedFile = $this->createStub(UploadedFile::class);\n        $uploadedFile->method('getPathname')->willReturn('/tmp/file.txt');\n        $uploadedFile->method('getClientMimeType')->willReturn('text/plain');\n        $guesser->method('guessMimeType')->willReturn('text/plain');\n\n        $this->expectException(MimeBaseTypeMismatchException::class);\n        $validator->validate($uploadedFile);\n    }\n\n    public function testValidateThrowsWhenClientMimeTypeDoesNotMatchGuessedMimeType(): void\n    {\n        $guesser = $this->createStub(MimeTypeGuesserInterface::class);\n        $validator = new MimeTypeConstraintValidator('image/', $guesser);\n\n        $uploadedFile = $this->createStub(UploadedFile::class);\n        $uploadedFile->method('getPathname')->willReturn('/tmp/file.jpg');\n        $uploadedFile->method('getClientMimeType')->willReturn('image/png');\n        $guesser->method('guessMimeType')->willReturn('image/jpeg');\n\n        $this->expectException(MimeGuessMismatchException::class);\n        $validator->validate($uploadedFile);\n    }\n\n    public function testValidateThrowsWhenMimeTypeCannotBeGuessed(): void\n    {\n        $guesser = $this->createStub(MimeTypeGuesserInterface::class);\n        $validator = new MimeTypeConstraintValidator('image/', $guesser);\n\n        $uploadedFile = $this->createStub(UploadedFile::class);\n        $uploadedFile->method('getPathname')->willReturn('/tmp/file.jpg');\n        $uploadedFile->method('getClientMimeType')->willReturn('image/jpeg');\n        $guesser->method('guessMimeType')->willReturn(null);\n\n        $this->expectException(MimeTypeGuessFailedException::class);\n        $validator->validate($uploadedFile);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Media/Validator/UploadValidityConstraintValidatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Media\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\Exception\\UploadInvalidException;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\Validator\\UploadValidityConstraintValidator;\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\HttpFoundation\\File\\UploadedFile;\n\nfinal class UploadValidityConstraintValidatorTest extends TestCase\n{\n    #[DoesNotPerformAssertions]\n    public function testValidateDoesNotThrowWhenUploadIsValid(): void\n    {\n        $validator = new UploadValidityConstraintValidator();\n        $uploadedFile = $this->createStub(UploadedFile::class);\n        $uploadedFile->method('isValid')->willReturn(true);\n\n        $validator->validate($uploadedFile);\n    }\n\n    public function testValidateThrowsWhenUploadHasError(): void\n    {\n        $validator = new UploadValidityConstraintValidator();\n        $uploadedFile = $this->createStub(UploadedFile::class);\n        $uploadedFile->method('isValid')->willReturn(false);\n        $uploadedFile->method('getError')->willReturn(\\UPLOAD_ERR_NO_FILE);\n\n        $this->expectException(UploadInvalidException::class);\n        $validator->validate($uploadedFile);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Product/Media/DataMapper/DataMapperTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Product\\Media\\DataMapper;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataMapper\\DataMapper as MediaDataMapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\Media;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaPath;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Media\\DataObject\\MediaType;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataMapper\\DataMapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMedia;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRole;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRoleSet;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class DataMapperTest extends TestCase\n{\n    public function testToData(): void\n    {\n        $productMedia = new ProductMedia(\n            Id::fromString('pm789'),\n            Id::fromString('product456'),\n            new Media(\n                Id::fromString('media123'),\n                new MediaPath('path/to/file.jpg'),\n                new MediaType('image/jpeg')\n            ),\n            new ProductMediaRoleSet(\n                ProductMediaRole::from(ProductMediaRole::ICON),\n                ProductMediaRole::from('custom')\n            ),\n        );\n        $productMedia->setPosition(5);\n        $data = (new DataMapper(new MediaDataMapper()))->toData($productMedia);\n\n        $this->assertEquals(\n            'pm789',\n            $data['id']\n        );\n        $this->assertEquals(\n            'product456',\n            $data['product_id']\n        );\n        $this->assertEquals(\n            'media123',\n            $data['media_id']\n        );\n        $this->assertEquals(\n            5,\n            $data['position']\n        );\n        $this->assertEquals(\n            [\n                'icon',\n                'custom'\n            ],\n            $data['roles']\n        );\n        $this->assertTrue($data['active']);\n    }\n\n    public function testFromData(): void\n    {\n        $result = (new DataMapper(new MediaDataMapper()))->fromData([\n            'id' => 'pm789',\n            'product_id' => 'product456',\n            'media_id' => 'media123',\n            'media_path' => 'path/to/file.jpg',\n            'media_mime_type' => 'image/jpeg',\n            'position' => 5,\n            'roles' => 'icon,custom,thumbnail,detail',\n            'active' => true,\n        ]);\n\n        $this->assertEquals(\n            'pm789',\n            (string)$result->getId()\n        );\n        $this->assertEquals(\n            'product456',\n            (string)$result->getProductId()\n        );\n        $this->assertEquals(\n            5,\n            $result->getPosition()\n        );\n        $this->assertTrue($result->getRoleSet()->has(ProductMediaRole::from(ProductMediaRole::ICON)));\n        $this->assertTrue($result->getRoleSet()->has(ProductMediaRole::from(ProductMediaRole::THUMBNAIL)));\n        $this->assertTrue($result->getRoleSet()->has(ProductMediaRole::from(ProductMediaRole::DETAIL)));\n        $this->assertTrue($result->isActive());\n        $this->assertEquals(\n            'media123',\n            (string)$result\n                ->getMedia()\n                ->getId()\n        );\n        $this->assertEquals(\n            'path/to/file.jpg',\n            (string) $result\n                ->getMedia()\n                ->getMediaPath()\n        );\n        $this->assertEquals(\n            'image/jpeg',\n            (string) $result\n                ->getMedia()\n                ->getMediaType()\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Product/Media/DataObject/ProductMediaRoleSetTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Product\\Media\\DataObject;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRole;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRoleSet;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ProductMediaRoleSetTest extends TestCase\n{\n    public function testAddRoleAddsNewRole(): void\n    {\n        $roleSet = new ProductMediaRoleSet(ProductMediaRole::from(ProductMediaRole::ICON));\n        $roleSet->addRole(ProductMediaRole::from(ProductMediaRole::THUMBNAIL));\n\n        $this->assertCount(2, $roleSet->getRoles());\n        $this->assertTrue($roleSet->has(ProductMediaRole::from(ProductMediaRole::ICON)));\n        $this->assertTrue($roleSet->has(ProductMediaRole::from(ProductMediaRole::THUMBNAIL)));\n    }\n\n    public function testAddRoleDoesNotAddDuplicate(): void\n    {\n        $roleSet = new ProductMediaRoleSet(ProductMediaRole::from(ProductMediaRole::ICON));\n        $roleSet->addRole(ProductMediaRole::from(ProductMediaRole::ICON));\n\n        $this->assertCount(1, $roleSet->getRoles());\n    }\n\n    public function testRemoveRole(): void\n    {\n        $roleSet = new ProductMediaRoleSet(\n            ProductMediaRole::from(ProductMediaRole::ICON),\n            ProductMediaRole::from(ProductMediaRole::THUMBNAIL),\n        );\n        $roleSet->removeRole(ProductMediaRole::from(ProductMediaRole::ICON));\n\n        $this->assertCount(1, $roleSet->getRoles());\n        $this->assertFalse($roleSet->has(ProductMediaRole::from(ProductMediaRole::ICON)));\n        $this->assertTrue($roleSet->has(ProductMediaRole::from(ProductMediaRole::THUMBNAIL)));\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Product/Media/DataObject/ProductMediaRoleTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Product\\Media\\DataObject;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\DataObject\\ProductMediaRole;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Media\\Exception\\EmptyProductMediaRoleException;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ProductMediaRoleTest extends TestCase\n{\n    public function testFromWithDifferentValues(): void\n    {\n        $this->assertNotEquals(\n            ProductMediaRole::from(ProductMediaRole::ICON)->value(),\n            ProductMediaRole::from(ProductMediaRole::THUMBNAIL)->value()\n        );\n    }\n\n    public function testFromWithSameValues(): void\n    {\n        $this->assertEquals(\n            ProductMediaRole::from(ProductMediaRole::DETAIL)->value(),\n            ProductMediaRole::from(ProductMediaRole::DETAIL)->value(),\n        );\n    }\n\n    public function testFromWithEmptyStringThrowsException(): void\n    {\n        $this->expectException(EmptyProductMediaRoleException::class);\n\n        ProductMediaRole::from('');\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Product/Search/Event/AfterProductSearchEventTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Product\\Search\\Event;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search\\Event\\AfterProductSearchEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search\\ProductSearchCriteria;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search\\ProductSearchResult;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Search\\Pagination;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Search\\SearchTerm;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class AfterProductSearchEventTest extends TestCase\n{\n    public function testGetSearchResult(): void\n    {\n        $searchCriteria = new ProductSearchCriteria(new Pagination(10, 0), new SearchTerm('test'));\n        $result = new ProductSearchResult([], 0);\n        $event = new AfterProductSearchEvent($searchCriteria, [], $result);\n\n        $this->assertSame($result, $event->getSearchResult());\n    }\n\n    public function testGetSearchCriteria(): void\n    {\n        $searchCriteria = new ProductSearchCriteria(new Pagination(10, 0), new SearchTerm('test'));\n        $result = new ProductSearchResult([], 0);\n        $event = new AfterProductSearchEvent($searchCriteria, [], $result);\n\n        $this->assertSame($searchCriteria, $event->getSearchCriteria());\n    }\n\n    public function testGetContext(): void\n    {\n        $searchCriteria = new ProductSearchCriteria(new Pagination(10, 0), new SearchTerm('test'));\n        $result = new ProductSearchResult([], 0);\n        $context = ['locale' => 'de_DE'];\n        $event = new AfterProductSearchEvent($searchCriteria, $context, $result);\n\n        $this->assertSame($context, $event->getContext());\n    }\n\n    public function testGetSearchResultWithContext(): void\n    {\n        $searchCriteria = new ProductSearchCriteria(new Pagination(10, 0), new SearchTerm('test'));\n        $result = new ProductSearchResult([], 0);\n        $event = new AfterProductSearchEvent($searchCriteria, [], $result);\n\n        $this->assertSame($result, $event->getSearchResult());\n    }\n\n    public function testSetSearchResultReplacesResult(): void\n    {\n        $searchCriteria = new ProductSearchCriteria(new Pagination(10, 0), new SearchTerm('test'));\n        $result = new ProductSearchResult([], 0);\n        $newResult = new ProductSearchResult([], 5);\n        $event = new AfterProductSearchEvent($searchCriteria, [], $result);\n\n        $event->setSearchResult($newResult);\n\n        $this->assertSame($newResult, $event->getSearchResult());\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Product/Search/Event/BeforeProductSearchEventTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Product\\Search\\Event;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search\\Event\\BeforeProductSearchEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Product\\Search\\ProductSearchCriteria;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Search\\Pagination;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Search\\SearchTerm;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class BeforeProductSearchEventTest extends TestCase\n{\n    public function testGetSearchCriteria(): void\n    {\n        $searchCriteria = new ProductSearchCriteria(new Pagination(10, 0), new SearchTerm('test'));\n        $event = new BeforeProductSearchEvent($searchCriteria);\n\n        $this->assertSame($searchCriteria, $event->getSearchCriteria());\n    }\n\n    public function testSetSearchCriteriaReplacesCriteria(): void\n    {\n        $searchCriteria = new ProductSearchCriteria(new Pagination(10, 0), new SearchTerm('test'));\n        $newSearchCriteria = new ProductSearchCriteria(new Pagination(5, 0), new SearchTerm('other'));\n        $event = new BeforeProductSearchEvent($searchCriteria);\n\n        $event->setSearchCriteria($newSearchCriteria);\n\n        $this->assertSame($newSearchCriteria, $event->getSearchCriteria());\n    }\n\n    public function testGetContextReturnsEmptyByDefault(): void\n    {\n        $searchCriteria = new ProductSearchCriteria(new Pagination(10, 0), new SearchTerm('test'));\n        $event = new BeforeProductSearchEvent($searchCriteria);\n\n        $this->assertSame([], $event->getContext());\n    }\n\n    public function testSetContextReplacesContext(): void\n    {\n        $searchCriteria = new ProductSearchCriteria(new Pagination(10, 0), new SearchTerm('test'));\n        $event = new BeforeProductSearchEvent($searchCriteria);\n\n        $event->setContext(['shop' => 1]);\n\n        $this->assertSame(['shop' => 1], $event->getContext());\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Review/Dao/ProductRatingDaoTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Review\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Dao\\ProductRatingDao;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataMapper\\ProductRatingDataMapperInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Dao\\InvalidObjectIdDaoException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\QueryBuilderFactoryInterface;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ProductRatingDaoTest extends TestCase\n{\n    #[DataProvider('invalidProductIdsProvider')]\n    public function testGetProductByIdWithInvalidId(bool|int|string|null $invalidProductId): void\n    {\n        $this->expectException(InvalidObjectIdDaoException::class);\n        $queryBuilderFactory = $this->createStub(QueryBuilderFactoryInterface::class);\n        $mapper = $this->createStub(ProductRatingDataMapperInterface::class);\n\n        $productRatingDao = new ProductRatingDao(\n            $queryBuilderFactory,\n            $mapper\n        );\n\n        $this->expectException(InvalidObjectIdDaoException::class);\n\n        $productRatingDao->getProductRatingById($invalidProductId);\n    }\n\n    public static function invalidProductIdsProvider(): array\n    {\n        return [\n            [null],\n            [false],\n            [true],\n            [5],\n            [''],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Review/DataMapper/ProductRatingDataMapperTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Review\\DataMapper;\n\nuse PHPUnit\\Framework\\TestCase;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataMapper\\ProductRatingDataMapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\ProductRating;\n\nfinal class ProductRatingDataMapperTest extends TestCase\n{\n    public function testMapping(): void\n    {\n        $mapper = new ProductRatingDataMapper();\n\n        $mappedProductRating = $this->getMappedProductRating();\n        $dataForMapping = $mapper->getData($mappedProductRating);\n\n        $productRating = new ProductRating();\n        $productRatingAfterMapping = $mapper->map($productRating, $dataForMapping);\n\n        $this->assertEquals(\n            $mappedProductRating,\n            $productRatingAfterMapping\n        );\n    }\n\n    public function testPrimaryKeyGetter(): void\n    {\n        $mapper = new ProductRatingDataMapper();\n        $mappedProductRating = $this->getMappedProductRating();\n\n        $expectedPrimaryKey = [\n            'OXID' => 'testId',\n        ];\n\n        $this->assertEquals(\n            $expectedPrimaryKey,\n            $mapper->getPrimaryKey($mappedProductRating)\n        );\n    }\n\n    private function getMappedProductRating(): ProductRating\n    {\n        $productRating = new ProductRating();\n        $productRating\n            ->setProductId('testId')\n            ->setRatingCount(7)\n            ->setRatingAverage(6.7);\n\n        return $productRating;\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Review/DataMapper/RatingDataMapperTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Review\\DataMapper;\n\nuse PHPUnit\\Framework\\TestCase;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataMapper\\RatingDataMapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\Rating;\n\nfinal class RatingDataMapperTest extends TestCase\n{\n    public function testMapping(): void\n    {\n        $mapper = new RatingDataMapper();\n\n        $mappedRating = $this->getMappedRating();\n        $dataForMapping = $mapper->getData($mappedRating);\n\n        $rating = new Rating();\n        $ratingAfterMapping = $mapper->map($rating, $dataForMapping);\n\n        $this->assertEquals(\n            $mappedRating,\n            $ratingAfterMapping\n        );\n    }\n\n    public function testPrimaryKeyGetter(): void\n    {\n        $mapper = new RatingDataMapper();\n        $mappedRating = $this->getMappedRating();\n\n        $expectedPrimaryKey = [\n            'OXID' => 'testId',\n        ];\n\n        $this->assertEquals(\n            $expectedPrimaryKey,\n            $mapper->getPrimaryKey($mappedRating)\n        );\n    }\n\n    private function getMappedRating(): Rating\n    {\n        $rating = new Rating();\n        $rating\n            ->setId('testId')\n            ->setRating(5)\n            ->setUserId('userId')\n            ->setObjectId('objectId')\n            ->setType('product')\n            ->setCreatedAt('time');\n\n        return $rating;\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Review/DataMapper/ReviewDataMapperTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Review\\DataMapper;\n\nuse PHPUnit\\Framework\\TestCase;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataMapper\\ReviewDataMapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\Review;\n\nfinal class ReviewDataMapperTest extends TestCase\n{\n    public function testMapping(): void\n    {\n        $mapper = new ReviewDataMapper();\n\n        $mappedReview = $this->getMappedReview();\n        $dataForMapping = $mapper->getData($mappedReview);\n\n        $review = new Review();\n        $reviewAfterMapping = $mapper->map($review, $dataForMapping);\n\n        $this->assertEquals(\n            $mappedReview,\n            $reviewAfterMapping\n        );\n    }\n\n    public function testPrimaryKeyGetter(): void\n    {\n        $mapper = new ReviewDataMapper();\n        $mappedReview = $this->getMappedReview();\n\n        $expectedPrimaryKey = [\n            'OXID' => 'testId',\n        ];\n\n        $this->assertEquals(\n            $expectedPrimaryKey,\n            $mapper->getPrimaryKey($mappedReview)\n        );\n    }\n\n    private function getMappedReview(): Review\n    {\n        $review = new Review();\n        $review\n            ->setId('testId')\n            ->setText('so so')\n            ->setRating(3)\n            ->setUserId('userId')\n            ->setObjectId('objectId')\n            ->setType('product')\n            ->setCreatedAt('time');\n\n        return $review;\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Review/Service/RatingCalculatorServiceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Review\\Service;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\Rating;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\RatingCalculatorService;\n\nfinal class RatingCalculatorServiceTest extends TestCase\n{\n    public function testGetAverage(): void\n    {\n        $ratingCalculatorService = new RatingCalculatorService();\n\n        $ratings = new ArrayCollection([\n            $this->createRating(5),\n            $this->createRating(3),\n            $this->createRating(3),\n            $this->createRating(2),\n        ]);\n\n        $average = $ratingCalculatorService->getAverage($ratings);\n\n        $this->assertEquals(\n            3.25,\n            $average\n        );\n    }\n\n    public function testGetAverageForNoRating(): void\n    {\n        $ratingCalculatorService = new RatingCalculatorService();\n\n        $ratings = new ArrayCollection([]);\n\n        $average = $ratingCalculatorService->getAverage($ratings);\n\n        $this->assertEquals(\n            0,\n            $average\n        );\n    }\n\n    private function createRating(int $ratingValue): Rating\n    {\n        $rating = new Rating();\n        $rating->setRating($ratingValue);\n\n        return $rating;\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Review/Service/ReviewAndRatingMergingServiceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Review\\Service;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\Rating;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\DataObject\\Review;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\ReviewAndRatingMergingService;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\ViewDataObject\\ReviewAndRating;\n\nfinal class ReviewAndRatingMergingServiceTest extends TestCase\n{\n    public function testMergingReviewWithRatingAndRatingWithReview(): void\n    {\n        $reviewAndRatingMergingService = new ReviewAndRatingMergingService();\n\n        $reviews = new ArrayCollection([\n            $this->getReviewWithRating(),\n        ]);\n\n        $ratings = new ArrayCollection([\n            $this->getRatingWithReview(),\n        ]);\n\n        $reviewAndRatingList = $reviewAndRatingMergingService->mergeReviewAndRating(\n            $reviews,\n            $ratings\n        );\n\n        $expectedReviewAndRatingList = new ArrayCollection([\n            $this->getReviewAndRatingViewObjectWithReviewAndWithRating(),\n        ]);\n\n        $this->assertEquals(\n            $expectedReviewAndRatingList,\n            $reviewAndRatingList\n        );\n    }\n\n    public function testMergingReviewWithoutRatingAndRatingWithoutReview(): void\n    {\n        $reviewAndRatingMergingService = new ReviewAndRatingMergingService();\n\n        $reviews = new ArrayCollection([\n            $this->getReviewWithoutRating(),\n        ]);\n\n        $ratings = new ArrayCollection([\n            $this->getRatingWithoutReview(),\n        ]);\n\n        $reviewAndRatingList = $reviewAndRatingMergingService->mergeReviewAndRating(\n            $reviews,\n            $ratings\n        );\n\n        $expectedReviewAndRatingList = new ArrayCollection([\n            $this->getReviewAndRatingViewObjectWithReviewAndWithoutRating(),\n            $this->getReviewAndRatingViewObjectWithoutReviewAndWithRating(),\n        ]);\n\n        $this->assertEquals(\n            $expectedReviewAndRatingList,\n            $reviewAndRatingList\n        );\n    }\n\n    private function getReviewWithRating(): Review\n    {\n        $review = new Review();\n        $review\n            ->setId('reviewId1')\n            ->setRating(5)\n            ->setObjectId('1')\n            ->setUserId('firstUserId')\n            ->setText('With');\n\n        return $review;\n    }\n\n    private function getReviewWithoutRating(): Review\n    {\n        $review = new Review();\n\n        $review\n            ->setId('reviewId2')\n            ->setRating(0)\n            ->setObjectId('1')\n            ->setUserId('firstUserId')\n            ->setText('Without');\n\n        return $review;\n    }\n\n    private function getRatingWithReview(): Rating\n    {\n        $rating = new Rating();\n\n        $rating\n            ->setId('ratingId1')\n            ->setRating(5)\n            ->setUserId('firstUserId')\n            ->setObjectId('1');\n\n        return $rating;\n    }\n\n    private function getRatingWithoutReview(): Rating\n    {\n        $rating = new Rating();\n\n        $rating\n            ->setId('ratingId2')\n            ->setRating(5)\n            ->setUserId('secondUserId')\n            ->setObjectId('1');\n\n        return $rating;\n    }\n\n    private function getReviewAndRatingViewObjectWithReviewAndWithRating(): ReviewAndRating\n    {\n        $reviewAndRating = new ReviewAndRating();\n        $reviewAndRating\n            ->setReviewId('reviewId1')\n            ->setRatingId('ratingId1')\n            ->setRating(5)\n            ->setObjectId('1')\n            ->setReviewText('With');\n\n        return $reviewAndRating;\n    }\n\n    private function getReviewAndRatingViewObjectWithReviewAndWithoutRating(): ReviewAndRating\n    {\n        $reviewAndRating = new ReviewAndRating();\n        $reviewAndRating\n            ->setReviewId('reviewId2')\n            ->setRatingId(false)\n            ->setRating(false)\n            ->setObjectId('1')\n            ->setReviewText('Without');\n\n        return $reviewAndRating;\n    }\n\n    private function getReviewAndRatingViewObjectWithoutReviewAndWithRating(): ReviewAndRating\n    {\n        $reviewAndRating = new ReviewAndRating();\n        $reviewAndRating\n            ->setReviewId(false)\n            ->setRatingId('ratingId2')\n            ->setRating(5)\n            ->setObjectId('1')\n            ->setReviewText(false);\n\n        return $reviewAndRating;\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Domain/Review/Service/UserReviewAndRatingServiceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Domain\\Review\\Service;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\ReviewAndRatingMergingServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\UserRatingServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\UserReviewAndRatingService;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\Service\\UserReviewServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Domain\\Review\\ViewDataObject\\ReviewAndRating;\n\nfinal class UserReviewAndRatingServiceTest extends TestCase\n{\n    public function testReviewAndRatingListSorting(): void\n    {\n        $reviewAndRatingMergingServiceMock = $this->createStub(ReviewAndRatingMergingServiceInterface::class);\n\n        $reviewAndRatingMergingServiceMock\n            ->method('mergeReviewAndRating')\n            ->willReturn($this->getUnsortedReviewAndRatingList());\n\n        $userReviewAndRatingService = new UserReviewAndRatingService(\n            $this->getUserReviewServiceStub(),\n            $this->getUserRatingServiceStub(),\n            $reviewAndRatingMergingServiceMock\n        );\n\n        $this->assertEquals(\n            $this->getSortedReviewAndRatingList(),\n            $userReviewAndRatingService->getReviewAndRatingList(1)\n        );\n    }\n\n    public function testReviewAndRatingListCount(): void\n    {\n        $reviewAndRatingMergingServiceMock = $this->createStub(ReviewAndRatingMergingServiceInterface::class);\n\n        $reviewAndRatingMergingServiceMock\n            ->method('mergeReviewAndRating')\n            ->willReturn($this->getUnsortedReviewAndRatingList());\n\n        $userReviewAndRatingService = new UserReviewAndRatingService(\n            $this->getUserReviewServiceStub(),\n            $this->getUserRatingServiceStub(),\n            $reviewAndRatingMergingServiceMock\n        );\n\n        $this->assertEquals(\n            $this->getSortedReviewAndRatingList()->count(),\n            $userReviewAndRatingService->getReviewAndRatingListCount(1)\n        );\n    }\n\n    private function getUserReviewServiceStub()\n    {\n        $userReviewService = $this->createStub(UserReviewServiceInterface::class);\n\n        $userReviewService\n            ->method('getReviews')\n            ->willReturn(new ArrayCollection());\n\n        return $userReviewService;\n    }\n\n    private function getUserRatingServiceStub()\n    {\n        $userRatingService = $this->createStub(UserRatingServiceInterface::class);\n\n        $userRatingService\n            ->method('getRatings')\n            ->willReturn(new ArrayCollection());\n\n        return $userRatingService;\n    }\n\n    private function getUnsortedReviewAndRatingList(): ArrayCollection\n    {\n        return new ArrayCollection([\n            $this->getFirstReviewAndRating(),\n            $this->getThirdReviewAndRating(),\n            $this->getSecondReviewAndRating(),\n        ]);\n    }\n\n    private function getSortedReviewAndRatingList(): ArrayCollection\n    {\n        return new ArrayCollection([\n            $this->getThirdReviewAndRating(),\n            $this->getSecondReviewAndRating(),\n            $this->getFirstReviewAndRating(),\n        ]);\n    }\n\n    private function getFirstReviewAndRating(): ReviewAndRating\n    {\n        $reviewAndRating = new ReviewAndRating();\n        $reviewAndRating->setCreatedAt('2011-02-16 15:21:20');\n\n        return $reviewAndRating;\n    }\n\n    private function getSecondReviewAndRating(): ReviewAndRating\n    {\n        $reviewAndRating = new ReviewAndRating();\n        $reviewAndRating->setCreatedAt('2017-02-16 15:21:20');\n\n        return $reviewAndRating;\n    }\n\n    private function getThirdReviewAndRating(): ReviewAndRating\n    {\n        $reviewAndRating = new ReviewAndRating();\n        $reviewAndRating->setCreatedAt('2018-02-16 15:21:20');\n\n        return $reviewAndRating;\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Api/HttpExceptionListenerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Api;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Api\\HttpExceptionListener;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\HttpFoundation\\Request;\nuse Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent;\nuse Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException;\nuse Symfony\\Component\\HttpKernel\\HttpKernelInterface;\nuse Throwable;\n\nclass HttpExceptionListenerTest extends TestCase\n{\n    public function testNotFoundHttpExceptionReturns404(): void\n    {\n        $listener = new HttpExceptionListener();\n        $event = $this->createExceptionEvent(new NotFoundHttpException('Resource not found'));\n\n        $listener->onKernelException($event);\n\n        $this->assertSame(404, $event->getResponse()->getStatusCode());\n    }\n\n    #[DataProvider('productionModeValuesProvider')]\n    public function testReturnsGenericMessageInProductionMode(string $envValue): void\n    {\n        putenv(\"OXID_DEBUG_MODE=$envValue\");\n\n        $listener = new HttpExceptionListener();\n        $event = $this->createExceptionEvent(new NotFoundHttpException('Sensitive internal path info'));\n\n        $listener->onKernelException($event);\n\n        $response = json_decode($event->getResponse()->getContent(), true);\n        $this->assertSame('Not Found', $response['error']);\n    }\n\n    public static function productionModeValuesProvider(): array\n    {\n        return [\n            'empty string' => [''],\n            'string 0' => ['0'],\n            'string false' => ['false'],\n            'string off' => ['off'],\n            'string no' => ['no'],\n            'random string' => ['random'],\n            'numeric 2' => ['2'],\n            'whitespace' => [' '],\n        ];\n    }\n\n    #[DataProvider('debugModeValuesProvider')]\n    public function testReturnsActualMessageInDebugMode(string $envValue): void\n    {\n        putenv(\"OXID_DEBUG_MODE=$envValue\");\n\n        $listener = new HttpExceptionListener();\n        $event = $this->createExceptionEvent(new NotFoundHttpException('Sensitive internal path info'));\n\n        $listener->onKernelException($event);\n\n        $response = json_decode($event->getResponse()->getContent(), true);\n        $this->assertSame('Sensitive internal path info', $response['error']);\n    }\n\n    public static function debugModeValuesProvider(): array\n    {\n        return [\n            'string 1' => ['1'],\n            'string true' => ['true'],\n            'string on' => ['on'],\n            'string yes' => ['yes'],\n            'uppercase TRUE' => ['TRUE'],\n            'mixed case True' => ['True'],\n        ];\n    }\n\n    private function createExceptionEvent(Throwable $exception): ExceptionEvent\n    {\n        $kernel = $this->createStub(HttpKernelInterface::class);\n        $request = Request::create('/api/test');\n\n        return new ExceptionEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, $exception);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Cache/Command/ClearCacheCommandTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Cache\\Command;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\Command\\ClearCacheCommand;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\ShopCacheCleanerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\Service\\ContainerCacheInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse PHPUnit\\Framework\\Attributes\\Group;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\n#[Group('cache')]\nfinal class ClearCacheCommandTest extends TestCase\n{\n    public function testClearCacheTriggersRegularAndTemplatesCleaners(): void\n    {\n        $command = new ClearCacheCommand(\n            $this->getContainerCacheMock(),\n            $this->getContextMock(),\n            $this->getShopCacheCleanerMock()\n        );\n\n        $command->run(\n            $this->createStub(InputInterface::class),\n            $this->createStub(OutputInterface::class),\n        );\n    }\n\n    private function getContainerCacheMock(): ContainerCacheInterface\n    {\n        $containerCacheMock = $this->createMock(ContainerCacheInterface::class);\n        $containerCacheMock->expects($this->once())->method('invalidate');\n\n        return $containerCacheMock;\n    }\n\n    private function getContextMock(): ContextInterface\n    {\n        $contextMock = $this->createMock(ContextInterface::class);\n        $contextMock->expects($this->once())->method('getAllShopIds')->willReturn([1]);\n\n        return $contextMock;\n    }\n\n    private function getShopCacheCleanerMock(): ShopCacheCleanerInterface\n    {\n        $shopCacheCleanerMock = $this->createMock(ShopCacheCleanerInterface ::class);\n        $shopCacheCleanerMock->expects($this->once())->method('clearAll');\n\n        return $shopCacheCleanerMock;\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Configuration/DataObject/DatabaseConfigurationTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Configuration\\DataObject;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\DataObject\\DatabaseConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\InvalidDatabaseConfigurationException;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class DatabaseConfigurationTest extends TestCase\n{\n    public function testGetParameters(): void\n    {\n        $scheme = 'mysql';\n        $driver = 'pdo_mysql';\n        $username = uniqid('user-', true);\n        $password = uniqid('secret-', true);\n        $server = uniqid('server-', true);\n        $port = 1234;\n        $database = uniqid('db-', true);\n        $encoding = 'utf8mb4';\n        $driverOptions = '\"SET @@SESSION.sql_mode=\\\"\\\"\"';\n        $url = sprintf(\n            '%s://%s:%s@%s:%d/%s?charset=%s&driverOptions[1002]=%s',\n            $scheme,\n            $username,\n            $password,\n            $server,\n            $port,\n            $database,\n            $encoding,\n            $driverOptions,\n        );\n\n        $databaseConfiguration = new DatabaseConfiguration($url);\n\n        $this->assertEquals($url, $databaseConfiguration->getDatabaseUrl());\n        $this->assertFalse($databaseConfiguration->isSocketConnection());\n        $this->assertEquals($driver, $databaseConfiguration->getDriver());\n        $this->assertEquals($username, $databaseConfiguration->getUser());\n        $this->assertEquals($password, $databaseConfiguration->getPass());\n        $this->assertEquals($server, $databaseConfiguration->getHost());\n        $this->assertEquals($port, $databaseConfiguration->getPort());\n        $this->assertEquals($database, $databaseConfiguration->getName());\n        $this->assertEquals($encoding, $databaseConfiguration->getCharset());\n        $this->assertEquals($driverOptions, $databaseConfiguration->getOptions()[1002]);\n    }\n\n    public function testGetParametersWithMinimalUrl(): void\n    {\n        $defaultPort = 3306;\n        $scheme = 'sqlite';\n        $driver = 'pdo_sqlite';\n        $username = uniqid('user-', true);\n        $server = uniqid('server-', true);\n        $url = sprintf(\n            '%s://%s@%s',\n            $scheme,\n            $username,\n            $server\n        );\n\n        $databaseConfiguration = new DatabaseConfiguration($url);\n\n        $this->assertEquals($url, $databaseConfiguration->getDatabaseUrl());\n        $this->assertFalse($databaseConfiguration->isSocketConnection());\n        $this->assertEquals($driver, $databaseConfiguration->getDriver());\n        $this->assertEquals($username, $databaseConfiguration->getUser());\n        $this->assertEmpty($databaseConfiguration->getPass());\n        $this->assertEquals($server, $databaseConfiguration->getHost());\n        $this->assertEquals($defaultPort, $databaseConfiguration->getPort());\n    }\n\n    public function testWithInvalidUrl(): void\n    {\n        $url = '123';\n\n        $this->expectException(InvalidDatabaseConfigurationException::class);\n\n        new DatabaseConfiguration($url);\n    }\n\n    public function testWithInvalidScheme(): void\n    {\n        $url = 'abc://def';\n\n        $this->expectException(InvalidDatabaseConfigurationException::class);\n\n        new DatabaseConfiguration($url);\n    }\n\n    public function testWithUnresolvableHost(): void\n    {\n        $url = 'mysql:abc';\n\n        $this->expectException(InvalidDatabaseConfigurationException::class);\n\n        new DatabaseConfiguration($url);\n    }\n\n    public function testWithUnresolvableUser(): void\n    {\n        $url = 'mysql://abc';\n\n        $databaseConfiguration = new DatabaseConfiguration($url);\n\n        $this->assertEmpty($databaseConfiguration->getUser());\n    }\n\n    public function testWithSocketConnection(): void\n    {\n        $username = uniqid('user-', true);\n        $server = 'localhost';\n        $socket = '/tmp/mysql.sock';\n        $url = sprintf(\n            'mysql://%s@%s?socket=(%s)',\n            $username,\n            $server,\n            $socket,\n        );\n\n        $databaseConfiguration = new DatabaseConfiguration($url);\n\n        $this->assertTrue($databaseConfiguration->isSocketConnection());\n        $this->assertEquals($socket, $databaseConfiguration->getSocket());\n    }\n\n    public function testWithUrlEncodedSocketConnection(): void\n    {\n        $socket = '/tmp/mysql.sock';\n        $socketEncoded = urlencode($socket);\n        $url = sprintf(\n            'mysql://root@localhost?socket=%s',\n            $socketEncoded,\n        );\n\n        $databaseConfiguration = new DatabaseConfiguration($url);\n\n        $this->assertEquals($socket, $databaseConfiguration->getSocket());\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/DIContainer/ContainerBuilderTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\DIContainer;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\DIContainer\\ContainerBuilder;\nuse OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\BasicContextStub;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ContainerBuilderTest extends TestCase\n{\n    public function testContainerParametersAreSet(): void\n    {\n        $sourcePath = uniqid('source-path-', true);\n        $contextStub = new BasicContextStub();\n        $contextStub->setSourcePath($sourcePath);\n        $containerBuilder = new ContainerBuilder($contextStub);\n        $symfonyContainerBuilder = $containerBuilder->getContainer();\n\n        $this->assertEquals(\n            $contextStub->getDefaultShopId(),\n            $symfonyContainerBuilder->getParameter('oxid_esales.current_shop_id')\n        );\n        $this->assertEquals(\n            $sourcePath,\n            $symfonyContainerBuilder->getParameter('oxid_esales.shop_source_directory')\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Database/IdTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Database;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Id;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class IdTest extends TestCase\n{\n    public function testGenerate(): void\n    {\n        $id1 = Id::generate();\n        $id2 = Id::generate();\n\n        $this->assertNotEquals($id1, $id2);\n    }\n\n    public function testFromString(): void\n    {\n        $id = 'notMd5';\n        $this->assertEquals($id, Id::fromString($id));\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Database/Logger/QueryFilterTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Database\\Logger;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Logger\\QueryLogFilter;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class QueryFilterTest extends TestCase\n{\n    public static function providerTestFiltering(): array\n    {\n        return [\n            [\n                \"select * from oxarticles\",\n                [],\n                false\n            ],\n            [\n                \"delete * from oxarticles\",\n                [],\n                true\n            ],\n            [\n                \"insert into oxarticles values ('some values')\",\n                [],\n                true\n            ],\n            [\n                \"update oxarticles set oxtitle = 'other title' where oxid = '_someid' \",\n                [],\n                true\n            ],\n            [\n                \"UPDATE oxarticles set oxtitle = 'other title' where oxid = '_someid' \",\n                [],\n                true\n            ],\n            [\n                \"update oxarticles set oxtitle = 'other title' where oxid = '_someid' \",\n                [\n                    'oxarticles'\n                ],\n                false\n            ],\n            [\n                \"yadda yadda yadda insert into blabla \",\n                [\n                    'ox'\n                ],\n                true\n            ],\n            [\n                \"yadda oxyadda yadda insert into oxblabla \",\n                [\n                    'ox'\n                ],\n                false\n            ],\n            [\n                \"yadda yadda yadda oxsession blabla \",\n                [],\n                false\n            ],\n            [\n                \"yadda yadda yadda oxcache blabla \",\n                [],\n                false\n            ],\n        ];\n    }\n\n\n    #[DataProvider('providerTestFiltering')]\n    public function testFiltering(string $query, array $skipLogTags, bool $expected): void\n    {\n        $queryFilter = new QueryLogFilter($skipLogTags);\n\n        $this->assertEquals($expected, $queryFilter->shouldLogQuery($query));\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/FileSystem/MasterImageHandlerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\FileSystem;\n\nuse org\\bovigo\\vfs\\vfsStream;\nuse org\\bovigo\\vfs\\vfsStreamDirectory;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\MasterImageHandler;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Filesystem\\Exception\\IOException;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\Filesystem\\Path;\n\nfinal class MasterImageHandlerTest extends TestCase\n{\n    /** @var vfsStreamDirectory */\n    private $rootDir;\n    private ?MasterImageHandler $imageHandler = null;\n\n    protected function setUp(): void\n    {\n        parent::setUp();\n\n        $this->createFileStructure();\n        $this->initImageHandler();\n    }\n\n    public function testCopyCreatesDirectoriesWithCorrectPermissions(): void\n    {\n        $destinationDirectory = 'dir1/dir2';\n\n        $this->imageHandler->copy(\n            Path::join($this->rootDir->url(), 'tmp/test-1.jpg'),\n            \"$destinationDirectory/test.jpg\"\n        );\n        $directoryPermissions = $this->rootDir->getChild(\"shop-source-path/$destinationDirectory\")->getPermissions();\n\n        $this->assertSame(0744, $directoryPermissions);\n    }\n\n    public function testCopyCreatesFilesWithCorrectPermissions(): void\n    {\n        $destinationFile = 'test.jpg';\n\n        $this->imageHandler->copy(\n            Path::join($this->rootDir->url(), '/tmp/test-1.jpg'),\n            $destinationFile\n        );\n        $filePermissions = $this->rootDir->getChild(\"shop-source-path/$destinationFile\")->getPermissions();\n\n        $this->assertSame(0644, $filePermissions);\n    }\n\n    public function testCopyTwiceOverwritesFile(): void\n    {\n        $destinationFile = 'test.jpg';\n\n        $this->imageHandler->copy(\n            Path::join($this->rootDir->url(), '/tmp/test-1.jpg'),\n            $destinationFile\n        );\n        $this->imageHandler->copy(\n            Path::join($this->rootDir->url(), '/tmp/test-2.jpg'),\n            $destinationFile\n        );\n        $fileContent = $this->rootDir->getChild(\"shop-source-path/$destinationFile\")->getContent();\n\n        $this->assertSame('test-content-2', $fileContent);\n    }\n\n    public function testUploadWithoutPostRequestWillThrow(): void\n    {\n        $sourceFile = '/tmp/test-1.jpg';\n\n        $this->expectException(IOException::class);\n\n        $this->imageHandler->upload(\n            Path::join($this->rootDir->url(), $sourceFile),\n            'test.jpg'\n        );\n    }\n\n    public function testRemove(): void\n    {\n        $existingFile = 'shop-file.jpg';\n        $this->assertTrue($this->rootDir->hasChild(\"shop-source-path/$existingFile\"));\n\n        $this->imageHandler->remove($existingFile);\n\n        $this->assertFalse($this->rootDir->hasChild(\"shop-source-path/$existingFile\"));\n    }\n\n    public function testExistsWithMissingFile(): void\n    {\n        $missingFile = uniqid('test_file_', true);\n\n        $exists = $this->imageHandler->exists($missingFile);\n\n        $this->assertFalse($exists);\n    }\n\n    public function testExistsWhenFileIsPresent(): void\n    {\n        $existingFile = 'shop-file.jpg';\n\n        $exists = $this->imageHandler->exists($existingFile);\n\n        $this->assertTrue($exists);\n    }\n\n    private function createFileStructure(): void\n    {\n        $directoryStructure = [\n            'tmp' => [\n                'test-1.jpg' => 'test-content-1',\n                'test-2.jpg' => 'test-content-2',\n            ],\n            'shop-source-path' => [\n                'shop-file.jpg' => 'shop-content',\n            ],\n        ];\n        $this->rootDir = vfsStream::setup('root', 0777);\n        vfsStream::create($directoryStructure, $this->rootDir);\n    }\n\n    private function initImageHandler(): void\n    {\n        $contextStub = $this->createStub(ContextInterface::class);\n        $contextStub->method('getSourcePath')->willReturn(vfsStream::url('root/shop-source-path'));\n        $this->imageHandler = new MasterImageHandler(\n            new Filesystem(),\n            $contextStub\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Form/FromTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Form;\n\nuse PHPUnit\\Framework\\TestCase;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\Form;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormField;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormValidatorInterface;\n\nfinal class FromTest extends TestCase\n{\n    public function testAddField(): void\n    {\n        $form = new Form();\n\n        $field = new FormField();\n        $field->setName('testField');\n\n        $form->add($field);\n\n        $this->assertSame($field, $form->testField);\n    }\n\n    public function testValidation(): void\n    {\n        $validator = $this->createStub(FormValidatorInterface::class);\n        $validator\n            ->method('isValid')\n            ->willReturn(false);\n\n        $validator\n            ->method('getErrors')\n            ->willReturn([]);\n\n        $form = new Form();\n        $form->addValidator($validator);\n\n        $this->assertFalse($form->isValid());\n    }\n\n    public function testValidationErrors(): void\n    {\n        $validator = $this->createStub(FormValidatorInterface::class);\n        $validator\n            ->method('isValid')\n            ->willReturn(false);\n\n        $validator\n            ->method('getErrors')\n            ->willReturn([\n                'something is wrong',\n                'alles ist kaput',\n            ]);\n\n        $anotherValidator = $this->createStub(FormValidatorInterface::class);\n        $anotherValidator\n            ->method('isValid')\n            ->willReturn(false);\n\n        $anotherValidator\n            ->method('getErrors')\n            ->willReturn([\n                'everything is wrong',\n                'etwas ist kaput',\n            ]);\n\n        $form = new Form();\n        $form->addValidator($validator);\n        $form->addValidator($anotherValidator);\n\n        $form->isValid();\n\n        $this->assertSame(\n            [\n                'something is wrong',\n                'alles ist kaput',\n                'everything is wrong',\n                'etwas ist kaput',\n            ],\n            $form->getErrors()\n        );\n    }\n\n    public function testFieldsGetter(): void\n    {\n        $form = new Form();\n\n        $field = new FormField();\n        $field->setName('testField');\n\n        $anotherField = new FormField();\n        $anotherField->setName('anotherTestField');\n\n        $form->add($field);\n        $form->add($anotherField);\n\n        $this->assertEquals(\n            $form->getFields(),\n            [\n                'testField'         => $field,\n                'anotherTestField'  => $anotherField,\n            ]\n        );\n    }\n\n    public function testRequestHandling(): void\n    {\n        $form = new Form();\n\n        $field = new FormField();\n        $field->setName('testField');\n\n        $form->add($field);\n        $form->handleRequest([\n            'testField' => 'testValue',\n        ]);\n\n        $this->assertSame(\n            'testValue',\n            $form->testField->getValue()\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Form/RequiredFieldsValidatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Form;\n\nuse PHPUnit\\Framework\\TestCase;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\Form;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\FormField;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Form\\RequiredFieldsValidator;\n\nfinal class RequiredFieldsValidatorTest extends TestCase\n{\n    public function testInvalidFormValidation(): void\n    {\n        $form = new Form();\n\n        $field = new FormField();\n        $field->setName('requiredField');\n        $field->setIsRequired(true);\n\n        $form->add($field);\n\n        $requiredFieldsValidator = new RequiredFieldsValidator();\n\n        $this->assertFalse($requiredFieldsValidator->isValid($form));\n    }\n\n    public function testValidFormValidation(): void\n    {\n        $form = new Form();\n\n        $field = new FormField();\n        $field->setName('requiredField');\n        $field->setIsRequired(true);\n        $field->setValue('123');\n\n        $form->add($field);\n\n        $requiredFieldsValidator = new RequiredFieldsValidator();\n\n        $this->assertTrue($requiredFieldsValidator->isValid($form));\n    }\n\n    public function testInvalidFormValidationErrors(): void\n    {\n        $form = new Form();\n\n        $field = new FormField();\n        $field->setName('requiredField');\n        $field->setIsRequired(true);\n\n        $form->add($field);\n\n        $requiredFieldsValidator = new RequiredFieldsValidator();\n        $requiredFieldsValidator->isValid($form);\n\n        $this->assertSame(\n            ['ERROR_MESSAGE_INPUT_NOTALLFIELDS'],\n            $requiredFieldsValidator->getErrors()\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/FormConfiguration/FromConfigurationTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\FormConfiguration;\n\nuse PHPUnit\\Framework\\TestCase;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FieldConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FormConfiguration\\FormConfiguration;\n\nfinal class FromConfigurationTest extends TestCase\n{\n    public function testAddFieldConfiguration(): void\n    {\n        $fieldConfiguration = new FieldConfiguration();\n        $fieldConfiguration->setName('testField');\n\n        $formConfiguration = new FormConfiguration();\n        $formConfiguration->addFieldConfiguration($fieldConfiguration);\n\n        $this->assertSame(\n            [$fieldConfiguration],\n            $formConfiguration->getFieldConfigurations()\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Mailing/Adapter/SymfonyMailerAdapterTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Mailing\\Adapter;\n\nuse OxidEsales\\Eshop\\Core\\Email;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\Email\\SymfonyMailerAdapter;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Mime\\Email as SymfonyEmail;\n\nclass SymfonyMailerAdapterTest extends TestCase\n{\n    public function testConvertBasicEmail(): void\n    {\n        $legacyEmail = $this->createBasicEmail();\n        $legacyEmail->setSubject('Test Subject');\n        $legacyEmail->setBody('<p>Test Body</p>');\n        $legacyEmail->setAltBody('Test Body');\n\n        $adapter = new SymfonyMailerAdapter();\n        $symfonyEmail = $adapter->convertToSymfonyEmail($legacyEmail);\n\n        $this->assertEquals('Test Subject', $symfonyEmail->getSubject());\n        $this->assertEquals('<p>Test Body</p>', $symfonyEmail->getHtmlBody());\n        $this->assertEquals('Test Body', $symfonyEmail->getTextBody());\n    }\n\n    public function testConvertWithMultipleRecipients(): void\n    {\n        $legacyEmail = $this->createBasicEmail();\n        $legacyEmail->setRecipient('second@example.com', 'Second User');\n\n        $adapter = new SymfonyMailerAdapter();\n        $symfonyEmail = $adapter->convertToSymfonyEmail($legacyEmail);\n\n        $to = $symfonyEmail->getTo();\n        $this->assertCount(2, $to);\n        $this->assertEquals('test@example.com', $to[0]->getAddress());\n        $this->assertEquals('second@example.com', $to[1]->getAddress());\n    }\n\n    public function testConvertWithFromName(): void\n    {\n        $legacyEmail = $this->createBasicEmail();\n\n        $adapter = new SymfonyMailerAdapter();\n        $symfonyEmail = $adapter->convertToSymfonyEmail($legacyEmail);\n\n        $from = $symfonyEmail->getFrom();\n        $this->assertCount(1, $from);\n        $this->assertEquals('sender@example.com', $from[0]->getAddress());\n        $this->assertEquals('Sender Name', $from[0]->getName());\n    }\n\n    public function testConvertWithReplyTo(): void\n    {\n        $legacyEmail = $this->createBasicEmail();\n        $legacyEmail->setReplyTo('reply@example.com', 'Reply User');\n\n        $adapter = new SymfonyMailerAdapter();\n        $symfonyEmail = $adapter->convertToSymfonyEmail($legacyEmail);\n\n        $replyTo = $symfonyEmail->getReplyTo();\n        $this->assertCount(1, $replyTo);\n        $this->assertEquals('reply@example.com', $replyTo[0]->getAddress());\n        $this->assertEquals('Reply User', $replyTo[0]->getName());\n    }\n\n    public function testConvertWithCcAndBcc(): void\n    {\n        $legacyEmail = $this->createBasicEmail();\n        $legacyEmail->addCC('cc@example.com', 'CC User');\n        $legacyEmail->addBCC('bcc@example.com', 'BCC User');\n\n        $adapter = new SymfonyMailerAdapter();\n        $symfonyEmail = $adapter->convertToSymfonyEmail($legacyEmail);\n\n        $cc = $symfonyEmail->getCc();\n        $bcc = $symfonyEmail->getBcc();\n\n        $this->assertCount(1, $cc);\n        $this->assertEquals('cc@example.com', $cc[0]->getAddress());\n        $this->assertEquals('CC User', $cc[0]->getName());\n\n        $this->assertCount(1, $bcc);\n        $this->assertEquals('bcc@example.com', $bcc[0]->getAddress());\n        $this->assertEquals('BCC User', $bcc[0]->getName());\n    }\n\n    public function testConvertPlainTextEmail(): void\n    {\n        $legacyEmail = $this->createBasicEmail();\n        $legacyEmail->isHTML(false);\n        $legacyEmail->setBody('Plain text body');\n\n        $adapter = new SymfonyMailerAdapter();\n        $symfonyEmail = $adapter->convertToSymfonyEmail($legacyEmail);\n\n        $this->assertNull($symfonyEmail->getHtmlBody());\n        $this->assertEquals('Plain text body', $symfonyEmail->getTextBody());\n    }\n\n    public function testConvertHtmlEmailWithAltBody(): void\n    {\n        $legacyEmail = $this->createBasicEmail();\n        $legacyEmail->isHTML(true);\n        $legacyEmail->setBody('<p>HTML body</p>');\n        $legacyEmail->setAltBody('Plain text alternative');\n\n        $adapter = new SymfonyMailerAdapter();\n        $symfonyEmail = $adapter->convertToSymfonyEmail($legacyEmail);\n\n        $this->assertEquals('<p>HTML body</p>', $symfonyEmail->getHtmlBody());\n        $this->assertEquals('Plain text alternative', $symfonyEmail->getTextBody());\n    }\n\n    public function testConvertWithStringAttachment(): void\n    {\n        $legacyEmail = $this->createBasicEmail();\n        $legacyEmail->addStringAttachment('string content', 'file.txt', 'base64', 'text/plain');\n\n        $adapter = new SymfonyMailerAdapter();\n        $symfonyEmail = $adapter->convertToSymfonyEmail($legacyEmail);\n\n        $attachments = $symfonyEmail->getAttachments();\n        $this->assertCount(1, $attachments);\n    }\n\n    public function testConvertWithCustomHeaders(): void\n    {\n        $legacyEmail = $this->createBasicEmail();\n        $legacyEmail->addCustomHeader('X-Custom-Header', 'CustomValue');\n\n        $adapter = new SymfonyMailerAdapter();\n        $symfonyEmail = $adapter->convertToSymfonyEmail($legacyEmail);\n\n        $headers = $symfonyEmail->getHeaders();\n\n        $this->assertTrue($headers->has('X-Custom-Header'));\n        $this->assertEquals('CustomValue', $headers->get('X-Custom-Header')->getBodyAsString());\n    }\n\n    public function testConvertWithPriority(): void\n    {\n        $legacyEmail = $this->createBasicEmail();\n        $legacyEmail->Priority = 1;\n\n        $adapter = new SymfonyMailerAdapter();\n        $symfonyEmail = $adapter->convertToSymfonyEmail($legacyEmail);\n\n        $this->assertEquals(SymfonyEmail::PRIORITY_HIGHEST, $symfonyEmail->getPriority());\n    }\n\n    public function testConvertWithLowPriority(): void\n    {\n        $legacyEmail = $this->createBasicEmail();\n        $legacyEmail->Priority = 5;\n\n        $adapter = new SymfonyMailerAdapter();\n        $symfonyEmail = $adapter->convertToSymfonyEmail($legacyEmail);\n\n        $this->assertEquals(SymfonyEmail::PRIORITY_LOWEST, $symfonyEmail->getPriority());\n    }\n\n    public function testConvertWithSender(): void\n    {\n        $legacyEmail = $this->createBasicEmail();\n        $legacyEmail->Sender = 'bounce@example.com';\n\n        $adapter = new SymfonyMailerAdapter();\n        $symfonyEmail = $adapter->convertToSymfonyEmail($legacyEmail);\n\n        $returnPath = $symfonyEmail->getReturnPath();\n        $this->assertNotNull($returnPath);\n        $this->assertEquals('bounce@example.com', $returnPath->getAddress());\n    }\n\n    public function testCidMappingNormalizesCidWithoutAtSymbol(): void\n    {\n        $cid = 'abc123def456';\n        $body = '<p>Image: <img src=\"cid:' . $cid . '\"></p>';\n\n        $legacyEmail = $this->createStub(Email::class);\n        $legacyEmail->method('getRecipient')->willReturn([['test@example.com', 'Test']]);\n        $legacyEmail->method('getFrom')->willReturn('from@example.com');\n        $legacyEmail->method('getFromName')->willReturn('From Name');\n        $legacyEmail->method('getSubject')->willReturn('Test Subject');\n        $legacyEmail->method('getBody')->willReturn($body);\n        $legacyEmail->method('getAltBody')->willReturn('');\n        $legacyEmail->method('getCharset')->willReturn('UTF-8');\n        $legacyEmail->method('getReplyTo')->willReturn([]);\n        $legacyEmail->method('getCc')->willReturn([]);\n        $legacyEmail->method('getBcc')->willReturn([]);\n        $legacyEmail->method('getCustomHeaders')->willReturn([]);\n        $legacyEmail->method('getAttachments')->willReturn([\n            [\n                0 => 'image data',\n                1 => 'image.png',\n                2 => 'image.png',\n                3 => 'base64',\n                4 => 'image/png',\n                5 => true,\n                6 => 'inline',\n                7 => $cid,\n            ]\n        ]);\n        $legacyEmail->ContentType = 'text/html';\n\n        $adapter = new SymfonyMailerAdapter();\n        $symfonyEmail = $adapter->convertToSymfonyEmail($legacyEmail);\n\n        $attachments = $symfonyEmail->getAttachments();\n        $this->assertCount(1, $attachments);\n        $this->assertEquals($cid . '@generated', $attachments[0]->getContentId());\n        $this->assertStringContainsString('cid:' . $cid . '@generated', $symfonyEmail->getHtmlBody());\n    }\n\n    public function testCidMappingPreservesCidWithAtSymbol(): void\n    {\n        $cid = 'abc123@example.com';\n        $body = '<p>Image: <img src=\"cid:' . $cid . '\"></p>';\n\n        $legacyEmail = $this->createStub(Email::class);\n        $legacyEmail->method('getRecipient')->willReturn([['test@example.com', 'Test']]);\n        $legacyEmail->method('getFrom')->willReturn('from@example.com');\n        $legacyEmail->method('getFromName')->willReturn('From Name');\n        $legacyEmail->method('getSubject')->willReturn('Test Subject');\n        $legacyEmail->method('getBody')->willReturn($body);\n        $legacyEmail->method('getAltBody')->willReturn('');\n        $legacyEmail->method('getCharset')->willReturn('UTF-8');\n        $legacyEmail->method('getReplyTo')->willReturn([]);\n        $legacyEmail->method('getCc')->willReturn([]);\n        $legacyEmail->method('getBcc')->willReturn([]);\n        $legacyEmail->method('getCustomHeaders')->willReturn([]);\n        $legacyEmail->method('getAttachments')->willReturn([\n            [\n                0 => 'image data',\n                1 => 'image.png',\n                2 => 'image.png',\n                3 => 'base64',\n                4 => 'image/png',\n                5 => true,\n                6 => 'inline',\n                7 => $cid,\n            ]\n        ]);\n        $legacyEmail->ContentType = 'text/html';\n\n        $adapter = new SymfonyMailerAdapter();\n        $symfonyEmail = $adapter->convertToSymfonyEmail($legacyEmail);\n\n        $attachments = $symfonyEmail->getAttachments();\n        $this->assertCount(1, $attachments);\n        $this->assertEquals($cid, $attachments[0]->getContentId());\n        $this->assertStringContainsString('cid:' . $cid, $symfonyEmail->getHtmlBody());\n    }\n\n    public function testCidMappingWithMultipleInlineImages(): void\n    {\n        $cid1 = 'image1abc';\n        $cid2 = 'image2@domain.com';\n        $body = '<p><img src=\"cid:' . $cid1 . '\"><img src=\"cid:' . $cid2 . '\"></p>';\n\n        $legacyEmail = $this->createStub(Email::class);\n        $legacyEmail->method('getRecipient')->willReturn([['test@example.com', 'Test']]);\n        $legacyEmail->method('getFrom')->willReturn('from@example.com');\n        $legacyEmail->method('getFromName')->willReturn('From Name');\n        $legacyEmail->method('getSubject')->willReturn('Test');\n        $legacyEmail->method('getBody')->willReturn($body);\n        $legacyEmail->method('getAltBody')->willReturn('');\n        $legacyEmail->method('getCharset')->willReturn('UTF-8');\n        $legacyEmail->method('getReplyTo')->willReturn([]);\n        $legacyEmail->method('getCc')->willReturn([]);\n        $legacyEmail->method('getBcc')->willReturn([]);\n        $legacyEmail->method('getCustomHeaders')->willReturn([]);\n        $legacyEmail->method('getAttachments')->willReturn([\n            [\n                0 => 'image1 data',\n                1 => 'image1.png',\n                2 => 'image1.png',\n                3 => 'base64',\n                4 => 'image/png',\n                5 => true,\n                6 => 'inline',\n                7 => $cid1,\n            ],\n            [\n                0 => 'image2 data',\n                1 => 'image2.png',\n                2 => 'image2.png',\n                3 => 'base64',\n                4 => 'image/png',\n                5 => true,\n                6 => 'inline',\n                7 => $cid2,\n            ]\n        ]);\n        $legacyEmail->ContentType = 'text/html';\n\n        $adapter = new SymfonyMailerAdapter();\n        $symfonyEmail = $adapter->convertToSymfonyEmail($legacyEmail);\n\n        $htmlBody = $symfonyEmail->getHtmlBody();\n\n        $this->assertStringContainsString('cid:' . $cid1 . '@generated', $htmlBody);\n        $this->assertStringContainsString('cid:' . $cid2, $htmlBody);\n        $this->assertStringNotContainsString('cid:' . $cid2 . '@generated', $htmlBody);\n    }\n\n    private function createBasicEmail(): Email\n    {\n        $legacyEmail = new Email();\n        $legacyEmail->setRecipient('test@example.com', 'Test User');\n        $legacyEmail->setFrom('sender@example.com', 'Sender Name');\n        $legacyEmail->setSubject('Test');\n        $legacyEmail->setBody('Body');\n\n        return $legacyEmail;\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Mailing/Factory/TransportFactoryTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Mailing\\Factory;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Mailing\\Factory\\TransportFactory;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Mailer\\Exception\\InvalidArgumentException;\nuse Symfony\\Component\\Mailer\\Transport\\TransportInterface;\n\nclass TransportFactoryTest extends TestCase\n{\n    public function testCreateReturnsTransportInterface(): void\n    {\n        $factory = new TransportFactory('null://null');\n\n        $this->assertInstanceOf(TransportInterface::class, $factory->create());\n    }\n\n    public function testCreateThrowsExceptionOnInvalidDsn(): void\n    {\n        $factory = new TransportFactory('invalid-dsn-without-scheme');\n\n        $this->expectException(InvalidArgumentException::class);\n        $factory->create();\n    }\n\n    public function testCreateThrowsExceptionOnEmptyDsn(): void\n    {\n        $factory = new TransportFactory('');\n\n        $this->expectException(InvalidArgumentException::class);\n        $factory->create();\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/Cache/InvalidateModuleCacheEventSubscriberTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\Cache;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Cache\\ShopCacheCleanerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Cache\\InvalidateModuleCacheEventSubscriber;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Cache\\ModuleCacheServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Event\\ModuleSetupEvent;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class InvalidateModuleCacheEventSubscriberTest extends TestCase\n{\n    public function testSubscriberCallsModuleCacheService(): void\n    {\n        $shopCacheCleaner = $this->getMockBuilder(ShopCacheCleanerInterface::class)->getMock();\n        $shopCacheCleaner\n            ->expects($this->once())\n            ->method('clear');\n\n        $event = new class (1, 'testModuleId') extends ModuleSetupEvent {\n        };\n\n        $subscriber = new InvalidateModuleCacheEventSubscriber($shopCacheCleaner);\n        $subscriber->invalidateModuleCache($event);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/Configuration/Bridge/ModuleConfigurationDaoBridgeTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\Configuration\\Bridge;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Bridge\\ModuleConfigurationDaoBridge;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleEnvironmentConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ModuleConfigurationDaoBridgeTest extends TestCase\n{\n    public function testGet(): void\n    {\n        $context = $this->createStub(ContextInterface::class);\n        $context\n            ->method('getCurrentShopId')\n            ->willReturn(1789);\n\n        $moduleConfigurationDao = $this->createMock(ModuleConfigurationDaoInterface::class);\n        $moduleConfigurationDao\n            ->expects($this->once())\n            ->method('get')\n            ->with('testModuleId', 1789);\n\n        $shopEnvironmentConfigurationDao = $this->createStub(ModuleEnvironmentConfigurationDaoInterface::class);\n\n        $bridge = new ModuleConfigurationDaoBridge($context, $moduleConfigurationDao, $shopEnvironmentConfigurationDao);\n        $bridge->get('testModuleId');\n    }\n\n    public function testSave(): void\n    {\n        $context = $this->createStub(ContextInterface::class);\n        $context\n            ->method('getCurrentShopId')\n            ->willReturn(1799);\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->setId('testId');\n\n        $moduleConfigurationDao = $this->createMock(ModuleConfigurationDaoInterface::class);\n        $moduleConfigurationDao\n            ->expects($this->once())\n            ->method('save')\n            ->with($moduleConfiguration, 1799);\n\n        $shopEnvironmentConfigurationDao = $this->createMock(ModuleEnvironmentConfigurationDaoInterface::class);\n        $shopEnvironmentConfigurationDao->expects($this->once())->method('remove');\n\n        $bridge = new ModuleConfigurationDaoBridge($context, $moduleConfigurationDao, $shopEnvironmentConfigurationDao);\n        $bridge->save($moduleConfiguration);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/Configuration/Cache/ClassPropertyModuleConfigurationCacheTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\Configuration\\Cache;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Cache\\ClassPropertyModuleConfigurationCache;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ClassPropertyModuleConfigurationCacheTest extends TestCase\n{\n    public function testPut(): void\n    {\n        $configuration = new ModuleConfiguration();\n        $configuration->setId('test');\n\n        $cache = new ClassPropertyModuleConfigurationCache();\n        $cache->put(2, $configuration);\n\n        $this->assertSame($configuration, $cache->get('test', 2));\n    }\n\n    public function testExists(): void\n    {\n        $cache = new ClassPropertyModuleConfigurationCache();\n\n        $this->assertFalse($cache->exists('test', 1));\n\n        $configuration = new ModuleConfiguration();\n        $configuration->setId('test');\n\n        $cache->put(1, $configuration);\n\n        $this->assertTrue($cache->exists('test', 1));\n    }\n\n    #[DoesNotPerformAssertions]\n    public function testNotExistentEvict(): void\n    {\n        $cache = new ClassPropertyModuleConfigurationCache();\n\n        $cache->evict('nonExistingModule', 3);\n    }\n\n    public function testEmptyCacheEvict(): void\n    {\n        $cache = new ClassPropertyModuleConfigurationCache();\n\n        $cache->evict('test', 3);\n\n        $this->addToAssertionCount(1);\n    }\n\n    public function testEvict(): void\n    {\n        $cache = new ClassPropertyModuleConfigurationCache();\n\n        $configuration = new ModuleConfiguration();\n        $configuration->setId('test');\n\n        $cache->put(3, $configuration);\n        $cache->evict('test', 3);\n\n        $this->assertFalse($cache->exists('test', 3));\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/Configuration/Dao/ModuleEnvironmentConfigurationExtenderTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\Configuration\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleEnvironmentConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopEnvironmentWithOrphanSettingEvent;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setting\\Setting;\nuse PHPUnit\\Framework\\TestCase;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleEnvironmentConfigurationExtender;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\nuse Prophecy\\Prophecy\\ObjectProphecy;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\n\nfinal class ModuleEnvironmentConfigurationExtenderTest extends TestCase\n{\n    use ProphecyTrait;\n\n    private ModuleEnvironmentConfigurationDaoInterface|ObjectProphecy $environmentDao;\n    private ModuleEnvironmentConfigurationExtender $environmentExtension;\n    private ObjectProphecy|EventDispatcherInterface $eventDispatcher;\n    private int $shopId = 1;\n\n    protected function setUp(): void\n    {\n        parent::setUp();\n        $this->environmentDao = $this->prophesize(ModuleEnvironmentConfigurationDaoInterface::class);\n        $this->eventDispatcher = $this->prophesize(EventDispatcherInterface::class);\n        $this->environmentExtension = new ModuleEnvironmentConfigurationExtender(\n            $this->environmentDao->reveal(),\n            $this->eventDispatcher->reveal()\n        );\n    }\n\n    public function testWithEmptyEnvironment(): void\n    {\n        $moduleConfiguration = $this->getTestModuleConfiguration();\n\n        $environmentConfiguration = [];\n        $this->environmentDao->get('testId', $this->shopId)->willReturn($environmentConfiguration);\n\n        $result = $this->environmentExtension->extend($moduleConfiguration, $this->shopId);\n\n        $this->assertEquals($this->getTestModuleConfiguration(), $result);\n    }\n\n    public function testWithEnvironment(): void\n    {\n        $moduleConfiguration = $this->getTestModuleConfiguration();\n\n        $environmentConfiguration = [\n            'moduleSettings' => [\n                'testSetting' => [\n                    'group' => 'envGroup',\n                    'value' => 'envValue',\n                    'constraints' => ['env'],\n                    'position' => 5,\n                ],\n            ],\n        ];\n        $this->environmentDao->get('testId', $this->shopId)->willReturn($environmentConfiguration);\n\n        $expectedConfiguration = $this->getTestModuleConfiguration();\n        $setting = $expectedConfiguration->getModuleSetting('testSetting');\n        $setting\n            ->setValue('envValue')\n            ->setConstraints(['env'])\n            ->setPositionInGroup(5)\n            ->setGroupName('envGroup');\n\n        $result = $this->environmentExtension->extend($moduleConfiguration, $this->shopId);\n\n        $this->assertEquals($expectedConfiguration, $result);\n    }\n\n    public function testWithNonExistentSettingInEnvironment(): void\n    {\n        $moduleConfiguration = $this->getTestModuleConfiguration();\n\n        $environmentConfiguration = [\n            'moduleSettings' => [\n                'nonExistent' => [\n                    'group' => 'envGroup',\n                    'value' => 'envValue',\n                    'constraints' => ['env'],\n                    'position' => 5,\n                ],\n            ],\n        ];\n        $event = new ShopEnvironmentWithOrphanSettingEvent(\n            $this->shopId,\n            'testId',\n            'nonExistent'\n        );\n        $this->environmentDao->get('testId', $this->shopId)->willReturn($environmentConfiguration);\n        $this->eventDispatcher\n            ->dispatch($event)\n            ->willReturnArgument();\n\n        $result = $this->environmentExtension->extend($moduleConfiguration, $this->shopId);\n\n        $this->assertEquals($this->getTestModuleConfiguration(), $result);\n    }\n\n    private function getTestModuleConfiguration(): ModuleConfiguration\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->setId('testId');\n\n        $setting = new Setting();\n        $setting\n            ->setName('testSetting')\n            ->setValue('originalValue')\n            ->setGroupName('originalGroup')\n            ->setConstraints(['123'])\n            ->setPositionInGroup(0);\n\n        $moduleConfiguration->addModuleSetting($setting);\n\n        return $moduleConfiguration;\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/Configuration/DataMapper/ModuleConfigurationDataMapperTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\Configuration\\DataMapper;\n\nuse MyVendor\\MyController\\Controller1;\nuse MyVendor\\MyController\\Controller2;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\ClassExtensionsDataMapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\ControllersDataMapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\EventsDataMapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfiguration\\ModuleSettingsDataMapper;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataMapper\\ModuleConfigurationDataMapperInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Tests\\ContainerTrait;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ModuleConfigurationDataMapperTest extends TestCase\n{\n    use ContainerTrait;\n\n    #[DataProvider('moduleConfigurationDataProvider')]\n    public function testToDataAndFromData(array $data, ModuleConfigurationDataMapperInterface $dataMapper): void\n    {\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration = $dataMapper->fromData($moduleConfiguration, $data);\n\n        $this->assertEquals(\n            $data,\n            $dataMapper->toData($moduleConfiguration)\n        );\n    }\n\n    public static function moduleConfigurationDataProvider(): array\n    {\n        return [\n            [\n                'data' => [\n                    ClassExtensionsDataMapper::MAPPING_KEY => [\n                        'shopClass1' => 'moduleClass1',\n                        'shopClass2' => 'moduleClass2'\n                    ]\n                ],\n                'dataMapper' => new ClassExtensionsDataMapper()\n\n            ],\n            [\n                'data' => [\n                    ControllersDataMapper::MAPPING_KEY => [\n                        'controller1' => Controller1::class,\n                        'controller2' => Controller2::class\n                    ]\n                ],\n                'dataMapper' => new ControllersDataMapper()\n\n            ],\n            [\n                'data' => [\n                    EventsDataMapper::MAPPING_KEY => [\n                            'onActivate'   => 'MyEvents::onActivate',\n                            'onDeactivate' => 'MyEvents::onDeactivate'\n                    ]\n                ],\n                'dataMapper' => new EventsDataMapper()\n\n            ],\n            [\n                'data' => [\n                    ModuleSettingsDataMapper::MAPPING_KEY => [\n                        'testEmptyBoolConfig' => [\n                            'group' => 'settingsEmpty',\n                            'type' => 'bool',\n                            'value' => 'false'\n                        ],\n                        'testFilledAArrConfig' => [\n                            'group' => 'settingsFilled',\n                            'type' => 'aarr',\n                            'value' => ['key1' => 'option1', 'key2' => 'option2']\n                        ]\n                    ]\n                ],\n                'dataMapper' => new ModuleSettingsDataMapper()\n\n            ]\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/Configuration/DataObject/ClassExtensionsChainTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\Configuration\\DataObject;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ClassExtensionsChain;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\ClassExtension;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\ExtensionNotInChainException;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ClassExtensionsChainTest extends TestCase\n{\n    public function testAddExtensionsIfChainIsEmpty(): void\n    {\n        $chain = new ClassExtensionsChain();\n\n        $chain->addExtensions(\n            [\n                new ClassExtension(\n                    'extendedClass',\n                    'firstExtension'\n                ),\n                new ClassExtension(\n                    'anotherExtendedClass',\n                    'someExtension'\n                )\n            ]\n        );\n\n        $this->assertEquals(\n            [\n                'extendedClass' => [\n                    'firstExtension',\n                ],\n                'anotherExtendedClass' => [\n                    'someExtension',\n                ],\n            ],\n            $chain->getChain()\n        );\n    }\n\n    public function testAddExtensionToChainIfAnotherExtensionsAlreadyExist(): void\n    {\n        $chain = new ClassExtensionsChain();\n\n        $chain->addExtensions(\n            [\n                new ClassExtension(\n                    'extendedClass',\n                    'firstExtension'\n                ),\n                new ClassExtension(\n                    'anotherExtendedClass',\n                    'someExtension'\n                ),\n                new ClassExtension(\n                    'extendedClass',\n                    'secondExtension'\n                ),\n                new ClassExtension(\n                    'extendedClass',\n                    'firstExtension'\n                )\n            ]\n        );\n\n        $chain->addExtension(\n            new ClassExtension(\n                'extendedClass',\n                'firstExtension'\n            )\n        );\n\n        $this->assertEquals(\n            [\n                'extendedClass' => [\n                    'firstExtension',\n                    'secondExtension',\n                ],\n                'anotherExtendedClass' => [\n                    'someExtension',\n                ]\n            ],\n            $chain->getChain()\n        );\n    }\n\n    public function testRemoveExtension(): void\n    {\n        $chain = new ClassExtensionsChain();\n        $chain->setChain(\n            [\n                'extendedClass1' => [\n                    'extension1',\n                    'extension2',\n                ],\n                'extendedClass2' => [\n                    'extension3',\n                ],\n                'extendedClass3' => [\n                    'extension4'\n                ]\n            ]\n        );\n        $chain->removeExtension(\n            new ClassExtension(\n                'extendedClass1',\n                'extension1'\n            )\n        );\n        $chain->removeExtension(\n            new ClassExtension(\n                'extendedClass2',\n                'extension3'\n            )\n        );\n\n        $this->assertEquals(\n            [\n                'extendedClass1' => [\n                    'extension2',\n                ],\n                'extendedClass3' => [\n                    'extension4'\n                ]\n            ],\n            $chain->getChain()\n        );\n    }\n\n\n    #[DataProvider('invalidExtensionProvider')]\n    public function testRemoveExtensionThrowsExceptionIfClassNotExistsInChain(ClassExtension $extension): void\n    {\n        $this->expectException(ExtensionNotInChainException::class);\n        $chain = new ClassExtensionsChain();\n        $chain->setChain(\n            [\n                'extendedClass1' => [\n                    'extension1',\n                    'extension2',\n                ]\n            ]\n        );\n        $this->expectException(\n            ExtensionNotInChainException::class\n        );\n        $chain->removeExtension($extension);\n    }\n\n    public static function invalidExtensionProvider(): array\n    {\n        return [\n            [\n                new ClassExtension(\n                    'notExistingExtended',\n                    'notExistingExtension'\n                )\n            ],\n            [\n                new ClassExtension(\n                    'extendedClass1',\n                    'notExistingExtension'\n                )\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/Configuration/DataObject/ModuleConfigurationTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\Configuration\\DataObject;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\ClassExtension;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\InvalidModuleIdException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\ModuleSettingNotFountException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setting\\Setting;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ModuleConfigurationTest extends TestCase\n{\n    public function testAddModuleSetting(): void\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n        $setting = new Setting();\n        $setting\n            ->setName('testSetting')\n            ->setValue([]);\n        $moduleConfiguration->addModuleSetting($setting);\n\n        $this->assertSame(\n            $setting,\n            $moduleConfiguration->getModuleSetting('testSetting')\n        );\n    }\n\n    public function testConfigurationHasSetting(): void\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n\n        $this->assertFalse($moduleConfiguration->hasModuleSetting('testSetting'));\n\n        $setting = new Setting();\n        $setting\n            ->setName('testSetting')\n            ->setValue([]);\n        $moduleConfiguration->addModuleSetting($setting);\n\n        $this->assertTrue($moduleConfiguration->hasModuleSetting('testSetting'));\n    }\n\n    public function testConfigurationHasClassExtension(): void\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n\n        $moduleConfiguration->addClassExtension(\n            new ClassExtension(\n                'extendedClassNamespace',\n                'expectedExtensionNamespace'\n            )\n        );\n\n        $this->assertTrue(\n            $moduleConfiguration->hasClassExtension('expectedExtensionNamespace')\n        );\n    }\n\n    public function testConfigurationDoesNotHaveClassExtension(): void\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n\n        $this->assertFalse(\n            $moduleConfiguration->hasClassExtension('expectedExtensionNamespace')\n        );\n\n        $moduleConfiguration->addClassExtension(\n            new ClassExtension(\n                'extendedClassNamespace',\n                'anotherExtensionNamespace'\n            )\n        );\n\n        $this->assertFalse(\n            $moduleConfiguration->hasClassExtension('expectedExtensionNamespace')\n        );\n    }\n\n    public function testGetModuleSettingWhenSettingNotFound(): void\n    {\n        $this->expectException(ModuleSettingNotFountException::class);\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->getModuleSetting('nonExistingSetting');\n    }\n\n    public function testInvalidModuleId(): void\n    {\n        $this->expectException(InvalidModuleIdException::class);\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->setId('idWith/Slash');\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/Configuration/DataObject/ShopConfigurationTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\Configuration\\DataObject;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\ClassExtension;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\ModuleConfigurationNotFoundException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ClassExtensionsChain;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ShopConfigurationTest extends TestCase\n{\n    private ShopConfiguration $shopConfiguration;\n\n    protected function setUp(): void\n    {\n        parent::setUp();\n        $this->shopConfiguration = new ShopConfiguration();\n    }\n\n    public function testGetModuleConfiguration(): void\n    {\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->setId('testModuleId');\n\n        $this->shopConfiguration->addModuleConfiguration($moduleConfiguration);\n        $this->assertSame($moduleConfiguration, $this->shopConfiguration->getModuleConfiguration('testModuleId'));\n    }\n\n    public function testGetModuleConfigurations(): void\n    {\n        $moduleConfiguration1 = new ModuleConfiguration();\n        $moduleConfiguration1->setId('firstModule');\n\n        $moduleConfiguration2 = new ModuleConfiguration();\n        $moduleConfiguration2->setId('secondModule');\n\n        $this->shopConfiguration\n             ->addModuleConfiguration($moduleConfiguration1)\n             ->addModuleConfiguration($moduleConfiguration2);\n\n        $this->assertSame(\n            [\n                'firstModule'   => $moduleConfiguration1,\n                'secondModule'  => $moduleConfiguration2,\n            ],\n            $this->shopConfiguration->getModuleConfigurations()\n        );\n    }\n\n    public function testHasModuleConfiguration(): void\n    {\n        $testModuleId = 'testModuleId';\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->setId($testModuleId);\n\n        $this->assertFalse(\n            $this->shopConfiguration->hasModuleConfiguration($testModuleId)\n        );\n\n        $this->shopConfiguration->addModuleConfiguration($moduleConfiguration);\n\n        $this->assertTrue(\n            $this->shopConfiguration->hasModuleConfiguration($testModuleId)\n        );\n    }\n\n    public function testGetModuleConfigurationThrowsExceptionIfModuleIdNotPresent(): void\n    {\n        $this->expectException(ModuleConfigurationNotFoundException::class);\n        $this->shopConfiguration->getModuleConfiguration('moduleIdNotPresent');\n    }\n\n    public function testDeleteModuleConfiguration(): void\n    {\n        $testModuleId = 'testModuleId';\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->setId($testModuleId);\n\n        $shopConfiguration = new ShopConfiguration();\n        $shopConfiguration->addModuleConfiguration($moduleConfiguration);\n\n        $shopConfiguration->deleteModuleConfiguration($testModuleId);\n\n        $this->assertFalse($shopConfiguration->hasModuleConfiguration($testModuleId));\n    }\n\n    public function testDeleteModuleConfigurationRemovesModuleExtensionFromChain(): void\n    {\n        $moduleExtensionToStay = new ClassExtension(\n            'shopClass',\n            'moduleExtensionToStay'\n        );\n        $moduleConfigurationToStay = new ModuleConfiguration();\n        $moduleConfigurationToStay->setId('moduleToStay');\n        $moduleConfigurationToStay->addClassExtension($moduleExtensionToStay);\n\n        $moduleExtensionToDelete = new ClassExtension(\n            'shopClass',\n            'moduleExtensionToDelete'\n        );\n        $moduleConfigurationToDelete = new ModuleConfiguration();\n        $moduleConfigurationToDelete->setId('moduleToDelete');\n        $moduleConfigurationToDelete->addClassExtension($moduleExtensionToDelete);\n\n        $shopConfiguration = new ShopConfiguration();\n        $shopConfiguration\n            ->addModuleConfiguration($moduleConfigurationToDelete)\n            ->addModuleConfiguration($moduleConfigurationToStay);\n\n        $shopConfiguration->getClassExtensionsChain()->addExtension($moduleExtensionToStay);\n        $shopConfiguration->getClassExtensionsChain()->addExtension($moduleExtensionToDelete);\n\n        $shopConfiguration->deleteModuleConfiguration('moduleToDelete');\n\n        $expectedClassExtensionChain = new ClassExtensionsChain();\n        $expectedClassExtensionChain->addExtension($moduleExtensionToStay);\n\n        $this->assertEquals(\n            $expectedClassExtensionChain,\n            $shopConfiguration->getClassExtensionsChain()\n        );\n    }\n\n    public function testDeleteModuleConfigurationThrowsExceptionIfModuleIdNotPresent(): void\n    {\n        $this->expectException(ModuleConfigurationNotFoundException::class);\n        $this->shopConfiguration->deleteModuleConfiguration('moduleIdNotPresent');\n    }\n\n    public function testChains(): void\n    {\n        $chain = new ClassExtensionsChain();\n\n        $this->shopConfiguration->setClassExtensionsChain($chain);\n\n        $this->assertSame(\n            $chain,\n            $this->shopConfiguration->getClassExtensionsChain()\n        );\n    }\n\n    public function testDefaultChains(): void\n    {\n        $chain = new ClassExtensionsChain();\n\n        $this->assertEquals(\n            $chain,\n            $this->shopConfiguration->getClassExtensionsChain()\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/Facade/ActiveModulesDataProviderBridgeTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\Facade;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Exception\\ShopConfigurationNotFoundException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ActiveModulesDataProviderBridge;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ActiveModulesDataProviderInterface;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Log\\LoggerInterface;\n\nfinal class ActiveModulesDataProviderBridgeTest extends TestCase\n{\n    public function testGetClassExtensionIfShopConfigurationIsMissing(): void\n    {\n        $logger = $this->createMock(LoggerInterface::class);\n        $logger->expects(self::once())->method('error');\n\n        $activeModulesDataProvider = $this->createStub(ActiveModulesDataProviderInterface::class);\n        $activeModulesDataProvider->method('getClassExtensions')->willThrowException(new ShopConfigurationNotFoundException());\n\n        $bridge = new ActiveModulesDataProviderBridge(\n            $activeModulesDataProvider,\n            $logger\n        );\n\n        $this->assertEquals([], $bridge->getClassExtensions());\n    }\n\n    public function testGetClassExtensionCallsOnlyOnce(): void\n    {\n        $activeModulesDataProvider = $this->createMock(ActiveModulesDataProviderInterface::class);\n        $activeModulesDataProvider\n            ->expects($this->once())\n            ->method('getClassExtensions')\n            ->willReturn(['test' => 'test']);\n\n        $bridge = new ActiveModulesDataProviderBridge(\n            $activeModulesDataProvider,\n            $this->createStub(LoggerInterface::class)\n        );\n\n        $this->assertEquals(['test' => 'test'], $bridge->getClassExtensions());\n        $this->assertEquals(['test' => 'test'], $bridge->getClassExtensions());\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/Install/Service/BootstrapModuleInstallerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\Install\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\DataObject\\OxidEshopPackage;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\BootstrapModuleInstaller;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleConfigurationInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ModuleFilesInstallerInterface;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class BootstrapModuleInstallerTest extends TestCase\n{\n    public function testInstallTriggersAllInstallers(): void\n    {\n        $path = 'packagePath';\n        $package = new OxidEshopPackage($path);\n\n        $moduleFilesInstaller = $this->createMock(ModuleFilesInstallerInterface::class);\n        $moduleFilesInstaller\n            ->expects($this->once())\n            ->method('install')\n            ->with($package);\n\n        $moduleProjectConfigurationInstaller = $this->createMock(ModuleConfigurationInstallerInterface::class);\n        $moduleProjectConfigurationInstaller\n            ->expects($this->once())\n            ->method('install')\n            ->with($path);\n\n\n        $moduleInstaller = new BootstrapModuleInstaller($moduleFilesInstaller, $moduleProjectConfigurationInstaller);\n        $moduleInstaller->install($package);\n    }\n\n    #[DataProvider('moduleInstallMatrixDataProvider')]\n    public function testIsInstalled(bool $filesInstalled, bool $projectConfigurationInstalled, bool $moduleInstalled): void\n    {\n        $moduleFilesInstaller = $this->createStub(ModuleFilesInstallerInterface::class);\n        $moduleFilesInstaller->method('isInstalled')->willReturn($filesInstalled);\n\n        $moduleProjectConfigurationInstaller = $this->createStub(ModuleConfigurationInstallerInterface::class);\n        $moduleProjectConfigurationInstaller->method('isInstalled')->willReturn($projectConfigurationInstalled);\n\n        $moduleInstaller = new BootstrapModuleInstaller($moduleFilesInstaller, $moduleProjectConfigurationInstaller);\n\n        $this->assertSame(\n            $moduleInstalled,\n            $moduleInstaller->isInstalled(new OxidEshopPackage('somePath'))\n        );\n    }\n\n    public static function moduleInstallMatrixDataProvider(): array\n    {\n        return [\n            [\n                'filesInstalled'                => false,\n                'projectConfigurationInstalled' => false,\n                'moduleInstalled'               => false,\n            ],\n            [\n                'filesInstalled'                => true,\n                'projectConfigurationInstalled' => false,\n                'moduleInstalled'               => false,\n            ],\n            [\n                'filesInstalled'                => false,\n                'projectConfigurationInstalled' => true,\n                'moduleInstalled'               => false,\n            ],\n            [\n                'filesInstalled'                => true,\n                'projectConfigurationInstalled' => true,\n                'moduleInstalled'               => true,\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/Install/Service/ProjectConfigurationGeneratorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\Install;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Install\\Service\\ProjectConfigurationGenerator;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ProjectConfigurationGeneratorTest extends TestCase\n{\n    private array $shops = [1, 2, 3];\n\n    public function testGenerateDefaultConfiguration(): void\n    {\n        $shopConfigurationDao = $this->createMock(ShopConfigurationDaoInterface::class);\n        $shopConfigurationDao\n            ->expects($this->exactly(3))\n            ->method('save');\n\n        $generator = new ProjectConfigurationGenerator($shopConfigurationDao, $this->getContext());\n        $generator->generate();\n    }\n\n    private function getContext(): ContextInterface\n    {\n        $context = $this->createStub(ContextInterface::class);\n        $context->method('getAllShopIds')->willReturn($this->shops);\n\n        return $context;\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/MetaData/Converter/MetaDataConverterAggregateTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\Converter\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Converter\\MetaDataConverterAggregate;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Converter\\MetaDataConverterInterface;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\TestCase;\n\n#[CoversClass(MetaDataConverterAggregate::class)]\nfinal class MetaDataConverterAggregateTest extends TestCase\n{\n    public function testConvert(): void\n    {\n        $metaData = ['some metadata contents'];\n        $converterStub = $this->createStub(MetaDataConverterInterface::class);\n        $converterStub->method('convert')->willReturn($metaData);\n\n        $metaDataFromConverter = (new MetaDataConverterAggregate($converterStub))->convert(['any']);\n        $this->assertSame($metaData, $metaDataFromConverter);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/MetaData/Converter/ModuleSettingsBooleanConverterTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\MetaData\\Converter;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Converter\\ModuleSettingsBooleanConverter;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataProvider;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ModuleSettingsBooleanConverterTest extends TestCase\n{\n    public static function convertToTrueDataProvider(): array\n    {\n        return [\n            ['true'],\n            ['True'],\n            ['1'],\n            [1],\n            [true],\n        ];\n    }\n\n    #[DataProvider('convertToTrueDataProvider')]\n    public function testConvertToTrue(string|int|bool $value): void\n    {\n        $metaData =\n            [\n                MetaDataProvider::METADATA_SETTINGS => [\n                    [\n                        'type' => 'bool', 'value' => $value\n                    ],\n                ]\n            ];\n        $converter = new ModuleSettingsBooleanConverter();\n\n        $convertedSettings = $converter->convert($metaData);\n        $this->assertTrue($convertedSettings[MetaDataProvider::METADATA_SETTINGS][0]['value']);\n    }\n\n    public static function convertToFalseDataProvider(): array\n    {\n        return [\n            ['false'],\n            ['False'],\n            ['0'],\n            [0],\n            [false],\n        ];\n    }\n\n    #[DataProvider('convertToFalseDataProvider')]\n    public function testConvertToFalse(string|int|bool $value): void\n    {\n        $metaData =\n            [\n                MetaDataProvider::METADATA_SETTINGS => [\n                    [\n                        'type' => 'bool', 'value' => $value\n                    ],\n                ]\n            ];\n        $converter = new ModuleSettingsBooleanConverter();\n\n        $convertedSettings = $converter->convert($metaData);\n        $this->assertFalse($convertedSettings[MetaDataProvider::METADATA_SETTINGS][0]['value']);\n    }\n\n    public static function whenNothingToConvertDataProvider(): array\n    {\n        return [\n            [[]],\n            [\n                [\n                    MetaDataProvider::METADATA_SETTINGS => [\n                        [\n                            'type' => 'str', 'value' => 'any'\n                        ],\n                    ]\n                ]\n            ],\n        ];\n    }\n\n    #[DataProvider('whenNothingToConvertDataProvider')]\n    public function testWhenNothingToConvert(array $metaData): void\n    {\n        $converter = new ModuleSettingsBooleanConverter();\n\n        $convertedSettings = $converter->convert($metaData);\n        $this->assertSame($metaData, $convertedSettings);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/MetaData/Dao/MetaDataNormalizerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\MetaData\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataNormalizer;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class MetaDataNormalizerTest extends TestCase\n{\n    public function testNormalizeMetaData(): void\n    {\n        $metaData =\n            [\n                'id'          => 'value1',\n                'settings'    => [\n                    [\n                        'constraints' => 'value1',\n                    ],\n                ],\n            ];\n        $expectedNormalizedData = [\n            'id'          => 'value1',\n            'settings'    => [\n                [\n                    'constraints' => ['value1'],\n                ],\n            ],\n        ];\n\n        $metaDataNormalizer = new MetaDataNormalizer();\n        $normalizedData = $metaDataNormalizer->normalizeData($metaData);\n\n        $this->assertEquals($expectedNormalizedData, $normalizedData);\n    }\n\n    public function testNormalizerConvertsModuleSettingConstraintsToArray(): void\n    {\n        $metadata = [\n            'settings' => [\n                ['constraints' => '1|2|3'],\n                ['constraints' => 'le|la|les'],\n            ]\n        ];\n\n        $this->assertSame(\n            [\n                'settings' => [\n                    ['constraints' => ['1', '2', '3']],\n                    ['constraints' => ['le', 'la', 'les']],\n                ]\n            ],\n            (new MetaDataNormalizer())->normalizeData($metadata)\n        );\n    }\n\n    #[DataProvider('multiLanguageFieldDataProvider')]\n    public function testNormalizerConvertsMultiLanguageFieldToArrayWithDefaultLanguageIfItIsString(string $fieldName, string $value): void\n    {\n        $metadata = [\n            $fieldName => $value,\n        ];\n\n        $this->assertSame(\n            [\n                $fieldName => [\n                    'en' => $value,\n                ]\n            ],\n            (new MetaDataNormalizer())->normalizeData($metadata)\n        );\n    }\n\n    #[DataProvider('multiLanguageFieldDataProvider')]\n    public function testNormalizerConvertsMultiLanguageFieldToArrayWithCustomLanguageIfItIsStringAndLangOptionIsSet(string $fieldName, string $value): void\n    {\n        $metadata = [\n            $fieldName => $value,\n            'lang'  => 'esperanto',\n        ];\n\n        $this->assertSame(\n            [\n                $fieldName => [\n                    'esperanto' => $value,\n                ],\n                'lang'  => 'esperanto',\n            ],\n            (new MetaDataNormalizer())->normalizeData($metadata)\n        );\n    }\n\n    public static function multiLanguageFieldDataProvider(): array\n    {\n        return [\n            ['title', 'some value'],\n            ['description', 'some value'],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/MetaData/Dao/MetaDataSchemataProviderTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\MetaData\\Dao;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataSchemataProvider;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\UnsupportedMetaDataVersionException;\nuse PHPUnit\\Framework\\TestCase;\n\n/**\n * Class MetaDataSchemataProviderTest\n *\n * @package OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\MetaData\\Dao\n */\nfinal class MetaDataSchemataProviderTest extends TestCase\n{\n    private array $metaDataSchemata;\n    private $schemaVersion20;\n    private $schemaVersion21;\n\n    public function testGetMetaDataSchemata(): void\n    {\n        $metaDataSchemata = new MetaDataSchemataProvider($this->metaDataSchemata);\n\n        $actualSchemata = $metaDataSchemata->getMetaDataSchemata();\n\n        $this->assertEquals($this->metaDataSchemata, $actualSchemata);\n    }\n\n    public function testGetMetadataSchemaForVersion(): void\n    {\n        $metaDataSchema = new MetaDataSchemataProvider($this->metaDataSchemata);\n        $actualSchema20 = $metaDataSchema->getMetaDataSchemaForVersion('2.0');\n        $actualSchema21 = $metaDataSchema->getMetaDataSchemaForVersion('2.1');\n\n        $this->assertEquals($this->schemaVersion20, $actualSchema20);\n        $this->assertEquals($this->schemaVersion21, $actualSchema21);\n    }\n\n    public function testGetFlippedMetadataSchemaForVersionThrowsExceptionOnUnsupportedVersion(): void\n    {\n        $this->expectException(UnsupportedMetaDataVersionException::class);\n        $unsupportedVersion = '0.0';\n        $metaDataSchema = new MetaDataSchemataProvider($this->metaDataSchemata);\n\n        $this->expectException(UnsupportedMetaDataVersionException::class);\n        $metaDataSchema->getFlippedMetaDataSchemaForVersion($unsupportedVersion);\n    }\n\n    public function testGetFlippedMetadataSchemaForVersion(): void\n    {\n        $expectedSchema20 = [\n            '20only'    => 0,\n            'subSchema' => [\n                'subKey1' => 0,\n                'subKey2' => 1\n            ],\n        ];\n        $metaDataSchema = new MetaDataSchemataProvider($this->metaDataSchemata);\n\n        $actualSchema20 = $metaDataSchema->getFlippedMetaDataSchemaForVersion('2.0');\n\n        $this->assertSame($expectedSchema20, $actualSchema20);\n    }\n\n    public function testGetMetadataSchemaForVersionThrowsExceptionOnUnsupportedVersion(): void\n    {\n        $this->expectException(UnsupportedMetaDataVersionException::class);\n        $unsupportedVersion = '0.0';\n        $metaDataSchema = new MetaDataSchemataProvider($this->metaDataSchemata);\n\n        $this->expectException(UnsupportedMetaDataVersionException::class);\n        $metaDataSchema->getMetaDataSchemaForVersion($unsupportedVersion);\n    }\n\n    protected function setUp(): void\n    {\n        parent::setUp();\n        $this->schemaVersion20 = [\n            '20only',\n            'subSchema' =>\n                ['subKey1',\n                 'subKey2',\n                ],\n        ];\n        $this->schemaVersion21 = [\n            '21only',\n            'subSchema' =>\n                ['subKey1',\n                 'subKey2',\n                ],\n        ];\n        $this->metaDataSchemata = [\n            '2.0' => $this->schemaVersion20,\n            '2.1' => $this->schemaVersion21,\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/MetaData/Validator/MetaDataSchemaValidatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\MetaData\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataProvider;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataSchemataProvider;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\UnsupportedMetaDataKeyException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\UnsupportedMetaDataValueTypeException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\UnsupportedMetaDataVersionException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator\\MetaDataSchemaValidator;\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\nuse PHPUnit\\Framework\\TestCase;\nuse stdClass;\n\nfinal class MetaDataSchemaValidatorTest extends TestCase\n{\n    private array $metaDataSchemata;\n    private $metaDataSchemaVersion20;\n    private $metaDataSchemaVersion21;\n\n    public function testValidateThrowsExceptionOnUnsupportedMetaDataVersion(): void\n    {\n        $this->expectException(UnsupportedMetaDataVersionException::class);\n        $metaDataToValidate = [];\n\n        $metaDataSchemata = new MetaDataSchemataProvider($this->metaDataSchemata);\n        $validator = new MetaDataSchemaValidator($metaDataSchemata);\n\n        $this->expectException(UnsupportedMetaDataVersionException::class);\n        $validator->validate('path/to/metadata.php', '1.2', $metaDataToValidate);\n    }\n\n    public function testValidateUnsupportedMetaDataKey(): void\n    {\n        $this->expectException(UnsupportedMetaDataKeyException::class);\n\n        $metaDataToValidate = [\n            'somePluginDirectories' => [],\n        ];\n\n        $metaDataSchemata = new MetaDataSchemataProvider($this->metaDataSchemata);\n        $validator = new MetaDataSchemaValidator($metaDataSchemata);\n\n        $validator->validate('path/to/metadata.php', '2.0', $metaDataToValidate);\n    }\n\n    /**\n     * This test covers metaData sections like 'blocks' or 'settings', which have their own well defined subKeys\n     */\n    public function testValidateUnsupportedMetaDataSubKey(): void\n    {\n        $this->expectException(UnsupportedMetaDataKeyException::class);\n\n        $metaDataToValidate = [\n            '20only'   => 'value',\n            'section1' => [\n                [\n                    'subkey1' => 'value1',\n                    'subkey2' => 'value1',\n                ],\n                [\n                    'subkey1'        => 'value2',\n                    'unsupportedKey' => 'value2',\n                ],\n            ]\n        ];\n\n        $metaDataSchemata = new MetaDataSchemataProvider($this->metaDataSchemata);\n        $validator = new MetaDataSchemaValidator($metaDataSchemata);\n\n        $validator->validate('path/to/metadata.php', '2.0', $metaDataToValidate);\n    }\n\n    /**\n     * This test covers metaData sections like 'extend', or 'templates', which have their custom subKeys\n     */\n    #[DoesNotPerformAssertions]\n    public function testExcludedSectionItemValidation(): void\n    {\n        $metaDataToValidate = [\n            '20only'                                             => 'value',\n            'section1'                                           => [\n                [\n                    'subKey1' => 'value1',\n                    'subKey2' => 'value1',\n                ],\n                [\n                    'subKey1' => 'value2',\n                    'subKey2' => 'value2',\n                ],\n            ],\n            MetaDataProvider::METADATA_EXTEND                    => [\n                'excludedsubkey1' => 'value2',\n                'excludedsubkey2' => 'value2',\n            ]\n        ];\n\n        $metaDataSchemata = new MetaDataSchemataProvider($this->metaDataSchemata);\n        $validator = new MetaDataSchemaValidator($metaDataSchemata);\n\n        $validator->validate('path/to/metadata.php', '2.0', $metaDataToValidate);\n    }\n\n    public function testValidateIsCaseSensitive(): void\n    {\n        $this->expectException(UnsupportedMetaDataKeyException::class);\n\n        $metaDataToValidate = [\n            '20ONLY'   => 'value', // This UPPERCASE key will not validate\n            'section1' => [\n                [\n                    'subkey1' => 'value1',\n                    'subkey2' => 'value1',\n                ],\n                [\n                    'subkey1' => 'value2',\n                    'subkey2' => 'value2',\n                ],\n            ]\n        ];\n\n        $metaDataSchemata = new MetaDataSchemataProvider($this->metaDataSchemata);\n        $validator = new MetaDataSchemaValidator($metaDataSchemata);\n\n        $validator->validate('path/to/metadata.php', '2.0', $metaDataToValidate);\n    }\n\n    public function testValidateThrowsExceptionOnUnsupportedMetaDataValueType(): void\n    {\n        $this->expectException(UnsupportedMetaDataValueTypeException::class);\n        $metaDataToValidate = [\n            '20only' => new stdClass(),\n        ];\n\n        $metaDataSchemata = new MetaDataSchemataProvider($this->metaDataSchemata);\n        $validator = new MetaDataSchemaValidator($metaDataSchemata);\n\n        $this->expectException(UnsupportedMetaDataValueTypeException::class);\n        $validator->validate('path/to/metadata.php', '2.0', $metaDataToValidate);\n    }\n\n    #[DoesNotPerformAssertions]\n    public function testValidateThrowsNoExceptionOnIncompleteFirstLevel(): void\n    {\n        $metaDataToValidate = [\n            // missing '20only'        => 'value',\n            'section1' => [\n                [\n                    'subKey1' => 'value1',\n                    'subKey2' => 'value1'\n                ],\n            ]\n        ];\n\n        $metaDataSchemata = new MetaDataSchemataProvider($this->metaDataSchemata);\n        $validator = new MetaDataSchemaValidator($metaDataSchemata);\n\n        $validator->validate('path/to/metadata.php', '2.0', $metaDataToValidate);\n    }\n\n    #[DoesNotPerformAssertions]\n    public function testValidateThrowsNoExceptionOnIncompleteSecondLevel(): void\n    {\n        $metaDataToValidate = [\n            '20only'   => 'value',\n            'section1' => [\n                [\n                    // missing 'subKey1' => 'value1',\n                    'subKey2' => 'value1'\n                ],\n            ]\n        ];\n\n        $metaDataSchemata = new MetaDataSchemataProvider($this->metaDataSchemata);\n        $validator = new MetaDataSchemaValidator($metaDataSchemata);\n\n        $validator->validate('path/to/metadata.php', '2.0', $metaDataToValidate);\n    }\n\n    protected function setUp(): void\n    {\n        parent::setUp();\n\n        $this->metaDataSchemaVersion20 = [\n            '20only',\n            'section1' =>\n                ['subKey1',\n                 'subKey2',\n                ],\n            'extend',\n        ];\n        $this->metaDataSchemaVersion21 = [\n            '21only',\n            'section1' =>\n                ['subKey1',\n                 'subKey2',\n                ],\n            'extend',\n        ];\n        $this->metaDataSchemata = [\n            '2.0' => $this->metaDataSchemaVersion20,\n            '2.1' => $this->metaDataSchemaVersion21,\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/MetaData/Validator/MetaDataValidatorAggregateTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\MetaData\\Validator;\n\nuse Exception;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator\\MetaDataValidatorAggregate;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator\\MetaDataValidatorInterface;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\TestCase;\n\n#[CoversClass(MetaDataValidatorAggregate::class)]\nfinal class MetaDataValidatorAggregateTest extends TestCase\n{\n    public function testValidate(): void\n    {\n        $this->expectException(Exception::class);\n\n        $validatorStub = $this->createStub(MetaDataValidatorInterface::class);\n        $validatorStub->method('validate')->willThrowException(new Exception());\n\n        (new MetaDataValidatorAggregate($validatorStub))->validate(['any']);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/MetaData/Validator/ModuleIdValidatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\MetaData\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataProvider;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\ModuleIdNotValidException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator\\ModuleIdValidator;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\nuse PHPUnit\\Framework\\TestCase;\n\n#[CoversClass(ModuleIdValidator::class)]\nfinal class ModuleIdValidatorTest extends TestCase\n{\n    #[DoesNotPerformAssertions]\n    public function testValidateWhenValid(): void\n    {\n        $metaData = [\n            MetaDataProvider::METADATA_ID => 'some_id'\n        ];\n\n        $validator = new ModuleIdValidator();\n        $validator->validate($metaData);\n    }\n\n    public static function validateInvalidIdProvidedDataProvider(): array\n    {\n        return [\n            [''],\n            [null],\n        ];\n    }\n\n    #[DataProvider('validateInvalidIdProvidedDataProvider')]\n    public function testValidateWhenInvalidIdProvided(?string $moduleId): void\n    {\n        $this->expectException(ModuleIdNotValidException::class);\n        $metaData = [\n            MetaDataProvider::METADATA_ID => $moduleId\n        ];\n\n        $validator = new ModuleIdValidator();\n        $validator->validate($metaData);\n    }\n\n    public function testValidateWhenIdNotProvided(): void\n    {\n        $this->expectException(ModuleIdNotValidException::class);\n        $metaData = [];\n\n        $validator = new ModuleIdValidator();\n        $validator->validate($metaData);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/MetaData/Validator/SchemaValidatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\MetaData\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataProvider;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataSchemataProvider;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\UnsupportedMetaDataKeyException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\UnsupportedMetaDataValueTypeException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\UnsupportedMetaDataVersionException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator\\MetaDataSchemaValidator;\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\nuse PHPUnit\\Framework\\TestCase;\nuse stdClass;\n\nfinal class SchemaValidatorTest extends TestCase\n{\n    private array $metaDataSchema = [\n        '2.1' => [\n            'key-in-schema-1',\n            'section-in-schema-1' =>\n                [\n                    'sub-key-in-schema-1',\n                    'sub-key-in-schema-2',\n                ],\n            'extend' => [],\n        ],\n    ];\n\n    #[DoesNotPerformAssertions]\n    public function testValidateWithMinimalValidStructure(): void\n    {\n        $this->getValidator()->validate('', '2.1', []);\n    }\n\n    public function testValidateThrowsExceptionOnUnsupportedMetaDataVersion(): void\n    {\n        $this->expectException(UnsupportedMetaDataVersionException::class);\n\n        $this->getValidator()->validate('', '1.2', []);\n    }\n\n    public function testValidateUnsupportedMetaDataKey(): void\n    {\n        $metaData = [\n            'moduleData' => [\n                'unsupported-key' => [],\n            ],\n        ];\n\n        $this->expectException(UnsupportedMetaDataKeyException::class);\n\n        $this->getValidator()->validate('', '2.1', $metaData);\n    }\n\n    public function testValidateWithUnsupportedMetaDataSubKey(): void\n    {\n        $metadata = [\n            'key-in-schema-1' => 'value',\n            'section-in-schema-1' => [\n                [\n                    'sub-key-in-schema-1' => 'value1',\n                    'sub-key-in-schema-2' => 'value1',\n                ],\n                [\n                    'sub-key1' => 'value2',\n                    'unsupported-sub-key' => 'value2',\n                ],\n            ],\n        ];\n\n        $this->expectException(UnsupportedMetaDataKeyException::class);\n\n        $this->getValidator()->validate('', '2.1', $metadata);\n    }\n\n    #[DoesNotPerformAssertions]\n    public function testValidateWithSectionExcludedFromValidation(): void\n    {\n        $sectionExcludedFromValidation = MetaDataProvider::METADATA_EXTEND;\n        $metadata = [\n            'key-in-schema-1' => 'value',\n            'section-in-schema-1' => [\n                [\n                    'sub-key-in-schema-1' => 'value1',\n                    'sub-key-in-schema-2' => 'value1',\n                ],\n            ],\n            $sectionExcludedFromValidation => [\n                'some-key' => 'value1',\n                'some-key-2' => ['some-sub-key-1' => 'some-value'],\n            ],\n        ];\n\n        $this->getValidator()->validate('', '2.1', $metadata);\n    }\n\n    public function testValidateWithKeyInWrongCaseWillFailValidation(): void\n    {\n        $this->expectException(UnsupportedMetaDataKeyException::class);\n\n        $metadata = [\n            'KEY-IN-SCHEMA-1' => 'value',\n        ];\n\n        $this->getValidator()->validate('', '2.1', $metadata);\n    }\n\n    public function testValidateWithNonScalarValueWillThrowException(): void\n    {\n        $unsupportedData = new stdClass();\n        $metadata = [\n            'key-in-schema-1' => $unsupportedData,\n        ];\n\n        $this->expectException(UnsupportedMetaDataValueTypeException::class);\n\n        $this->getValidator()->validate('', '2.1', $metadata);\n    }\n\n    private function getValidator(): MetaDataSchemaValidator\n    {\n        return new MetaDataSchemaValidator(\n            new MetaDataSchemataProvider(\n                $this->metaDataSchema\n            )\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/MetaData/Validator/ShopModuleSettingBooleanValidatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\MetaData\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Dao\\MetaDataProvider;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Exception\\SettingNotValidException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\MetaData\\Validator\\ModuleSettingBooleanValidator;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\nuse PHPUnit\\Framework\\TestCase;\n\n#[CoversClass(ModuleSettingBooleanValidator::class)]\nfinal class ShopModuleSettingBooleanValidatorTest extends TestCase\n{\n    public static function validationPassWithDataProvider(): array\n    {\n        return [\n            ['true'],\n            ['TRUE'],\n            ['false'],\n            [1],\n            ['1'],\n            ['0'],\n            [0],\n        ];\n    }\n\n    /**\n     *\n     * @deprecated   since v6.4.0 (2019-06-10);This is not recommended values for use,\n     *               only boolean values should be used.\n     */\n    #[DataProvider('validationPassWithDataProvider')]\n    #[DoesNotPerformAssertions]\n    public function testValidationPassWithBackwardsCompatibleValues(string|int $value): void\n    {\n        $this->executeValidationForBoolSetting($value);\n    }\n\n    public static function validationPassDataProvider(): array\n    {\n        return [\n            [false],\n            [true],\n        ];\n    }\n\n    #[DataProvider('validationPassDataProvider')]\n    #[DoesNotPerformAssertions]\n    public function testValidationPass(bool $value): void\n    {\n        $this->executeValidationForBoolSetting($value);\n    }\n\n    public static function validationFailsDataProvider(): array\n    {\n        return [\n            ['any random value'],\n            [''],\n            [11],\n        ];\n    }\n\n    #[DataProvider('validationFailsDataProvider')]\n    public function testValidationFails(string|int $value): void\n    {\n        $this->expectException(SettingNotValidException::class);\n        $this->executeValidationForBoolSetting($value);\n    }\n\n    #[DoesNotPerformAssertions]\n    public function testWhenStringTypeProvided(): void\n    {\n        $settings =\n            [\n                MetaDataProvider::METADATA_ID => 'test_id',\n                MetaDataProvider::METADATA_SETTINGS => [\n                    [\n                    'type' => 'str', 'value' => 'String value'\n                    ],\n                ]\n            ];\n        $validator = new ModuleSettingBooleanValidator();\n\n        $validator->validate($settings);\n    }\n\n    #[DoesNotPerformAssertions]\n    public function testWhenNoTypeProvided(): void\n    {\n        $settings =\n            [\n                MetaDataProvider::METADATA_ID => 'test_id',\n                MetaDataProvider::METADATA_SETTINGS => [\n                    [\n                        'value' => 'Any value'\n                    ],\n                ]\n            ];\n        $validator = new ModuleSettingBooleanValidator();\n\n        $validator->validate($settings);\n    }\n\n    private function executeValidationForBoolSetting(string|int|bool $value): void\n    {\n        $settings =\n            [\n                MetaDataProvider::METADATA_ID => 'test_id',\n                MetaDataProvider::METADATA_SETTINGS => [\n                    [\n                        'type' => 'bool', 'value' => $value\n                    ],\n                ]\n            ];\n\n        $validator = new ModuleSettingBooleanValidator();\n\n        $validator->validate($settings);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/Path/ModuleAssetsPathResolverTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\Path;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModuleAssetsPathResolver;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse PHPUnit\\Framework\\TestCase;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\n\nfinal class ModuleAssetsPathResolverTest extends TestCase\n{\n    use ProphecyTrait;\n\n    public function testGetAssetsPath(): void\n    {\n        $context = $this->prophesize(BasicContextInterface::class);\n        $context->getOutPath()->willReturn('outDirectory');\n\n        $pathResolver = new ModuleAssetsPathResolver($context->reveal());\n\n        $this->assertEquals(\n            'outDirectory/modules/moduleId',\n            $pathResolver->getAssetsPath('moduleId')\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/Path/ModulePathResolverTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\Path;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ModuleConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Path\\ModulePathResolver;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ModulePathResolverTest extends TestCase\n{\n    public function testGetFullModulePathFromConfiguration(): void\n    {\n        $context = $this->createStub(BasicContextInterface::class);\n        $context\n            ->method('getShopRootPath')\n            ->willReturn('shopRoot');\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration\n            ->setId('testModuleId')\n            ->setModuleSource('vendor/modulePath');\n\n        $moduleConfigurationDao = $this->createMock(ModuleConfigurationDaoInterface::class);\n        $moduleConfigurationDao\n            ->expects($this->once())\n            ->method('get')\n            ->with('testModuleId', 1)\n            ->willReturn($moduleConfiguration);\n\n        $pathResolver = new ModulePathResolver($moduleConfigurationDao, $context);\n\n        $this->assertSame(\n            'shopRoot/vendor/modulePath',\n            $pathResolver->getFullModulePathFromConfiguration('testModuleId', 1)\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/Setup/Validator/ClassExtensionsModuleSettingValidatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\Setup\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\ShopAdapterInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator\\ClassExtensionsValidator;\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\nuse PHPUnit\\Framework\\TestCase;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\ClassExtension;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Exception\\InvalidClassExtensionNamespaceException;\n\nfinal class ClassExtensionsModuleSettingValidatorTest extends TestCase\n{\n    #[DoesNotPerformAssertions]\n    public function testValidClassExtensionsModuleSetting(): void\n    {\n        $anyExistentClass = self::class;\n\n        $shopAdapter = $this->createStub(ShopAdapterInterface::class);\n        $shopAdapter\n            ->method('isNamespace')\n            ->willReturn(true);\n        $shopAdapter\n            ->method('isShopEditionNamespace')\n            ->willReturn(false);\n        $shopAdapter\n            ->method('isShopUnifiedNamespace')\n            ->willReturn(true);\n\n        $validator = new ClassExtensionsValidator($shopAdapter);\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->addClassExtension(new ClassExtension($anyExistentClass, 'moduleClass'));\n\n        $validator->validate($moduleConfiguration, 1);\n    }\n\n    public function testNamespaceOfPatchedClassMustNotBeShopEditionNamespace(): void\n    {\n        $this->expectException(InvalidClassExtensionNamespaceException::class);\n        $shopAdapter = $this->createStub(ShopAdapterInterface::class);\n        $shopAdapter\n            ->method('isNamespace')\n            ->willReturn(true);\n        $shopAdapter\n            ->method('isShopEditionNamespace')\n            ->willReturn(true);\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->addClassExtension(new ClassExtension('shopClass', 'moduleClass'));\n\n\n        $validator = new ClassExtensionsValidator($shopAdapter);\n        $this->expectException(InvalidClassExtensionNamespaceException::class);\n        $validator->validate($moduleConfiguration, 1);\n    }\n\n    public function testNamespaceOfPatchedClassIsShopUnifiedNamespaceButClassDoesNotExist(): void\n    {\n        $this->expectException(InvalidClassExtensionNamespaceException::class);\n        $shopAdapter = $this->createStub(ShopAdapterInterface::class);\n        $shopAdapter\n            ->method('isNamespace')\n            ->willReturn(true);\n        $shopAdapter\n            ->method('isShopEditionNamespace')\n            ->willReturn(false);\n        $shopAdapter\n            ->method('isShopUnifiedNamespace')\n            ->willReturn(true);\n\n        $validator = new ClassExtensionsValidator($shopAdapter);\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->addClassExtension(new ClassExtension('nonExistentClass', 'moduleClass'));\n\n        $this->expectException(InvalidClassExtensionNamespaceException::class);\n        $validator->validate($moduleConfiguration, 1);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/Setup/Validator/ControllersValidatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\Setup\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\Dao\\ShopConfigurationDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\Controller;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ShopConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Exception\\ControllersDuplicationModuleConfigurationException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator\\ControllersValidator;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\State\\ModuleStateServiceInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\ShopAdapterInterface;\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Log\\LoggerInterface;\n\nfinal class ControllersValidatorTest extends TestCase\n{\n    #[DoesNotPerformAssertions]\n    public function testValidationWithCorrectSetting(): void\n    {\n        $shopAdapter = $this->createStub(ShopAdapterInterface::class);\n        $shopAdapter\n            ->method('getShopControllerClassMap')\n            ->willReturn([\n                'shopControllerName' => 'shopControllerNamespace',\n            ]);\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->setId('moduleId');\n        $moduleConfiguration->addController(new Controller('alreadyActiveModuleControllerName', 'alreadyActiveModuleControllerNamespace'));\n\n        $shopConfiguration = new ShopConfiguration();\n        $shopConfiguration->addModuleConfiguration($moduleConfiguration);\n\n        $shopConfigurationSettingDao = $this->createStub(ShopConfigurationDaoInterface::class);\n        $shopConfigurationSettingDao\n            ->method('get')\n            ->willReturn($shopConfiguration);\n\n        $moduleStateService = $this->createStub(ModuleStateServiceInterface::class);\n        $moduleStateService->method('isActive')->willReturn(true);\n\n        $validator = new ControllersValidator(\n            $shopAdapter,\n            $shopConfigurationSettingDao,\n            $this->createStub(LoggerInterface::class),\n        );\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->addController(\n            new Controller(\n                'newModuleControllerName',\n                'newModuleControllerNamespace'\n            )\n        );\n\n        $validator->validate($moduleConfiguration, 1);\n    }\n\n    public function testValidationWithDuplicatedControllerNamespace(): void\n    {\n        $this->expectException(ControllersDuplicationModuleConfigurationException::class);\n\n        $shopAdapter = $this->createStub(ShopAdapterInterface::class);\n        $shopAdapter\n            ->method('getShopControllerClassMap')\n            ->willReturn([\n                'anotherModuleControllerId' => 'duplicatedNamespace',\n            ]);\n\n        $validator = new ControllersValidator(\n            $shopAdapter,\n            $this->createStub(ShopConfigurationDaoInterface::class),\n            $this->createStub(LoggerInterface::class),\n        );\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->addController(\n            new Controller('someId', 'duplicatedNamespace')\n        );\n\n        $validator->validate($moduleConfiguration, 1);\n    }\n\n    public function testValidationWithDuplicatedControllerId(): void\n    {\n        $this->expectException(ControllersDuplicationModuleConfigurationException::class);\n\n        $shopAdapter = $this->createStub(ShopAdapterInterface::class);\n        $shopAdapter\n            ->method('getShopControllerClassMap')\n            ->willReturn([\n                'duplicatedid' => 'anotherModuleNamespace',\n            ]);\n\n        $validator = new ControllersValidator(\n            $shopAdapter,\n            $this->createStub(ShopConfigurationDaoInterface::class),\n            $this->createStub(LoggerInterface::class),\n        );\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->addController(\n            new Controller('duplicatedId', 'controllerNamespace')\n        );\n\n        $validator->validate($moduleConfiguration, 1);\n    }\n\n    public function testValidatorLogsErrorIfModuleControllerAlreadyExistsInControllersMap(): void\n    {\n        $shopAdapter = $this->createStub(ShopAdapterInterface::class);\n        $shopAdapter\n            ->method('getShopControllerClassMap')\n            ->willReturn([\n                'sameid' => 'sameNamespace',\n            ]);\n\n        $logger = $this->getMockBuilder(LoggerInterface::class)->getMock();\n        $logger->expects($this->once())->method('error');\n\n        $validator = new ControllersValidator(\n            $shopAdapter,\n            $this->createStub(ShopConfigurationDaoInterface::class),\n            $logger,\n        );\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->setId('testModule');\n        $moduleConfiguration->addController(\n            new Controller('sameId', 'sameNamespace')\n        );\n\n        $validator->validate($moduleConfiguration, 1);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/Setup/Validator/EventsModuleSettingValidatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\Setup\\Validator;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Configuration\\DataObject\\ModuleConfiguration\\Event;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Exception\\ModuleSettingNotValidException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Setup\\Validator\\EventsValidator;\nuse OxidEsales\\EshopCommunity\\Tests\\Integration\\Internal\\Framework\\Module\\TestData\\TestModule\\ModuleEvents;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class EventsModuleSettingValidatorTest extends TestCase\n{\n    #[DoesNotPerformAssertions]\n    public function testValidate(): void\n    {\n        $validator = $this->createValidator();\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->addEvent(new Event('onActivate', ModuleEvents::class . '::onActivate'));\n        $moduleConfiguration->addEvent(new Event('onDeactivate', ModuleEvents::class . '::onDeactivate'));\n\n        $validator->validate($moduleConfiguration, 1);\n    }\n\n    public function testValidateThrowsExceptionIfEventsDefinedAreNotCallable(): void\n    {\n        $this->expectException(ModuleSettingNotValidException::class);\n        $validator = $this->createValidator();\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->addEvent(new Event('onActivate', 'SomeNamespace\\\\class::noCallableMethod'));\n        $moduleConfiguration->addEvent(new Event('onDeactivate', 'SomeNamespace\\\\class::noCallableMethod'));\n\n        $this->expectException(ModuleSettingNotValidException::class);\n        $validator->validate($moduleConfiguration, 1);\n    }\n\n    /**\n     * @throws ModuleSettingNotValidException\n     */\n    #[DataProvider('invalidEventsProvider')]\n    #[DoesNotPerformAssertions]\n    public function testValidateDoesNotValidateSyntax(Event $invalidEvent): void\n    {\n        $validator = $this->createValidator();\n\n        $moduleConfiguration = new ModuleConfiguration();\n        $moduleConfiguration->addEvent($invalidEvent);\n\n        $validator->validate($moduleConfiguration, 1);\n    }\n\n    public static function invalidEventsProvider(): array\n    {\n        return [\n            [new Event('invalidEvent', 'noCallableMethod')],\n            [new Event('', '')]\n        ];\n    }\n\n    private function createValidator(): EventsValidator\n    {\n        return new EventsValidator();\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Module/Template/Locator/ModulesMenuFileLocatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Module\\Template\\Locator;\n\nuse Prophecy\\Prophecy\\ObjectProphecy;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Facade\\ActiveModulesDataProviderInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Module\\Template\\Locator\\ModulesMenuFileLocator;\nuse PHPUnit\\Framework\\TestCase;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\nuse Symfony\\Component\\Filesystem\\Filesystem;\n\nfinal class ModulesMenuFileLocatorTest extends TestCase\n{\n    use ProphecyTrait;\n\n    /** @var ActiveModulesDataProviderInterface */\n    private ObjectProphecy $activeModulesDataProvider;\n    /** @var Filesystem */\n    private ObjectProphecy $filesystem;\n\n    protected function setUp(): void\n    {\n        parent::setUp();\n\n        $this->activeModulesDataProvider = $this->prophesize(ActiveModulesDataProviderInterface::class);\n        $this->filesystem = $this->prophesize(Filesystem::class);\n    }\n\n    public function testLocateWithEmptyPaths(): void\n    {\n        $this->activeModulesDataProvider->getModulePaths()->willReturn([]);\n\n        $paths = (new ModulesMenuFileLocator($this->activeModulesDataProvider->reveal(), $this->filesystem->reveal()))\n            ->locate();\n\n        $this->assertSame([], $paths);\n    }\n\n    public function testLocateWithActiveModuleAndExistingFile(): void\n    {\n        $modulePath = 'some-active-module/path';\n        $menuPath = \"$modulePath/menu.xml\";\n        $this->activeModulesDataProvider->getModulePaths()->willReturn([$modulePath]);\n        $this->filesystem->exists($menuPath)->willReturn(true);\n\n        $paths = (new ModulesMenuFileLocator($this->activeModulesDataProvider->reveal(), $this->filesystem->reveal()))\n            ->locate();\n\n        $this->assertSame([$menuPath], $paths);\n    }\n\n    public function testLocateWithActiveModuleAndMissingFile(): void\n    {\n        $modulePath = 'some-active-module/path';\n        $menuPath = \"$modulePath/menu.xml\";\n        $this->activeModulesDataProvider->getModulePaths()->willReturn([$modulePath]);\n        $this->filesystem->exists($menuPath)->willReturn(false);\n\n        $paths = (new ModulesMenuFileLocator($this->activeModulesDataProvider->reveal(), $this->filesystem->reveal()))\n            ->locate();\n\n        $this->assertSame([], $paths);\n    }\n\n    public function testLocateWithMultipleFiles(): void\n    {\n        $modulePath1 = 'some-active-module/path-1';\n        $menuPath1 = \"$modulePath1/menu.xml\";\n        $modulePath2 = 'some-active-module/path-2';\n        $menuPath2 = \"$modulePath2/menu.xml\";\n        $modulePath3 = 'some-active-module/path-3';\n        $menuPath3 = \"$modulePath3/menu.xml\";\n        $this->activeModulesDataProvider->getModulePaths()->willReturn([\n            $modulePath1,\n            $modulePath2,\n            $modulePath3,\n        ]);\n        $this->filesystem->exists($menuPath1)->willReturn(true);\n        $this->filesystem->exists($menuPath2)->willReturn(false);\n        $this->filesystem->exists($menuPath3)->willReturn(true);\n\n        $paths = (new ModulesMenuFileLocator($this->activeModulesDataProvider->reveal(), $this->filesystem->reveal()))\n            ->locate();\n\n        $this->assertSame([$menuPath1, $menuPath3], $paths);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/RateLimiter/ApiRateLimitListenerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\RateLimiter;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\RateLimiter\\ApiRateLimiterFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\RateLimiter\\ApiRateLimitListener;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\RateLimiter\\ClientIdentifierProvider;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContext;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\HttpFoundation\\Request;\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Symfony\\Component\\HttpKernel\\Event\\RequestEvent;\nuse Symfony\\Component\\HttpKernel\\HttpKernelInterface;\nuse Symfony\\Component\\HttpKernel\\KernelEvents;\n\nclass ApiRateLimitListenerTest extends TestCase\n{\n    private string $testId;\n\n    protected function setUp(): void\n    {\n        $this->testId = uniqid();\n    }\n\n    public function testGetSubscribedEvents(): void\n    {\n        $this->assertArrayHasKey(KernelEvents::REQUEST, ApiRateLimitListener::getSubscribedEvents());\n    }\n\n    public function testIgnoresNonApiRequests(): void\n    {\n        $listener = $this->createListener(limit: 1);\n\n        $request = Request::create('/shop/home');\n        $event = $this->createRequestEvent($request);\n\n        $listener->onKernelRequest($event);\n\n        $this->assertFalse($event->hasResponse());\n    }\n\n    public function testAllowsApiRequestsWithinLimit(): void\n    {\n        $listener = $this->createListener(limit: 10);\n\n        $request = Request::create('/api/test');\n        $event = $this->createRequestEvent($request);\n\n        $listener->onKernelRequest($event);\n\n        $this->assertFalse($event->hasResponse());\n        $this->assertTrue($request->attributes->has('_rate_limit_info'));\n    }\n\n    public function testBlocksApiRequestsExceedingLimit(): void\n    {\n        $listener = $this->createListener(limit: 2);\n        $ip = $this->uniqueIp();\n\n        for ($i = 0; $i < 2; $i++) {\n            $request = Request::create('/api/test', 'GET', [], [], [], ['REMOTE_ADDR' => $ip]);\n            $event = $this->createRequestEvent($request);\n            $listener->onKernelRequest($event);\n            $this->assertFalse($event->hasResponse());\n        }\n\n        $request = Request::create('/api/test', 'GET', [], [], [], ['REMOTE_ADDR' => $ip]);\n        $event = $this->createRequestEvent($request);\n        $listener->onKernelRequest($event);\n\n        $this->assertTrue($event->hasResponse());\n        $this->assertSame(Response::HTTP_TOO_MANY_REQUESTS, $event->getResponse()->getStatusCode());\n    }\n\n    public function testDisabledRateLimiterAllowsAllRequests(): void\n    {\n        $listener = $this->createListener(enabled: false, limit: 1);\n        $ip = $this->uniqueIp();\n\n        for ($i = 0; $i < 5; $i++) {\n            $request = Request::create('/api/test', 'GET', [], [], [], ['REMOTE_ADDR' => $ip]);\n            $event = $this->createRequestEvent($request);\n            $listener->onKernelRequest($event);\n            $this->assertFalse($event->hasResponse());\n        }\n    }\n\n    public function testExcludedRoutesAreNotRateLimited(): void\n    {\n        $listener = $this->createListener(limit: 1, excludedRoutes: ['/api/health']);\n        $ip = $this->uniqueIp();\n\n        for ($i = 0; $i < 5; $i++) {\n            $request = Request::create('/api/health', 'GET', [], [], [], ['REMOTE_ADDR' => $ip]);\n            $event = $this->createRequestEvent($request);\n            $listener->onKernelRequest($event);\n            $this->assertFalse($event->hasResponse());\n        }\n    }\n\n    public function testExcludedRoutesWithWildcardPattern(): void\n    {\n        $listener = $this->createListener(limit: 1, excludedRoutes: ['/api/public/*']);\n        $ip = $this->uniqueIp();\n\n        for ($i = 0; $i < 5; $i++) {\n            $request = Request::create('/api/public/anything', 'GET', [], [], [], ['REMOTE_ADDR' => $ip]);\n            $event = $this->createRequestEvent($request);\n            $listener->onKernelRequest($event);\n            $this->assertFalse($event->hasResponse());\n        }\n    }\n\n    public function testWildcardPatternDoesNotMatchExactPath(): void\n    {\n        $listener = $this->createListener(limit: 1, excludedRoutes: ['/api/public/*']);\n        $ip = $this->uniqueIp();\n\n        $request1 = Request::create('/api/public', 'GET', [], [], [], ['REMOTE_ADDR' => $ip]);\n        $event1 = $this->createRequestEvent($request1);\n        $listener->onKernelRequest($event1);\n        $this->assertFalse($event1->hasResponse());\n\n        $request2 = Request::create('/api/public', 'GET', [], [], [], ['REMOTE_ADDR' => $ip]);\n        $event2 = $this->createRequestEvent($request2);\n        $listener->onKernelRequest($event2);\n        $this->assertTrue($event2->hasResponse());\n    }\n\n    public function testNonExcludedRoutesAreRateLimited(): void\n    {\n        $listener = $this->createListener(limit: 1, excludedRoutes: ['/api/public/*']);\n        $ip = $this->uniqueIp();\n\n        $request1 = Request::create('/api/private/data', 'GET', [], [], [], ['REMOTE_ADDR' => $ip]);\n        $event1 = $this->createRequestEvent($request1);\n        $listener->onKernelRequest($event1);\n        $this->assertFalse($event1->hasResponse());\n\n        $request2 = Request::create('/api/private/data', 'GET', [], [], [], ['REMOTE_ADDR' => $ip]);\n        $event2 = $this->createRequestEvent($request2);\n        $listener->onKernelRequest($event2);\n        $this->assertTrue($event2->hasResponse());\n    }\n\n    public function testRateLimitResponseContainsRetryAfterHeaders(): void\n    {\n        $listener = $this->createListener(limit: 1);\n        $ip = $this->uniqueIp();\n\n        $request1 = Request::create('/api/test', 'GET', [], [], [], ['REMOTE_ADDR' => $ip]);\n        $event1 = $this->createRequestEvent($request1);\n        $listener->onKernelRequest($event1);\n\n        $request2 = Request::create('/api/test', 'GET', [], [], [], ['REMOTE_ADDR' => $ip]);\n        $event2 = $this->createRequestEvent($request2);\n        $listener->onKernelRequest($event2);\n\n        $this->assertTrue($event2->hasResponse());\n        $response = $event2->getResponse();\n        $this->assertTrue($response->headers->has('Retry-After'));\n        $this->assertTrue($response->headers->has('X-RateLimit-Reset'));\n    }\n\n    public function testDifferentClientsHaveSeparateLimits(): void\n    {\n        $listener = $this->createListener(limit: 1);\n\n        $request1 = Request::create('/api/test', 'GET', [], [], [], ['REMOTE_ADDR' => $this->uniqueIp()]);\n        $event1 = $this->createRequestEvent($request1);\n        $listener->onKernelRequest($event1);\n        $this->assertFalse($event1->hasResponse());\n\n        $request2 = Request::create('/api/test', 'GET', [], [], [], ['REMOTE_ADDR' => $this->uniqueIp()]);\n        $event2 = $this->createRequestEvent($request2);\n        $listener->onKernelRequest($event2);\n        $this->assertFalse($event2->hasResponse());\n    }\n\n    public function testIgnoresSubRequests(): void\n    {\n        $listener = $this->createListener(limit: 1);\n\n        $request = Request::create('/api/test');\n        $event = $this->createRequestEvent($request, HttpKernelInterface::SUB_REQUEST);\n\n        $listener->onKernelRequest($event);\n\n        $this->assertFalse($event->hasResponse());\n        $this->assertFalse($request->attributes->has('_rate_limit_info'));\n    }\n\n    public function testHandlesNullClientIp(): void\n    {\n        $listener = $this->createListener(limit: 100);\n\n        $request = Request::create('/api/test');\n        Request::setTrustedProxies([], Request::HEADER_X_FORWARDED_FOR);\n\n        $event = $this->createRequestEvent($request);\n        $listener->onKernelRequest($event);\n\n        $this->assertFalse($event->hasResponse());\n        $this->assertTrue($request->attributes->has('_rate_limit_info'));\n    }\n\n    private function createListener(\n        bool $enabled = true,\n        int $limit = 100,\n        array $excludedRoutes = []\n    ): ApiRateLimitListener {\n        $factory = new ApiRateLimiterFactory($limit, 60, 'token_bucket', new BasicContext());\n\n        return new ApiRateLimitListener($enabled, $excludedRoutes, $factory, new ClientIdentifierProvider());\n    }\n\n    private function createRequestEvent(\n        Request $request,\n        int $requestType = HttpKernelInterface::MAIN_REQUEST\n    ): RequestEvent {\n        $kernel = $this->createStub(HttpKernelInterface::class);\n        return new RequestEvent($kernel, $request, $requestType);\n    }\n\n    private function uniqueIp(): string\n    {\n        return $this->testId . '_' . uniqid();\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/RateLimiter/ApiRateLimiterFactoryTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\RateLimiter;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\RateLimiter\\ApiRateLimiterFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContext;\nuse PHPUnit\\Framework\\TestCase;\n\nclass ApiRateLimiterFactoryTest extends TestCase\n{\n    public function testCreateReturnsLimiter(): void\n    {\n        $factory = $this->createFactory(limit: 100, interval: 60, policy: 'token_bucket');\n\n        $this->assertTrue($factory->create('test_client_' . uniqid())->consume()->isAccepted());\n    }\n\n    public function testCreateSharesStateForSameIdentifier(): void\n    {\n        $factory = $this->createFactory(limit: 2, interval: 60, policy: 'token_bucket');\n        $clientId = 'same_client_' . uniqid();\n\n        $factory->create($clientId)->consume();\n        $factory->create($clientId)->consume();\n\n        $this->assertFalse($factory->create($clientId)->consume()->isAccepted());\n    }\n\n    public function testLimiterRejectsRequestsExceedingLimit(): void\n    {\n        $factory = $this->createFactory(limit: 3, interval: 60, policy: 'token_bucket');\n        $clientId = 'test_client_' . uniqid();\n\n        $this->assertTrue($factory->create($clientId)->consume()->isAccepted());\n        $this->assertTrue($factory->create($clientId)->consume()->isAccepted());\n        $this->assertTrue($factory->create($clientId)->consume()->isAccepted());\n        $this->assertFalse($factory->create($clientId)->consume()->isAccepted());\n    }\n\n    public function testSlidingWindowPolicy(): void\n    {\n        $factory = $this->createFactory(limit: 3, interval: 60, policy: 'sliding_window');\n        $clientId = 'test_client_' . uniqid();\n\n        $this->assertTrue($factory->create($clientId)->consume()->isAccepted());\n        $this->assertTrue($factory->create($clientId)->consume()->isAccepted());\n        $this->assertTrue($factory->create($clientId)->consume()->isAccepted());\n        $this->assertFalse($factory->create($clientId)->consume()->isAccepted());\n    }\n\n    private function createFactory(int $limit, int $interval, string $policy): ApiRateLimiterFactory\n    {\n        return new ApiRateLimiterFactory($limit, $interval, $policy, new BasicContext());\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/RateLimiter/RateLimitHeadersListenerTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\RateLimiter;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\RateLimiter\\RateLimitHeadersListener;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\HttpFoundation\\Request;\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Symfony\\Component\\HttpKernel\\Event\\ResponseEvent;\nuse Symfony\\Component\\HttpKernel\\HttpKernelInterface;\nuse Symfony\\Component\\HttpKernel\\KernelEvents;\n\nclass RateLimitHeadersListenerTest extends TestCase\n{\n    public function testGetSubscribedEvents(): void\n    {\n        $events = RateLimitHeadersListener::getSubscribedEvents();\n\n        $this->assertArrayHasKey(KernelEvents::RESPONSE, $events);\n        $this->assertSame(['onKernelResponse', -10], $events[KernelEvents::RESPONSE]);\n    }\n\n    public function testIgnoresSubRequests(): void\n    {\n        $listener = new RateLimitHeadersListener();\n\n        $request = Request::create('/api/test');\n        $request->attributes->set('_rate_limit_info', [\n            'limit' => 100,\n            'remaining' => 99,\n            'reset' => time() + 60,\n        ]);\n\n        $response = new Response();\n        $event = $this->createResponseEvent($request, $response, HttpKernelInterface::SUB_REQUEST);\n\n        $listener->onKernelResponse($event);\n\n        $this->assertFalse($response->headers->has('X-RateLimit-Limit'));\n        $this->assertFalse($response->headers->has('X-RateLimit-Remaining'));\n        $this->assertFalse($response->headers->has('X-RateLimit-Reset'));\n    }\n\n    public function testIgnoresRequestsWithoutRateLimitInfo(): void\n    {\n        $listener = new RateLimitHeadersListener();\n\n        $request = Request::create('/api/test');\n        $response = new Response();\n        $event = $this->createResponseEvent($request, $response);\n\n        $listener->onKernelResponse($event);\n\n        $this->assertFalse($response->headers->has('X-RateLimit-Limit'));\n        $this->assertFalse($response->headers->has('X-RateLimit-Remaining'));\n        $this->assertFalse($response->headers->has('X-RateLimit-Reset'));\n    }\n\n    public function testAddsRateLimitHeadersToResponse(): void\n    {\n        $listener = new RateLimitHeadersListener();\n\n        $resetTime = time() + 60;\n        $request = Request::create('/api/test');\n        $request->attributes->set('_rate_limit_info', [\n            'limit' => 100,\n            'remaining' => 95,\n            'reset' => $resetTime,\n        ]);\n\n        $response = new Response();\n        $event = $this->createResponseEvent($request, $response);\n\n        $listener->onKernelResponse($event);\n\n        $this->assertTrue($response->headers->has('X-RateLimit-Limit'));\n        $this->assertTrue($response->headers->has('X-RateLimit-Remaining'));\n        $this->assertTrue($response->headers->has('X-RateLimit-Reset'));\n\n        $this->assertSame('100', $response->headers->get('X-RateLimit-Limit'));\n        $this->assertSame('95', $response->headers->get('X-RateLimit-Remaining'));\n        $this->assertSame((string) $resetTime, $response->headers->get('X-RateLimit-Reset'));\n    }\n\n    private function createResponseEvent(\n        Request $request,\n        Response $response,\n        int $requestType = HttpKernelInterface::MAIN_REQUEST\n    ): ResponseEvent {\n        $kernel = $this->createStub(HttpKernelInterface::class);\n        return new ResponseEvent($kernel, $request, $requestType, $response);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Templating/Locator/AdminTemplateFileLocatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Templating\\Locator;\n\nuse OxidEsales\\Eshop\\Core\\Config;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator\\AdminTemplateFileLocator;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class AdminTemplateFileLocatorTest extends TestCase\n{\n    public function testLocate(): void\n    {\n        $templateName = 'test_template.tpl';\n        $locator = new AdminTemplateFileLocator($this->getConfigMock($templateName));\n        $this->assertSame('pathToTpl/' . $templateName, $locator->locate($templateName));\n    }\n\n    private function getConfigMock(string $templateName): Config\n    {\n        $config = $this\n            ->getMockBuilder(Config::class)\n            ->getMock();\n        $config\n            ->method('getTemplatePath')\n            ->with($templateName, true)\n            ->willReturn('pathToTpl/' . $templateName);\n\n        return $config;\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Templating/Locator/EditionMenuFileLocatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Templating\\Locator;\n\nuse org\\bovigo\\vfs\\vfsStream;\nuse org\\bovigo\\vfs\\vfsStreamDirectory;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition\\Edition;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator\\EditionMenuFileLocator;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Bridge\\AdminThemeBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\BasicContextStub;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Filesystem\\Filesystem;\n\nfinal class EditionMenuFileLocatorTest extends TestCase\n{\n    private vfsStreamDirectory $vfsStreamDirectory;\n\n    #[DataProvider('dataProviderTestLocate')]\n    public function testLocate(string $edition): void\n    {\n        $this->createModuleStructure($edition);\n        $locator = new EditionMenuFileLocator(\n            $this->getAdminThemeStub(),\n            $this->getContext($edition),\n            new Filesystem()\n        );\n\n        $expectedPath = $this->vfsStreamDirectory->url() .\n            '/testSourcePath' .\n            $edition .\n            '/Application/views/admin/menu.xml';\n        $this->assertSame([$expectedPath], $locator->locate());\n    }\n\n    public static function dataProviderTestLocate(): array\n    {\n        return [\n            ['CE'],\n            ['PE'],\n            ['EE'],\n        ];\n    }\n\n    private function getAdminThemeStub(): AdminThemeBridgeInterface\n    {\n        $adminTheme = $this->createStub(AdminThemeBridgeInterface::class);\n        $adminTheme->method('getActiveTheme')->willReturn('admin');\n\n        return $adminTheme;\n    }\n\n    private function getContext(string $edition): BasicContextStub\n    {\n        $context = new BasicContextStub();\n        $context->setEdition(Edition::from($edition));\n        $context->setSourcePath($this->vfsStreamDirectory->url() . '/testSourcePathCE');\n        $context->setProfessionalEditionSourcePath($this->vfsStreamDirectory->url() . '/testSourcePathPE');\n        $context->setEnterpriseEditionSourcePath($this->vfsStreamDirectory->url() . '/testSourcePathEE');\n\n        return $context;\n    }\n\n    private function createModuleStructure(string $edition): void\n    {\n        $shopPath = 'testSourcePath' . $edition;\n        $structure = [\n            $shopPath => [\n                'Application' => [\n                    'views' => [\n                        'admin' => [\n                            'menu.xml' => '*this is menu xml for test*'\n                        ]\n                    ]\n                ]\n            ]\n        ];\n        $this->vfsStreamDirectory = vfsStream::setup('root', null, $structure);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Templating/Locator/EditionUserFileLocatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Templating\\Locator;\n\nuse org\\bovigo\\vfs\\vfsStream;\nuse org\\bovigo\\vfs\\vfsStreamDirectory;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Edition\\Edition;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator\\EditionUserFileLocator;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Bridge\\AdminThemeBridgeInterface;\nuse OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\BasicContextStub;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Filesystem\\Filesystem;\n\nfinal class EditionUserFileLocatorTest extends TestCase\n{\n    private vfsStreamDirectory $vfsStreamDirectory;\n\n    #[DataProvider('dataProviderTestLocate')]\n    public function testLocate(string $edition): void\n    {\n        $this->createModuleStructure($edition);\n        $locator = new EditionUserFileLocator(\n            $this->getAdminThemeStub(),\n            $this->getContext($edition),\n            new Filesystem()\n        );\n\n        $expectedPath = $this->vfsStreamDirectory->url() .\n            '/testSourcePath' .\n            $edition .\n            '/Application/views/admin/user.xml';\n        $this->assertSame([$expectedPath], $locator->locate());\n    }\n\n    public static function dataProviderTestLocate(): array\n    {\n        return [\n            ['CE'],\n            ['PE'],\n            ['EE'],\n        ];\n    }\n\n    private function getAdminThemeStub(): AdminThemeBridgeInterface\n    {\n        $adminTheme = $this->createStub(AdminThemeBridgeInterface::class);\n        $adminTheme->method('getActiveTheme')->willReturn('admin');\n\n        return $adminTheme;\n    }\n\n    private function getContext(string $edition): BasicContextStub\n    {\n        $context = new BasicContextStub();\n        $context->setEdition(Edition::from($edition));\n        $context->setCommunityEditionSourcePath($this->vfsStreamDirectory->url() . '/testSourcePathCE');\n        $context->setProfessionalEditionSourcePath($this->vfsStreamDirectory->url() . '/testSourcePathPE');\n        $context->setEnterpriseEditionSourcePath($this->vfsStreamDirectory->url() . '/testSourcePathEE');\n\n        return $context;\n    }\n\n    private function createModuleStructure(string $edition): void\n    {\n        $shopPath = 'testSourcePath' . $edition;\n        $structure = [\n            $shopPath => [\n                'Application' => [\n                    'views' => [\n                        'admin' => [\n                            'user.xml' => '*this is menu xml for test*'\n                        ]\n                    ]\n                ]\n            ]\n        ];\n\n        $this->vfsStreamDirectory = vfsStream::setup('root', null, $structure);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Templating/Locator/TemplateFileLocatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Templating\\Locator;\n\nuse OxidEsales\\Eshop\\Core\\Config;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\Locator\\TemplateFileLocator;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class TemplateFileLocatorTest extends TestCase\n{\n    public function testLocate(): void\n    {\n        $templateName = 'test_template.tpl';\n        $locator = new TemplateFileLocator($this->getConfigMock($templateName));\n        $this->assertSame('pathToTpl/' . $templateName, $locator->locate($templateName));\n    }\n\n    private function getConfigMock(string $templateName): Config\n    {\n        $config = $this\n            ->getMockBuilder(Config::class)\n            ->getMock();\n        $config\n            ->method('getTemplatePath')\n            ->with($templateName, false)\n            ->willReturn('pathToTpl/' . $templateName);\n\n        return $config;\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Templating/TemplateRendererTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Templating;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateEngineInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Templating\\TemplateRenderer;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\ContextInterface;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class TemplateRendererTest extends TestCase\n{\n    #[DataProvider('twigTemplateNameFileDataProvider')]\n    public function testRenderTemplateFilenameExtension(string $filename, string $expectedFilename): void\n    {\n        $engine = $this->getEngineStub();\n        $engine->method('render')\n            ->willReturnCallback(function ($templateName) {\n                return $templateName;\n            });\n\n        $renderer = new TemplateRenderer($engine, $this->getContextStub(), 'html.twig');\n\n        $this->assertSame($expectedFilename, $renderer->renderTemplate($filename, []));\n    }\n\n    public function testRenderTemplateCallsRender(): void\n    {\n        $response = 'rendered template';\n        $engine = $this->getEngineMock();\n        $engine->expects($this->once())\n            ->method('render')\n            ->with('template.html.twig')\n            ->willReturn($response);\n\n        $renderer = new TemplateRenderer($engine, $this->getContextStub(), 'html.twig');\n\n        $this->assertSame($response, $renderer->renderTemplate('template', []));\n    }\n\n    public function testRenderFragment(): void\n    {\n        $response = 'rendered template';\n        $engine = $this->getEngineMock();\n        $engine->expects($this->once())\n            ->method('renderFragment')\n            ->with('template')\n            ->willReturn($response);\n\n        $renderer = new TemplateRenderer($engine, $this->getContextStub(), 'html.twig');\n\n        $this->assertSame($response, $renderer->renderFragment('template', 'testId', []));\n    }\n\n    public function testRenderFragmentIfDemoShop(): void\n    {\n        $engine = $this->getEngineMock();\n        $engine->expects($this->never())\n            ->method('renderFragment')\n            ->with('template');\n\n        $context = $this->getContextMock();\n        $context->expects($this->once())\n            ->method('isShopInDemoMode')\n            ->willReturn(true);\n        $renderer = new TemplateRenderer($engine, $context, 'html.twig');\n\n        $this->assertSame('template', $renderer->renderFragment('template', 'testId', []));\n    }\n\n    public function testGetExistingEngine(): void\n    {\n        $engine = $this->getEngineStub();\n\n        $renderer = new TemplateRenderer($engine, $this->getContextStub(), 'html.twig');\n\n        $this->assertSame($engine, $renderer->getTemplateEngine());\n    }\n\n    public function testExists(): void\n    {\n        $templateName = 'template';\n        $fileNameExtension = 'html.twig';\n        $engine = $this->getEngineMock();\n        $engine->expects($this->once())\n            ->method('exists')\n            ->with(\"$templateName.$fileNameExtension\")\n            ->willReturn(true);\n\n        $renderer = new TemplateRenderer($engine, $this->getContextStub(), $fileNameExtension);\n\n        $this->assertTrue($renderer->exists($templateName));\n    }\n\n    private function getContextStub(): ContextInterface\n    {\n        return $this->createStub(ContextInterface::class);\n    }\n\n    private function getContextMock(): ContextInterface\n    {\n        return $this->createMock(ContextInterface::class);\n    }\n\n    private function getEngineMock(): TemplateEngineInterface\n    {\n        return $this\n            ->getMockBuilder(TemplateEngineInterface::class)\n            ->getMock();\n    }\n\n    private function getEngineStub(): TemplateEngineInterface\n    {\n        return $this->createStub(TemplateEngineInterface::class);\n    }\n\n    public static function twigTemplateNameFileDataProvider(): array\n    {\n        return [\n            [\n                'template',\n                'template.html.twig',\n            ],\n            [\n                'template.html.twig',\n                'template.html.twig'\n            ],\n            [\n                'some/path/template_name.html.twig',\n                'some/path/template_name.html.twig'\n            ],\n            [\n                'some/path/template.name.html.twig',\n                'some/path/template.name.html.twig'\n            ],\n            [\n                'some/path/template.name',\n                'some/path/template.name.html.twig'\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Framework/Theme/Bridge/AdminThemeBridgeTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Framework\\Theme\\Bridge;\n\nuse PHPUnit\\Framework\\TestCase;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Theme\\Bridge\\AdminThemeBridge;\n\nfinal class AdminThemeBridgeTest extends TestCase\n{\n    public function testGetActiveTheme(): void\n    {\n        $this->assertSame('admin', (new AdminThemeBridge('admin'))->getActiveTheme());\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Setup/Directory/DirectoryValidatorTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Setup\\Directory;\n\nuse org\\bovigo\\vfs\\vfsStream;\nuse org\\bovigo\\vfs\\vfsStreamDirectory;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Directory\\DirectoryValidator;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Directory\\NoPermissionDirectoryException;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class DirectoryValidatorTest extends TestCase\n{\n    private vfsStreamDirectory $dir;\n\n    protected function setUp(): void\n    {\n        $structure = [\n            'test-folder' => [\n                'out' => [\n                    'pictures' => [\n                        'promo' => [],\n                        'master' => [],\n                        'generated' => [],\n                        'media' => []\n                    ],\n                    'media' => [],\n                ],\n                'log' => [],\n                'tmp' => []\n            ],\n            'var' => []\n        ];\n\n        $this->dir = vfsStream::setup('root', 0777, $structure);\n\n        parent::setUp();\n    }\n\n    public function testNoPermissionDirectories(): void\n    {\n        $this->dir->getChild('test-folder/out/pictures/promo')->chmod(0555);\n\n        $context = $this->createStub(BasicContextInterface::class);\n        $context->method('getSourcePath')->willReturn($this->dir->getChild('test-folder')->url());\n        $directoryValidator = new DirectoryValidator($context);\n\n        $this->expectException(NoPermissionDirectoryException::class);\n\n        $directoryValidator->validateDirectory(\n            $this->dir->getChild('test-folder/tmp')->url()\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Setup/Htaccess/HtaccessDaoFactoryTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Setup\\Htaccess;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\HtaccessAccessException;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\HtaccessDao;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\HtaccessDaoFactory;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Utility\\BasicContextInterface;\nuse PHPUnit\\Framework\\TestCase;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\n\nfinal class HtaccessDaoFactoryTest extends TestCase\n{\n    use ProphecyTrait;\n\n    public function testCreateRootHtaccessDaoWithExistingPathWillReturnExpected(): void\n    {\n        $basicContext = $this->prophesize(BasicContextInterface::class);\n        $basicContext->getSourcePath()->willReturn(__DIR__ . '/testData');\n\n        $testContext = new HtaccessDao(__DIR__ . '/testData/.htaccess');\n\n        $accessDao = (new HtaccessDaoFactory($basicContext->reveal()))->createRootHtaccessDao();\n\n        $this->assertEquals($accessDao, $testContext);\n\n        $basicContext->getSourcePath()->shouldBeCalledOnce();\n    }\n\n    public function testCreateRootHtaccessDaoWithNonExistingPathWillThrow(): void\n    {\n        $path = 'some-non-existing-path';\n        $basicContext = $this->prophesize(BasicContextInterface::class);\n        $basicContext->getSourcePath()->willReturn($path);\n\n        $this->expectException(HtaccessAccessException::class);\n\n        (new HtaccessDaoFactory($basicContext->reveal()))->createRootHtaccessDao();\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Setup/Htaccess/HtaccessDaoTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Setup\\Htaccess;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\HtaccessAccessException;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\HtaccessDao;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class HtaccessDaoTest extends TestCase\n{\n    protected function setUp(): void\n    {\n        parent::setUp();\n        copy($this->getHtaccessTplPath(), $this->getTestFilePath());\n    }\n\n    protected function tearDown(): void\n    {\n        unlink($this->getTestFilePath());\n        parent::tearDown();\n    }\n\n    public function testSetRewriteBaseWithNonExistingFileWillThrow(): void\n    {\n        $this->expectException(HtaccessAccessException::class);\n\n        (new HtaccessDao(''))->setRewriteBase('some-string');\n    }\n\n    public function testSetRewriteBaseWithCorrectFileWillUpdateFile(): void\n    {\n        $rewriteBase = '/some-string';\n        $this->assertStringNotContainsString($rewriteBase, file_get_contents($this->getTestFilePath()));\n\n        (new HtaccessDao($this->getTestFilePath()))->setRewriteBase($rewriteBase);\n\n        $this->assertStringContainsString(\"RewriteBase $rewriteBase\", file_get_contents($this->getTestFilePath()));\n    }\n\n    private function getTestFilePath(): string\n    {\n        return $this->getHtaccessTplPath() . '-test';\n    }\n\n    private function getHtaccessTplPath(): string\n    {\n        return __DIR__ . '/testData/.htaccess';\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Setup/Htaccess/HtaccessUpdaterTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Setup\\Htaccess;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\HtaccessDaoFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\HtaccessDaoInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\HtaccessUpdater;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\ShopBaseUrl;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Url\\UrlParserInterface;\nuse PHPUnit\\Framework\\TestCase;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\n\nfinal class HtaccessUpdaterTest extends TestCase\n{\n    use ProphecyTrait;\n\n    public function testUpdateRewriteBaseDirectiveWithUrlPathWillCallDaoWithExpected(): void\n    {\n        $url = new ShopBaseUrl('http://some-url.com/some-path');\n        $urlPath = '/some-path';\n        $htaccessDaoFactory = $this->prophesize(HtaccessDaoFactoryInterface::class);\n        $htaccessDao = $this->prophesize(HtaccessDaoInterface::class);\n        $htaccessDaoFactory->createRootHtaccessDao()->willReturn($htaccessDao);\n        $urlParser = $this->prophesize(UrlParserInterface::class);\n        $urlParser->getPathWithoutTrailingSlash($url->getUrl())->willReturn($urlPath);\n\n        (new HtaccessUpdater(\n            $htaccessDaoFactory->reveal(),\n            $urlParser->reveal()\n        ))->updateRewriteBaseDirective($url);\n\n        $htaccessDao->setRewriteBase($urlPath)->shouldHaveBeenCalledOnce();\n    }\n\n    public function testUpdateRewriteBaseDirectiveWithEmptyUrlPathWillCallDaoWithExpected(): void\n    {\n        $url = new ShopBaseUrl('http://some-url.com/');\n        $rewriteBaseForEmptyPath = '/';\n        $htaccessDaoFactory = $this->prophesize(HtaccessDaoFactoryInterface::class);\n        $htaccessDao = $this->prophesize(HtaccessDaoInterface::class);\n        $htaccessDaoFactory->createRootHtaccessDao()->willReturn($htaccessDao);\n        $urlParser = $this->prophesize(UrlParserInterface::class);\n        $urlParser->getPathWithoutTrailingSlash($url->getUrl())->willReturn('');\n\n        (new HtaccessUpdater(\n            $htaccessDaoFactory->reveal(),\n            $urlParser->reveal()\n        ))->updateRewriteBaseDirective($url);\n\n        $htaccessDao->setRewriteBase($rewriteBaseForEmptyPath)->shouldHaveBeenCalledOnce();\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Setup/Htaccess/ShopBaseUrlTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Setup\\Htaccess;\n\nuse Iterator;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\InvalidShopUrlException;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\ShopBaseUrl;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ShopBaseUrlTest extends TestCase\n{\n    public static function validUrlsDataProvider(): Iterator\n    {\n        yield ['https://www.oxid-esales.com/en/'];\n        yield ['http://localhost.local'];\n        yield ['https://äää.üüü'];\n        yield ['https://127.0.0.1'];\n    }\n\n    #[DataProvider('validUrlsDataProvider')]\n    public function testWithValidUrls(string $url): void\n    {\n        $object = new ShopBaseUrl($url);\n\n        $this->assertEquals($url, $object->getUrl());\n    }\n\n    public static function invalidUrlsDataProvider(): Iterator\n    {\n        yield [''];\n        yield ['123.456'];\n        yield ['address.com'];\n    }\n\n    #[DataProvider('invalidUrlsDataProvider')]\n    public function testWithInvalidUrls(string $url): void\n    {\n        $this->expectException(InvalidShopUrlException::class);\n\n        new ShopBaseUrl($url);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Setup/Htaccess/testData/.htaccess",
    "content": "<IfModule mod_rewrite.c>\n    Options +FollowSymLinks\n    RewriteEngine On\n    RewriteBase /\n\n    RewriteRule ^(graphql/)    widget.php?cl=graphql   [QSA,NC,L]\n\n    RewriteCond %{REQUEST_URI}     config\\.inc\\.php [NC]\n    RewriteRule ^config\\.inc\\.php  index\\.php       [R=301,L]\n\n    RewriteCond %{REQUEST_URI} setup   [NC]\n    RewriteRule ^setup(.*)$    Setup$1 [R=301,L]\n\n    RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK)\n    RewriteRule .* - [F]\n\n    RewriteCond %{REQUEST_URI} oxseo\\.php$\n    RewriteCond %{QUERY_STRING} mod_rewrite_module_is=off\n    RewriteRule oxseo\\.php$ oxseo.php?mod_rewrite_module_is=on [L]\n\n    RewriteCond %{REQUEST_URI} !(\\/admin\\/|\\/Core\\/|\\/Application\\/|\\/export\\/|\\/modules\\/|\\/out\\/|\\/Setup\\/|\\/tmp\\/|\\/views\\/)\n    RewriteCond %{REQUEST_FILENAME} !-f\n    RewriteCond %{REQUEST_FILENAME} !-d\n    RewriteRule !(\\.html|\\/|\\.jpe?g|\\.css|\\.pdf|\\.doc|\\.gif|\\.png|\\.js|\\.htc|\\.svg)$ %{REQUEST_URI}/ [NC,R=301,L]\n\n    RewriteCond %{REQUEST_URI} !(\\/admin\\/|\\/Core\\/|\\/Application\\/|\\/export\\/|\\/modules\\/|\\/out\\/|\\/Setup\\/|\\/tmp\\/|\\/views\\/)\n    RewriteCond %{REQUEST_FILENAME} !-f\n    RewriteCond %{REQUEST_FILENAME} !-d\n    RewriteRule (\\.html|\\/)$ oxseo.php\n\n\n    RewriteCond %{REQUEST_URI} (\\/out\\/pictures\\/generated\\/)\n    RewriteCond %{REQUEST_FILENAME} !-f\n    RewriteCond %{REQUEST_FILENAME} !-d\n    RewriteRule (\\.jpe?g|\\.gif|\\.png|\\.svg)$ getimg.php [NC]\n\n    RewriteRule ^(vendor/) - [F,L,NC]\n    RewriteRule ^migration - [R=403,L]\n</IfModule>\n\n# disabling log file access from outside\n<FilesMatch \"(EXCEPTION_LOG\\.txt|\\.log|\\.tpl|pkg\\.rev|\\.ini|pkg\\.info|\\.pem|composer\\.json|composer\\.lock|test_config\\.yml)$\">\n   <IfModule mod_authz_core.c>\n       Require all denied\n   </IfModule>\n   <IfModule !mod_authz_core.c>\n       Order allow,deny\n       Deny from all\n   </IfModule>\n</FilesMatch>\n\n# Prevent .ht* files from being sent to outside requests\n<Files ~ \"^\\.ht\">\n    <IfModule mod_authz_core.c>\n        Require all denied\n    </IfModule>\n    <IfModule !mod_authz_core.c>\n        Order allow,deny\n        Deny from all\n    </IfModule>\n</Files>\n\nOptions -Indexes\nDirectoryIndex index.php index.html\n"
  },
  {
    "path": "tests/Unit/Internal/Setup/Language/DefaultLanguageTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Setup\\Language;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Language\\DefaultLanguage;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Language\\IncorrectLanguageException;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class DefaultLanguageTest extends TestCase\n{\n    public function testReturnsCorrectCode(): void\n    {\n        $language = new DefaultLanguage('de');\n\n        $this->assertEquals('de', $language->getCode());\n    }\n\n    public function testThrowsExceptionOnIncorrectLanguage(): void\n    {\n        $this->expectException(IncorrectLanguageException::class);\n\n        new DefaultLanguage('esperanto');\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Setup/ShopSetupCommandTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Setup;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Database\\Configuration\\DataObject\\DatabaseConfiguration;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Database\\ShopDbManagerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Directory\\DirectoryValidatorInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\HtaccessUpdaterInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Htaccess\\ShopBaseUrl;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Language\\DefaultLanguage;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Language\\LanguageInstallerInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Parameters\\SetupParameters;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Parameters\\SetupParametersFactoryInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\ShopConfiguration\\ShopConfigurationUpdaterInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\ShopSetupCommand;\nuse OxidEsales\\EshopCommunity\\Internal\\Setup\\Validator\\SetupInfrastructureValidatorInterface;\nuse PHPUnit\\Framework\\TestCase;\nuse Prophecy\\Argument\\Token\\TypeToken;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\nuse Prophecy\\Prophecy\\ObjectProphecy;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Tester\\CommandTester;\n\nfinal class ShopSetupCommandTest extends TestCase\n{\n    use ProphecyTrait;\n\n    private ShopDbManagerInterface|ObjectProphecy $shopDbManager;\n    private HtaccessUpdaterInterface|ObjectProphecy $htaccessUpdateService;\n    private ObjectProphecy|DirectoryValidatorInterface $directoryValidator;\n    private ObjectProphecy|LanguageInstallerInterface $languageInstaller;\n    private ObjectProphecy|SetupInfrastructureValidatorInterface $SetupInfrastructureValidator;\n    private SetupParametersFactoryInterface|ObjectProphecy $setupParametersFactory;\n    private ShopConfigurationUpdaterInterface|ObjectProphecy $shopConfigurationUpdater;\n\n    public function testExecute(): void\n    {\n        $commandTester = new CommandTester($this->createCommand());\n\n        $commandTester->execute([]);\n\n        $this->setupParametersFactory\n            ->create(new TypeToken(DefaultLanguage::class))\n            ->shouldHaveBeenCalledOnce();\n        $this->SetupInfrastructureValidator\n            ->validate(new TypeToken(SetupParameters::class))\n            ->shouldHaveBeenCalledOnce();\n        $this->shopDbManager\n            ->create(new TypeToken(DatabaseConfiguration::class))\n            ->shouldHaveBeenCalledOnce();\n        $this->languageInstaller\n            ->install(new TypeToken(DefaultLanguage::class))\n            ->shouldHaveBeenCalledOnce();\n        $this->htaccessUpdateService\n            ->updateRewriteBaseDirective(new TypeToken(ShopBaseUrl::class))\n            ->shouldHaveBeenCalledOnce();\n        $this->shopConfigurationUpdater\n            ->saveShopSetupTime()\n            ->shouldHaveBeenCalledOnce();\n        $this->assertEquals(Command::SUCCESS, $commandTester->getStatusCode());\n    }\n\n    private function createCommand(): Command\n    {\n        $this->prepareMocks();\n        return new ShopSetupCommand(\n            $this->setupParametersFactory->reveal(),\n            $this->SetupInfrastructureValidator->reveal(),\n            $this->shopDbManager->reveal(),\n            $this->languageInstaller->reveal(),\n            $this->htaccessUpdateService->reveal(),\n            $this->shopConfigurationUpdater->reveal(),\n            'language',\n        );\n    }\n\n    private function prepareMocks(): void\n    {\n        $this->setupParametersFactory = $this->prophesize(SetupParametersFactoryInterface::class);\n        $this->SetupInfrastructureValidator = $this->prophesize(SetupInfrastructureValidatorInterface::class);\n        $this->shopDbManager = $this->prophesize(ShopDbManagerInterface::class);\n        $this->languageInstaller = $this->prophesize(LanguageInstallerInterface::class);\n        $this->htaccessUpdateService = $this->prophesize(HtaccessUpdaterInterface::class);\n        $this->shopConfigurationUpdater = $this->prophesize(ShopConfigurationUpdaterInterface::class);\n\n        $setupParameters = $this->prophesize(SetupParameters::class);\n        $setupParameters\n            ->getShopBaseUrl()\n            ->willReturn(new ShopBaseUrl('https://localhost.local'));\n        $setupParameters\n            ->getDbConfig()\n            ->willReturn(new DatabaseConfiguration(\n                getenv('OXID_DB_URL')\n            ));\n        $setupParameters\n            ->getLanguage()\n            ->willReturn(new DefaultLanguage('de'));\n        $this->setupParametersFactory\n            ->create(new TypeToken(DefaultLanguage::class))\n            ->willReturn($setupParameters);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Transition/Adapter/Configuration/Utility/ShopSettingEncoderTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Transition\\Adapter\\Configuration\\Utility;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Exception\\InvalidShopSettingValueException;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Config\\Utility\\ShopSettingEncoder;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\nuse stdClass;\n\nfinal class ShopSettingEncoderTest extends TestCase\n{\n    #[DataProvider('settingDataProvider')]\n    public function testEncoding(bool|string|int|array $value, string|int $encodedValue, string $encodingType): void\n    {\n        $shopSettingEncoder = new ShopSettingEncoder();\n\n        $this->assertSame(\n            $encodedValue,\n            $shopSettingEncoder->encode($encodingType, $value)\n        );\n    }\n\n    #[DataProvider('settingDataProvider')]\n    public function testDecoding(bool|string|int|array $value, string|int $encodedValue, string $encodingType): void\n    {\n        $shopSettingEncoder = new ShopSettingEncoder();\n\n        $this->assertSame(\n            $value,\n            $shopSettingEncoder->decode($encodingType, $encodedValue)\n        );\n    }\n\n    public function testEncodingInvalidValue(): void\n    {\n        $this->expectException(InvalidShopSettingValueException::class);\n        $shopSettingEncoder = new ShopSettingEncoder();\n\n        $this->expectException(InvalidShopSettingValueException::class);\n        $shopSettingEncoder->encode('object', new stdClass());\n    }\n\n    public static function settingDataProvider(): array\n    {\n        return [\n            [\n                true,\n                '1',\n                'bool'\n            ],\n            [\n                'some string',\n                'some string',\n                'string'\n            ],\n            [\n                2,\n                2,\n                'int'\n            ],\n            [\n                ['value'],\n                serialize(['value']),\n                'arr'\n            ],\n            [\n                ['value'],\n                serialize(['value']),\n                'aarr'\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Transition/Adapter/TemplateLogic/IncludeDynamicLogicTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Transition\\Adapter\\TemplateLogic;\n\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse OxidEsales\\EshopCommunity\\Internal\\Transition\\Adapter\\TemplateLogic\\IncludeDynamicLogic;\nuse PHPUnit\\Framework\\TestCase;\n\n#[CoversClass(IncludeDynamicLogic::class)]\nfinal class IncludeDynamicLogicTest extends TestCase\n{\n    private IncludeDynamicLogic $includeDynamicLogic;\n\n    public function setup(): void\n    {\n        $this->includeDynamicLogic = new IncludeDynamicLogic();\n    }\n\n\n    #[DataProvider('getIncludeDynamicPrefixTests')]\n    public function testIncludeDynamicPrefix(array $parameters, array $expected): void\n    {\n        $this->assertEquals($this->includeDynamicLogic->includeDynamicPrefix($parameters), $expected);\n    }\n\n\n    #[DataProvider('getRenderForCacheTests')]\n    public function testRenderForCache(array $parameters, string $expected): void\n    {\n        $this->assertEquals($this->includeDynamicLogic->renderForCache($parameters), $expected);\n    }\n\n    public static function getIncludeDynamicPrefixTests(): array\n    {\n        return [\n            [[], []],\n            [['param1' => 'val1', 'param2' => 2], ['_param1' => 'val1', '_param2' => 2]],\n            [['type' => 'custom'], []],\n            [['type' => 'custom', 'param1' => 'val1', 'param2' => 2], ['_custom_param1' => 'val1', '_custom_param2' => 2]],\n            [['type' => 'custom', 'file' => 'file.tpl'], []],\n            [['type' => 'custom', 'file' => 'file.tpl', 'param' => 'val'], ['_custom_param' => 'val']]\n        ];\n    }\n\n    public static function getRenderForCacheTests(): array\n    {\n        return [\n            [[], '<oxid_dynamic></oxid_dynamic>'],\n            [['param1' => 'val1', 'param2' => 2], '<oxid_dynamic> param1=\\'dmFsMQ==\\' param2=\\'Mg==\\'</oxid_dynamic>'],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Utility/Authentication/Policy/PasswordPolicyTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Utility\\Authentication\\Policy;\n\nuse PHPUnit\\Framework\\Attributes\\DoesNotPerformAssertions;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Authentication\\Exception\\PasswordPolicyException;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Authentication\\Policy\\PasswordPolicy;\nuse PHPUnit\\Framework\\TestCase;\n\n/**\n * Class PasswordVerificationServiceTest\n */\nfinal class PasswordPolicyTest extends TestCase\n{\n    #[DoesNotPerformAssertions]\n    public function testPasswordPolicyAcceptsUtf8EncodedStrings(): void\n    {\n        $passwordUtf8 = 'äääää';\n\n        $passwordPolicy = new PasswordPolicy();\n        $passwordPolicy->enforcePasswordPolicy($passwordUtf8);\n    }\n\n    /**\n     * @throws PasswordPolicyException\n     */\n    #[DataProvider('unsupportedEncodingDataProvider')]\n    public function testPasswordPolicyRejectsStringNonUtf8Encoding(string $unsupportedEncoding): void\n    {\n        $this->expectException(PasswordPolicyException::class);\n        $this->expectExceptionMessage('The password policy requires UTF-8 encoded strings');\n\n        $passwordUtf8 = 'äääää';\n        $passwordIso = mb_convert_encoding($passwordUtf8, $unsupportedEncoding);\n\n        $passwordPolicy = new PasswordPolicy();\n        $passwordPolicy->enforcePasswordPolicy($passwordIso);\n    }\n\n    public static function unsupportedEncodingDataProvider(): array\n    {\n        return\n            [\n                ['UTF-32'],\n                ['UTF-32BE'],\n                ['UTF-32LE'],\n                ['UTF-16'],\n                ['UTF-16BE'],\n                ['UTF-16LE'],\n                ['ISO-8859-1'],\n                ['ISO-8859-15'],\n                ['Windows-1252']\n            ];\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Utility/Hash/Service/Argon2IPasswordHashServiceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Utility\\Hash\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Authentication\\Policy\\PasswordPolicyInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Hash\\Service\\Argon2IPasswordHashService;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Hash\\Service\\PasswordHashServiceInterface;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function defined;\n\nfinal class Argon2IPasswordHashServiceTest extends TestCase\n{\n    protected function setUp(): void\n    {\n        if (!defined('PASSWORD_ARGON2I')) {\n            $this->markTestSkipped('Argon2I is not available on this system.');\n        }\n    }\n\n    public function testHashForGivenPasswordIsEncryptedWithProperAlgorithm(): void\n    {\n        $passwordHashService = $this->getPasswordHashService();\n        $hash = $passwordHashService->hash('secret');\n        $info = password_get_info($hash);\n\n        $this->assertSame(PASSWORD_ARGON2I, $info['algo']);\n    }\n\n    public function testHashForEmptyPasswordIsEncryptedWithProperAlgorithm(): void\n    {\n        $passwordHashService = $this->getPasswordHashService();\n        $hash = $passwordHashService->hash('');\n        $info = password_get_info($hash);\n\n        $this->assertSame(PASSWORD_ARGON2I, $info['algo']);\n    }\n\n    public function testConsecutiveHashingTheSamePasswordProducesDifferentHashes(): void\n    {\n        $password = 'secret';\n\n        $passwordHashService = $this->getPasswordHashService();\n        $hash_1 = $passwordHashService->hash($password);\n        $hash_2 = $passwordHashService->hash($password);\n\n        $this->assertNotSame($hash_1, $hash_2);\n    }\n\n    public function testPasswordNeedsRehashReturnsTrueOnChangedAlgorithm(): void\n    {\n        $passwordHashedWithOriginalAlgorithm = password_hash('secret', PASSWORD_BCRYPT);\n        $passwordHashService = $this->getPasswordHashService();\n\n        $this->assertTrue(\n            $passwordHashService->passwordNeedsRehash($passwordHashedWithOriginalAlgorithm)\n        );\n    }\n\n    public function testHashWithValidCostOption(): void\n    {\n        $passwordHashService = $this->getPasswordHashService();\n        $hash = $passwordHashService->hash('secret');\n\n        $info = password_get_info($hash);\n\n        $this->assertSame(PASSWORD_ARGON2I, $info['algo']);\n        $this->assertSame(\n            [\n                'memory_cost' => PASSWORD_ARGON2_DEFAULT_MEMORY_COST,\n                'time_cost' => PASSWORD_ARGON2_DEFAULT_TIME_COST,\n                'threads' => PASSWORD_ARGON2_DEFAULT_THREADS\n            ],\n            $info['options']\n        );\n    }\n\n    private function getPasswordHashService(): PasswordHashServiceInterface\n    {\n        $passwordPolicyMock = $this->getPasswordPolicyMock();\n\n        return new Argon2IPasswordHashService(\n            $passwordPolicyMock,\n            PASSWORD_ARGON2_DEFAULT_MEMORY_COST,\n            PASSWORD_ARGON2_DEFAULT_TIME_COST,\n            PASSWORD_ARGON2_DEFAULT_THREADS\n        );\n    }\n\n    private function getPasswordPolicyMock(): PasswordPolicyInterface\n    {\n        return $this->createStub(PasswordPolicyInterface::class);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Utility/Hash/Service/BcryptPasswordHashServiceTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Utility\\Hash\\Service;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Authentication\\Policy\\PasswordPolicyInterface;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Hash\\Exception\\PasswordHashException;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Hash\\Service\\BcryptPasswordHashService;\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Hash\\Service\\PasswordHashServiceInterface;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class BcryptPasswordHashServiceTest extends TestCase\n{\n    public function testHashForGivenPasswordIsEncryptedWithProperAlgorithm(): void\n    {\n        $password = 'secret';\n        $passwordHashService = $this->getPasswordHashServiceMock();\n        $hash = $passwordHashService->hash($password);\n        $info = password_get_info($hash);\n\n        $this->assertSame(PASSWORD_BCRYPT, $info['algo']);\n    }\n\n    public function testHashForEmptyPasswordIsEncryptedWithProperAlgorithm(): void\n    {\n        $password = '';\n\n        $passwordHashService = $this->getPasswordHashServiceMock();\n        $hash = $passwordHashService->hash($password);\n        $info = password_get_info($hash);\n\n        $this->assertSame(PASSWORD_BCRYPT, $info['algo']);\n    }\n\n    public function testConsecutiveHashingTheSamePasswordProducesDifferentHashes(): void\n    {\n        $password = 'secret';\n\n        $passwordHashService = $this->getPasswordHashServiceMock();\n        $hash_1 = $passwordHashService->hash($password);\n        $hash_2 = $passwordHashService->hash($password);\n\n        $this->assertNotSame($hash_1, $hash_2);\n    }\n\n    public function testPasswordNeedsRehashReturnsTrueOnChangedParameters(): void\n    {\n        $passwordHash = password_hash('secret', PASSWORD_BCRYPT, ['cost' => 4 + 1]);\n\n        $passwordHashService = $this->getPasswordHashServiceMock();\n\n        $this->assertTrue($passwordHashService->passwordNeedsRehash($passwordHash));\n    }\n\n    public function testPasswordNeedsRehashReturnsTrueOnUnknownHash(): void\n    {\n        $passwordHash = 'some_unrecognizable_custom_hash';\n\n        $passwordHashService = $this->getPasswordHashServiceMock();\n\n        $this->assertTrue($passwordHashService->passwordNeedsRehash($passwordHash));\n    }\n\n    public function testPasswordNeedsRehashReturnsFalseOnSameAlgorithmAndOptions(): void\n    {\n        $passwordHash = password_hash('secret', PASSWORD_BCRYPT, ['cost' => 4]);\n\n        $passwordHashService = $this->getPasswordHashServiceMock();\n\n        $this->assertFalse($passwordHashService->passwordNeedsRehash($passwordHash));\n    }\n\n    public function testHashWithWithValidCostOptionValue(): void\n    {\n        $passwordHashService = $this->getPasswordHashServiceMock();\n\n        $hash = $passwordHashService->hash('secret');\n        $info = password_get_info($hash);\n\n        $this->assertSame(4, $info['options']['cost']);\n    }\n\n    #[DataProvider('invalidCostOptionDataProvider')]\n    public function testHashWithInvalidCostOptionValueThrowsPasswordHashException($invalidCostOption): void\n    {\n        $this->expectException(PasswordHashException::class);\n\n        $this->getPasswordHashServiceMock($invalidCostOption);\n    }\n\n    public static function invalidCostOptionDataProvider(): array\n    {\n        return [\n            [-5],\n            [0],\n            [3], // Cost must not be smaller than 4\n            [32] // Cost must not be bigger than 31\n        ];\n    }\n\n\n    private function getPasswordHashServiceMock(int $cost = 4): PasswordHashServiceInterface\n    {\n        $passwordPolicy = $this->getPasswordPolicyMock();\n\n        return new BcryptPasswordHashService(\n            $passwordPolicy,\n            $cost\n        );\n    }\n\n    private function getPasswordPolicyMock(): PasswordPolicyInterface\n    {\n        return $this->createStub(PasswordPolicyInterface::class);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/Utility/Url/UrlParserTest.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nnamespace OxidEsales\\EshopCommunity\\Tests\\Unit\\Internal\\Utility\\Url;\n\nuse OxidEsales\\EshopCommunity\\Internal\\Utility\\Url\\UrlParser;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class UrlParserTest extends TestCase\n{\n    #[DataProvider('getPathWithoutTrailingSlashDataProvider')]\n    public function testGetPathWithoutTrailingSlashWithDataProvider(string $url, string $exp): void\n    {\n        $act = (new UrlParser())->getPathWithoutTrailingSlash($url);\n\n        $this->assertSame($exp, $act);\n    }\n\n    public static function getPathWithoutTrailingSlashDataProvider(): array\n    {\n        return [\n            ['https://abc.def.com', ''],\n            ['https://abc.def.com/', ''],\n            ['http://abc.def.com/some-path/component-string.php', '/some-path/component-string.php'],\n            ['http://abc.def.com/some-path/component-string/', '/some-path/component-string'],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/bootstrap.php",
    "content": "<?php\n\n/**\n * Copyright © OXID eSales AG. All rights reserved.\n * See LICENSE file for license details.\n */\n\ndeclare(strict_types=1);\n\nuse OxidEsales\\EshopCommunity\\Core\\Autoload\\BackwardsCompatibilityAutoload;\nuse OxidEsales\\EshopCommunity\\Core\\Autoload\\ModuleAutoload;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\Env\\DotenvLoader;\nuse OxidEsales\\EshopCommunity\\Internal\\Framework\\FileSystem\\ProjectRootLocator;\nuse Symfony\\Component\\Filesystem\\Path;\n\ndefine('INSTALLATION_ROOT_PATH', (new ProjectRootLocator())->getProjectRoot());\nconst VENDOR_PATH = INSTALLATION_ROOT_PATH . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR;\ndefine('OX_BASE_PATH', Path::join(INSTALLATION_ROOT_PATH, 'source') . DIRECTORY_SEPARATOR);\n\nrequire VENDOR_PATH . DIRECTORY_SEPARATOR . 'autoload.php';\nspl_autoload_register([BackwardsCompatibilityAutoload::class, 'autoload']);\nspl_autoload_register([ModuleAutoload::class, 'autoload']);\n\nrequire_once Path::join(OX_BASE_PATH, 'oxfunctions.php');\nrequire_once Path::join(OX_BASE_PATH, 'overridablefunctions.php');\n\n(new DotenvLoader(INSTALLATION_ROOT_PATH))->loadEnvironmentVariables();\n\ndate_default_timezone_set(getenv('OXID_DEFAULT_TIMEZONE') ?: 'Europe/Berlin');\n"
  },
  {
    "path": "tests/codeception.yml",
    "content": "namespace: OxidEsales\\EshopCommunity\\Tests\\Codeception\nsupport_namespace: Support\nparams:\n    - Codeception/Config/params.php\npaths:\n    tests: Codeception\n    output: Codeception/_output\n    data: Codeception/Support/Data\n    support: Codeception/Support\n    envs: Codeception/_envs\nactor_suffix: Tester\nsettings:\n    colors: true\n    log: true\nextensions:\n    enabled:\n        - Codeception\\Extension\\RunFailed\n"
  },
  {
    "path": "tests/scripts/check_log.sh",
    "content": "#!/bin/bash\n#\n# Usage:\n# check_log.sh path/to/log path/to/failure/patterns\n# check_log checks if a given log file is not empty or contains patterns that are\n# listed in the pattern file.\n# It requires two inputs:\n# The first input is the name of the log file to check, and the second input is the\n# pattern file, where every line contains a grep -E compatible pattern to search for\n# The script returns 0, if the log file exists, is not empty and does not contain any\n# of the patterns in the pattern file. It returns 1 otherwise\n\nLOG_FILE=\"${1}\"\nPATTERN_FILE=\"${2}\"\nRESULT=0\n\nif [ ! -e \"${LOG_FILE}\" ]; then\n    echo -e \"\\033[0;31mLog file '${LOG_FILE}' does not exist! Seems like no tests have been run!\\033[0m\"\n    RESULT=1\nfi\n\nif [ ! -s \"${LOG_FILE}\" ]; then\n    echo -e \"\\033[0;31mLog file '${LOG_FILE}' is empty! Seems like no tests have been run!\\033[0m\"\n    RESULT=1\nfi\nif [ ! -f \"${PATTERN_FILE}\" ]; then\n    echo -e \"\\033[0;31mPattern file '${PATTERN_FILE}' does not exist!\\033[0m\"\n    RESULT=1\nfi\n[[ ${RESULT} -gt 0 ]] && exit 1\n\n# shellcheck disable=SC2016\nsed -e 's|(.*)\\r|$1|' -i \"${PATTERN_FILE}\"\nwhile read -r LINE ; do\n    if [ -n \"${LINE}\" ]; then\n        if grep -q -E \"${LINE}\" \"${LOG_FILE}\"; then\n            echo -e \"\\033[0;31m Log contains matching pattern ${LINE}\\033[0m\"\n            grep -E \"${LINE}\" \"${LOG_FILE}\"\n            RESULT=1\n        else\n            echo -e \"\\033[0;32m Log does not contain matching pattern ${LINE}\"\n        fi\n    fi\ndone <\"${PATTERN_FILE}\"\n\nif [ -s \"/var/sync/logs/error_log.txt\" ]; then\n    echo -e \"\\033[0;31mPHP error log is not empty!\\033[0m\"\n    cat /var/sync/logs/error_log.txt\n    RESULT=1\nfi\n\nexit ${RESULT}\n"
  },
  {
    "path": "tests/scripts/codeception.sh",
    "content": "#!/bin/bash\nset -e\nset -x\nSUITE=\"${1}\"\nSHARD_STEP=\"${2}\"\nSHARD_COUNT=\"${3}\"\n\nfunction init() {\n    # shellcheck disable=SC2128\n    if [[ ${BASH_SOURCE} = */* ]]; then\n        SCRIPT_DIR=${BASH_SOURCE%/*}/\n    else\n        SCRIPT_DIR=./\n    fi\n    if [ -z \"${ABSOLUTE_PATH}\" ]; then\n        ABSOLUTE_PATH=\"$(pwd)\"\n    else\n        ABSOLUTE_PATH=\"/var/www/${ABSOLUTE_PATH}\"\n    fi\n    TEST_DIR='tests'\n    if [ ! -d \"${ABSOLUTE_PATH}/${TEST_DIR}\" ]; then\n        TEST_DIR='Tests'\n        if [ ! -d \"${ABSOLUTE_PATH}/${TEST_DIR}\" ]; then\n            echo -e \"\\033[0;31m###  Could not find folder tests or Tests in ${ABSOLUTE_PATH} ###\\033[0m\"\n            exit 1\n        fi\n    fi\n\n    [[ ! -d \"${ABSOLUTE_PATH}/${TEST_DIR}/Output\" ]] && mkdir \"${ABSOLUTE_PATH}/${TEST_DIR}/Output\"\n    [[ ! -d \"${ABSOLUTE_PATH}/${TEST_DIR}/Reports\" ]] && mkdir \"${ABSOLUTE_PATH}/${TEST_DIR}/Reports\"\n\n    OUTPUT_DIR=\"${ABSOLUTE_PATH}/${TEST_DIR}/Output\"\n    REPORT_DIR=\"${ABSOLUTE_PATH}/${TEST_DIR}/Reports\"\n\n    if [ -z \"${SELENIUM_SERVER_HOST}\" ]; then\n        export SELENIUM_SERVER_HOST=selenium\n    fi\n\n    if [ -z \"${SUITE}\" ]; then\n        SUITE=\"Acceptance\"\n        if [ ! -d \"${ABSOLUTE_PATH}/${TEST_DIR}/Codeception/${SUITE}\" ]; then\n            SUITE=\"acceptance\"\n            if [ ! -d \"${ABSOLUTE_PATH}/${TEST_DIR}/Codeception/${SUITE}\" ]; then\n                echo -e \"\\033[0;31mCould not find suite Acceptance or acceptance in ${TEST_DIR}/Codeception\\033[0m\"\n                exit 1\n            fi\n        fi\n    fi\n    if [ -n \"${SHARD_STEP}\" ]; then\n        LOG_FILE=\"${OUTPUT_DIR}/codeception_${SUITE}_shard_${SHARD_STEP}.txt\"\n    else\n        LOG_FILE=\"${OUTPUT_DIR}/codeception_${SUITE}.txt\"\n    fi\n    PATTERN_FILE=\"${SCRIPT_DIR}codeception_failure_pattern.txt\"\n\n    CODECEPTION=\"vendor/bin/codecept\"\n    if [ ! -f \"${CODECEPTION}\" ]; then\n        CODECEPTION=\"/var/www/${CODECEPTION}\"\n        if [ ! -f \"${CODECEPTION}\" ]; then\n            echo -e \"\\033[0;31mCould not find codecept in vendor/bin or /var/www/vendor/bin\\033[0m\"\n            exit 1\n        fi\n    fi\n\n    if [ -z \"${SHARD_STEP}${SHARDS_COUNT}\" ]; then\n        SHARDING=\"Sharding: Off\"\n    else\n        SHARDING=\"Sharding: ${SHARD_STEP}/${SHARD_COUNT}\"\n        if [[ ! ${SHARD_STEP} =~ ^[0-9]+$ ]]; then\n            echo \"Argument 2 (SHARD_STEP) must be numerical\"\n            exit 1\n        fi\n        if [[ ! ${SHARD_COUNT} =~ ^[0-9]+$ ]]; then\n            echo \"Argument 3 (SHARD_COUNT) must be numerical\"\n            exit 1\n        fi\n        if [ ${SHARD_STEP} -gt ${SHARD_COUNT} ]; then\n            echo \"Argument 2 (SHARD_STEP) must less or equal to shards count\"\n            exit 1\n        fi\n    fi\n\n    cat <<EOF\n        Path: ${ABSOLUTE_PATH}\n        Script directory: ${SCRIPT_DIR}\n        Output directory: ${OUTPUT_DIR}\n        Report directory: ${REPORT_DIR}\n        Selenium host: ${SELENIUM_SERVER_HOST}\n        Suite: ${SUITE}\n        Codeception: ${CODECEPTION}\n        ${SHARDING}\n        Log file: ${LOG_FILE}\n        Failure patterns: ${PATTERN_FILE}\nEOF\n}\n\n# wait for selenium host\nfunction wait_for_selenium() {\n    local I=60\n    until [ $I -le 0 ]; do\n        curl -sSjkL \"http://${SELENIUM_SERVER_HOST}:4444/wd/hub/status\" | grep '\"ready\": true' && break\n        echo \".\"\n        sleep 1\n        ((I--))\n    done\n    set -e\n    curl -sSjkL \"http://${SELENIUM_SERVER_HOST}:4444/wd/hub/status\"\n}\n\ninit\nwait_for_selenium\n\n\"${CODECEPTION}\" build -c \"${ABSOLUTE_PATH}/${TEST_DIR}/codeception.yml\"\nRESULT=$?\necho \"Codeception build exited with error code ${RESULT}\"\n\nif [[ -n \"${SHARD_STEP}\" && -n \"${SHARD_COUNT}\" ]]; then\n    \"${CODECEPTION}\" run \"${SUITE}\" \\\n        -c \"${ABSOLUTE_PATH}/${TEST_DIR}/codeception.yml\" \\\n        ${CODECEPTION_OPTIONS} \\\n        -o \"paths: output: ${OUTPUT_DIR}\" \\\n        --shard ${SHARD_STEP}/${SHARD_COUNT} 2>&1 |\n        tee \"${LOG_FILE}\"\n    RESULT=$?\n    echo \"Codeception shard ${SHARD_STEP} run exited with error code ${RESULT}\"\nelse\n    \"${CODECEPTION}\" run \"${SUITE}\" \\\n        -c \"${ABSOLUTE_PATH}/${TEST_DIR}/codeception.yml\" \\\n        ${CODECEPTION_OPTIONS} \\\n        -o \"paths: output: ${OUTPUT_DIR}\" 2>&1 |\n        tee \"${LOG_FILE}\"\n    RESULT=$?\n    echo \"Codeception run exited with error code ${RESULT}\"\nfi\n\"${SCRIPT_DIR}check_log.sh\" \"${LOG_FILE}\" \"${PATTERN_FILE}\"\n"
  },
  {
    "path": "tests/scripts/codeception_failure_pattern.txt",
    "content": "fail\n\\\\.\\\\=\\\\=\nWarning\nNotice\nDeprecated\nFatal\nError\nDID NOT FINISH\nTest file \".+\" not found\nCannot open file\nNo tests executed\nCould not read\nWarnings: [1-9][0-9]*\nErrors: [1-9][0-9]*\nFailed: [1-9][0-9]*\nDeprecations: [1-9][0-9]*\nRisky: [1-9][0-9]*\n"
  },
  {
    "path": "tests/scripts/deprecated.sh",
    "content": "#!/bin/bash\nset -e\n\nfunction init() {\n    export XDEBUG_MODE=coverage\n\n    # shellcheck disable=SC2128\n    if [[ ${BASH_SOURCE} = */* ]]; then\n        SCRIPT_DIR=${BASH_SOURCE%/*}/\n    else\n        SCRIPT_DIR=./\n    fi\n    if [ -z \"${ABSOLUTE_PATH}\" ]; then\n        ABSOLUTE_PATH=\"$(pwd)\"\n    else\n        ABSOLUTE_PATH=\"/var/www/${ABSOLUTE_PATH}\"\n    fi\n    TESTDIR='tests'\n    if [ ! -d \"${ABSOLUTE_PATH}/${TESTDIR}\" ]; then\n        TESTDIR='Tests'\n        if [ ! -d \"${ABSOLUTE_PATH}/${TESTDIR}\" ]; then\n            echo -e \"\\033[0;31m###  Could not find folder tests or Tests in ${ABSOLUTE_PATH} ###\\033[0m\"\n            exit 1\n        fi\n    fi\n\n    [[ ! -d \"${ABSOLUTE_PATH}/${TESTDIR}/Output\" ]] && mkdir \"${ABSOLUTE_PATH}/${TESTDIR}/Output\"\n    [[ ! -d \"${ABSOLUTE_PATH}/${TESTDIR}/Reports\" ]] && mkdir \"${ABSOLUTE_PATH}/${TESTDIR}/Reports\"\n\n    OUTPUT_DIR=\"${ABSOLUTE_PATH}/${TESTDIR}/Output\"\n    REPORT_DIR=\"${ABSOLUTE_PATH}/${TESTDIR}/Reports\"\n\n    if [ -z \"${SELENIUM_SERVER_HOST}\" ]; then\n        SELENIUM_SERVER_HOST='selenium'\n    fi\n\n    if [ -z \"${SUITE}\" ]; then\n        SUITE=\"AllTestsUnit\"\n    fi\n    LOG_FILE=\"${OUTPUT_DIR}/deprecated_tests.txt\"\n    PATTERN_FILE=\"${SCRIPT_DIR}codeception_failure_pattern.txt\"\n\n    RUNTEST=\"vendor/bin/runtests\"\n    if [ ! -f \"${RUNTEST}\" ]; then\n        RUNTEST=\"/var/www/${RUNTEST}\"\n        if [ ! -f \"${RUNTEST}\" ]; then\n            echo -e \"\\033[0;31mCould not find runtests in vendor/bin or /var/www/vendor/bin\\033[0m\"\n            exit 1\n        fi\n    fi\n\n    cat <<EOF\n        Path: ${ABSOLUTE_PATH}\n        Script directory: ${SCRIPT_DIR}\n        Output directory: ${OUTPUT_DIR}\n        Report directory: ${REPORT_DIR}\n        Selenium host: ${SELENIUM_SERVER_HOST}\n        Suite: ${SUITE}\n        Runtest: ${RUNTEST}\n        Log file: ${LOG_FILE}\n        Failure patterns: ${PATTERN_FILE}\nEOF\n}\n\ninit\ncp vendor/oxid-esales/testing-library/test_config.yml.dist test_config.yml\n\"${RUNTEST}\" \\\n    --coverage-clover=${REPORT_DIR}/coverage_deprecated_tests.xml \"${SUITE}\" 2>&1 | tee \"${LOG_FILE}\"\nRESULT=$?\necho \"runtest exited with error code ${RESULT}\"\n\n\"${SCRIPT_DIR}check_log.sh\" \"${LOG_FILE}\" \"${PATTERN_FILE}\"\n"
  },
  {
    "path": "tests/scripts/deprecated_failure_pattern.txt",
    "content": "fail\n\\\\.\\\\=\\\\=\nWarning\nNotice\nDeprecated\nFatal\nError\nDID NOT FINISH\nTest file \".+\" not found\nCannot open file\nNo tests executed\nCould not read\nWarnings: [1-9][0-9]*\nErrors: [1-9][0-9]*\nFailed: [1-9][0-9]*\nDeprecations: [1-9][0-9]*\nRisky: [1-9][0-9]*\n"
  },
  {
    "path": "tests/scripts/integration.sh",
    "content": "#!/bin/bash\nset -e\nexport XDEBUG_MODE=coverage\nfunction init() {\n    # shellcheck disable=SC2128\n    if [[ ${BASH_SOURCE} = */* ]]; then\n        SCRIPT_DIR=${BASH_SOURCE%/*}/\n    else\n        SCRIPT_DIR=./\n    fi\n    if [ -z \"${ABSOLUTE_PATH}\" ]; then\n        ABSOLUTE_PATH=\"$(pwd)\"\n    else\n        ABSOLUTE_PATH=\"/var/www/${ABSOLUTE_PATH}\"\n    fi\n    TESTDIR='tests'\n    if [ ! -d \"${ABSOLUTE_PATH}/${TESTDIR}\" ]; then\n        TESTDIR='Tests'\n        if [ ! -d \"${ABSOLUTE_PATH}/${TESTDIR}\" ]; then\n            echo -e \"\\033[0;31m###  Could not find folder tests or Tests in ${ABSOLUTE_PATH} ###\\033[0m\"\n            exit 1\n        fi\n    fi\n    [[ ! -d \"${ABSOLUTE_PATH}/${TESTDIR}/Output\" ]] && mkdir \"${ABSOLUTE_PATH}/${TESTDIR}/Output\"\n    [[ ! -d \"${ABSOLUTE_PATH}/${TESTDIR}/Reports\" ]] && mkdir \"${ABSOLUTE_PATH}/${TESTDIR}/Reports\"\n\n    OUTPUT_DIR=\"${ABSOLUTE_PATH}/${TESTDIR}/Output\"\n    REPORT_DIR=\"${ABSOLUTE_PATH}/${TESTDIR}/Reports\"\n\n    if [ -z \"${SUITE}\" ]; then\n        SUITE=\"${ABSOLUTE_PATH}/${TESTDIR}/Integration\"\n    fi\n\n    LOG_FILE=\"${OUTPUT_DIR}/phpunit_integration.txt\"\n    PATTERN_FILE=\"${SCRIPT_DIR}integration_failure_pattern.txt\"\n\n    PHPUNIT=\"vendor/bin/phpunit\"\n    if [ ! -f \"${PHPUNIT}\" ]; then\n        PHPUNIT=\"/var/www/${PHPUNIT}\"\n        if [ ! -f \"${PHPUNIT}\" ]; then\n            echo -e \"\\033[0;31mCould not find phpunit in vendor/bin or /var/www/vendor/bin\\033[0m\"\n            exit 1\n        fi\n    fi\n\n    BOOTSTRAP=\"/var/www/source/bootstrap.php\"\n    if [ ! -f \"${BOOTSTRAP}\" ]; then\n        BOOTSTRAP=\"/var/www/vendor/oxid-esales/oxideshop-ce/tests/bootstrap.php\"\n        if [ ! -f \"${BOOTSTRAP}\" ]; then\n            echo -e \"\\033[0;31mCould not find bootstrap.php in /var/www/tests or /var/www/oxid-esales/oxideshop-ce/tests\\033[0m\"\n            find /var/www -iname \"bootstrap.php\"\n            exit 1\n        fi\n    fi\n\n    XML_FILE=\"${ABSOLUTE_PATH}/phpunit.xml\"\n    COVERAGE_FILE=\"${REPORT_DIR}/coverage_phpunit_integration.xml\"\n\n    cat <<EOF\n        Path: ${ABSOLUTE_PATH}\n        Script directory: ${SCRIPT_DIR}\n        Output directory: ${OUTPUT_DIR}\n        Report directory: ${REPORT_DIR}\n        Suite: ${SUITE}\n        Bootstrap: ${BOOTSTRAP}\n        Config: ${XML_FILE}\n        Phpunit: ${PHPUNIT}\n        Coverage: ${COVERAGE_FILE}\n        Log file: ${LOG_FILE}\n        Failure patterns: ${PATTERN_FILE}\nEOF\n}\n\ninit\n\n\"${PHPUNIT}\" \\\n    -c \"${XML_FILE}\" \\\n    --bootstrap \"${BOOTSTRAP}\" \\\n    --coverage-clover=\"${COVERAGE_FILE}\" \\\n    ${INTEGRATION_OPTIONS} \\\n    \"${SUITE}\" 2>&1 \\\n| tee \"${LOG_FILE}\"\nRESULT=$?\necho \"phpunit exited with error code ${RESULT}\"\n\"${SCRIPT_DIR}check_log.sh\" \"${LOG_FILE}\" \"${PATTERN_FILE}\"\n"
  },
  {
    "path": "tests/scripts/integration_failure_pattern.txt",
    "content": "fail\n\\\\.\\\\=\\\\=\nWarning\nNotice\nDeprecated\nFatal\nError\nDID NOT FINISH\nTest file \".+\" not found\nCannot open file\nNo tests executed\nCould not read\nWarnings: [1-9][0-9]*\nErrors: [1-9][0-9]*\nFailed: [1-9][0-9]*\nDeprecations: [1-9][0-9]*\nRisky: [1-9][0-9]*\n"
  },
  {
    "path": "tests/scripts/php-cs-report.sh",
    "content": "#!/bin/bash\n# shellcheck disable=SC2013\n# If there are multiple words in a line,  while building FILES, it doesn't hurt\n# shellcheck disable=SC2086\n# We want FILES to count as multiple arguments\nset -e\nenv\nset -x\n\nPHPCS_DIFF_ONLY='true'\nPHPCS_DIFF_FILTER='\\.php$'\nTESTDIR='tests'\nif [ ! -d \"${TESTDIR}\" ]; then\n    TESTDIR='Tests'\n    if [ ! -d \"${TESTDIR}\" ]; then\n        echo -e \"\\033[0;31m###  Could not find folder tests or Tests in $(pwd) ###\\033[0m\"\n        exit 1\n    fi\nfi\n\nPHPCS_CONFIG=\"$(pwd)/phpcs.xml.dist\"\n\nif [ \"${PHPCS_DIFF_ONLY}\" == \"true\" ]; then\n    echo -e \"\\033[0;35m###  Use git diff for phpcs using filter '${PHPCS_DIFF_FILTER}' ###\\033[0m\"\n    if [ \"${GITHUB_EVENT_NAME}\" == 'pull_request' ]; then\n        URL=\"https://github.com/OXID-eSales/oxideshop_ce.git\"\n        git clone --depth 2 \"${URL}\" --branch ${GITHUB_BASE_REF} --single-branch .phpcs\n        git -C .phpcs fetch origin ${GITHUB_REF}:tmp_pr\n        git -C .phpcs checkout tmp_pr\n    else\n        URL=\"https://github.com/OXID-eSales/oxideshop_ce.git\"\n        git clone --depth 2 \"${URL}\" --branch \"${GITHUB_REF_NAME}\" --single-branch .phpcs\n    fi\n    git -C .phpcs diff --name-only --diff-filter=AM \"${GITHUB_REF}\" HEAD~1 | grep \"${PHPCS_DIFF_FILTER}\" | while read -r file; do\n    if [[ -f \"$file\" ]]; then\n        echo \"$file\"\n    fi\n    done >.changed-files.txt || true\n    if [[ -f \".changed-files.txt\" && -s \".changed-files.txt\" ]]; then\n        cat .changed-files.txt\n        FILES=\"\"\n        for FILE in $(cat .changed-files.txt); do\n            FILES=\"${FILES} ${FILE}\"\n        done\n    else\n        echo \"No files to scan\"\n        exit 0\n    fi\n    rm -rf .changed-files.txt .phpcs\n    vendor/bin/phpcs \\\n        --standard=\"${PHPCS_CONFIG}\" \\\n        --report=json \\\n        --report-file=${TESTDIR}/Reports/phpcs.report.json \\\n        ${FILES} \\\n    ||true\n    # As the first one does not produce legible output, this gives us something to see in the log\n    vendor/bin/phpcs \\\n        --standard=\"${PHPCS_CONFIG}\" \\\n        --report=full \\\n        ${FILES}\nelse\n    echo -e \"\\033[0;35m###  Use full file list for phpcs using filter '${PHPCS_DIFF_FILTER}' ###\\033[0m\"\n    cd .phpcs\n    vendor/bin/phpcs \\\n        --standard=\"${PHPCS_CONFIG}\" \\\n        --report=json \\\n        --report-file=${TESTDIR}/Reports/phpcs.report.json \\\n    ||true\n    # As the first one does not produce legible output, this gives us something to see in the log\n    vendor/bin/phpcs \\\n        --standard=\"${PHPCS_CONFIG}\" \\\n        --report=full\nfi\n"
  },
  {
    "path": "tests/scripts/unit.sh",
    "content": "#!/bin/bash\nset -e\nexport XDEBUG_MODE=coverage\nfunction init() {\n    # shellcheck disable=SC2128\n    if [[ ${BASH_SOURCE} = */* ]]; then\n        SCRIPT_DIR=${BASH_SOURCE%/*}/\n    else\n        SCRIPT_DIR=./\n    fi\n    if [ -z \"${ABSOLUTE_PATH}\" ]; then\n        ABSOLUTE_PATH=\"$(pwd)\"\n    else\n        ABSOLUTE_PATH=\"/var/www/${ABSOLUTE_PATH}\"\n    fi\n    TESTDIR='tests'\n    if [ ! -d \"${ABSOLUTE_PATH}/${TESTDIR}\" ]; then\n        TESTDIR='Tests'\n        if [ ! -d \"${ABSOLUTE_PATH}/${TESTDIR}\" ]; then\n            echo -e \"\\033[0;31m###  Could not find folder tests or Tests in ${ABSOLUTE_PATH} ###\\033[0m\"\n            exit 1\n        fi\n    fi\n    [[ ! -d \"${ABSOLUTE_PATH}/${TESTDIR}/Output\" ]] && mkdir \"${ABSOLUTE_PATH}/${TESTDIR}/Output\"\n    [[ ! -d \"${ABSOLUTE_PATH}/${TESTDIR}/Reports\" ]] && mkdir \"${ABSOLUTE_PATH}/${TESTDIR}/Reports\"\n\n    OUTPUT_DIR=\"${ABSOLUTE_PATH}/${TESTDIR}/Output\"\n    REPORT_DIR=\"${ABSOLUTE_PATH}/${TESTDIR}/Reports\"\n\n    if [ -z \"${SUITE}\" ]; then\n        SUITE=\"${ABSOLUTE_PATH}/${TESTDIR}/Unit\"\n    fi\n\n    LOG_FILE=\"${OUTPUT_DIR}/phpunit_unit.txt\"\n    PATTERN_FILE=\"${SCRIPT_DIR}unit_failure_pattern.txt\"\n\n    PHPUNIT=\"vendor/bin/phpunit\"\n    if [ ! -f \"${PHPUNIT}\" ]; then\n        PHPUNIT=\"/var/www/${PHPUNIT}\"\n        if [ ! -f \"${PHPUNIT}\" ]; then\n            echo -e \"\\033[0;31mCould not find phpunit in vendor/bin or /var/www/vendor/bin\\033[0m\"\n            exit 1\n        fi\n    fi\n\n    XML_FILE=\"${ABSOLUTE_PATH}/phpunit.xml\"\n    COVERAGE_FILE=\"${REPORT_DIR}/coverage_phpunit_unit.xml\"\n\n    cat <<EOF\n        Path: ${ABSOLUTE_PATH}\n        Script directory: ${SCRIPT_DIR}\n        Output directory: ${OUTPUT_DIR}\n        Report directory: ${REPORT_DIR}\n        Suite: ${SUITE}\n        Config: ${XML_FILE}\n        Phpunit: ${PHPUNIT}\n        Coverage: ${COVERAGE_FILE}\n        Log file: ${LOG_FILE}\n        Failure patterns: ${PATTERN_FILE}\nEOF\n}\n\ninit\n\n\"${PHPUNIT}\" \\\n    -c \"${XML_FILE}\" \\\n    --coverage-clover=\"${COVERAGE_FILE}\" \\\n    ${UNIT_OPTIONS} \\\n    \"${SUITE}\" 2>&1 \\\n| tee \"${LOG_FILE}\"\nRESULT=$?\necho \"phpunit exited with error code ${RESULT}\"\n\"${SCRIPT_DIR}check_log.sh\" \"${LOG_FILE}\" \"${PATTERN_FILE}\"\n"
  },
  {
    "path": "tests/scripts/unit_failure_pattern.txt",
    "content": "fail\n\\\\.\\\\=\\\\=\nWarning\nNotice\nDeprecated\nFatal\nError\nDID NOT FINISH\nTest file \".+\" not found\nCannot open file\nNo tests executed\nCould not read\nWarnings: [1-9][0-9]*\nErrors: [1-9][0-9]*\nFailed: [1-9][0-9]*\nDeprecations: [1-9][0-9]*\nRisky: [1-9][0-9]*"
  },
  {
    "path": "var/.gitignore",
    "content": "!configuration/parameters.yaml.dist\n!/configuration/\n/configuration/*\n!/configuration/services.yaml.dist\n/configuration.dev/*\n!/configuration.dev/\n!/configuration.dev/parameters.yaml.dist\n!/configuration.dev/services.yaml.dist\n!/configuration/shops/\n/configuration/shops/*\n!/configuration/shops/1/\n/configuration/shops/1/*\n!/configuration/shops/1/parameters.yaml.dist\n!/configuration/shops/1/services.yaml.dist\n/configuration.dev/shops/*\n!/configuration.dev/shops/\n/configuration.dev/shops/1/*\n!/configuration.dev/shops/1/\n!/configuration.dev/shops/1/parameters.yaml.dist\n!/configuration.dev/shops/1/services.yaml.dist\n"
  }
]